wmbiff: replaced strncpy with memcpy.
[dockapps.git] / wmbiff / wmbiff / Pop3Client.c
blobd1640e34e436b5fd98be2dc02b52f501dbc6c2e3
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 )
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,
38 struct connection_state *scs,
39 char *unused);
40 static struct connection_state *authenticate_apop( /*@notnull@ */ Pop3 *pc,
41 struct connection_state *scs,
42 char *apop_str);
43 #endif
44 static struct connection_state *authenticate_plaintext( /*@notnull@ */ Pop3 *pc,
45 struct connection_state *scs,
46 char *unused);
48 void pop3_cacheHeaders( /*@notnull@ */ Pop3 *pc);
50 extern void imap_releaseHeaders(Pop3 *pc __attribute__((unused)),
51 struct msglst *h);
53 extern struct connection_state *state_for_pcu(Pop3 *pc);
55 static struct authentication_method {
56 const char *name;
57 /* callback returns the connection state pointer if successful,
58 NULL if failed */
59 struct connection_state *(*auth_callback) (Pop3 *pc,
60 struct connection_state *scs,
61 char *apop_str);
62 } auth_methods[] = {
64 #ifdef HAVE_GCRYPT_H
65 "cram-md5", authenticate_md5}, {
66 "apop", authenticate_apop}, {
67 #endif
68 "plaintext", authenticate_plaintext}, {
69 NULL, NULL}
72 /*@null@*/
73 struct connection_state *pop3Login(Pop3 *pc)
75 int fd;
76 char buf[BUF_SIZE];
77 char apop_str[BUF_SIZE] = "";
78 char *ptr1, *ptr2;
79 struct authentication_method *a;
80 struct connection_state *scs;
81 char *connection_name;
83 if ((fd = sock_connect(PCU.serverName, PCU.serverPort)) == -1) {
84 POP_DM(pc, DEBUG_ERROR, "Not Connected To Server '%s:%d'\n",
85 PCU.serverName, PCU.serverPort);
86 return NULL;
89 connection_name = malloc(strlen(PCU.serverName) + 20);
90 sprintf(connection_name, "%s:%d", PCU.serverName, PCU.serverPort);
92 if (PCU.dossl != 0) {
93 scs = initialize_gnutls(fd, connection_name, pc, PCU.serverName);
94 if (scs == NULL) {
95 POP_DM(pc, DEBUG_ERROR, "Failed to initialize TLS\n");
96 return NULL;
98 } else {
99 scs = initialize_unencrypted(fd, connection_name, pc);
102 tlscomm_gets(buf, BUF_SIZE, scs);
103 POP_DM(pc, DEBUG_INFO, "%s", buf);
105 /* Detect APOP, copy challenge into apop_str */
106 for (ptr1 = buf + strlen(buf), ptr2 = NULL; ptr1 > buf; --ptr1) {
107 if (*ptr1 == '>') {
108 ptr2 = ptr1;
109 } else if (*ptr1 == '<') {
110 if (ptr2) {
111 memcpy(apop_str, ptr1, 1 + ptr2 - ptr1);
113 break;
117 /* try each authentication method in turn. */
118 for (a = auth_methods; a->name != NULL; a++) {
119 /* was it specified or did the user leave it up to us? */
120 if (PCU.authList[0] == '\0' || strstr(PCU.authList, a->name))
121 /* did it work? */
122 if ((a->auth_callback(pc, scs, apop_str)) != NULL)
123 return (scs);
126 /* if authentication worked, we won't get here */
127 POP_DM(pc, DEBUG_ERROR,
128 "All Pop3 authentication methods failed for '%s@%s:%d'\n",
129 PCU.userName, PCU.serverName, PCU.serverPort);
130 tlscomm_printf(scs, "QUIT\r\n");
131 tlscomm_close(scs);
133 return NULL;
136 int pop3CheckMail( /*@notnull@ */ Pop3 *pc)
138 struct connection_state *scs;
139 int read;
140 char buf[BUF_SIZE];
142 scs = pop3Login(pc);
143 if (scs == NULL)
144 return -1;
146 tlscomm_printf(scs, "STAT\r\n");
147 if( ! tlscomm_expect(scs, "+", buf, BUF_SIZE) ) {
148 POP_DM(pc, DEBUG_ERROR,
149 "Error Receiving Stats '%s@%s:%d'\n",
150 PCU.userName, PCU.serverName, PCU.serverPort);
151 POP_DM(pc, DEBUG_INFO, "It said: %s\n", buf);
152 return -1;
153 } else {
154 sscanf(buf, "+OK %d", &(pc->TotalMsgs));
157 /* - Updated - Mark Hurley - debian4tux@telocity.com
158 * In compliance with RFC 1725
159 * which removed the LAST command, any servers
160 * which follow this spec will return:
161 * -ERR unimplimented
162 * We will leave it here for those servers which haven't
163 * caught up with the spec.
165 tlscomm_printf(scs, "LAST\r\n");
166 tlscomm_gets(buf, BUF_SIZE, scs);
167 if (buf[0] != '+') {
168 /* it is not an error to receive this according to RFC 1725 */
169 /* no error should be returned */
170 pc->UnreadMsgs = pc->TotalMsgs;
171 // there's also a LIST command... not sure how to make use of it. */
172 } else {
173 sscanf(buf, "+OK %d", &read);
174 pc->UnreadMsgs = pc->TotalMsgs - read;
177 tlscomm_printf(scs, "QUIT\r\n");
178 tlscomm_close(scs);
180 return 0;
184 struct msglst *pop_getHeaders( /*@notnull@ */ Pop3 *pc)
186 if (pc->headerCache == NULL)
187 pop3_cacheHeaders(pc);
188 if (pc->headerCache != NULL)
189 pc->headerCache->in_use = 1;
190 return pc->headerCache;
195 int pop3Create(Pop3 *pc, const char *str)
197 /* POP3 format: pop3:user:password@server[:port] */
198 /* new POP3 format: pop3:user password server [port] */
199 /* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
200 int i;
201 int matchedchars;
202 /* ([^: ]+) user
203 ([^@]+) or ([^ ]+) password
204 ([^: ]+) server
205 ([: ][0-9]+)? optional port
206 ' *' gobbles trailing whitespace before authentication types.
207 use separate regexes for old and new types to permit
208 use of '@' in passwords
210 const char *regexes[] = {
211 "pop3s?:([^: ]{1,32}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
212 "pop3s?:([^: ]{1,32}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
213 // "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^: ]+)( [0-9]+)? *",
214 // "pop3:([^: ]{1,32}):([^@]{0,32})@([^: ]+)(:[0-9]+)? *",
215 NULL
217 struct regulo regulos[] = {
218 {1, PCU.userName, regulo_strcpy},
219 {2, PCU.password, regulo_strcpy},
220 {3, PCU.serverName, regulo_strcpy},
221 {4, &PCU.serverPort, regulo_atoi},
222 {6, PCU.authList, regulo_strcpy_tolower},
223 {0, NULL, NULL}
226 if (Relax) {
227 regexes[0] =
228 "pop3:([^: ]{1,32}):([^@]{0,32})@([^/: ]+)(:[0-9]+)?( *(.*))?$";
229 regexes[1] =
230 "pop3:([^: ]{1,32}) ([^ ]{1,32}) ([^/: ]+)( [0-9]+)?( *(.*))?$";
233 if (strncmp("pop3s:", str, 6) == 0) {
234 #ifdef HAVE_GNUTLS_GNUTLS_H
235 PCU.dossl = 1;
236 #else
237 printf("This copy of wmbiff was not compiled with gnutls;\n"
238 "imaps is unavailable. Exiting to protect your\n"
239 "passwords and privacy.\n");
240 exit(EXIT_FAILURE);
241 #endif
242 } else {
243 PCU.dossl = 0;
247 /* defaults */
248 PCU.serverPort = (PCU.dossl != 0) ? 995 : 110;
249 PCU.authList[0] = '\0';
251 for (matchedchars = 0, i = 0;
252 regexes[i] != NULL && matchedchars <= 0; i++) {
253 matchedchars = regulo_match(regexes[i], str, regulos);
256 /* failed to match either regex */
257 if (matchedchars <= 0) {
258 pc->label[0] = '\0';
259 POP_DM(pc, DEBUG_ERROR, "Couldn't parse line %s (%d)\n"
260 " If this used to work, run wmbiff with the -relax option, and\n "
261 " send mail to "PACKAGE_BUGREPORT" with the hostname\n"
262 " of your mail server.\n", str, matchedchars);
263 return -1;
265 // grab_authList(str + matchedchars, PCU.authList);
267 PCU.password_len = strlen(PCU.password);
268 if (PCU.password[0] == '\0') {
269 PCU.interactive_password = 1;
270 } else {
271 // ENFROB(PCU.password);
274 POP_DM(pc, DEBUG_INFO, "userName= '%s'\n", PCU.userName);
275 POP_DM(pc, DEBUG_INFO, "password is %ld chars long\n",
276 strlen(PCU.password));
277 POP_DM(pc, DEBUG_INFO, "serverName= '%s'\n", PCU.serverName);
278 POP_DM(pc, DEBUG_INFO, "serverPort= '%d'\n", PCU.serverPort);
279 POP_DM(pc, DEBUG_INFO, "authList= '%s'\n", PCU.authList);
281 pc->checkMail = pop3CheckMail;
282 pc->getHeaders = pop_getHeaders;
283 pc->TotalMsgs = 0;
284 pc->UnreadMsgs = 0;
285 pc->OldMsgs = -1;
286 pc->OldUnreadMsgs = -1;
288 return 0;
292 #ifdef HAVE_GCRYPT_H
293 static struct connection_state *authenticate_md5(Pop3 *pc,
294 struct connection_state *scs,
295 char *apop_str __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,
353 struct connection_state *scs,
354 char *apop_str)
356 gcry_md_hd_t gmh;
357 gcry_error_t rc;
358 char buf[BUF_SIZE];
359 unsigned char *md5;
362 if (apop_str[0] == '\0') {
363 /* server doesn't support apop. */
364 return (NULL);
366 POP_DM(pc, DEBUG_INFO, "APOP challenge: %s\n", apop_str);
367 strcat(apop_str, PCU.password);
369 rc = gcry_md_open(&gmh, GCRY_MD_MD5, 0);
370 if (rc != 0) {
371 POP_DM(pc, DEBUG_ERROR, "unable to initialize gcrypt md5.\n");
372 return NULL;
374 gcry_md_write(gmh, (unsigned char *) apop_str, strlen(apop_str));
375 gcry_md_final(gmh);
376 md5 = gcry_md_read(gmh, 0);
377 Bin2Hex(md5, 16, buf);
378 gcry_md_close(gmh);
380 POP_DM(pc, DEBUG_INFO, "APOP response: %s %s\n", PCU.userName, buf);
381 tlscomm_printf(scs, "APOP %s %s\r\n", PCU.userName, buf);
382 tlscomm_gets(buf, BUF_SIZE, scs);
384 if (!strncmp(buf, "+OK", 3))
385 return scs; /* AUTH successful */
386 else {
387 POP_DM(pc, DEBUG_ERROR,
388 "APOP AUTH failed for user '%s@%s:%d'\n",
389 PCU.userName, PCU.serverName, PCU.serverPort);
390 POP_DM(pc, DEBUG_INFO, "It said %s", buf);
391 return NULL;
394 #endif /* HAVE_GCRYPT_H */
396 /*@null@*/
397 static struct connection_state *authenticate_plaintext( /*@notnull@ */ Pop3 *pc,
398 struct connection_state *scs,
399 char *apop_str __attribute__((unused)))
401 char buf[BUF_SIZE];
403 tlscomm_printf(scs, "USER %s\r\n", PCU.userName);
404 if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
405 POP_DM(pc, DEBUG_ERROR,
406 "Error reading from server authenticating '%s@%s:%d'\n",
407 PCU.userName, PCU.serverName, PCU.serverPort);
408 return NULL;
410 if (buf[0] != '+') {
411 POP_DM(pc, DEBUG_ERROR,
412 "Failed user name when authenticating '%s@%s:%d'\n",
413 PCU.userName, PCU.serverName, PCU.serverPort);
414 /* deb #128863 might be easier if we printed: */
415 POP_DM(pc, DEBUG_ERROR, "The server's error message was: %s\n",
416 buf);
417 return NULL;
421 tlscomm_printf(scs, "PASS %s\r\n", PCU.password);
422 if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
423 POP_DM(pc, DEBUG_ERROR,
424 "Error reading from server (2) authenticating '%s@%s:%d'\n",
425 PCU.userName, PCU.serverName, PCU.serverPort);
426 return NULL;
428 if (strncmp(buf, "-ERR [AUTH] Password required", 20) == 0) {
429 if (PCU.interactive_password) {
430 PCU.password[0] = '\0';
431 ask_user_for_password(pc, 1); /* 1=overwrite the cache */
432 tlscomm_printf(scs, "PASS %s\r\n", PCU.password);
433 if (tlscomm_gets(buf, BUF_SIZE, scs) == 0) {
434 POP_DM(pc, DEBUG_ERROR,
435 "Error reading from server (2) authenticating '%s@%s:%d'\n",
436 PCU.userName, PCU.serverName, PCU.serverPort);
437 return NULL;
441 if (buf[0] != '+') {
442 POP_DM(pc, DEBUG_ERROR,
443 "Failed password when authenticating '%s@%s:%d'\n",
444 PCU.userName, PCU.serverName, PCU.serverPort);
445 POP_DM(pc, DEBUG_ERROR, "The server's error message was: %s\n",
446 buf);
447 return NULL;
450 return scs;
453 void pop3_cacheHeaders( /*@notnull@ */ Pop3 *pc)
455 char buf[BUF_SIZE];
456 struct connection_state *scs;
457 int i;
459 if (pc->headerCache != NULL) {
460 /* decrement the reference count, and free our version */
461 imap_releaseHeaders(pc, pc->headerCache);
462 pc->headerCache = NULL;
465 POP_DM(pc, DEBUG_INFO, "working headers\n");
466 /* login the server */
467 scs = pop3Login(pc);
468 if (scs == NULL)
469 return;
470 /* pc->UnreadMsgs = pc->TotalMsgs - read; */
471 pc->headerCache = NULL;
472 for (i = pc->TotalMsgs - pc->UnreadMsgs + 1; i <= pc->TotalMsgs; ++i) {
473 struct msglst *m;
474 m = malloc(sizeof(struct msglst));
476 m->subj[0] = '\0';
477 m->from[0] = '\0';
478 POP_DM(pc, DEBUG_INFO, "search: %s", buf);
480 tlscomm_printf(scs, "TOP %i 0\r\n", i);
481 while (tlscomm_gets(buf, 256, scs) && buf[0] != '.') {
482 if (!strncasecmp(buf, "From: ", 6)) {
483 /* manage the from in heads */
484 strncpy(m->from, buf + 6, FROM_LEN - 1);
485 m->from[FROM_LEN - 1] = '\0';
486 } else if (!strncasecmp(buf, "Subject: ", 9)) {
487 /* manage subject */
488 strncpy(m->subj, buf + 9, SUBJ_LEN - 1);
489 m->subj[SUBJ_LEN - 1] = '\0';
491 if (!m->subj[0]) {
492 strncpy(m->subj, "[NO SUBJECT]", 14);
494 if (!m->from[0]) {
495 strncpy(m->from, "[ANONYMOUS]", 14);
498 m->next = pc->headerCache;
499 pc->headerCache = m;
500 pc->headerCache->in_use = 0;
502 tlscomm_printf(scs, "QUIT\r\n");
503 tlscomm_close(scs);
506 /* vim:set ts=4: */
507 static void ask_user_for_password( /*@notnull@ */ Pop3 *pc, int bFlushCache)
509 /* see if we already have a password, as provided in the config file, or
510 already requested from the user. */
511 if (PCU.interactive_password) {
512 if (strlen(PCU.password) == 0) {
513 /* we need to grab the password from the user. */
514 char *password;
515 POP_DM(pc, DEBUG_INFO, "asking for password %d\n",
516 bFlushCache);
517 password =
518 passwordFor(PCU.userName, PCU.serverName, pc, bFlushCache);
519 if (password != NULL) {
520 if (strlen(password) + 1 > BUF_SMALL) {
521 DMA(DEBUG_ERROR, "Password is too long.\n");
522 memset(PCU.password, 0, BUF_SMALL - 1);
523 } else {
524 strncpy(PCU.password, password, BUF_SMALL - 1);
525 PCU.password_len = strlen(PCU.password);
527 free(password);
528 // ENFROB(PCU.password);