2 * imap.c -- Routines for communication with an IMAP server
4 * Copyright (C) 2003 Hugo Villeneuve <hugo@hugovil.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
21 /* Define filename_M */
31 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 #include <ctype.h> /* for isdigit() */
45 #define IMAP4_ENDL "\r\n" /* CRLF */
47 #define IMAP4_CMD_CAPABILITY "CAPABILITY"
48 #define IMAP4_CMD_LOGIN "LOGIN"
49 #define IMAP4_CMD_SELECT "SELECT"
50 #define IMAP4_CMD_EXAMINE "EXAMINE"
51 #define IMAP4_CMD_LOGOUT "LOGOUT"
52 #define IMAP4_CMD_SEARCH_UNSEEN "SEARCH UNSEEN"
54 /* Responses from IMAP4 server. */
55 #define IMAP4_RSP_SUCCESS "OK"
56 #define IMAP4_RSP_FAILURE "NO"
57 #define IMAP4_RSP_PROTOCOL_ERR "BAD"
58 #define IMAP4_RSP_SEARCH_UNSEEN "* SEARCH " /* This is the line that will be returned by
59 * the IMAP4 server after receiving the
60 * "SEARCH UNSEEN" command, followed by the
61 * messages ID of the unseen messages. */
64 static int tlabel
= 0;
65 static int tlabel_len
;
66 static int unseen_string_found
;
68 /* Defined in network.c */
69 extern char tx_buffer
[WMNOTIFY_BUFSIZE
+ 1];
70 extern char rx_buffer
[WMNOTIFY_BUFSIZE
+ 1];
74 IMAP4_ReceiveResponse( void )
80 /* All interactions transmitted by client and server are in the form of
81 lines, that is, strings that end with a CRLF. The protocol receiver
82 of an IMAP4rev1 client or server is either reading a line, or is
83 reading a sequence of octets with a known count followed by a line. */
86 len
= WmnotifyGetResponse( rx_buffer
, WMNOTIFY_BUFSIZE
);
88 /* An error occured. WmnotifyGetResponse() should have printed an error message. */
92 /* The return value will be 0 when the peer has performed an orderly shutdown. */
93 if( wmnotify_infos
.debug
) {
94 fprintf( stderr
, "IMAP server has closed connection.\n" );
98 else if( len
== WMNOTIFY_BUFSIZE
) {
99 if( wmnotify_infos
.debug
) {
100 ErrorLocation( __FILE__
, __LINE__
);
101 fprintf( stderr
, "Response too big (%d bytes) to fit in receive buffer.\n", len
);
106 /* We suppose that, if a partial response packet was sent, it is not broken in the middle
107 of a line (to confirm). Normally, each string is terminated by CRLF. */
108 if( STREQ_LEN( &rx_buffer
[ len
- 2 ], IMAP4_ENDL
, 2 ) == false ) {
109 /* No CRLF found at the end of the buffer --> not handled by wmnotify. */
110 ErrorLocation( __FILE__
, __LINE__
);
111 fprintf( stderr
, "Response buffer doesn't contain CRLF at the end.\n" );
115 if( wmnotify_infos
.debug
) {
116 printf( "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" );
117 printf( "IMAP4 Server Response (size %d bytes):\n", len
);
118 printf( "%s", rx_buffer
);
119 printf( "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" );
122 /* Converting the last CRLF into a LF followed by a NULL termination character. */
123 rx_buffer
[ len
- 2 ] = '\n';
124 rx_buffer
[ len
- 1 ] = '\0';
126 /* Check the Server Completion Response returned by the IMAP4 server. There are currently
127 * three Server Completion Responses codes: success ("OK"), failure ("NO") and protocol error
131 while( ( token
= strsep( &stringp
, "\n" ) ) != NULL
) {
133 /* In case no delimiter was found, the token is taken to
134 be the entire string *stringp, and *stringp is made NULL. */
135 if( stringp
== NULL
) {
136 if( token
[0] == '\0' ) {
137 /* This means we finished parsing the last line of the buffer, but we need to
138 get more data to continue process the next part of the IMAP4 response. */
142 /* This should never happen. */
143 ErrorLocation( __FILE__
, __LINE__
);
144 fprintf( stderr
, " Delimiter not found in strsep() call.\n" );
149 if( token
== NULL
) {
150 /* This should never happen. */
151 ErrorLocation( __FILE__
, __LINE__
);
152 fprintf( stderr
, " NULL token returned by strsep().\n" );
156 if( token
[0] == '*' ) {
157 /* Untagged response. If there is a space after the SEARCH response, it means
158 * at least 1 message is unseen. */
159 if( STREQ_LEN( token
, IMAP4_RSP_SEARCH_UNSEEN
, strlen(IMAP4_RSP_SEARCH_UNSEEN
) ) == true ) {
160 unseen_string_found
= true;
164 /* Must be the status... */
166 /* We check for the correct transaction label plus a space. */
167 if( STREQ_LEN( token
, tx_buffer
, tlabel_len
+ 1 ) == true ) {
168 token
+= tlabel_len
+ 1;
169 if( STREQ_LEN( token
, IMAP4_RSP_SUCCESS
, strlen(IMAP4_RSP_SUCCESS
) ) == true ) {
170 goto end
; /* OK, no errors. */
172 else if( STREQ_LEN( token
, IMAP4_RSP_PROTOCOL_ERR
, strlen(IMAP4_RSP_PROTOCOL_ERR
) ) == true ) {
173 fprintf( stderr
, "%s: Protocol error (%s).\n", PACKAGE
, token
);
176 else if( STREQ_LEN( token
, IMAP4_RSP_FAILURE
, strlen(IMAP4_RSP_FAILURE
) ) == true ) {
177 fprintf( stderr
, "%s: Failure (%s).\n", PACKAGE
, token
);
181 fprintf( stderr
, "%s: Unknown error code (%s).\n", PACKAGE
, token
);
186 fprintf( stderr
, "%s: Error, transaction label mismatch.\n", PACKAGE
);
190 } /* while( token ) */
192 /* Get next part of IMAP4 response. */
205 IMAP4_SendCommand( int argc
, char *argv
[] )
210 /* Adding Transaction Label. */
214 len
+= sprintf( tx_buffer
+ len
, "%d", tlabel
);
217 /* Adding command and it's arguments. */
218 for( i
= 0; i
< argc
; i
++ ) {
219 len
+= sprintf( tx_buffer
+ len
, " %s", argv
[i
] );
222 if( wmnotify_infos
.debug
) {
223 tx_buffer
[len
] = '\0';
224 printf( ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" );
225 printf( "IMAP4 Client Command (size %d bytes):\n%s\n", len
, tx_buffer
);
226 printf( ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" );
229 /* Adding termination characters. */
230 len
+= sprintf( tx_buffer
+ len
, IMAP4_ENDL
);
232 len
= WmnotifySendData( tx_buffer
, len
);
237 len
= IMAP4_ReceiveResponse();
247 IMAP4_CheckForNewMail( void )
250 int new_messages
= 0;
253 status
= ConnectionEstablish( wmnotify_infos
.server_name
, wmnotify_infos
.port
);
254 if( status
!= EXIT_SUCCESS
) {
259 argv
[0] = IMAP4_CMD_LOGIN
;
260 argv
[1] = wmnotify_infos
.username
;
261 argv
[2] = wmnotify_infos
.password
;
262 status
= IMAP4_SendCommand( 3, argv
);
263 if( status
!= EXIT_SUCCESS
) {
268 /* Selecting the mailbox first. */
269 argv
[0] = IMAP4_CMD_EXAMINE
;
270 argv
[1] = wmnotify_infos
.imap_folder
;
271 status
= IMAP4_SendCommand( 2, argv
);
272 if( status
!= EXIT_SUCCESS
) {
277 /* Searching in selected mailbox for new messages. We must use the UNSEEN search criteria
278 * instead of NEW (combination of RECENT and UNSEEN). If there is a new message, RECENT
279 * and UNSEEN will have entries. But if we recheck again later, RECENT will report zero.
280 * RECENT, when set, simply means that there are new messages since our last visit.
281 But, on the other hand, when using EXAMINE, no messages should lose their RECENT flag. */
282 unseen_string_found
= false;
283 argv
[0] = IMAP4_CMD_SEARCH_UNSEEN
;
285 status
= IMAP4_SendCommand( 1, argv
);
286 if( status
!= EXIT_SUCCESS
) {
291 if( unseen_string_found
== true ) {
296 argv
[0] = IMAP4_CMD_LOGOUT
;
297 status
= IMAP4_SendCommand( 1, argv
);
298 if( status
!= EXIT_SUCCESS
) {
302 status
= ConnectionTerminate();
303 if( status
!= EXIT_SUCCESS
) {