Added support for IMAP4 commands that may span multiple packets by calling recv(...
[dockapps/wmnotify.git] / src / imap.c
index 6c272d5..798d5b6 100644 (file)
 #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;