| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | package sftp | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2016-07-23 11:30:33 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2016-07-15 12:11:34 +08:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2016-07-27 02:32:37 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | 	"syscall" | 
					
						
							| 
									
										
										
										
											2016-07-30 02:22:07 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/pkg/errors" | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | var maxTxPacket uint32 = 1 << 15 | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | type handleHandler func(string) string | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 02:42:18 +08:00
										 |  |  | // Handlers contains the 4 SFTP server request handlers.
 | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | type Handlers struct { | 
					
						
							| 
									
										
										
										
											2016-07-09 05:13:03 +08:00
										 |  |  | 	FileGet  FileReader | 
					
						
							|  |  |  | 	FilePut  FileWriter | 
					
						
							|  |  |  | 	FileCmd  FileCmder | 
					
						
							|  |  |  | 	FileInfo FileInfoer | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 02:42:18 +08:00
										 |  |  | // RequestServer abstracts the sftp protocol with an http request-like protocol
 | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | type RequestServer struct { | 
					
						
							|  |  |  | 	serverConn | 
					
						
							| 
									
										
										
										
											2016-07-12 11:19:49 +08:00
										 |  |  | 	Handlers        Handlers | 
					
						
							|  |  |  | 	pktChan         chan packet | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | 	openRequests    map[string]*Request | 
					
						
							|  |  |  | 	openRequestLock sync.RWMutex | 
					
						
							| 
									
										
										
										
											2016-07-27 02:32:37 +08:00
										 |  |  | 	handleCount     int | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 02:42:18 +08:00
										 |  |  | // NewRequestServer creates/allocates/returns new RequestServer.
 | 
					
						
							|  |  |  | // Normally there there will be one server per user-session.
 | 
					
						
							|  |  |  | func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer { | 
					
						
							|  |  |  | 	return &RequestServer{ | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 		serverConn: serverConn{ | 
					
						
							|  |  |  | 			conn: conn{ | 
					
						
							|  |  |  | 				Reader:      rwc, | 
					
						
							|  |  |  | 				WriteCloser: rwc, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2016-07-19 03:58:23 +08:00
										 |  |  | 		Handlers:     h, | 
					
						
							| 
									
										
										
										
											2016-07-12 11:19:49 +08:00
										 |  |  | 		pktChan:      make(chan packet, sftpServerWorkerCount), | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | 		openRequests: make(map[string]*Request), | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | func (rs *RequestServer) nextRequest(r *Request) string { | 
					
						
							|  |  |  | 	rs.openRequestLock.Lock() | 
					
						
							|  |  |  | 	defer rs.openRequestLock.Unlock() | 
					
						
							| 
									
										
										
										
											2016-07-27 02:32:37 +08:00
										 |  |  | 	rs.handleCount++ | 
					
						
							|  |  |  | 	handle := strconv.Itoa(rs.handleCount) | 
					
						
							|  |  |  | 	rs.openRequests[handle] = r | 
					
						
							|  |  |  | 	return handle | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (rs *RequestServer) getRequest(handle string) (*Request, bool) { | 
					
						
							|  |  |  | 	rs.openRequestLock.Lock() | 
					
						
							|  |  |  | 	defer rs.openRequestLock.Unlock() | 
					
						
							|  |  |  | 	r, ok := rs.openRequests[handle] | 
					
						
							|  |  |  | 	return r, ok | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (rs *RequestServer) closeRequest(handle string) { | 
					
						
							|  |  |  | 	rs.openRequestLock.Lock() | 
					
						
							|  |  |  | 	defer rs.openRequestLock.Unlock() | 
					
						
							|  |  |  | 	if _, ok := rs.openRequests[handle]; ok { | 
					
						
							|  |  |  | 		delete(rs.openRequests, handle) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 02:42:18 +08:00
										 |  |  | // Close the read/write/closer to trigger exiting the main server loop
 | 
					
						
							| 
									
										
										
										
											2016-07-23 07:20:00 +08:00
										 |  |  | func (rs *RequestServer) Close() error { return rs.conn.Close() } | 
					
						
							| 
									
										
										
										
											2016-07-21 07:48:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 02:42:18 +08:00
										 |  |  | // Serve requests for user session
 | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | func (rs *RequestServer) Serve() error { | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 	var wg sync.WaitGroup | 
					
						
							|  |  |  | 	wg.Add(sftpServerWorkerCount) | 
					
						
							|  |  |  | 	for i := 0; i < sftpServerWorkerCount; i++ { | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			defer wg.Done() | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | 			if err := rs.packetWorker(); err != nil { | 
					
						
							|  |  |  | 				rs.conn.Close() // shuts down recvPacket
 | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 	var pktType uint8 | 
					
						
							|  |  |  | 	var pktBytes []byte | 
					
						
							|  |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | 		pktType, pktBytes, err = rs.recvPacket() | 
					
						
							| 
									
										
										
										
											2016-07-19 02:50:45 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-07-12 11:19:49 +08:00
										 |  |  | 		pkt, err := makePacket(rxPacket{fxp(pktType), pktBytes}) | 
					
						
							| 
									
										
										
										
											2016-07-19 02:50:45 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-07-12 11:19:49 +08:00
										 |  |  | 		rs.pktChan <- pkt | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | 	close(rs.pktChan) // shuts down sftpServerWorkers
 | 
					
						
							|  |  |  | 	wg.Wait()         // wait for all workers to exit
 | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-09 03:38:35 +08:00
										 |  |  | func (rs *RequestServer) packetWorker() error { | 
					
						
							| 
									
										
										
										
											2016-07-12 11:19:49 +08:00
										 |  |  | 	for pkt := range rs.pktChan { | 
					
						
							| 
									
										
										
										
											2016-07-26 02:52:07 +08:00
										 |  |  | 		var rpkt responsePacket | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 		switch pkt := pkt.(type) { | 
					
						
							|  |  |  | 		case *sshFxInitPacket: | 
					
						
							| 
									
										
										
										
											2016-07-13 08:36:12 +08:00
										 |  |  | 			rpkt = sshFxVersionPacket{sftpProtocolVersion, nil} | 
					
						
							| 
									
										
										
										
											2016-07-12 11:19:49 +08:00
										 |  |  | 		case *sshFxpClosePacket: | 
					
						
							| 
									
										
										
										
											2016-07-30 02:22:07 +08:00
										 |  |  | 			handle := pkt.getHandle() | 
					
						
							| 
									
										
										
										
											2016-07-12 11:19:49 +08:00
										 |  |  | 			rs.closeRequest(handle) | 
					
						
							| 
									
										
										
										
											2016-07-13 08:36:12 +08:00
										 |  |  | 			rpkt = statusFromError(pkt, nil) | 
					
						
							| 
									
										
										
										
											2016-07-15 12:11:34 +08:00
										 |  |  | 		case *sshFxpRealpathPacket: | 
					
						
							|  |  |  | 			rpkt = cleanPath(pkt) | 
					
						
							| 
									
										
										
										
											2016-07-13 10:31:46 +08:00
										 |  |  | 		case isOpener: | 
					
						
							| 
									
										
										
										
											2016-07-30 02:22:07 +08:00
										 |  |  | 			handle := rs.nextRequest(newRequest(pkt.getPath())) | 
					
						
							| 
									
										
										
										
											2016-07-13 10:31:46 +08:00
										 |  |  | 			rpkt = sshFxpHandlePacket{pkt.id(), handle} | 
					
						
							| 
									
										
										
										
											2016-07-13 08:36:12 +08:00
										 |  |  | 		case hasHandle: | 
					
						
							| 
									
										
										
										
											2016-07-30 02:22:07 +08:00
										 |  |  | 			handle := pkt.getHandle() | 
					
						
							|  |  |  | 			request, ok := rs.getRequest(handle) | 
					
						
							|  |  |  | 			if !ok { | 
					
						
							|  |  |  | 				rpkt = statusFromError(pkt, syscall.EBADF) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				rpkt = rs.handle(request, pkt) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case hasPath: | 
					
						
							|  |  |  | 			request := newRequest(pkt.getPath()) | 
					
						
							|  |  |  | 			rpkt = rs.handle(request, pkt) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			return errors.Errorf("unexpected packet type %T", pkt) | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-07-12 11:19:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-30 02:22:07 +08:00
										 |  |  | 		err := rs.sendPacket(rpkt) | 
					
						
							| 
									
										
										
										
											2016-07-19 02:50:45 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-07-07 02:59:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-07-13 08:36:12 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-26 02:52:07 +08:00
										 |  |  | func cleanPath(pkt *sshFxpRealpathPacket) responsePacket { | 
					
						
							| 
									
										
										
										
											2016-07-19 03:40:41 +08:00
										 |  |  | 	path := pkt.getPath() | 
					
						
							|  |  |  | 	if !filepath.IsAbs(path) { | 
					
						
							| 
									
										
										
										
											2016-07-23 07:20:00 +08:00
										 |  |  | 		path = "/" + path | 
					
						
							|  |  |  | 	} // all paths are absolute
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-19 03:40:41 +08:00
										 |  |  | 	cleaned_path := filepath.Clean(path) | 
					
						
							| 
									
										
										
										
											2016-07-15 12:11:34 +08:00
										 |  |  | 	return &sshFxpNamePacket{ | 
					
						
							|  |  |  | 		ID: pkt.id(), | 
					
						
							|  |  |  | 		NameAttrs: []sshFxpNameAttr{{ | 
					
						
							|  |  |  | 			Name:     cleaned_path, | 
					
						
							|  |  |  | 			LongName: cleaned_path, | 
					
						
							|  |  |  | 			Attrs:    emptyFileStat, | 
					
						
							|  |  |  | 		}}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-30 02:22:07 +08:00
										 |  |  | func (rs *RequestServer) handle(request *Request, pkt packet) responsePacket { | 
					
						
							|  |  |  | 	// called here to keep packet handling out of request for testing
 | 
					
						
							|  |  |  | 	request.populate(pkt) | 
					
						
							|  |  |  | 	// fmt.Println("Request Method: ", request.Method)
 | 
					
						
							|  |  |  | 	rpkt, err := request.handle(rs.Handlers) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err = errorAdapter(err) | 
					
						
							|  |  |  | 		rpkt = statusFromError(pkt, err) | 
					
						
							| 
									
										
										
										
											2016-07-19 02:50:45 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-13 08:36:12 +08:00
										 |  |  | 	return rpkt | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-07-23 11:30:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // os.ErrNotExist should convert to ssh_FX_NO_SUCH_FILE, but is not recognized
 | 
					
						
							|  |  |  | // by statusFromError. So we convert to syscall.ENOENT which it does.
 | 
					
						
							|  |  |  | func errorAdapter(err error) error { | 
					
						
							|  |  |  | 	if err == os.ErrNotExist { | 
					
						
							|  |  |  | 		return syscall.ENOENT | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } |