2cd350fb2523fcbbfc692ba80b5f8f5d1a6e6375
[dockapps.git] / wmbiff / wmbiff / Pop3Client.c
blob2cd350fb2523fcbbfc692ba80b5f8f5d1a6e6375
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 static int haveBeenWarned;
237 PCU.dossl = 1;
238 if (!haveBeenWarned) {
239 printf("wmbiff uses gnutls for TLS/SSL encryption support:\n"
240 " If you distribute software that uses gnutls, don't forget\n"
241 " to warn the users of your software that gnutls is at a\n"
242 " testing phase and may be totally insecure.\n"
243 "\nConsider yourself warned.\n");
244 haveBeenWarned = 1;
246 #else
247 printf("This copy of wmbiff was not compiled with gnutls;\n"
248 "imaps is unavailable. Exiting to protect your\n"
249 "passwords and privacy.\n");
250 exit(EXIT_FAILURE);
251 #endif
252 } else {
253 PCU.dossl = 0;
257 /* defaults */
258 PCU.serverPort = (PCU.dossl != 0) ? 995 : 110;
259 PCU.authList[0] = '\0';
261 for (matchedchars = 0, i = 0;
262 regexes[i] != NULL && matchedchars <= 0; i++) {
263 matchedchars = regulo_match(regexes[i], str, regulos);
266 /* failed to match either regex */
267 if (matchedchars <= 0) {
268 pc->label[0] = '\0';
269 POP_DM(pc, DEBUG_ERROR, "Couldn't parse line %s (%d)\n"
270 " If this used to work, run wmbiff with the -relax option, and\n "
271 " send mail to wmbiff-devel@lists.sourceforge.net with the hostname\n"
272 " of your mail server.\n", str, matchedchars);
273 return -1;
275 // grab_authList(str + matchedchars, PCU.authList);
277 PCU.password_len = strlen(PCU.password);
278 if (PCU.password[0] == '\0') {
279 PCU.interactive_password = 1;
280 } else {
281 // ENFROB(PCU.password);
284 POP_DM(pc, DEBUG_INFO, "userName= '%s'\n", PCU.userName);
285 POP_DM(pc, DEBUG_INFO, "password is %ld chars long\n",
286 strlen(PCU.password));
287 POP_DM(pc, DEBUG_INFO, "serverName= '%s'\n", PCU.serverName);
288 POP_DM(pc, DEBUG_INFO, "serverPort= '%d'\n", PCU.serverPort);
289 POP_DM(pc, DEBUG_INFO, "authList= '%s'\n", PCU.authList);
291 pc->checkMail = pop3CheckMail;
292 pc->getHeaders = pop_getHeaders;
293 pc->TotalMsgs = 0;
294 pc->UnreadMsgs = 0;
295 pc->OldMsgs = -1;
296 pc->OldUnreadMsgs = -1;
298 return 0;
302 #ifdef HAVE_GCRYPT_H
303 static struct connection_state *authenticate_md5(Pop3 pc, struct connection_state * scs, char *apop_str
304 __attribute__ ((unused)))
306 char buf[BUF_SIZE];
307 char buf2[BUF_SIZE];
308 unsigned char *md5;
309 gcry_md_hd_t gmh;
310 gcry_error_t rc;
312 /* See if MD5 is supported */
313 tlscomm_printf(scs, "AUTH CRAM-MD5\r\n");
314 tlscomm_gets(buf, BUF_SIZE, scs);
315 POP_DM(pc, DEBUG_INFO, "%s", buf);
317 if (buf[0] != '+' || buf[1] != ' ') {
318 /* nope, not supported. */
319 return NULL;
322 Decode_Base64(buf + 2, buf2);
323 POP_DM(pc, DEBUG_INFO, "CRAM-MD5 challenge: %s\n", buf2);
325 strcpy(buf, PCU.userName);
326 strcat(buf, " ");
329 rc = gcry_md_open(&gmh, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
330 if (rc != 0) {
331 POP_DM(pc, DEBUG_ERROR, "unable to initialize gcrypt md5.\n");
332 return NULL;
334 gcry_md_setkey(gmh, PCU.password, strlen(PCU.password));
335 gcry_md_write(gmh, (unsigned char *) buf2, strlen(buf2));
336 gcry_md_final(gmh);
337 md5 = gcry_md_read(gmh, 0);
338 /* hmac_md5(buf2, strlen(buf2), PCU.password,
339 strlen(PCU.password), md5); */
340 Bin2Hex(md5, 16, buf2);
341 gcry_md_close(gmh);
343 strcat(buf, buf2);
344 POP_DM(pc, DEBUG_INFO, "CRAM-MD5 response: %s\n", buf);
345 Encode_Base64(buf, buf2);
347 tlscomm_printf(scs, "%s\r\n", buf2);
348 tlscomm_gets(buf, BUF_SIZE, scs);
350 if (!strncmp(buf, "+OK", 3))
351 return scs; /* AUTH successful */
352 else {
353 POP_DM(pc, DEBUG_ERROR,
354 "CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
355 PCU.userName, PCU.serverName, PCU.serverPort);
356 fprintf(stderr, "It said %s", buf);
357 return NULL;
361 static struct connection_state *authenticate_apop(Pop3 pc, struct connection_state * scs, char *apop_str)
363 gcry_md_hd_t gmh;
364 gcry_error_t rc;
365 char buf[BUF_SIZE];
366 unsigned char *md5;
369 if (apop_str[0] == '\0') {
370 /* server doesn't support apop. */
371 return (NULL);
373 POP_DM(pc, DEBUG_INFO, "APOP challenge: %s\n", apop_str);
374 strcat(apop_str, PCU.password);
376 rc = gcry_md_open(&gmh, GCRY_MD_MD5, 0);
377 if (rc != 0) {
378 POP_DM(pc, DEBUG_ERROR, "unable to initialize gcrypt md5.\n");
379 return NULL;
381 gcry_md_write(gmh, (unsigned char *) apop_str, strlen(apop_str));
382 gcry_md_final(gmh);
383 md5 = gcry_md_read(gmh, 0);
384 Bin2Hex(md5, 16, buf);
385 gcry_md_close(gmh);
387 POP_DM(pc, DEBUG_INFO, "APOP response: %s %s\n", PCU.userName, buf);
388 tlscomm_printf(scs, "APOP %s %s\r\n", PCU.userName, buf);
389 tlscomm_gets(buf, BUF_SIZE, scs);
391 if (!strncmp(buf, "+OK", 3))
392 return scs; /* AUTH successful */
393 else {
394 POP_DM(pc, DEBUG_ERROR,
395 "APOP AUTH failed for user '%s@%s:%d'\n",
396 PCU.userName, PCU.serverName, PCU.serverPort);
397 POP_DM(pc, DEBUG_INFO, "It said %s", buf);
398 return NULL;
401 #endif /* HAVE_GCRYPT_H */
403 /*@null@*/
404 static struct connection_state *authenticate_plaintext( /*@notnull@ */ Pop3 pc,
405 struct connection_state * scs, char *apop_str
406 __attribute__ ((unused)))
408 char buf[BUF_SIZE];
410 tlscomm_printf(scs, "USER %s\r\n", PCU.userName);
411 if (tlscomm_gets(buf, BUF_SIZE, scs) == NULL) {
412 POP_DM(pc, DEBUG_ERROR,
413 "Error reading from server authenticating '%s@%s:%d'\n",
414 PCU.userName, PCU.serverName, PCU.serverPort);
415 return NULL;
417 if (buf[0] != '+') {
418 POP_DM(pc, DEBUG_ERROR,
419 "Failed user name when authenticating '%s@%s:%d'\n",
420 PCU.userName, PCU.serverName, PCU.serverPort);
421 /* deb #128863 might be easier if we printed: */
422 POP_DM(pc, DEBUG_ERROR, "The server's error message was: %s\n",
423 buf);
424 return NULL;
428 tlscomm_printf(scs, "PASS %s\r\n", PCU.password);
429 if (tlscomm_gets(buf, BUF_SIZE, scs) == NULL) {
430 POP_DM(pc, DEBUG_ERROR,
431 "Error reading from server (2) authenticating '%s@%s:%d'\n",
432 PCU.userName, PCU.serverName, PCU.serverPort);
433 return NULL;
435 if (strncmp(buf, "-ERR [AUTH] Password required", 20) == 0) {
436 if (PCU.interactive_password) {
437 PCU.password[0] = '\0';
438 ask_user_for_password(pc, 1); /* 1=overwrite the cache */
439 tlscomm_printf(scs, "PASS %s\r\n", PCU.password);
440 if (tlscomm_gets(buf, BUF_SIZE, scs) == NULL) {
441 POP_DM(pc, DEBUG_ERROR,
442 "Error reading from server (2) authenticating '%s@%s:%d'\n",
443 PCU.userName, PCU.serverName, PCU.serverPort);
444 return NULL;
448 if (buf[0] != '+') {
449 POP_DM(pc, DEBUG_ERROR,
450 "Failed password when authenticating '%s@%s:%d'\n",
451 PCU.userName, PCU.serverName, PCU.serverPort);
452 POP_DM(pc, DEBUG_ERROR, "The server's error message was: %s\n",
453 buf);
454 return NULL;
457 return scs;
460 void pop3_cacheHeaders( /*@notnull@ */ Pop3 pc)
462 char buf[BUF_SIZE];
463 struct connection_state *scs;
464 int i;
466 if (pc->headerCache != NULL) {
467 /* decrement the reference count, and free our version */
468 imap_releaseHeaders(pc, pc->headerCache);
469 pc->headerCache = NULL;
472 POP_DM(pc, DEBUG_INFO, "working headers\n");
473 /* login the server */
474 scs = pop3Login(pc);
475 if (scs == NULL)
476 return;
477 /* pc->UnreadMsgs = pc->TotalMsgs - read; */
478 pc->headerCache = NULL;
479 for (i = pc->TotalMsgs - pc->UnreadMsgs + 1; i <= pc->TotalMsgs; ++i) {
480 struct msglst *m;
481 m = malloc(sizeof(struct msglst));
483 m->subj[0] = '\0';
484 m->from[0] = '\0';
485 POP_DM(pc, DEBUG_INFO, "search: %s", buf);
487 tlscomm_printf(scs, "TOP %i 0\r\n", i);
488 while (tlscomm_gets(buf, 256, scs) && buf[0] != '.') {
489 if (!strncasecmp(buf, "From: ", 6)) {
490 /* manage the from in heads */
491 strncpy(m->from, buf + 6, FROM_LEN - 1);
492 m->from[FROM_LEN - 1] = '\0';
493 } else if (!strncasecmp(buf, "Subject: ", 9)) {
494 /* manage subject */
495 strncpy(m->subj, buf + 9, SUBJ_LEN - 1);
496 m->subj[SUBJ_LEN - 1] = '\0';
498 if (!m->subj[0]) {
499 strncpy(m->subj, "[NO SUBJECT]", 14);
501 if (!m->from[0]) {
502 strncpy(m->from, "[ANONYMOUS]", 14);
505 m->next = pc->headerCache;
506 pc->headerCache = m;
507 pc->headerCache->in_use = 0;
509 tlscomm_printf(scs, "QUIT\r\n");
510 tlscomm_close(scs);
513 /* vim:set ts=4: */
514 static void ask_user_for_password( /*@notnull@ */ Pop3 pc, int bFlushCache)
516 /* see if we already have a password, as provided in the config file, or
517 already requested from the user. */
518 if (PCU.interactive_password) {
519 if (strlen(PCU.password) == 0) {
520 /* we need to grab the password from the user. */
521 char *password;
522 POP_DM(pc, DEBUG_INFO, "asking for password %d\n",
523 bFlushCache);
524 password =
525 passwordFor(PCU.userName, PCU.serverName, pc, bFlushCache);
526 if (password != NULL) {
527 if (strlen(password) + 1 > BUF_SMALL) {
528 DMA(DEBUG_ERROR, "Password is too long.\n");
529 memset(PCU.password, 0, BUF_SMALL - 1);
530 } else {
531 strncpy(PCU.password, password, BUF_SMALL - 1);
532 PCU.password_len = strlen(PCU.password);
534 free(password);
535 // ENFROB(PCU.password);