| 
									
										
										
										
											2016-06-13 19:37:38 +08:00
										 |  |  | package sftp | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-15 18:04:25 +08:00
										 |  |  | import ( | 
					
						
							| 
									
										
										
										
											2020-10-30 16:36:20 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	"regexp" | 
					
						
							| 
									
										
										
										
											2020-09-11 00:11:47 +08:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 	"syscall" | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/pkg/errors" | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							| 
									
										
										
										
											2020-10-30 16:36:20 +08:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	sshfx "github.com/pkg/sftp/internal/encoding/ssh/filexfer" | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 	typeDirectory = "d" | 
					
						
							|  |  |  | 	typeFile      = "[^d]" | 
					
						
							| 
									
										
										
										
											2016-06-15 18:04:25 +08:00
										 |  |  | ) | 
					
						
							| 
									
										
										
										
											2016-06-13 19:37:38 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | func TestRunLsWithExamplesDirectory(t *testing.T) { | 
					
						
							|  |  |  | 	path := "examples" | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 	item, _ := os.Stat(path) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	result := runLs(path, item) | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 	runLsTestHelper(t, result, typeDirectory, path) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestRunLsWithLicensesFile(t *testing.T) { | 
					
						
							|  |  |  | 	path := "LICENSE" | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 	item, _ := os.Stat(path) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	result := runLs(path, item) | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 	runLsTestHelper(t, result, typeFile, path) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-10 01:43:14 +08:00
										 |  |  | /* | 
					
						
							|  |  |  |    The format of the `longname' field is unspecified by this protocol. | 
					
						
							|  |  |  |    It MUST be suitable for use in the output of a directory listing | 
					
						
							|  |  |  |    command (in fact, the recommended operation for a directory listing | 
					
						
							|  |  |  |    command is to simply display this data).  However, clients SHOULD NOT | 
					
						
							|  |  |  |    attempt to parse the longname field for file attributes; they SHOULD | 
					
						
							|  |  |  |    use the attrs field instead. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     The recommended format for the longname field is as follows: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         -rwxr-xr-x   1 mjos     staff      348911 Mar 25 14:29 t-filexfer | 
					
						
							|  |  |  |         1234567890 123 12345678 12345678 12345678 123456789012 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    Here, the first line is sample output, and the second field indicates | 
					
						
							|  |  |  |    widths of the various fields.  Fields are separated by spaces.  The | 
					
						
							|  |  |  |    first field lists file permissions for user, group, and others; the | 
					
						
							|  |  |  |    second field is link count; the third field is the name of the user | 
					
						
							|  |  |  |    who owns the file; the fourth field is the name of the group that | 
					
						
							|  |  |  |    owns the file; the fifth field is the size of the file in bytes; the | 
					
						
							|  |  |  |    sixth field (which actually may contain spaces, but is fixed to 12 | 
					
						
							|  |  |  |    characters) is the file modification time, and the seventh field is | 
					
						
							|  |  |  |    the file name.  Each field is specified to be a minimum of certain | 
					
						
							|  |  |  |    number of character positions (indicated by the second line above), | 
					
						
							|  |  |  |    but may also be longer if the data does not fit in the specified | 
					
						
							|  |  |  |    length. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     The SSH_FXP_ATTRS response has the following format: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         uint32     id | 
					
						
							|  |  |  |         ATTRS      attrs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    where `id' is the request identifier, and `attrs' is the returned | 
					
						
							|  |  |  |    file attributes as described in Section ``File Attributes''. | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | */ | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | func runLsTestHelper(t *testing.T, result, expectedType, path string) { | 
					
						
							|  |  |  | 	// using regular expressions to make tests work on all systems
 | 
					
						
							|  |  |  | 	// a virtual file system (like afero) would be needed to mock valid filesystem checks
 | 
					
						
							|  |  |  | 	// expected layout is:
 | 
					
						
							|  |  |  | 	// drwxr-xr-x   8 501      20            272 Aug  9 19:46 examples
 | 
					
						
							| 
									
										
										
										
											2017-08-10 01:43:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	t.Log(result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sparce := strings.Split(result, " ") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var fields []string | 
					
						
							|  |  |  | 	for _, field := range sparce { | 
					
						
							|  |  |  | 		if field == "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fields = append(fields, field) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	perms, linkCnt, user, group, size := fields[0], fields[1], fields[2], fields[3], fields[4] | 
					
						
							|  |  |  | 	dateTime := strings.Join(fields[5:8], " ") | 
					
						
							|  |  |  | 	filename := fields[8] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// permissions (len 10, "drwxr-xr-x")
 | 
					
						
							|  |  |  | 	const ( | 
					
						
							|  |  |  | 		rwxs = "[-r][-w][-xsS]" | 
					
						
							|  |  |  | 		rwxt = "[-r][-w][-xtT]" | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^"+expectedType+rwxs+rwxs+rwxt+"$", perms); !ok { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal("unexpected error:", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Errorf("runLs(%q): permission field mismatch, expected dir, got: %#v, err: %#v", path, perms, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// link count (len 3, number)
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	const ( | 
					
						
							|  |  |  | 		number = "(?:[0-9]+)" | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^"+number+"$", linkCnt); !ok { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal("unexpected error:", err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 		t.Errorf("runLs(%q): link count field mismatch, got: %#v, err: %#v", path, linkCnt, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// username / uid (len 8, number or string)
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	const ( | 
					
						
							|  |  |  | 		name = "(?:[a-z_][a-z0-9_]*)" | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^(?:"+number+"|"+name+")+$", user); !ok { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal("unexpected error:", err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 		t.Errorf("runLs(%q): username / uid mismatch, expected user, got: %#v, err: %#v", path, user, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// groupname / gid (len 8, number or string)
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	if ok, err := regexp.MatchString("^(?:"+number+"|"+name+")+$", group); !ok { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal("unexpected error:", err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 		t.Errorf("runLs(%q): groupname / gid mismatch, expected group, got: %#v, err: %#v", path, group, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// filesize (len 8)
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	if ok, err := regexp.MatchString("^"+number+"$", size); !ok { | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Fatal("unexpected error:", err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 		t.Errorf("runLs(%q): filesize field mismatch, expected size in bytes, got: %#v, err: %#v", path, size, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// mod time (len 12, e.g. Aug  9 19:46)
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	_, err := time.Parse("Jan 2 15:04", dateTime) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 		_, err = time.Parse("Jan 2 2006", dateTime) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			t.Errorf("runLs.dateTime = %#v should match `Jan 2 15:04` or `Jan 2 2006`: %+v", dateTime, err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// filename
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	if path != filename { | 
					
						
							|  |  |  | 		t.Errorf("runLs.filename = %#v, expected: %#v", filename, path) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func clientServerPair(t *testing.T) (*Client, *Server) { | 
					
						
							|  |  |  | 	cr, sw := io.Pipe() | 
					
						
							|  |  |  | 	sr, cw := io.Pipe() | 
					
						
							| 
									
										
										
										
											2020-03-18 16:36:07 +08:00
										 |  |  | 	var options []ServerOption | 
					
						
							|  |  |  | 	if *testAllocator { | 
					
						
							|  |  |  | 		options = append(options, WithAllocator()) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	server, err := NewServer(struct { | 
					
						
							|  |  |  | 		io.Reader | 
					
						
							|  |  |  | 		io.WriteCloser | 
					
						
							| 
									
										
										
										
											2020-03-18 16:36:07 +08:00
										 |  |  | 	}{sr, sw}, options...) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	go server.Serve() | 
					
						
							|  |  |  | 	client, err := NewClientPipe(cr, cw) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("%+v\n", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return client, server | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | const extensionBad = "notexist@example.net" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type BadExtendedPacket struct { | 
					
						
							|  |  |  | 	Payload string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ep *BadExtendedPacket) Type() sshfx.PacketType { | 
					
						
							|  |  |  | 	return sshfx.PacketTypeExtended | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | func (ep *BadExtendedPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) { | 
					
						
							|  |  |  | 	p := &sshfx.ExtendedPacket{ | 
					
						
							|  |  |  | 		ExtendedRequest: extensionBad, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Data: ep, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return p.MarshalPacket(reqid, b) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ep *BadExtendedPacket) MarshalInto(buf *sshfx.Buffer) { | 
					
						
							|  |  |  | 	buf.AppendString(ep.Payload) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ep *BadExtendedPacket) MarshalBinary() ([]byte, error) { | 
					
						
							|  |  |  | 	size := 4 + len(ep.Payload) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	buf := sshfx.NewBuffer(make([]byte, 0, size)) | 
					
						
							|  |  |  | 	ep.MarshalInto(buf) | 
					
						
							|  |  |  | 	return buf.Bytes(), nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | func (ep *BadExtendedPacket) UnmarshalFrom(buf *sshfx.Buffer) (err error) { | 
					
						
							|  |  |  | 	if ep.Payload, err = buf.ConsumeString(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-10-29 04:54:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | func (ep *BadExtendedPacket) UnmarshalBinary(data []byte) (err error) { | 
					
						
							|  |  |  | 	return ep.UnmarshalFrom(sshfx.NewBuffer(data)) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-15 02:42:19 +08:00
										 |  |  | func checkServerAllocator(t *testing.T, server *Server) { | 
					
						
							|  |  |  | 	if server.pktMgr.alloc == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	checkAllocatorBeforeServerClose(t, server.pktMgr.alloc) | 
					
						
							|  |  |  | 	server.Close() | 
					
						
							|  |  |  | 	checkAllocatorAfterServerClose(t, server.pktMgr.alloc) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | // test that errors are sent back when we request an invalid extended packet operation
 | 
					
						
							| 
									
										
										
										
											2018-03-19 22:32:22 +08:00
										 |  |  | // this validates the following rfc draft is followed https://tools.ietf.org/html/draft-ietf-secsh-filexfer-extensions-00
 | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | func TestInvalidExtendedPacket(t *testing.T) { | 
					
						
							|  |  |  | 	client, server := clientServerPair(t) | 
					
						
							|  |  |  | 	defer client.Close() | 
					
						
							|  |  |  | 	defer server.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	badPacket := &BadExtendedPacket{ | 
					
						
							|  |  |  | 		Payload: "foobar", | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	err := client.sendPacket(badPacket, nil) | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							|  |  |  | 		t.Fatal("expected error from sendPacket, but got none") | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-19 22:32:22 +08:00
										 |  |  | 	statusErr, ok := err.(*StatusError) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 		t.Fatalf("failed to convert error from sendPacket to *StatusError: %T: %v", err, err) | 
					
						
							| 
									
										
										
										
											2018-03-19 22:32:22 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if code := sshfx.Status(statusErr.Code); code != sshfx.StatusOPUnsupported { | 
					
						
							|  |  |  | 		t.Errorf("statusErr.Code = %s, wanted %s", code, sshfx.StatusOPUnsupported) | 
					
						
							| 
									
										
										
										
											2018-03-19 22:32:22 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-15 02:42:19 +08:00
										 |  |  | 	checkServerAllocator(t, server) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // test that server handles concurrent requests correctly
 | 
					
						
							|  |  |  | func TestConcurrentRequests(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2019-01-19 07:59:30 +08:00
										 |  |  | 	skipIfWindows(t) | 
					
						
							| 
									
										
										
										
											2020-09-11 00:11:47 +08:00
										 |  |  | 	filename := "/etc/passwd" | 
					
						
							|  |  |  | 	if runtime.GOOS == "plan9" { | 
					
						
							|  |  |  | 		filename = "/lib/ndb/local" | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	client, server := clientServerPair(t) | 
					
						
							|  |  |  | 	defer client.Close() | 
					
						
							|  |  |  | 	defer server.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	concurrency := 2 | 
					
						
							|  |  |  | 	var wg sync.WaitGroup | 
					
						
							|  |  |  | 	wg.Add(concurrency) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < concurrency; i++ { | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			defer wg.Done() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for j := 0; j < 1024; j++ { | 
					
						
							| 
									
										
										
										
											2020-09-11 00:11:47 +08:00
										 |  |  | 				f, err := client.Open(filename) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					t.Errorf("failed to open file: %v", err) | 
					
						
							| 
									
										
										
										
											2020-09-11 00:11:47 +08:00
										 |  |  | 					continue | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				if err := f.Close(); err != nil { | 
					
						
							|  |  |  | 					t.Errorf("failed t close file: %v", err) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	wg.Wait() | 
					
						
							| 
									
										
										
										
											2020-03-15 02:42:19 +08:00
										 |  |  | 	checkServerAllocator(t, server) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Test error conversion
 | 
					
						
							|  |  |  | func TestStatusFromError(t *testing.T) { | 
					
						
							|  |  |  | 	type test struct { | 
					
						
							|  |  |  | 		err error | 
					
						
							| 
									
										
										
										
											2021-02-22 20:00:27 +08:00
										 |  |  | 		pkt *sshFxpStatusPacket | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-22 20:00:27 +08:00
										 |  |  | 	tpkt := func(id, code uint32) *sshFxpStatusPacket { | 
					
						
							|  |  |  | 		return &sshFxpStatusPacket{ | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 			ID:          id, | 
					
						
							|  |  |  | 			StatusError: StatusError{Code: code}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-30 23:04:37 +08:00
										 |  |  | 	testCases := []test{ | 
					
						
							| 
									
										
										
										
											2020-07-25 16:17:41 +08:00
										 |  |  | 		{syscall.ENOENT, tpkt(1, sshFxNoSuchFile)}, | 
					
						
							|  |  |  | 		{&os.PathError{Err: syscall.ENOENT}, | 
					
						
							| 
									
										
										
										
											2019-08-30 23:04:37 +08:00
										 |  |  | 			tpkt(2, sshFxNoSuchFile)}, | 
					
						
							| 
									
										
										
										
											2020-07-25 16:17:41 +08:00
										 |  |  | 		{&os.PathError{Err: errors.New("foo")}, tpkt(3, sshFxFailure)}, | 
					
						
							|  |  |  | 		{ErrSSHFxEOF, tpkt(4, sshFxEOF)}, | 
					
						
							|  |  |  | 		{ErrSSHFxOpUnsupported, tpkt(5, sshFxOPUnsupported)}, | 
					
						
							|  |  |  | 		{io.EOF, tpkt(6, sshFxEOF)}, | 
					
						
							|  |  |  | 		{os.ErrNotExist, tpkt(7, sshFxNoSuchFile)}, | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-30 23:04:37 +08:00
										 |  |  | 	for _, tc := range testCases { | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 		tc.pkt.StatusError.msg = tc.err.Error() | 
					
						
							| 
									
										
										
										
											2021-02-22 20:00:27 +08:00
										 |  |  | 		assert.Equal(t, tc.pkt, statusFromError(tc.pkt.ID, tc.err)) | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // This was written to test a race b/w open immediately followed by a stat.
 | 
					
						
							|  |  |  | // Previous to this the Open would trigger the use of a worker pool, then the
 | 
					
						
							|  |  |  | // stat packet would come in an hit the pool and return faster than the open
 | 
					
						
							|  |  |  | // (returning a file-not-found error).
 | 
					
						
							|  |  |  | // The below by itself wouldn't trigger the race however, I needed to add a
 | 
					
						
							|  |  |  | // small sleep in the openpacket code to trigger the issue. I wanted to add a
 | 
					
						
							|  |  |  | // way to inject that in the code but right now there is no good place for it.
 | 
					
						
							|  |  |  | // I'm thinking after I convert the server into a request-server backend I
 | 
					
						
							|  |  |  | // might be able to do something with the runWorker method passed into the
 | 
					
						
							|  |  |  | // packet manager. But with the 2 implementations fo the server it just doesn't
 | 
					
						
							|  |  |  | // fit well right now.
 | 
					
						
							|  |  |  | func TestOpenStatRace(t *testing.T) { | 
					
						
							|  |  |  | 	client, server := clientServerPair(t) | 
					
						
							|  |  |  | 	defer client.Close() | 
					
						
							|  |  |  | 	defer server.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	ch := make(chan result, 3) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 	// openpacket finishes to fast to trigger race in tests
 | 
					
						
							|  |  |  | 	// need to add a small sleep on server to openpackets somehow
 | 
					
						
							|  |  |  | 	tmppath := path.Join(os.TempDir(), "stat_race") | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	defer os.Remove(tmppath) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 	id1 := client.nextID() | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	client.dispatchPacket(ch, id1, &sshfx.OpenPacket{ | 
					
						
							|  |  |  | 		Filename: tmppath, | 
					
						
							|  |  |  | 		PFlags:   flags(os.O_RDWR | os.O_CREATE | os.O_TRUNC), | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 	id2 := client.nextID() | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 	client.dispatchPacket(ch, id2, &sshfx.LStatPacket{ | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 		Path: tmppath, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	r := <-ch | 
					
						
							|  |  |  | 	if r.err != nil { | 
					
						
							|  |  |  | 		t.Fatal("unexpected error:", r.err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch r.pkt.RequestID { | 
					
						
							|  |  |  | 	case id1: | 
					
						
							|  |  |  | 	case id2: | 
					
						
							|  |  |  | 		t.Fatal("race condition detected: LStat response returned first") | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		t.Fatal("unexpected id in response:", r.pkt.RequestID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch r.pkt.PacketType { | 
					
						
							|  |  |  | 	case sshfx.PacketTypeHandle: | 
					
						
							|  |  |  | 		// First, we should get the Open response: Handle
 | 
					
						
							|  |  |  | 		var handle sshfx.HandlePacket | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := handle.UnmarshalPacketBody(&r.pkt.Data); err != nil { | 
					
						
							|  |  |  | 			t.Fatal("unexpected error:", err) | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		defer client.close(handle.Handle) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case sshfx.PacketTypeStatus: | 
					
						
							|  |  |  | 		var status sshfx.StatusPacket | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := status.UnmarshalPacketBody(&r.pkt.Data); err != nil { | 
					
						
							|  |  |  | 			t.Fatal("unexpected error:", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Fatal("unexpected status packet:", status) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		t.Fatal("unexpected type:", r.pkt.PacketType) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	r = <-ch | 
					
						
							|  |  |  | 	if r.err != nil { | 
					
						
							|  |  |  | 		t.Fatal("unexpected error:", r.err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if r.pkt.RequestID != id2 { | 
					
						
							|  |  |  | 		t.Fatal("unexpected id in response:", r.pkt.RequestID) | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-24 20:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	switch r.pkt.PacketType { | 
					
						
							|  |  |  | 	case sshfx.PacketTypeAttrs: | 
					
						
							|  |  |  | 		// Second, we should get the LStat response: Attrs
 | 
					
						
							|  |  |  | 		// We can go ahead and ignore this one.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case sshfx.PacketTypeStatus: | 
					
						
							|  |  |  | 		var status sshfx.StatusPacket | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := status.UnmarshalPacketBody(&r.pkt.Data); err != nil { | 
					
						
							|  |  |  | 			t.Fatal("unexpected error:", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Fatal("unexpected status packet:", status) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		t.Fatal("unexpected type:", r.pkt.PacketType) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-15 02:42:19 +08:00
										 |  |  | 	checkServerAllocator(t, server) | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2020-09-14 12:38:04 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Ensure that proper error codes are returned for non existent files, such
 | 
					
						
							|  |  |  | // that they are mapped back to a 'not exists' error on the client side.
 | 
					
						
							|  |  |  | func TestStatNonExistent(t *testing.T) { | 
					
						
							|  |  |  | 	client, server := clientServerPair(t) | 
					
						
							|  |  |  | 	defer client.Close() | 
					
						
							|  |  |  | 	defer server.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, file := range []string{"/doesnotexist", "/doesnotexist/a/b"} { | 
					
						
							|  |  |  | 		_, err := client.Stat(file) | 
					
						
							| 
									
										
										
										
											2020-09-14 22:58:12 +08:00
										 |  |  | 		if !os.IsNotExist(err) { | 
					
						
							| 
									
										
										
										
											2020-09-14 12:38:04 +08:00
										 |  |  | 			t.Errorf("expected 'does not exist' err for file %q.  got: %v", file, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-10-30 16:36:20 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestServerWithBrokenClient(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2021-02-22 20:00:27 +08:00
										 |  |  | 	validInit := sp(&sshFxInitPacket{Version: 3}) | 
					
						
							|  |  |  | 	brokenOpen := sp(&sshFxpOpenPacket{Path: "foo"}) | 
					
						
							| 
									
										
										
										
											2020-10-30 16:36:20 +08:00
										 |  |  | 	brokenOpen = brokenOpen[:len(brokenOpen)-2] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, clientInput := range [][]byte{ | 
					
						
							|  |  |  | 		// Packet length zero (never valid). This used to crash the server.
 | 
					
						
							|  |  |  | 		{0, 0, 0, 0}, | 
					
						
							|  |  |  | 		append(validInit, 0, 0, 0, 0), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Client hangs up mid-packet.
 | 
					
						
							|  |  |  | 		append(validInit, brokenOpen...), | 
					
						
							|  |  |  | 	} { | 
					
						
							|  |  |  | 		srv, err := NewServer(struct { | 
					
						
							|  |  |  | 			io.Reader | 
					
						
							|  |  |  | 			io.WriteCloser | 
					
						
							|  |  |  | 		}{ | 
					
						
							|  |  |  | 			bytes.NewReader(clientInput), | 
					
						
							|  |  |  | 			&sink{}, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err = srv.Serve() | 
					
						
							|  |  |  | 		assert.Error(t, err) | 
					
						
							|  |  |  | 		srv.Close() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |