From db28d5c6f7928b707bffa193ad93dfa7556ab0ad Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Sat, 25 May 2013 17:25:00 -0400 Subject: [PATCH] Added support for IMAP4 commands that may span multiple packets by calling recv() until full IMAP4 response is received Now using the same Tx and Rx buffers for POP3 and IMAP4 --- ChangeLog | 3 + src/imap.c | 165 +++++++++++++++++++++++++++++++++++++++----------- src/imap.h | 17 ------ src/network.c | 5 ++ src/network.h | 5 ++ src/pop3.c | 7 ++- src/pop3.h | 7 +-- 7 files changed, 147 insertions(+), 62 deletions(-) diff --git a/ChangeLog b/ChangeLog index f52ccd3..bbc4cf1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,7 @@ 2005-06-22 Hugo Villeneuve + * Now using the same Tx and Rx buffers for POP3 and IMAP4. + * Added support for IMAP4 commands that may span multiple + packets by calling recv() until full IMAP4 response is received. * Removed autogenerated files from subversion repository. 2004-02-24 Hugo Villeneuve diff --git a/src/imap.c b/src/imap.c index 6c272d5..798d5b6 100644 --- a/src/imap.c +++ b/src/imap.c @@ -25,84 +25,159 @@ #include "imap.h" -const static char line_delimiter[] = "\n"; +#define IMAP4_ENDL "\r\n" /* CRLF */ -static int tlabel = 0; -static int tlabel_len; +#define IMAP4_CMD_CAPABILITY "CAPABILITY" +#define IMAP4_CMD_LOGIN "LOGIN" +#define IMAP4_CMD_SELECT "SELECT" +#define IMAP4_CMD_EXAMINE "EXAMINE" +#define IMAP4_CMD_LOGOUT "LOGOUT" +#define IMAP4_CMD_SEARCH_UNSEEN "SEARCH UNSEEN" + +/* Responses from IMAP4 server. */ +#define IMAP4_RSP_SUCCESS "OK" +#define IMAP4_RSP_FAILURE "NO" +#define IMAP4_RSP_PROTOCOL_ERR "BAD" +#define IMAP4_RSP_SEARCH_UNSEEN "* SEARCH " /* This is the line that will be returned by + * the IMAP4 server after receiving the + * "SEARCH UNSEEN" command, followed by the + * messages ID of the unseen messages. */ -static char tx_buffer[IMAP4_IN_BUF_SIZE]; -static char rx_buffer[IMAP4_IN_BUF_SIZE]; +static int tlabel = 0; +static int tlabel_len; static int unseen_string_found; +/* Defined in network.c */ +extern char tx_buffer[WMNOTIFY_BUFSIZE + 1]; +extern char rx_buffer[WMNOTIFY_BUFSIZE + 1]; + static int IMAP4_ReceiveResponse( void ) { int len; char *token; + char *stringp; + + /* All interactions transmitted by client and server are in the form of + lines, that is, strings that end with a CRLF. The protocol receiver + of an IMAP4rev1 client or server is either reading a line, or is + reading a sequence of octets with a known count followed by a line. */ + + get_packet: + len = WmnotifyGetResponse( rx_buffer, WMNOTIFY_BUFSIZE ); - len = WmnotifyGetResponse( rx_buffer, IMAP4_IN_BUF_SIZE ); if( len < 0 ) { + /* An error occured. */ perror( PACKAGE ); ErrorLocation( __FILE__, __LINE__ ); - return len; + goto error; + } + else if( len == 0 ) { + /* The return value will be 0 when the peer has performed an orderly shutdown. */ + if( wmnotify_infos.debug ) { + fprintf( stderr, "IMAP server has closed connection.\n" ); + } + goto error; + } + else if( len == WMNOTIFY_BUFSIZE ) { + /* This shouldn't happen, the examine command doesn't requires that much data. */ + if( wmnotify_infos.debug ) { + ErrorLocation( __FILE__, __LINE__ ); + fprintf( stderr, "Response too big to fit in receive buffer.\n" ); + } + goto error; } - rx_buffer[ len - 2 ] = '\0'; + /* We suppose that, if a partial response packet was sent, it is not broken in the middle + of a line (to confirm). Normally, each string is terminated by CRLF. */ + if( STREQ_LEN( &rx_buffer[ len - 2 ], IMAP4_ENDL, 2 ) == FALSE ) { + /* No CRLF found at the end of the buffer --> not handled by wmnotify. */ + ErrorLocation( __FILE__, __LINE__ ); + fprintf( stderr, "Response buffer doesn't contain CRLF at the end.\n" ); + goto error; + } if( wmnotify_infos.debug ) { - printf( "Response: \"%s\"\n", rx_buffer ); + printf( "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" ); + printf( "IMAP4 Server Response (size %d bytes):\n", len ); + printf( "%s", rx_buffer ); + printf( "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" ); } - + + /* Converting the last CRLF into a LF followed by a NULL termination character. */ + rx_buffer[ len - 2 ] = '\n'; + rx_buffer[ len - 1 ] = '\0'; + /* Check the Server Completion Response returned by the IMAP4 server. There are currently * three Server Completion Responses codes: success ("OK"), failure ("NO") and protocol error * ("BAD"). */ - token = strtok( rx_buffer, line_delimiter ); + stringp = rx_buffer; + + while( ( token = strsep( &stringp, "\n" ) ) != NULL ) { + + /* In case no delimiter was found, the token is taken to + be the entire string *stringp, and *stringp is made NULL. */ + if( stringp == NULL ) { + /* This should never happen. */ + ErrorLocation( __FILE__, __LINE__ ); + fprintf( stderr, " Delimiter not found in strsep() call.\n" ); + goto error; + } + + if( token == NULL ) { + /* This should never happen. */ + ErrorLocation( __FILE__, __LINE__ ); + fprintf( stderr, " NULL token returned by strsep().\n" ); + goto error; + } - while( len >= 0 ) { if( token[0] == '*' ) { - /* untagged response. */ - if( STREQ_LEN( token, IMAP4_UNSEEN, strlen(IMAP4_UNSEEN) ) == TRUE ) { + /* Untagged response. If there is a space after the SEARCH response, it means + * at least 1 message is unseen. */ + if( STREQ_LEN( token, IMAP4_RSP_SEARCH_UNSEEN, strlen(IMAP4_RSP_SEARCH_UNSEEN) ) == TRUE ) { unseen_string_found = TRUE; } - goto loop_next; /* Next iteration of the while() loop (next line). */ } else { + /* Must be the status... */ + /* We check for the correct transaction label plus a space. */ if( STREQ_LEN( token, tx_buffer, tlabel_len + 1 ) == TRUE ) { token += tlabel_len + 1; - if( STREQ_LEN( token, IMAP4_SUCCESS, strlen(IMAP4_SUCCESS) ) == TRUE ) { - break; /* OK, no errors. */ + if( STREQ_LEN( token, IMAP4_RSP_SUCCESS, strlen(IMAP4_RSP_SUCCESS) ) == TRUE ) { + goto end; /* OK, no errors. */ } - else if( STREQ_LEN( token, IMAP4_PROTOCOL_ERR, strlen(IMAP4_PROTOCOL_ERR) ) == TRUE ) { + else if( STREQ_LEN( token, IMAP4_RSP_PROTOCOL_ERR, strlen(IMAP4_RSP_PROTOCOL_ERR) ) == TRUE ) { fprintf( stderr, "%s: Protocol error (%s).\n", PACKAGE, token ); - len = -1; - break; + goto error; } - else if( STREQ_LEN( token, IMAP4_FAILURE, strlen(IMAP4_FAILURE) ) == TRUE ) { + else if( STREQ_LEN( token, IMAP4_RSP_FAILURE, strlen(IMAP4_RSP_FAILURE) ) == TRUE ) { fprintf( stderr, "%s: Failure (%s).\n", PACKAGE, token ); - len = -1; - break; + goto error; } else { fprintf( stderr, "%s: Unknown error code (%s).\n", PACKAGE, token ); - len = -1; - break; + goto error; } } else { fprintf( stderr, "%s: Error, transaction label mismatch.\n", PACKAGE ); - len = -1; - break; + goto error; } } - - loop_next: - token = strtok( NULL, line_delimiter ); - } - + } /* while( token ) */ + + /* Get next part of IMAP4 response. */ + goto get_packet; + + end: + /* No error. */ return len; + + error: + return -1; } @@ -126,7 +201,9 @@ IMAP4_SendCommand( int argc, char *argv[] ) if( wmnotify_infos.debug ) { tx_buffer[len] = '\0'; - printf( "Command: \"%s\"\n", tx_buffer ); + printf( ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" ); + printf( "IMAP4 Client Command (size %d bytes):\n%s\n", len, tx_buffer ); + printf( ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" ); } /* Adding termination characters. */ @@ -159,7 +236,7 @@ IMAP4_CheckForNewMail( void ) goto end; } - argv[0] = IMAP_CMD_LOGIN; + argv[0] = IMAP4_CMD_LOGIN; argv[1] = wmnotify_infos.username; argv[2] = wmnotify_infos.password; status = IMAP4_SendCommand( 3, argv ); @@ -168,8 +245,8 @@ IMAP4_CheckForNewMail( void ) goto imap4_logout; } - unseen_string_found = FALSE; - argv[0] = IMAP_CMD_EXAMINE; + /* Selecting the mailbox first. */ + argv[0] = IMAP4_CMD_EXAMINE; argv[1] = "inbox"; status = IMAP4_SendCommand( 2, argv ); if( status != EXIT_SUCCESS ) { @@ -177,12 +254,26 @@ IMAP4_CheckForNewMail( void ) goto imap4_logout; } + /* Searching in selected mailbox for new messages. We must use the UNSEEN search criteria + * instead of NEW (combination of RECENT and UNSEEN). If there is a new message, RECENT + * and UNSEEN will have entries. But if we recheck again later, RECENT will report zero. + * RECENT, when set, simply means that there are new messages since our last visit. + But, on the other hand, when using EXAMINE, no messages should lose their RECENT flag. */ + unseen_string_found = FALSE; + argv[0] = IMAP4_CMD_SEARCH_UNSEEN; + argv[1] = ""; + status = IMAP4_SendCommand( 1, argv ); + if( status != EXIT_SUCCESS ) { + new_messages = -1; + goto imap4_logout; + } + if( unseen_string_found == TRUE ) { new_messages = 1; } imap4_logout: - argv[0] = IMAP_CMD_LOGOUT; + argv[0] = IMAP4_CMD_LOGOUT; status = IMAP4_SendCommand( 1, argv ); if( status != EXIT_SUCCESS ) { new_messages = -1; diff --git a/src/imap.h b/src/imap.h index 75d7841..e759456 100644 --- a/src/imap.h +++ b/src/imap.h @@ -13,23 +13,6 @@ #endif -#define IMAP4_IN_BUF_SIZE 1024 - -#define IMAP4_SUCCESS "OK" -#define IMAP4_FAILURE "NO" -#define IMAP4_PROTOCOL_ERR "BAD" - -#define IMAP4_UNSEEN "* OK [UNSEEN" - - -#define IMAP_CMD_CAPABILITY "CAPABILITY" -#define IMAP_CMD_LOGIN "LOGIN" -#define IMAP_CMD_EXAMINE "EXAMINE" -#define IMAP_CMD_LOGOUT "LOGOUT" - -#define IMAP4_ENDL "\r\n" /* CRLF */ - - int IMAP4_CheckForNewMail( void ); diff --git a/src/network.c b/src/network.c index 2520000..caa1c28 100644 --- a/src/network.c +++ b/src/network.c @@ -30,6 +30,11 @@ #define RECV_FLAGS 0 +/* Common buffers for IMAP4 and POP3. */ +char tx_buffer[WMNOTIFY_BUFSIZE + 1]; +char rx_buffer[WMNOTIFY_BUFSIZE + 1]; + + int SocketOpen( char *server_name, int port ) { diff --git a/src/network.h b/src/network.h index e291aa2..19a9f2c 100644 --- a/src/network.h +++ b/src/network.h @@ -13,6 +13,11 @@ #include +/* POP3 responses may be up to 512 characters long, including the terminating + CRLF. */ +#define WMNOTIFY_BUFSIZE 1024 + + int SocketOpen( char *server_name, int port ); diff --git a/src/pop3.c b/src/pop3.c index d9da74f..268647c 100644 --- a/src/pop3.c +++ b/src/pop3.c @@ -23,8 +23,9 @@ #include "pop3.h" -static char tx_buffer[POP3_IN_BUF_SIZE]; -static char rx_buffer[POP3_IN_BUF_SIZE]; +/* Defined in network.c */ +extern char tx_buffer[WMNOTIFY_BUFSIZE + 1]; +extern char rx_buffer[WMNOTIFY_BUFSIZE + 1]; static int @@ -32,7 +33,7 @@ POP3_ReceiveResponse( void ) { int len; - len = WmnotifyGetResponse( rx_buffer, POP3_IN_BUF_SIZE ); + len = WmnotifyGetResponse( rx_buffer, WMNOTIFY_BUFSIZE ); if( len < 0 ) { perror( PACKAGE ); ErrorLocation( __FILE__, __LINE__ ); diff --git a/src/pop3.h b/src/pop3.h index 6f018ce..a617f1a 100644 --- a/src/pop3.h +++ b/src/pop3.h @@ -21,16 +21,13 @@ # define _SCOPE_ extern #endif -/* POP3 responses may be up to 512 characters long, including the terminating - CRLF. */ -#define POP3_IN_BUF_SIZE 512 + +#define POP3_ENDL "\r\n" /* CRLF */ #define POP3_CMD_USERNAME "USER" #define POP3_CMD_PASSWORD "PASS" #define POP3_CMD_STAT "STAT" #define POP3_CMD_QUIT "QUIT" -#define POP3_ENDL "\r\n" /* CRLF */ - #define POP3_RSP_SUCCESS "+OK" #define POP3_RSP_FAILURE "-ERR" -- 2.20.1