[go: up one dir, main page]

Menu

[r1]: / libpop3.c  Maximize  Restore  History

Download this file

420 lines (378 with data), 16.3 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
/*
libpop3 is a simple POP3 library that allows to connect to a pop3
mailbox, and retrieve/remove messages. This library is using the
RFC1939 as reference, and uses most basic POP3 commands to provide
maximum compatibility across servers.
The library uses following POP3 commands:
USER, PASS, LIST, RETR, DELE, QUIT.
Copyright (C) 2012,2013,2014 Mateusz Viste
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <arpa/inet.h> /* htons() */
#include <netdb.h> /* getaddrinfo() */
#include <stdio.h> /* snprintf() */
#include <stdlib.h> /* free() */
#include <string.h> /* memset() */
#include <sys/select.h> /* select() */
#include <sys/socket.h> /* recv(), send() */
#include <time.h> /* time() */
#include <unistd.h> /* close() */
#include "libpop3.h"
/* The list of defined values below is used for convenience, to avoid human bugs in the code of the library. */
#define ERR_COULDNTCONNECT -1
#define ERR_CONNCLOSEDUNEXP -2
#define ERR_SELECTERR -3
#define ERR_TIMEOUT -4
#define ERR_LINEOVERFLOW -5
#define ERR_BANNERWAIT -6
#define ERR_SRVNOTREADYNEGBANN -7
#define ERR_AUTHREJECT -8
#define ERR_LISTERR -9
#define ERR_MALLOCFAILED -10
#define ERR_LISTCORRUPTED -11
#define ERR_QUIT -12
#define ERR_DELEFAILED -13
#define ERR_RETRERR -14
#define ERR_BUFFTOOSHORT -15
#define ERR_DNSERR -16
/* This is an internal function, hidden to the external world. It's used to read a line of text from the 'sock' socket. Data is stored into the 'buff' buffer, up to maxlen bytes. The function will add a NULL char at the end of the buffer. \r and \n end of line bytes are stripped. The function waits no longer than 'timeout' seconds.
Returns the length of the line on success (can be zero), or a negative value otherwise. The returned error code can be translated into a humanly readable error message via libpop3_strerr(). */
static int libpop3_sockgetline(int sock, char *buff, int maxlen, int timeout) {
struct timeval selecttimeout;
fd_set fdsettable; /* this is a set of file descriptors for select() to poll */
time_t timestart = time(NULL); /* save current time */
int selectresult, bufflen = 0;
for (;;) {
selecttimeout.tv_usec = 0; /* set the select() timeout to block for 1s max */
selecttimeout.tv_sec = 1; /* we do it at every loop, because the timeval becomes undefined after every select() call */
FD_ZERO(&fdsettable); /* clear the whole set of descriptors, we will rebuild it in a moment */
FD_SET(sock, &fdsettable); /* add the socket to read */
selectresult = select(sock+1, &fdsettable, NULL, NULL, &selecttimeout); /* poll socket via select() */
if (selectresult > 0) { /* if there is anything to read... */
char bytebuff;
int recvres = recv(sock, &bytebuff, 1, MSG_WAITALL);
if (recvres <= 0) return(ERR_CONNCLOSEDUNEXP);
if (recvres == 1) {
if (bytebuff != '\r') { /* ignore CR chars */
if (bytebuff == '\n') { /* end of line */
buff[bufflen] = 0;
return(bufflen);
}
buff[bufflen] = bytebuff;
bufflen += 1;
if (bufflen == maxlen) return(ERR_LINEOVERFLOW);
}
}
} else if (selectresult < 0) { /* select() error */
return(ERR_SELECTERR);
}
/* check for timeout */
if ((time(NULL) - timestart) >= timeout) return(ERR_TIMEOUT);
}
}
/* Connect to the host 'host' on port 'port'. 'host' can be a hostname, an IPv4 address or an IPv6 address.
Returns a socket on success, or negative value on failure. */
static int tcpconnect(char *host, int port) {
int sock;
struct addrinfo hints;
struct addrinfo *result, *rp;
char portstr[8];
snprintf(portstr, 8, "%d", port); /* generates a string with port, as required by getaddrinfo */
/* Obtain address(es) matching host/port */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_protocol = 0; /* Any protocol */
if (getaddrinfo(host, portstr, &hints, &result) != 0) return(ERR_DNSERR);
/* getaddrinfo() returns a list of address structures. Try each address until we successfully connect(2).
If socket(2) (or connect(2)) fails, we (close the socket and) try the next address. */
for (rp = result; rp != NULL; rp = rp->ai_next) {
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sock >= 0) {
if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1) break; /* Success */
close(sock);
}
}
if (rp == NULL) { /* No address succeeded */
freeaddrinfo(result); /* No longer needed */
return(ERR_COULDNTCONNECT);
}
freeaddrinfo(result); /* No longer needed */
return(sock);
}
/* Deallocaates all memory used by the 'mlist' maillist. This will call free() on every node of the linked list. If 'mlist' is NULL, nothing will be done. */
void libpop3_destroymlist(struct maillist *mlist) {
struct maillist *curmlist = mlist, *nextmlist;
while (curmlist != NULL) {
nextmlist = curmlist->nextmail;
free(curmlist);
curmlist = nextmlist;
}
}
/* Connects to the 'server' POP3 server on port 'port', trying to authenticate using login 'login' and password 'pass'. The 'server' parameter can be a FQDN, or an IPv4/IPv6 IP address. The list of mails will be generated automatically, and the first mail of the linked list will be pointed by *mlist. *mlist will be set to NULL if no mail awaits on the server. Don't forget to call libpop3_destroymlist() on the new maillist when you are done!
Returns a socket to the POP3 connection on success, and a negative number on failure. Failure codes can then be transformed into humanly readable error messages via the libpop3_strerr() function. */
int libpop3_connect(char *server, int port, char *login, char *pass, struct maillist **mlist) {
int sock; /* Socket descriptor */
#define pop3buff_maxlen 1024
char pop3buff[pop3buff_maxlen];
char *mailidstr, *lengthstr;
struct maillist *curlist;
int x, state;
sock = tcpconnect(server, port);
if (sock < 0) return(sock); /* quit on error */
/* wait for the answer and check it */
if (libpop3_sockgetline(sock, pop3buff, pop3buff_maxlen, 30) < 1) {
close(sock);
return(ERR_BANNERWAIT);
} else if (pop3buff[0] != '+') { /* check answer */
close(sock);
return(ERR_SRVNOTREADYNEGBANN);
}
/* send the USER command */
snprintf(pop3buff, pop3buff_maxlen, "USER %s\r\n", login);
if (send(sock, pop3buff, strlen(pop3buff), MSG_WAITALL) != (signed) strlen(pop3buff)) {
close(sock);
return(ERR_CONNCLOSEDUNEXP);
}
/* wait for the answer and check it */
if (libpop3_sockgetline(sock, pop3buff, pop3buff_maxlen, 30) < 1) {
close(sock);
return(ERR_CONNCLOSEDUNEXP);
} else if (pop3buff[0] != '+') { /* check answer */
close(sock);
return(ERR_AUTHREJECT);
}
/* send the PASS command */
snprintf(pop3buff, pop3buff_maxlen, "PASS %s\r\n", pass);
if (send(sock, pop3buff, strlen(pop3buff), MSG_WAITALL) != (signed) strlen(pop3buff)) {
close(sock);
return(ERR_CONNCLOSEDUNEXP);
}
/* wait for the answer and check it */
if (libpop3_sockgetline(sock, pop3buff, pop3buff_maxlen, 30) < 1) {
close(sock);
return(ERR_CONNCLOSEDUNEXP);
} else if (pop3buff[0] != '+') { /* check answer */
close(sock);
return(ERR_AUTHREJECT);
}
/* send the LIST command */
snprintf(pop3buff, pop3buff_maxlen, "LIST\r\n");
if (send(sock, pop3buff, strlen(pop3buff), MSG_WAITALL) != (signed) strlen(pop3buff)) {
close(sock);
return(ERR_CONNCLOSEDUNEXP);
}
/* wait for the answer and check it */
if (libpop3_sockgetline(sock, pop3buff, pop3buff_maxlen, 10) < 1) {
close(sock);
return(ERR_CONNCLOSEDUNEXP);
} else if (pop3buff[0] != '+') { /* check answer */
close(sock);
return(ERR_LISTERR);
}
*mlist = NULL;
curlist = *mlist;
/* wait for the multiline answer */
for (;;) {
if (libpop3_sockgetline(sock, pop3buff, pop3buff_maxlen, 10) < 1) {
close(sock);
return(ERR_CONNCLOSEDUNEXP);
}
if (pop3buff[0] == '.') break; /* end of list */
if (curlist == NULL) { /* this is the first mail */
*mlist = malloc(sizeof(struct maillist));
curlist = *mlist;
} else { /* this is some other mail */
curlist->nextmail = malloc(sizeof(struct maillist));
curlist = curlist->nextmail;
}
if (curlist == NULL) { /* memory error */
libpop3_destroymlist(*mlist); /* free all memory that we took so far */
*mlist = NULL; /* set mlist to NULL to indicate that there is nothing there */
return(ERR_MALLOCFAILED);
}
curlist->nextmail = NULL; /* write the linked list terminator */
/* process the answer line to retrieve the mail id and size */
mailidstr = pop3buff;
lengthstr = NULL;
state = 0;
for (x = 0; pop3buff[x] != 0; x++) {
if (pop3buff[x] == ' ') {
pop3buff[x] = 0; /* replace spaces by string terminators */
if (state == 0) state = 1; /* if was reading mailid, switch to 'looking for size' */
} else { /* character is not a space */
if (state == 1) { /* looking for size? found it! */
state = 2;
lengthstr = &pop3buff[x];
}
}
}
if (lengthstr == NULL) { /* corrupted LIST result */
libpop3_destroymlist(*mlist);
*mlist = NULL;
return(ERR_LISTCORRUPTED);
}
/* write values into the curlist structure and reloop */
curlist->mailid = atoi(mailidstr);
curlist->length = atoi(lengthstr);
}
return(sock); /* return the socket to the POP3 connection */
}
/* Gets the content of the mail with id 'mailid' from server pointed by 'popconn'. The content of the mail is written into the memory space pointed by 'mailmsg', up to 'maxlen' bytes. the mail will be terminated with a NULL byte as a string terminator. Also, the function needs a 4 additional bytes of memory for its own needs, therefore make sure to allocate mailmsg with at least 5 bytes more than the actual expected length of the message.
Returns the length of the fetched message on success, a negative value otherwise. The returned error code can be translated into an error message via libpop3_strerr(). */
int libpop3_getmail(int popconn, int mailid, char *mailmsg, int maxlen) {
char buff[256];
int msglen = 0, state = 0, lastlf = 0;
snprintf(buff, 256, "RETR %d\r\n", mailid);
if (send(popconn, buff, strlen(buff), MSG_WAITALL) != (signed) strlen(buff)) {
close(popconn);
return(ERR_CONNCLOSEDUNEXP);
}
/* wait for the answer and check it */
if (libpop3_sockgetline(popconn, buff, 256, 20) < 1) {
close(popconn);
return(ERR_CONNCLOSEDUNEXP);
} else if (buff[0] != '+') { /* check answer */
return(ERR_RETRERR);
}
/* start processing the mail, and store it into mailmsg */
for (;;) {
if (recv(popconn, buff, 1, MSG_WAITALL) != 1) return(ERR_CONNCLOSEDUNEXP);
if (buff[0] != '\r') {
if (buff[0] == '\n') {
if (state == 2) { /* end of message */
msglen = lastlf + 1; /* trim out the dot terminator */
break;
} else {
lastlf = msglen;
state = 1;
}
} else { /* not a LF */
if (buff[0] == '.') {
if (state == 1) {
state = 2;
} else if (state == 2) {
msglen -= 1; /* ignore dot stuffing */
state = 0;
}
} else {
state = 0;
}
}
}
mailmsg[msglen++] = buff[0];
if (msglen >= maxlen) return(ERR_BUFFTOOSHORT);
}
mailmsg[msglen] = 0;
return(msglen);
}
/* Removes the mail with id 'mailid' from the pop3 connection pointed by 'popconn'. Returns 0 on success, non-zero otherwise. The returned error code can be translated into an error message via libpop3_strerr(). */
int libpop3_delmail(int popconn, int mailid) {
char buff[1024];
snprintf(buff, 1024, "DELE %d\r\n", mailid);
if (send(popconn, buff, strlen(buff), MSG_WAITALL) != (signed) strlen(buff)) {
close(popconn);
return(ERR_CONNCLOSEDUNEXP);
}
/* wait for the answer and check it */
if (libpop3_sockgetline(popconn, buff, 1024, 30) < 1) {
close(popconn);
return(ERR_CONNCLOSEDUNEXP);
} else if (buff[0] != '+') { /* check answer */
return(ERR_DELEFAILED);
}
return(0);
}
/* Closes the communication with the pop3 server pointed by 'popconn'. Returns 0 on success, non-zero otherwise. The returned error code can be translated into an error message via libpop3_strerr(). */
int libpop3_disconnect(int popconn) {
char buff[1024];
if (send(popconn, "QUIT\r\n", 6, MSG_WAITALL) != 6) {
close(popconn);
return(ERR_CONNCLOSEDUNEXP);
}
/* wait for the answer and check it */
if (libpop3_sockgetline(popconn, buff, 1024, 30) < 1) {
close(popconn);
return(ERR_CONNCLOSEDUNEXP);
} else if (buff[0] != '+') { /* check answer */
close(popconn);
return(ERR_QUIT);
}
close(popconn);
return(0);
}
/* Translates a libpop3 error code into a humanly readable error message. Takes the libpop3 error code as parameter, and returns a pointer to the error message string. */
char *libpop3_strerr(int errcode) {
if (errcode >= 0) return("Success");
switch (errcode) {
case ERR_COULDNTCONNECT:
return("Could not establish a TCP connection with the remote server");
break;
case ERR_CONNCLOSEDUNEXP:
return("Connection with the server has been unexpectedly closed");
break;
case ERR_SELECTERR:
return("Unexpected select() error");
break;
case ERR_TIMEOUT:
return("Timeout");
break;
case ERR_LINEOVERFLOW:
return("Line buffer overflow");
break;
case ERR_BANNERWAIT:
return("Connection error while waiting for server banner");
break;
case ERR_SRVNOTREADYNEGBANN:
return("POP3 server not ready (negative banner)");
break;
case ERR_AUTHREJECT:
return("POP3 auth failed");
break;
case ERR_LISTERR:
return("Server rejected our LIST request");
break;
case ERR_MALLOCFAILED:
return("Not enough memory");
break;
case ERR_LISTCORRUPTED:
return("Server sent a corrupted answer to LIST");
break;
case ERR_QUIT:
return("Server returned an error condition before QUITing");
break;
case ERR_DELEFAILED:
return("DELE operation failed on server");
break;
case ERR_RETRERR:
return("RETR operation failed on server");
break;
case ERR_BUFFTOOSHORT:
return("Supplied memory buffer is too short");
break;
case ERR_DNSERR:
return("Name resolution failure");
break;
default:
return("Unknown error");
break;
}
}