Merge pull request #554 from dsnet/master

Properly handle io.EOF error conditions when reading
diff --git a/conn.go b/conn.go
index 1ce9725..3bb2ba1 100644
--- a/conn.go
+++ b/conn.go
@@ -18,7 +18,9 @@
 }
 
 // the orderID is used in server mode if the allocator is enabled.
-// For the client mode just pass 0
+// For the client mode just pass 0.
+// It returns io.EOF if the connection is closed and
+// there are no more packets to read.
 func (c *conn) recvPacket(orderID uint32) (uint8, []byte, error) {
 	return recvPacket(c, c.alloc, orderID)
 }
diff --git a/packet.go b/packet.go
index b06d0b9..1232ff1 100644
--- a/packet.go
+++ b/packet.go
@@ -290,6 +290,11 @@
 		b = make([]byte, length)
 	}
 	if _, err := io.ReadFull(r, b[:length]); err != nil {
+		// ReadFull only returns EOF if it has read no bytes.
+		// In this case, that means a partial packet, and thus unexpected.
+		if err == io.EOF {
+			err = io.ErrUnexpectedEOF
+		}
 		debug("recv packet %d bytes: err %v", length, err)
 		return 0, nil, err
 	}
diff --git a/server.go b/server.go
index 503454e..2e419f5 100644
--- a/server.go
+++ b/server.go
@@ -327,7 +327,7 @@
 }
 
 // Serve serves SFTP connections until the streams stop or the SFTP subsystem
-// is stopped.
+// is stopped. It returns nil if the server exits cleanly.
 func (svr *Server) Serve() error {
 	defer func() {
 		if svr.pktMgr.alloc != nil {
@@ -353,6 +353,10 @@
 	for {
 		pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
 		if err != nil {
+			// Check whether the connection terminated cleanly in-between packets.
+			if err == io.EOF {
+				err = nil
+			}
 			// we don't care about releasing allocated pages here, the server will quit and the allocator freed
 			break
 		}