| 
									
										
										
										
											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" | 
					
						
							| 
									
										
										
										
											2021-06-05 05:18:41 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											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" | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2018-01-27 09:26:44 +08:00
										 |  |  | 	"syscall" | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							| 
									
										
										
										
											2020-10-30 16:36:20 +08:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	// permissions (len 10, "drwxr-xr-x")
 | 
					
						
							|  |  |  | 	got := result[0:10] | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 	if ok, err := regexp.MatchString("^"+expectedType+"[rwx-]{9}$", got); !ok { | 
					
						
							|  |  |  | 		t.Errorf("runLs(%#v, *FileInfo): permission field mismatch, expected dir, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// space
 | 
					
						
							|  |  |  | 	got = result[10:11] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^\\s$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): spacer 1 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// link count (len 3, number)
 | 
					
						
							|  |  |  | 	got = result[12:15] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): link count field mismatch, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// spacer
 | 
					
						
							|  |  |  | 	got = result[15:16] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^\\s$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): spacer 2 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// username / uid (len 8, number or string)
 | 
					
						
							|  |  |  | 	got = result[16:24] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): username / uid mismatch, expected user, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// spacer
 | 
					
						
							|  |  |  | 	got = result[24:25] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^\\s$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): spacer 3 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// groupname / gid (len 8, number or string)
 | 
					
						
							|  |  |  | 	got = result[25:33] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): groupname / gid mismatch, expected group, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// spacer
 | 
					
						
							|  |  |  | 	got = result[33:34] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^\\s$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): spacer 4 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// filesize (len 8)
 | 
					
						
							|  |  |  | 	got = result[34:42] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): filesize field mismatch, expected size in bytes, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// spacer
 | 
					
						
							|  |  |  | 	got = result[42:43] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^\\s$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): spacer 5 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// mod time (len 12, e.g. Aug  9 19:46)
 | 
					
						
							|  |  |  | 	got = result[43:55] | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	layout := "Jan  2 15:04" | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	_, err := time.Parse(layout, got) | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		layout = "Jan  2 2006" | 
					
						
							|  |  |  | 		_, err = time.Parse(layout, got) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): mod time field mismatch, expected date layout %s, got: %#v, err: %#v", path, layout, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// spacer
 | 
					
						
							|  |  |  | 	got = result[55:56] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^\\s$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): spacer 6 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:12:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// filename
 | 
					
						
							|  |  |  | 	got = result[56:] | 
					
						
							|  |  |  | 	if ok, err := regexp.MatchString("^"+path+"$", got); !ok { | 
					
						
							| 
									
										
										
										
											2017-08-13 20:00:08 +08:00
										 |  |  | 		t.Errorf("runLs(%#v, *FileInfo): name field mismatch, expected examples, got: %#v, err: %#v", path, got, err) | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type sshFxpTestBadExtendedPacket struct { | 
					
						
							|  |  |  | 	ID        uint32 | 
					
						
							|  |  |  | 	Extension string | 
					
						
							|  |  |  | 	Data      string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p sshFxpTestBadExtendedPacket) id() uint32 { return p.ID } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p sshFxpTestBadExtendedPacket) MarshalBinary() ([]byte, error) { | 
					
						
							| 
									
										
										
										
											2020-10-29 04:54:31 +08:00
										 |  |  | 	l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
 | 
					
						
							|  |  |  | 		4 + len(p.Extension) + | 
					
						
							|  |  |  | 		4 + len(p.Data) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-29 04:54:31 +08:00
										 |  |  | 	b := make([]byte, 4, l) | 
					
						
							| 
									
										
										
										
											2019-08-30 23:04:37 +08:00
										 |  |  | 	b = append(b, sshFxpExtended) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	b = marshalUint32(b, p.ID) | 
					
						
							|  |  |  | 	b = marshalString(b, p.Extension) | 
					
						
							|  |  |  | 	b = marshalString(b, p.Data) | 
					
						
							| 
									
										
										
										
											2020-10-29 04:54:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	return b, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	badPacket := sshFxpTestBadExtendedPacket{client.nextID(), "thisDoesn'tExist", "foobar"} | 
					
						
							| 
									
										
										
										
											2020-10-29 06:20:19 +08:00
										 |  |  | 	typ, data, err := client.clientConn.sendPacket(nil, badPacket) | 
					
						
							| 
									
										
										
										
											2018-03-19 22:32:22 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("unexpected error from sendPacket: %s", err) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-30 23:04:37 +08:00
										 |  |  | 	if typ != sshFxpStatus { | 
					
						
							| 
									
										
										
										
											2018-03-19 22:32:22 +08:00
										 |  |  | 		t.Fatalf("received non-FPX_STATUS packet: %v", typ) | 
					
						
							| 
									
										
										
										
											2017-08-10 13:49:14 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-19 22:32:22 +08:00
										 |  |  | 	err = unmarshalStatus(badPacket.id(), data) | 
					
						
							|  |  |  | 	statusErr, ok := err.(*StatusError) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		t.Fatal("failed to convert error from unmarshalStatus to *StatusError") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-30 23:04:37 +08:00
										 |  |  | 	if statusErr.Code != sshFxOPUnsupported { | 
					
						
							|  |  |  | 		t.Errorf("statusErr.Code => %d, wanted %d", statusErr.Code, sshFxOPUnsupported) | 
					
						
							| 
									
										
										
										
											2018-03-19 22:32:22 +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() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 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") | 
					
						
							|  |  |  | 	pflags := flags(os.O_RDWR | os.O_CREATE | os.O_TRUNC) | 
					
						
							|  |  |  | 	ch := make(chan result, 3) | 
					
						
							|  |  |  | 	id1 := client.nextID() | 
					
						
							| 
									
										
										
										
											2021-02-22 20:00:27 +08:00
										 |  |  | 	client.dispatchRequest(ch, &sshFxpOpenPacket{ | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 		ID:     id1, | 
					
						
							|  |  |  | 		Path:   tmppath, | 
					
						
							|  |  |  | 		Pflags: pflags, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	id2 := client.nextID() | 
					
						
							| 
									
										
										
										
											2021-02-22 20:00:27 +08:00
										 |  |  | 	client.dispatchRequest(ch, &sshFxpLstatPacket{ | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 		ID:   id2, | 
					
						
							|  |  |  | 		Path: tmppath, | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2020-10-29 06:14:21 +08:00
										 |  |  | 	testreply := func(id uint32) { | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 		r := <-ch | 
					
						
							|  |  |  | 		switch r.typ { | 
					
						
							| 
									
										
										
										
											2019-08-30 23:04:37 +08:00
										 |  |  | 		case sshFxpAttrs, sshFxpHandle: // ignore
 | 
					
						
							|  |  |  | 		case sshFxpStatus: | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 			err := normaliseError(unmarshalStatus(id, r.data)) | 
					
						
							|  |  |  | 			assert.NoError(t, err, "race hit, stat before open") | 
					
						
							|  |  |  | 		default: | 
					
						
							| 
									
										
										
										
											2020-10-29 04:08:44 +08:00
										 |  |  | 			t.Fatal("unexpected type:", r.typ) | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-10-29 06:14:21 +08:00
										 |  |  | 	testreply(id1) | 
					
						
							|  |  |  | 	testreply(id2) | 
					
						
							| 
									
										
										
										
											2018-08-21 03:39:58 +08:00
										 |  |  | 	os.Remove(tmppath) | 
					
						
							| 
									
										
										
										
											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() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |