Browse Source

refactor socket EOF handling

handling EOF already at the socket level isn't a very good idea - it
breaks the abstraction, and makes implementing sane semantics hard.
Oswald Buddenhagen 10 years ago
parent
commit
9e15ab4a5a
3 changed files with 47 additions and 23 deletions
  1. 26 6
      src/drv_imap.c
  2. 21 8
      src/socket.c
  3. 0 9
      src/socket.h

+ 26 - 6
src/drv_imap.c

@@ -114,6 +114,8 @@ typedef struct imap_store {
 
 
 	/* Used during sequential operations like connect */
 	/* Used during sequential operations like connect */
 	enum { GreetingPending = 0, GreetingBad, GreetingOk, GreetingPreauth } greeting;
 	enum { GreetingPending = 0, GreetingBad, GreetingOk, GreetingPreauth } greeting;
+	int expectBYE; /* LOGOUT is in progress */
+	int expectEOF; /* received LOGOUT's OK or unsolicited BYE */
 	int canceling; /* imap_cancel() is in progress */
 	int canceling; /* imap_cancel() is in progress */
 	union {
 	union {
 		void (*imap_open)( store_t *srv, void *aux );
 		void (*imap_open)( store_t *srv, void *aux );
@@ -674,7 +676,7 @@ parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts )
 {
 {
 	list_t *cur, **curp;
 	list_t *cur, **curp;
 	char *s = *sp, *d, *p;
 	char *s = *sp, *d, *p;
-	int bytes;
+	int n, bytes;
 	char c;
 	char c;
 
 
 	assert( sts );
 	assert( sts );
@@ -724,7 +726,13 @@ parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts )
 			s[cur->len] = 0;
 			s[cur->len] = 0;
 
 
 		  getbytes:
 		  getbytes:
-			bytes -= socket_read( &ctx->conn, s, bytes );
+			n = socket_read( &ctx->conn, s, bytes );
+			if (n < 0) {
+			  badeof:
+				error( "IMAP error: unexpected EOF from %s\n", ctx->conn.name );
+				goto bail;
+			}
+			bytes -= n;
 			if (bytes > 0)
 			if (bytes > 0)
 				goto postpone;
 				goto postpone;
 
 
@@ -738,6 +746,8 @@ parse_imap_list( imap_store_t *ctx, char **sp, parse_list_state_t *sts )
 		  getline:
 		  getline:
 			if (!(s = socket_read_line( &ctx->conn )))
 			if (!(s = socket_read_line( &ctx->conn )))
 				goto postpone;
 				goto postpone;
+			if (s == (void *)~0)
+				goto badeof;
 			if (DFlags & VERBOSE) {
 			if (DFlags & VERBOSE) {
 				printf( "%s%s\n", ctx->label, s );
 				printf( "%s%s\n", ctx->label, s );
 				fflush( stdout );
 				fflush( stdout );
@@ -1218,6 +1228,12 @@ imap_socket_read( void *aux )
 		}
 		}
 		if (!(cmd = socket_read_line( &ctx->conn )))
 		if (!(cmd = socket_read_line( &ctx->conn )))
 			return;
 			return;
+		if (cmd == (void *)~0) {
+			if (!ctx->expectEOF)
+				error( "IMAP error: unexpected EOF from %s\n", ctx->conn.name );
+			/* A clean shutdown sequence ends with bad_callback as well (see imap_cleanup()). */
+			break;
+		}
 		if (DFlags & VERBOSE) {
 		if (DFlags & VERBOSE) {
 			printf( "%s%s\n", ctx->label, cmd );
 			printf( "%s%s\n", ctx->label, cmd );
 			fflush( stdout );
 			fflush( stdout );
@@ -1250,12 +1266,14 @@ imap_socket_read( void *aux )
 					goto dogreet;
 					goto dogreet;
 				}
 				}
 			} else if (!strcmp( "BYE", arg )) {
 			} else if (!strcmp( "BYE", arg )) {
-				if (ctx->conn.state != SCK_CLOSING) {
-					ctx->conn.state = SCK_CLOSING;
+				if (!ctx->expectBYE) {
 					ctx->greeting = GreetingBad;
 					ctx->greeting = GreetingBad;
 					error( "IMAP error: unexpected BYE response: %s\n", cmd );
 					error( "IMAP error: unexpected BYE response: %s\n", cmd );
+					/* We just wait for the server to close the connection now. */
+					ctx->expectEOF = 1;
+				} else {
+					/* We still need to wait for the LOGOUT's tagged OK. */
 				}
 				}
-				/* We just wait for the server to close the connection now. */
 			} else if (ctx->greeting == GreetingPending) {
 			} else if (ctx->greeting == GreetingPending) {
 				error( "IMAP error: bogus greeting response %s\n", arg );
 				error( "IMAP error: bogus greeting response %s\n", arg );
 				break;
 				break;
@@ -1470,7 +1488,7 @@ imap_cleanup( void )
 	for (ctx = unowned; ctx; ctx = nctx) {
 	for (ctx = unowned; ctx; ctx = nctx) {
 		nctx = ctx->next;
 		nctx = ctx->next;
 		set_bad_callback( ctx, (void (*)(void *))imap_cancel_store, ctx );
 		set_bad_callback( ctx, (void (*)(void *))imap_cancel_store, ctx );
-		((imap_store_t *)ctx)->conn.state = SCK_CLOSING;
+		((imap_store_t *)ctx)->expectBYE = 1;
 		imap_exec( (imap_store_t *)ctx, 0, imap_cleanup_p2, "LOGOUT" );
 		imap_exec( (imap_store_t *)ctx, 0, imap_cleanup_p2, "LOGOUT" );
 	}
 	}
 }
 }
@@ -1481,6 +1499,8 @@ imap_cleanup_p2( imap_store_t *ctx,
 {
 {
 	if (response == RESP_NO)
 	if (response == RESP_NO)
 		imap_cancel_store( &ctx->gen );
 		imap_cancel_store( &ctx->gen );
+	else if (response == RESP_OK)
+		ctx->expectEOF = 1;
 }
 }
 
 
 /******************* imap_open_store *******************/
 /******************* imap_open_store *******************/

+ 21 - 8
src/socket.c

@@ -43,6 +43,15 @@
 # include <openssl/x509v3.h>
 # include <openssl/x509v3.h>
 #endif
 #endif
 
 
+enum {
+	SCK_CONNECTING,
+#ifdef HAVE_LIBSSL
+	SCK_STARTTLS,
+#endif
+	SCK_READY,
+	SCK_EOF
+};
+
 static void
 static void
 socket_fail( conn_t *conn )
 socket_fail( conn_t *conn )
 {
 {
@@ -67,11 +76,12 @@ ssl_return( const char *func, conn_t *conn, int ret )
 	case SSL_ERROR_SSL:
 	case SSL_ERROR_SSL:
 		if (!(err = ERR_get_error())) {
 		if (!(err = ERR_get_error())) {
 			if (ret == 0) {
 			if (ret == 0) {
-				if (conn->state != SCK_CLOSING)
-					error( "Socket error: secure %s %s: unexpected EOF\n", func, conn->name );
-			} else {
-				sys_error( "Socket error: secure %s %s", func, conn->name );
+				/* Callers take the short path out, so signal higher layers from here. */
+				conn->state = SCK_EOF;
+				conn->read_callback( conn->callback_aux );
+				return 0;
 			}
 			}
+			sys_error( "Socket error: secure %s %s", func, conn->name );
 		} else {
 		} else {
 			error( "Socket error: secure %s %s: %s\n", func, conn->name, ERR_error_string( err, 0 ) );
 			error( "Socket error: secure %s %s: %s\n", func, conn->name, ERR_error_string( err, 0 ) );
 		}
 		}
@@ -582,10 +592,9 @@ do_read( conn_t *sock, char *buf, int len )
 			sys_error( "Socket error: read from %s", sock->name );
 			sys_error( "Socket error: read from %s", sock->name );
 			socket_fail( sock );
 			socket_fail( sock );
 		} else if (!n) {
 		} else if (!n) {
-			if (sock->state != SCK_CLOSING)
-				error( "Socket error: read from %s: unexpected EOF\n", sock->name );
-			socket_fail( sock );
-			return -1;
+			/* EOF. Callers take the short path out, so signal higher layers from here. */
+			sock->state = SCK_EOF;
+			sock->read_callback( sock->callback_aux );
 		}
 		}
 	}
 	}
 
 
