Update gnutls code (require at least 2.2.0).
[dockapps.git] / wmbiff / wmbiff / Pop3Client.c
blob6792c42c030f14e13b01a83698f09750b0c9825a
1 /* $Id: Pop3Client.c,v 1.23 2004/12/12 00:01:53 bluehal Exp $ */
2 /* Author : Scott Holden ( scotth@thezone.net )
3 Modified : Yong-iL Joh ( tolkien@mizi.com )
4 Modified : Jorge GarcĂ­a ( Jorge.Garcia@uv.es )
5 Modified ; Mark Hurley ( debian4tux@telocity.com )
6 Modified : Neil Spring ( nspring@cs.washington.edu )
7 *
8 * Pop3 Email checker.
10 * Last Updated : Tue Nov 13 13:45:23 PST 2001
13 #ifdef HAVE_CONFIG_H
14 #include <config.h>
15 #endif
17 #include "Client.h"
18 #include "charutil.h"
19 #include "regulo.h"
20 #include "MessageList.h"
21 #include <strings.h>
22 #include "tlsComm.h"
23 #include "passwordMgr.h"
25 #ifdef USE_DMALLOC
26 #include <dmalloc.h>
27 #endif
29 extern int Relax;
30 /* temp */
31 static void ask_user_for_password( /*@notnull@ */ Pop3 pc, int bFlushCache);
33 #define PCU (pc->u).pop_imap
34 #define POP_DM(pc, lvl, args...) DM(pc, lvl, "pop3: " args)
36 #ifdef HAVE_GCRYPT_H
37 static struct connection_state *authenticate_md5( /*@notnull@ */ Pop3 pc, struct connection_state * scs,
38 char *unused);
39 static struct connection_state *authenticate_apop( /*@notnull@ */ Pop3 pc, struct connection_state * scs,
40 char *apop_str);
41 #endif
42 static struct connection_state *authenticate_plaintext( /*@notnull@ */ Pop3 pc, struct connection_state * scs,
43 char *unused);
45 void pop3_cacheHeaders( /*@notnull@ */ Pop3 pc);
47 extern void imap_releaseHeaders(Pop3 pc
48 __attribute__ ((unused)),
49 struct msglst *h);
51 extern struct connection_state *state_for_pcu(Pop3 pc);
53 static struct authentication_method {
54 const char *name;
55 /* callback returns the connection state pointer if successful,
56 NULL if failed */
57 struct connection_state *(*auth_callback) (Pop3 pc, struct connection_state * scs, char *apop_str);
58 } auth_methods[] = {
60 #ifdef HAVE_GCRYPT_H
61 "cram-md5", authenticate_md5}, {
62 "apop", authenticate_apop}, {
63 #endif
64 "plaintext", authenticate_plaintext}, {
65 NULL, NULL}
68 /*@null@*/
69 struct connection_state *pop3Login(Pop3 pc)
71 int fd;
72 char buf[BUF_SIZE];
73 char apop_str[BUF_SIZE];
74 char *ptr1, *ptr2;
75 struct authentication_method *a;
76 struct connection_state *scs;
77 char *connection_name;
80 apop_str[0] = '\0'; /* if defined, server supports apop */
82 if ((fd = sock_connect(PCU.serverName, PCU.serverPort)) == -1) {
83 POP_DM(pc, DEBUG_ERROR, "Not Connected To Server '%s:%d'\n",
84 PCU.serverName, PCU.serverPort);
85 return NULL;
88 connection_name = malloc(strlen(PCU.serverName) + 20);
89 sprintf(connection_name, "%s:%d", PCU.serverName, PCU.serverPort);
91 if (PCU.dossl != 0) {
92 scs = initialize_gnutls(fd, connection_name, pc, PCU.serverName);
93 if (scs == NULL) {
94 POP_DM(pc, DEBUG_ERROR, "Failed to initialize TLS\n");
95 return NULL;
97 } else {
98 scs = initialize_unencrypted(fd, connection_name, pc);
101 tlscomm_gets(buf, BUF_SIZE, scs);
102 POP_DM(pc, DEBUG_INFO, "%s", buf);
104 /* Detect APOP, copy challenge into apop_str */
105 for (ptr1 = buf + strlen(buf), ptr2 = NULL; ptr1 > buf; --ptr1) {
106 if (*ptr1 == '>') {
107 ptr2 = ptr1;
108 } else if (*ptr1 == '<') {
109 if (ptr2) {
110 *(ptr2 + 1) = 0;
111 strncpy(apop_str, ptr1, BUF_SIZE);
113 break;
118 /* try each authentication method in turn. */
119 for (a = auth_methods; a->name != NULL; a++) {
120 /* was it specified or did the user leave it up to us? */
121 if (PCU.authList[0] == '\0' || strstr(PCU.authList, a->name))
122 /* did it work? */
123 if ((a->auth_callback(pc, scs, apop_str)) != NULL)
124 return (scs);
127 /* if authentication worked, we won't get here */
128 POP_DM(pc, DEBUG_ERROR,
129 "All Pop3 authentication methods failed for '%s@%s:%d'\n",
130 PCU.userName, PCU.serverName, PCU.serverPort);
131 tlscomm_printf(scs, "QUIT\r\n");
132 tlscomm_close(scs);
134 return NULL;
137 int pop3CheckMail( /*@notnull@ */ Pop3 pc)
139 struct connection_state *scs;
140 int read;
141 char buf[BUF_SIZE];
143 scs = pop3Login(pc);
144 if (scs == NULL)
145 return -1;
147 tlscomm_printf(scs, "STAT\r\n");
148 if( ! tlscomm_expect(scs, "+", buf, BUF_SIZE) ) {
149 POP_DM(pc, DEBUG_ERROR,
150 "Error Receiving Stats '%s@%s:%d'\n",
151 PCU.userName, PCU.serverName, PCU.serverPort);
152 POP_DM(pc, DEBUG_INFO, "It said: %s\n", buf);
153 return -1;
154 } else {
155 sscanf(buf, "+OK %d", &(pc->TotalMsgs));
158 /* - Updated - Mark Hurley - debian4tux@telocity.com
159 * In compliance with RFC 1725
160 * which removed the LAST command, any servers
161 * which follow this spec will return:
162 * -ERR unimplimented
163 * We will leave it here for those servers which haven't
164 * caught up with the spec.
166 tlscomm_printf(scs, "LAST\r\n");
167 tlscomm_gets(buf, BUF_SIZE, scs);
168 if (buf[0] != '+') {
169 /* it is not an error to receive this according to RFC 1725 */
170 /* no error should be returned */
171 pc->UnreadMsgs = pc->TotalMsgs;
172 // there's also a LIST command... not sure how to make use of it. */
173 } else {
174 sscanf(buf, "+OK %d", &read);
175 pc->UnreadMsgs = pc->TotalMsgs - read;
178 tlscomm_printf(scs, "QUIT\r\n");
179 tlscomm_close(scs);
181 return 0;
185 struct msglst *pop_getHeaders( /*@notnull@ */ Pop3 pc)
187 if (pc->headerCache == NULL)
188 pop3_cacheHeaders(pc);
189 if (pc->headerCache != NULL)
190 pc->headerCache->in_use = 1;
191 return pc->headerCache;
196 int pop3Create(Pop3 pc, const char *str)
198 /* POP3 format: pop3:user:password@server[:port] */
199 /* new POP3 format: pop3:user password server [port] */
200 /* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
201 int i;
202 int matchedchars;
203 /* ([^: ]+) user
204 ([^@]+) or ([^ ]+) password
205 ([^: ]+) server
206 ([: ][0-9]+)? optional port
207 ' *' gobbles trailing whitespace before authentication types.
208 use separate regexes for old and new types to permit
209 use of '@' in passwords
211 const char *regexes[] = {
212 "pop3s?:([^: ]{1,32}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
213 "pop3s?:([^: ]{1,32}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
214 // "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^: ]+)( [0-9]+)? *",
215 // "pop3:([^: ]{1,32}):([^@]{0,32})@([^: ]+)(:[0-9]+)? *",
216 NULL
218 struct regulo regulos[] = {
219 {1, PCU.userName, regulo_strcpy},
220 {2, PCU.password, regulo_strcpy},
221 {3, PCU.serverName, regulo_strcpy},
222 {4, &PCU.serverPort, regulo_atoi},
223 {6, PCU.authList, regulo_strcpy_tolower},
224 {0, NULL, NULL}
227 if (Relax) {
228 regexes[0] =
229 "pop3:([^: ]{1,32}):([^@]{0,32})@([^/: ]+)(:[0-9]+)?( *(.*))?$";
230 regexes[1] =
231 "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^/: ]+)( [0-9]+)?( *(.*))?$";
234 if (strncmp("pop3s:", str, 6) == 0) {
235 #ifdef HAVE_GNUTLS_GNUTLS_H
236 PCU.dossl = 1;
237 #else
238 printf("This copy of wmbiff was not compiled with gnutls;\n"
239 "imaps is unavailable. Exiting to protect your\n"
240 "passwords and privacy.\n");
241 exit(EXIT_FAILURE);
242 #endif
243 } else {
244 PCU.dossl = 0;
248 /* defaults */
249 PCU.serverPort = (PCU.dossl != 0) ? 995 : 110;
250 PCU.authList[0] = '\0';
252 for (matchedchars = 0, i = 0;
253 regexes[i] != NULL && matchedchars <= 0; i++) {
254 matchedchars = regulo_match(regexes[i], str, regulos);
257 /* failed to match either regex */
258 if (matchedchars <= 0) {
259 pc->label[0] = '\0';
260 POP_DM(pc, DEBUG_ERROR, "Couldn't parse line %s (%d)\n"
261 " If this used to work, run wmbiff with the -relax option, and\n "
262 " send mail to wmbiff-devel@lists.sourceforge.net with the hostname\n"
263 " of your mail server.\n", str, matchedchars);
264 return -1;
266 // grab_authList(str + matchedchars, PCU.authList);
268 PCU.password_len = strlen(PCU.password);
269 if (PCU.password[0] == '\0') {
270 PCU.interactive_password = 1;
271 } else {
272 // ENFROB(PCU.password);
275 POP_DM(pc, DEBUG_INFO, "userName= '%s'\n", PCU.userName);
276 POP_DM(pc, DEBUG_INFO, "password is %ld chars long\n",
277 strlen(PCU.password));
278 POP_DM(pc, DEBUG_INFO, "serverName= '%s'\n", PCU.serverName);
279 POP_DM(pc, DEBUG_INFO, "serverPort= '%d'\n", PCU.serverPort);
280 POP_DM(pc, DEBUG_INFO, "authList= '%s'\n", PCU.authList);
282 pc->checkMail = pop3CheckMail;
283 pc->getHeaders = pop_getHeaders;
284 pc->TotalMsgs = 0;
285 pc->UnreadMsgs = 0;
286 pc->OldMsgs = -1;
287 pc->OldUnreadMsgs = -1;
289 return 0;
293 #ifdef HAVE_GCRYPT_H
294 static struct connection_state *authenticate_md5(Pop3 pc, struct connection_state * scs, char *apop_str
295 __attribute__ ((unused)))
297 char buf[BUF_SIZE];
298 char buf2[BUF_SIZE];
299 unsigned char *md5;
300 gcry_md_hd_t gmh;
301 gcry_error_t rc;
303 /* See if MD5 is supported */
304 tlscomm_printf(scs, "AUTH CRAM-MD5\r\n");
305 tlscomm_gets(buf, BUF_SIZE, scs);
306 POP_DM(pc, DEBUG_INFO, "%s", buf);
308 if (buf[0] != '+' || buf[1] != ' ') {
309 /* nope, not supported. */
310 return NULL;
313 Decode_Base64(buf + 2, buf2);
314 POP_DM(pc, DEBUG_INFO, "CRAM-MD5 challenge: %s\n", buf2);
316 strcpy(buf, PCU.userName);
317 strcat(buf, " ");
320 rc = gcry_md_open(&gmh, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
321 if (rc != 0) {
322 POP_DM(pc, DEBUG_ERROR, "unable to initialize gcrypt md5.\n");
323 return NULL;
325 gcry_md_setkey(gmh, PCU.password, strlen(PCU.password));
326 gcry_md_write(gmh, (unsigned char *) buf2, strlen(buf2));
327 gcry_md_final(gmh);
328 md5 = gcry_md_read(gmh, 0);
329 /* hmac_md5(buf2, strlen(buf2), PCU.password,
330 strlen(PCU.password), md5); */
331 Bin2Hex(md5, 16, buf2);
332 gcry_md_close(gmh);
334 strcat(buf, buf2);
335 POP_DM(pc, DEBUG_INFO, "CRAM-MD5 response: %s\n", buf);
336 Encode_Base64(buf, buf2);
338 tlscomm_printf(scs, "%s\r\n", buf2);
339 tlscomm_gets(buf, BUF_SIZE, scs);
341 if (!strncmp(buf, "+OK", 3))
342 return scs; /* AUTH successful */
343 else {
344 POP_DM(pc, DEBUG_ERROR,
345 "CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
346 PCU.userName, PCU.serverName, PCU.serverPort);
347 fprintf(stderr, "It said %s", buf);
348 return NULL;
352 static struct connection_state *authenticate_apop(Pop3 pc, struct connection_state * scs, char *apop_str)
354 gcry_md_hd_t gmh;
355 gcry_error_t rc;
356 char buf[BUF_SIZE];
357 unsigned char *md5;
360 if (apop_str[0] == '\0') {
361 /* server doesn't support apop. */
362 return (NULL);
364 POP_DM(pc, DEBUG_INFO, "APOP challenge: %s\n", apop_str);
365 strcat(apop_str, PCU.password);
367 rc = gcry_md_open(&gmh, GCRY_MD_MD5, 0);
368 if (rc != 0) {
369 POP_DM(pc, DEBUG_ERROR, "unable to initialize gcrypt md5.\n");
370 return NULL;
372 gcry_md_write(gmh, (unsigned char *) apop_str, strlen(apop_str));
373 gcry_md_final(gmh);
374 md5 = gcry_md_read(gmh, 0);
375 Bin2Hex(md5, 16, buf);
376 gcry_md_close(gmh);
378 POP_DM(pc, DEBUG_INFO, "APOP response: %s %s\n", PCU.userName, buf);
379 tlscomm_printf(scs, "APOP %s %s\r\n", PCU.userName, buf);
380 tlscomm_gets(buf, BUF_SIZE, scs);
382 if (!strncmp(buf, "+OK", 3))
383 return scs; /* AUTH successful */
384 else {
385 POP_DM(pc, DEBUG_ERROR,
386 "APOP AUTH failed for user '%s@%s:%d'\n",
387 PCU.userName, PCU.serverName, PCU.serverPort);
388 POP_DM(pc, DEBUG_INFO, "It said %s", buf);
389 return NULL;
392 #endif /* HAVE_GCRYPT_H */
394 /*@null@*/
395 static struct connection_state *authenticate_plaintext( /*@notnull@ */ Pop3 pc,
396 struct connection_state * scs, char *apop_str
397 __attribute__ ((unused)))
399 char buf[BUF_SIZE];
401 tlscomm_printf(scs, "USER %s\r\n", PCU.userName);
402 if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
403 POP_DM(pc, DEBUG_ERROR,
404 "Error reading from server authenticating '%s@%s:%d'\n",
405 PCU.userName, PCU.serverName, PCU.serverPort);
406 return NULL;
408 if (buf[0] != '+') {
409 POP_DM(pc, DEBUG_ERROR,
410 "Failed user name when authenticating '%s@%s:%d'\n",
411 PCU.userName, PCU.serverName, PCU.serverPort);
412 /* deb #128863 might be easier if we printed: */
413 POP_DM(pc, DEBUG_ERROR, "The server's error message was: %s\n",
414 buf);
415 return NULL;
419 tlscomm_printf(scs, "PASS %s\r\n", PCU.password);
420 if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
421 POP_DM(pc, DEBUG_ERROR,
422 "Error reading from server (2) authenticating '%s@%s:%d'\n",
423 PCU.userName, PCU.serverName, PCU.serverPort);
424 return NULL;
426 if (strncmp(buf, "-ERR [AUTH] Password required", 20) == 0) {
427 if (PCU.interactive_password) {
428 PCU.password[0] = '\0';
429 ask_user_for_password(pc, 1); /* 1=overwrite the cache */
430 tlscomm_printf(scs, "PASS %s\r\n", PCU.password);
431 if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
432 POP_DM(pc, DEBUG_ERROR,
433 "Error reading from server (2) authenticating '%s@%s:%d'\n",
434 PCU.userName, PCU.serverName, PCU.serverPort);
435 return NULL;
439 if (buf[0] != '+') {
440 POP_DM(pc, DEBUG_ERROR,
441 "Failed password when authenticating '%s@%s:%d'\n",
442 PCU.userName, PCU.serverName, PCU.serverPort);
443 POP_DM(pc, DEBUG_ERROR, "The server's error message was: %s\n",
444 buf);
445 return NULL;
448 return scs;
451 void pop3_cacheHeaders( /*@notnull@ */ Pop3 pc)
453 char buf[BUF_SIZE];
454 struct connection_state *scs;
455 int i;
457 if (pc->headerCache != NULL) {
458 /* decrement the reference count, and free our version */
459 imap_releaseHeaders(pc, pc->headerCache);
460 pc->headerCache = NULL;
463 POP_DM(pc, DEBUG_INFO, "working headers\n");
464 /* login the server */
465 scs = pop3Login(pc);
466 if (scs == NULL)
467 return;
468 /* pc->UnreadMsgs = pc->TotalMsgs - read; */
469 pc->headerCache = NULL;
470 for (i = pc->TotalMsgs - pc->UnreadMsgs + 1; i <= pc->TotalMsgs; ++i) {
471 struct msglst *m;
472 m = malloc(sizeof(struct msglst));
474 m->subj[0] = '\0';
475 m->from[0] = '\0';
476 POP_DM(pc, DEBUG_INFO, "search: %s", buf);
478 tlscomm_printf(scs, "TOP %i 0\r\n", i);
479 while (tlscomm_gets(buf, 256, scs) && buf[0] != '.') {
480 if (!strncasecmp(buf, "From: ", 6)) {
481 /* manage the from in heads */
482 strncpy(m->from, buf + 6, FROM_LEN - 1);
483 m->from[FROM_LEN - 1] = '\0';
484 } else if (!strncasecmp(buf, "Subject: ", 9)) {
485 /* manage subject */
486 strncpy(m->subj, buf + 9, SUBJ_LEN - 1);
487 m->subj[SUBJ_LEN - 1] = '\0';
489 if (!m->subj[0]) {
490 strncpy(m->subj, "[NO SUBJECT]", 14);
492 if (!m->from[0]) {
493 strncpy(m->from, "[ANONYMOUS]", 14);
496 m->next = pc->headerCache;
497 pc->headerCache = m;
498 pc->headerCache->in_use = 0;
500 tlscomm_printf(scs, "QUIT\r\n");
501 tlscomm_close(scs);
504 /* vim:set ts=4: */
505 static void ask_user_for_password( /*@notnull@ */ Pop3 pc, int bFlushCache)
507 /* see if we already have a password, as provided in the config file, or
508 already requested from the user. */
509 if (PCU.interactive_password) {
510 if (strlen(PCU.password) == 0) {
511 /* we need to grab the password from the user. */
512 char *password;
513 POP_DM(pc, DEBUG_INFO, "asking for password %d\n",
514 bFlushCache);
515 password =
516 passwordFor(PCU.userName, PCU.serverName, pc, bFlushCache);
517 if (password != NULL) {
518 if (strlen(password) + 1 > BUF_SMALL) {
519 DMA(DEBUG_ERROR, "Password is too long.\n");
520 memset(PCU.password, 0, BUF_SMALL - 1);
521 } else {
522 strncpy(PCU.password, password, BUF_SMALL - 1);
523 PCU.password_len = strlen(PCU.password);
525 free(password);
526 // ENFROB(PCU.password);