@@ -656,6 +665,8 @@ int
 socket_read( conn_t *conn, char *buf, int len )
 socket_read( conn_t *conn, char *buf, int len )
 {
 {
 	int n = conn->bytes;
 	int n = conn->bytes;
+	if (!n && conn->state == SCK_EOF)
+		return -1;
 	if (n > len)
 	if (n > len)
 		n = len;
 		n = len;
 	memcpy( buf, conn->buf + conn->offset, n );
 	memcpy( buf, conn->buf + conn->offset, n );
@@ -680,6 +691,8 @@ socket_read_line( conn_t *b )
 			memmove( b->buf, b->buf + b->offset, b->bytes );
 			memmove( b->buf, b->buf + b->offset, b->bytes );
 			b->offset = 0;
 			b->offset = 0;
 		}
 		}
+		if (b->state == SCK_EOF)
+			return (void *)~0;
 		return 0;
 		return 0;
 	}
 	}
 	n = p + 1 - s;
 	n = p + 1 - s;

+ 0 - 9
src/socket.h

@@ -29,15 +29,6 @@
 #include <zlib.h>
 #include <zlib.h>
 #endif
 #endif
 
 
-enum {
-	SCK_CONNECTING,
-#ifdef HAVE_LIBSSL
-	SCK_STARTTLS,
-#endif
-	SCK_READY,
-	SCK_CLOSING
-};
-
 #ifdef HAVE_LIBSSL
 #ifdef HAVE_LIBSSL
 typedef struct ssl_st SSL;
 typedef struct ssl_st SSL;
 typedef struct ssl_ctx_st SSL_CTX;
 typedef struct ssl_ctx_st SSL_CTX;