wmbiff: Remove #define _GNU_SOURCE; already defined in CFLAGS.
[dockapps.git] / wmbiff / wmbiff / Imap4Client.c
blobba12a669711a210f6fdd77b37d94d61d5ca8b514
1 /* rewrite of the IMAP code by Neil Spring
2 * (nspring@cs.washington.edu) to support gnutls and
3 * persistent connections to servers. */
5 /* Originally written by Yong-iL Joh (tolkien@mizi.com),
6 * modified by Jorge Garcia (Jorge.Garcia@uv.es), and
7 * modified by Jay Francis (jtf@u880.org) to support
8 * CRAM-MD5 */
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
14 #include "Client.h"
15 #include "charutil.h"
16 #include "tlsComm.h"
17 #include "passwordMgr.h"
18 #include "regulo.h"
19 #include "MessageList.h"
21 #include <sys/types.h>
22 #include <stdio.h>
23 #include <assert.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include <time.h>
27 #include <ctype.h>
28 #include <strings.h>
30 #ifdef USE_DMALLOC
31 #include <dmalloc.h>
32 #endif
34 #define PCU (pc->u).pop_imap
36 extern int Relax;
38 #define IMAP_DM(pc, lvl, args...) DM(pc, lvl, "imap4: " args)
40 #ifdef HAVE_MEMFROB
41 #define DEFROB(x) memfrob(x, x ## _len)
42 #define ENFROB(x) memfrob(x, x ## _len)
43 #else
44 #define DEFROB(x)
45 #define ENFROB(x)
46 #endif
48 /* this array maps server:port pairs to file descriptors, so
49 that when more than one mailbox is queried from a server,
50 we only use one socket. It's limited in size by the
51 number of different mailboxes displayed. */
52 #define FDMAP_SIZE 5
53 static struct fdmap_struct {
54 char *user_server_port; /* tuple, in string form */
55 /*@owned@ */ struct connection_state *cs;
56 } fdmap[FDMAP_SIZE];
58 static void ask_user_for_password( /*@notnull@ */ Pop3 pc,
59 int bFlushCache);
61 /* authentication callbacks */
62 #ifdef HAVE_GCRYPT_H
63 static int authenticate_md5( /*@notnull@ */ Pop3 pc,
64 struct connection_state *scs,
65 const char *capabilities);
66 #endif
67 static int authenticate_plaintext( /*@notnull@ */ Pop3 pc,
68 struct connection_state *scs,
69 const char *capabilities);
71 /* the auth_methods array maps authentication identifiers
72 to the callback that will attempt to authenticate */
73 static struct imap_authentication_method {
74 const char *name;
75 /* callback returns 1 if successful, 0 if failed */
76 int (*auth_callback) ( /*@notnull@ */ Pop3 pc,
77 struct connection_state * scs,
78 const char *capabilities);
79 } auth_methods[] = {
81 #ifdef HAVE_GCRYPT_H
82 "cram-md5", authenticate_md5}, {
83 #endif
84 "plaintext", authenticate_plaintext}, {
85 NULL, NULL}
89 /* recover a socket from the connection cache */
90 /*@null@*/
91 /*@dependent@*/
92 static struct connection_state *state_for_pcu(Pop3 pc)
94 char *connection_id;
95 struct connection_state *retval = NULL;
96 int i;
97 connection_id =
98 malloc(strlen(PCU.userName) + strlen(PCU.serverName) + 22);
99 sprintf(connection_id, "%s|%s|%d", PCU.userName, PCU.serverName,
100 PCU.serverPort);
101 for (i = 0; i < FDMAP_SIZE; i++)
102 if (fdmap[i].user_server_port != NULL &&
103 (strcmp(connection_id, fdmap[i].user_server_port) == 0)) {
104 retval = fdmap[i].cs;
106 free(connection_id);
107 return (retval);
110 /* bind to the connection cache */
111 static void bind_state_to_pcu(Pop3 pc,
112 /*@owned@ */ struct connection_state *scs)
114 char *connection_id;
115 int i;
116 if (scs == NULL) {
117 abort();
119 connection_id =
120 malloc(strlen(PCU.userName) + strlen(PCU.serverName) + 22);
121 sprintf(connection_id, "%s|%s|%d", PCU.userName, PCU.serverName,
122 PCU.serverPort);
123 for (i = 0; i < FDMAP_SIZE && fdmap[i].cs != NULL; i++);
124 if (i == FDMAP_SIZE) {
125 /* should never happen */
126 IMAP_DM(pc, DEBUG_ERROR,
127 "Tried to open too many IMAP connections. Sorry!\n");
128 exit(EXIT_FAILURE);
130 fdmap[i].user_server_port = connection_id;
131 fdmap[i].cs = scs;
134 /* remove from the connection cache */
135 static
136 /*@owned@*/
137 /*@null@*/
138 struct connection_state *unbind(
139 /*@returned@*/ struct connection_state
140 *scs)
142 int i;
143 struct connection_state *retval = NULL;
144 assert(scs != NULL);
146 for (i = 0; i < FDMAP_SIZE && fdmap[i].cs != scs; i++);
147 if (i < FDMAP_SIZE) {
148 free(fdmap[i].user_server_port);
149 fdmap[i].user_server_port = NULL;
150 retval = fdmap[i].cs;
151 fdmap[i].cs = NULL;
153 return (retval);
156 /* creates a connection to the server, if a matching one doesn't exist. */
157 /* *always* returns null, just declared this wasy to match other protocols. */
158 /*@null@*/
159 FILE *imap_open(Pop3 pc)
161 static int complained_already; /* we have to succeed once before
162 complaining again about failure */
163 struct connection_state *scs;
164 struct imap_authentication_method *a;
165 char *connection_name;
166 int sd;
167 char capabilities[BUF_SIZE];
168 char buf[BUF_SIZE];
171 if (state_for_pcu(pc) != NULL) {
172 /* don't need to open. */
173 return NULL;
176 /* got this far; we're going to create a connection_state
177 structure, although it might be a blacklist entry */
178 connection_name = malloc(strlen(PCU.serverName) + 20);
179 sprintf(connection_name, "%s:%d", PCU.serverName, PCU.serverPort);
181 assert(pc != NULL);
183 /* no cached connection */
184 sd = sock_connect((const char *) PCU.serverName, PCU.serverPort);
185 if (sd == -1) {
186 if (complained_already == 0) {
187 IMAP_DM(pc, DEBUG_ERROR, "Couldn't connect to %s:%d: %s\n",
188 PCU.serverName, PCU.serverPort,
189 errno ? strerror(errno) : "");
190 complained_already = 1;
192 if (errno == ETIMEDOUT) {
193 /* temporarily bump the interval, in a crude way:
194 fast forward time so that the mailbox isn't
195 checked for a while. */
196 pc->prevtime = time(0) + 60 * 5; /* now + 60 seconds per min * 5 minutes */
197 /* TCP's retry (how much time has elapsed while
198 the connect times out) is around 3 minutes;
199 here we just try to allow checking local
200 mailboxes more often while remote things are
201 unavailable or disconnected. */
203 return NULL;
206 /* build the connection using STARTTLS */
207 if (PCU.dossl != 0 && (PCU.serverPort == 143)) {
208 /* setup an unencrypted binding long enough to invoke STARTTLS */
209 scs = initialize_unencrypted(sd, connection_name, pc);
211 /* can we? */
212 tlscomm_printf(scs, "a000 CAPABILITY\r\n");
213 if (tlscomm_expect(scs, "* CAPABILITY", capabilities, BUF_SIZE) ==
215 goto communication_failure;
217 if (!strstr(capabilities, "STARTTLS")) {
218 IMAP_DM(pc, DEBUG_ERROR,
219 "server doesn't support ssl imap on port 143.");
220 goto communication_failure;
223 /* we sure can! */
224 IMAP_DM(pc, DEBUG_INFO, "Negotiating TLS within IMAP");
225 tlscomm_printf(scs, "a001 STARTTLS\r\n");
227 if (tlscomm_expect(scs, "a001 ", buf, BUF_SIZE) == 0)
228 goto communication_failure;
230 if (strstr(buf, "a001 OK") == 0) {
231 /* we didn't see the success message in the response */
232 IMAP_DM(pc, DEBUG_ERROR, "couldn't negotiate tls. :(\n");
233 goto communication_failure;
236 /* we don't need the unencrypted state anymore */
237 /* note that communication_failure will close the
238 socket and free via tls_close() */
239 free(scs); /* fall through will scs = initialize_gnutls(sd); */
242 /* either we've negotiated ssl from starttls, or
243 we're starting an encrypted connection now */
244 if (PCU.dossl != 0) {
245 scs = initialize_gnutls(sd, connection_name, pc, PCU.serverName);
246 if (scs == NULL) {
247 IMAP_DM(pc, DEBUG_ERROR, "Failed to initialize TLS\n");
248 return NULL;
250 } else {
251 scs = initialize_unencrypted(sd, connection_name, pc);
254 /* authenticate; first find out how */
255 /* note that capabilities may have changed since last
256 time we may have asked, if we called STARTTLS, my
257 server will allow plain password login within an
258 encrypted session. */
259 tlscomm_printf(scs, "a000 CAPABILITY\r\n");
260 if (tlscomm_expect(scs, "* CAPABILITY", capabilities, BUF_SIZE) == 0) {
261 IMAP_DM(pc, DEBUG_ERROR, "unable to query capability string");
262 goto communication_failure;
265 /* try each authentication method in turn. */
266 for (a = auth_methods; a->name != NULL; a++) {
267 /* was it specified or did the user leave it up to us? */
268 if (PCU.authList[0] == '\0'
269 || strstr(PCU.authList, a->name) != NULL)
270 /* try the authentication method */
271 if ((a->auth_callback(pc, scs, capabilities)) != 0) {
272 /* store this well setup connection in the cache */
273 bind_state_to_pcu(pc, scs);
274 complained_already = 0;
275 return NULL;
279 /* if authentication worked, we won't get here */
280 IMAP_DM(pc, DEBUG_ERROR,
281 "All authentication methods failed for '%s@%s:%d'\n",
282 PCU.userName, PCU.serverName, PCU.serverPort);
283 communication_failure:
284 tlscomm_printf(scs, "a002 LOGOUT\r\n");
285 tlscomm_close(scs);
286 return NULL;
290 void imap_cacheHeaders( /*@notnull@ */ Pop3 pc);
292 int imap_checkmail( /*@notnull@ */ Pop3 pc)
294 /* recover connection state from the cache */
295 struct connection_state *scs = state_for_pcu(pc);
296 char buf[BUF_SIZE];
297 char examine_expect[BUF_SIZE];
298 static int command_id;
300 /* if it's not in the cache, try to open */
301 if (scs == NULL) {
302 IMAP_DM(pc, DEBUG_INFO, "Need new connection to %s@%s\n",
303 PCU.userName, PCU.serverName);
304 (void) imap_open(pc);
305 scs = state_for_pcu(pc);
307 if (scs == NULL) {
308 return -1;
311 if (tlscomm_is_blacklisted(scs) != 0) {
312 /* unresponsive server, don't bother. */
313 return -1;
316 command_id++;
317 tlscomm_printf(scs, "a%03d EXAMINE %s\r\n", command_id, pc->path);
318 snprintf(examine_expect, BUF_SIZE, "a%03d OK", command_id);
319 if (tlscomm_expect(scs, examine_expect, buf, 127) == 0) {
320 tlscomm_close(unbind(scs));
321 return -1;
324 command_id++;
325 tlscomm_printf(scs, "a%03d CLOSE\r\n", command_id);
326 snprintf(examine_expect, BUF_SIZE, "a%03d OK", command_id);
327 if (tlscomm_expect(scs, examine_expect, buf, 127) == 0) {
328 tlscomm_close(unbind(scs));
329 return -1;
332 /* if we've got it by now, try the status query */
333 command_id++;
334 tlscomm_printf(scs, "a%03d STATUS %s (MESSAGES UNSEEN)\r\n",
335 command_id % 1000, pc->path);
336 if (tlscomm_expect(scs, "* STATUS", buf, 127) != 0) {
337 /* a valid response? */
338 // doesn't support spaces: (void) sscanf(buf, "* STATUS %*s (MESSAGES %d UNSEEN %d)",
339 const char *msg;
340 msg = strstr(buf, "(MESSAGES");
341 if (msg != NULL)
342 (void) sscanf(msg, "(MESSAGES %d UNSEEN %d)",
343 &(pc->TotalMsgs), &(pc->UnreadMsgs));
344 /* update the cached headers if evidence that change
345 has occurred; not necessarily complete. */
346 if (pc->UnreadMsgs != pc->OldUnreadMsgs ||
347 pc->TotalMsgs != pc->OldMsgs) {
348 if (PCU.wantCacheHeaders) {
349 imap_cacheHeaders(pc);
352 } else {
353 /* something went wrong. bail. */
354 tlscomm_close(unbind(scs));
355 return -1;
357 return 0;
360 void
361 imap_releaseHeaders(Pop3 pc __attribute__ ((unused)), struct msglst *h)
363 assert(h != NULL);
364 /* allow the list to be released next time around */
365 if (h->in_use <= 0) {
366 /* free the old one */
367 while (h != NULL) {
368 struct msglst *n = h->next;
369 free(h);
370 h = n;
372 } else {
373 h->in_use--;
377 void imap_cacheHeaders( /*@notnull@ */ Pop3 pc)
379 struct connection_state *scs = state_for_pcu(pc);
380 char *msgid;
381 char buf[BUF_SIZE];
383 if (scs == NULL) {
384 (void) imap_open(pc);
385 scs = state_for_pcu(pc);
387 if (scs == NULL) {
388 return;
390 if (tlscomm_is_blacklisted(scs) != 0) {
391 return;
394 if (pc->headerCache != NULL) {
395 /* decrement the reference count, and free our version */
396 imap_releaseHeaders(pc, pc->headerCache);
397 pc->headerCache = NULL;
400 IMAP_DM(pc, DEBUG_INFO, "working headers\n");
402 tlscomm_printf(scs, "a004 EXAMINE %s\r\n", pc->path);
403 if (tlscomm_expect(scs, "a004 OK", buf, 127) == 0) {
404 tlscomm_close(unbind(scs));
405 return;
407 IMAP_DM(pc, DEBUG_INFO, "examine ok\n");
409 /* if we've got it by now, try the status query */
410 tlscomm_printf(scs, "a005 SEARCH UNSEEN\r\n");
411 if (tlscomm_expect(scs, "* SEARCH", buf, 127) == 0) {
412 tlscomm_close(unbind(scs));
413 return;
415 IMAP_DM(pc, DEBUG_INFO, "search: %s", buf);
416 if (strlen(buf) < 9)
417 return; /* search turned up nothing */
418 msgid = strtok(buf + 9, " \r\n");
419 pc->headerCache = NULL;
420 /* the isdigit cruft is to deal with EOL */
421 if (msgid != NULL && isdigit(msgid[0]))
422 do {
423 struct msglst *m = malloc(sizeof(struct msglst));
424 char hdrbuf[128];
425 int fetch_command_done = FALSE;
426 tlscomm_printf(scs, "a04 FETCH %s (FLAGS "
427 "BODY[HEADER.FIELDS (FROM SUBJECT)])\r\n",
428 msgid);
429 if (tlscomm_expect(scs, "* ", hdrbuf, 127)) {
430 m->subj[0] = '\0';
431 m->from[0] = '\0';
432 while (m->subj[0] == '\0' || m->from[0] == '\0') {
433 if (tlscomm_expect(scs, "", hdrbuf, 127)) {
434 if (strncasecmp(hdrbuf, "Subject:", 8) == 0) {
435 strncpy(m->subj, hdrbuf + 9, SUBJ_LEN - 1);
436 m->subj[SUBJ_LEN - 1] = '\0';
437 } else if (strncasecmp(hdrbuf, "From: ", 5) == 0) {
438 strncpy(m->from, hdrbuf + 6, FROM_LEN - 1);
439 m->from[FROM_LEN - 1] = '\0';
440 } else if (strncasecmp
441 (hdrbuf, "a04 OK FETCH", 5) == 0) {
442 /* server says we're done getting this header, which
443 may occur if the message has no subject */
444 if (m->from[0] == '\0') {
445 strcpy(m->from, " ");
447 if (m->subj[0] == '\0') {
448 strcpy(m->subj, "(no subject)");
450 fetch_command_done = TRUE;
452 } else {
453 IMAP_DM(pc, DEBUG_ERROR,
454 "timedout looking for headers.: %s",
455 hdrbuf);
456 strcpy(m->from, "wmbiff");
457 strcpy(m->subj, "failure");
460 IMAP_DM(pc, DEBUG_INFO, "From: '%s' Subj: '%s'\n",
461 m->from, m->subj);
462 m->next = pc->headerCache;
463 pc->headerCache = m;
464 pc->headerCache->in_use = 0; /* initialize that it isn't locked */
465 } else {
466 IMAP_DM(pc, DEBUG_ERROR, "error fetching: %s", hdrbuf);
468 if (!fetch_command_done) {
469 tlscomm_expect(scs, "a04 OK", hdrbuf, 127);
472 while ((msgid = strtok(NULL, " \r\n")) != NULL
473 && isdigit(msgid[0]));
475 tlscomm_printf(scs, "a06 CLOSE\r\n"); /* return to polling state */
476 /* may be unneeded tlscomm_expect(scs, "a06 OK CLOSE\r\n" ); see if it worked? */
477 IMAP_DM(pc, DEBUG_INFO, "worked headers\n");
480 /* a client is asking for the headers, hand em a reference, increase the
481 one-bit reference counter */
482 struct msglst *imap_getHeaders( /*@notnull@ */ Pop3 pc)
484 if (pc->headerCache == NULL)
485 imap_cacheHeaders(pc);
486 if (pc->headerCache != NULL)
487 pc->headerCache->in_use = 1;
488 return pc->headerCache;
491 /* parse the config line to setup the Pop3 structure */
492 int imap4Create( /*@notnull@ */ Pop3 pc, const char *const str)
494 int i;
495 int matchedchars;
496 /* special characters aren't allowed in hostnames, rfc 1034 */
497 const char *regexes[] = {
498 // type : username : password @ hostname (/ name)?(:port)?
499 ".*imaps?:([^: ]{1,256}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(/(\"[^\"]+\")|([^:@ ]+))?(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
500 ".*imaps?:([^: ]{1,256}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)(/(\"[^\"]+\")|([^: ]+))?( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
501 NULL
503 char *unaliased_str;
505 struct regulo regulos[] = {
506 {1, PCU.userName, regulo_strcpy},
507 {2, PCU.password, regulo_strcpy},
508 {3, PCU.serverName, regulo_strcpy},
509 {4, pc->path, regulo_strcpy_skip1},
510 {7, &PCU.serverPort, regulo_atoi},
511 {9, PCU.authList, regulo_strcpy_tolower},
512 {0, NULL, NULL}
515 if (Relax) {
516 regexes[0] =
517 ".*imaps?:([^: ]{1,256}):([^@]{0,32})@([^/: ]+)(/(\"[^\"]+\")|([^:@ ]+))?(:[0-9]+)?( *(.*))?$";
518 regexes[1] =
519 ".*imaps?:([^: ]{1,256}) ([^ ]{1,32}) ([^/: ]+)(/(\"[^\"]+\")|([^: ]+))?( [0-9]+)?( *(.*))?$";
523 /* IMAP4 format: imap:user:password@server/mailbox[:port] */
524 /* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
525 if (strncmp("sslimap:", str, 8) == 0 || strncmp("imaps:", str, 6) == 0) {
526 #ifdef HAVE_GNUTLS_GNUTLS_H
527 PCU.dossl = 1;
528 #else
529 printf("This copy of wmbiff was not compiled with gnutls;\n"
530 "imaps is unavailable. Exiting to protect your\n"
531 "passwords and privacy.\n");
532 exit(EXIT_FAILURE);
533 #endif
534 } else {
535 PCU.dossl = 0;
538 /* defaults */
539 PCU.serverPort = (PCU.dossl != 0) ? 993 : 143;
540 PCU.authList[0] = '\0';
542 /* argh, str and pc->path are aliases, so we can't just write the default
543 value into the string we're about to parse. */
544 unaliased_str = strdup(str);
545 strcpy(pc->path, "INBOX");
547 for (matchedchars = 0, i = 0;
548 regexes[i] != NULL && matchedchars <= 0; i++) {
549 matchedchars = regulo_match(regexes[i], unaliased_str, regulos);
552 /* failed to match either regex */
553 if (matchedchars <= 0) {
554 pc->label[0] = '\0';
555 IMAP_DM(pc, DEBUG_ERROR, "Couldn't parse line %s (%d)\n"
556 " If this used to work, run wmbiff with the -relax option, and\n"
557 " send mail to "PACKAGE_BUGREPORT" with the hostname\n"
558 " of your mail server.\n", unaliased_str, matchedchars);
559 return -1;
562 PCU.password_len = strlen(PCU.password);
563 if (PCU.password[0] == '\0') {
564 PCU.interactive_password = 1;
565 } else {
566 ENFROB(PCU.password);
569 // grab_authList(unaliased_str + matchedchars, PCU.authList);
571 free(unaliased_str);
573 IMAP_DM(pc, DEBUG_INFO, "userName= '%s'\n", PCU.userName);
574 IMAP_DM(pc, DEBUG_INFO, "password is %d characters long\n",
575 (int) PCU.password_len);
576 IMAP_DM(pc, DEBUG_INFO, "serverName= '%s'\n", PCU.serverName);
577 IMAP_DM(pc, DEBUG_INFO, "serverPath= '%s'\n", pc->path);
578 IMAP_DM(pc, DEBUG_INFO, "serverPort= '%d'\n", PCU.serverPort);
579 IMAP_DM(pc, DEBUG_INFO, "authList= '%s'\n", PCU.authList);
581 if (strcmp(pc->action, "msglst") == 0 ||
582 strcmp(pc->fetchcmd, "msglst") == 0 ||
583 strcmp(pc->button2, "msglst") == 0) {
584 PCU.wantCacheHeaders = 1;
585 } else {
586 PCU.wantCacheHeaders = 0;
588 pc->checkMail = imap_checkmail;
589 pc->getHeaders = imap_getHeaders;
590 pc->releaseHeaders = imap_releaseHeaders;
591 pc->TotalMsgs = 0;
592 pc->UnreadMsgs = 0;
593 pc->OldMsgs = -1;
594 pc->OldUnreadMsgs = -1;
595 return 0;
598 static int authenticate_plaintext( /*@notnull@ */ Pop3 pc,
599 struct connection_state *scs,
600 const char *capabilities)
602 char buf[BUF_SIZE];
603 /* is login prohibited? */
604 /* "An IMAP client which complies with [rfc2525, section 3.2]
605 * MUST NOT issue the LOGIN command if this capability is present.
607 if (strstr(capabilities, "LOGINDISABLED")) {
608 IMAP_DM(pc, DEBUG_ERROR,
609 "Plaintext auth prohibited by server: (LOGINDISABLED).\n");
610 goto plaintext_failed;
613 ask_user_for_password(pc, 0);
614 do {
615 /* login */
616 DEFROB(PCU.password);
617 tlscomm_printf(scs, "a001 LOGIN %s \"%s\"\r\n", PCU.userName,
618 PCU.password);
619 ENFROB(PCU.password);
620 if (tlscomm_expect(scs, "a001 ", buf, BUF_SIZE) == 0) {
621 IMAP_DM(pc, DEBUG_ERROR,
622 "Did not get a response to the LOGIN command.\n");
623 goto plaintext_failed;
626 if (buf[5] != 'O') {
627 IMAP_DM(pc, DEBUG_ERROR, "IMAP Login failed: %s\n", buf);
628 /* if we're prompting the user, ask again, else fail */
629 if (PCU.interactive_password) {
630 PCU.password[0] = '\0';
631 ask_user_for_password(pc, 1); /* 1=overwrite the cache */
632 } else {
633 goto plaintext_failed;
635 } else {
636 return (1);
639 while (1);
641 plaintext_failed:
642 return (0);
645 #ifdef HAVE_GCRYPT_H
646 static int
647 authenticate_md5(Pop3 pc,
648 struct connection_state *scs, const char *capabilities)
650 char buf[BUF_SIZE];
651 char buf2[BUF_SIZE];
652 unsigned char *md5;
653 gcry_md_hd_t gmh;
654 gcry_error_t rc;
656 if (!strstr(capabilities, "AUTH=CRAM-MD5")) {
657 /* server doesn't support cram-md5. */
658 return 0;
661 tlscomm_printf(scs, "a007 AUTHENTICATE CRAM-MD5\r\n");
662 if (tlscomm_expect(scs, "+ ", buf, BUF_SIZE) == 0)
663 goto expect_failure;
665 Decode_Base64(buf + 2, buf2);
666 IMAP_DM(pc, DEBUG_INFO, "CRAM-MD5 challenge: %s\n", buf2);
668 strcpy(buf, PCU.userName);
669 strcat(buf, " ");
670 ask_user_for_password(pc, 0);
671 rc = gcry_md_open(&gmh, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
672 if (rc != 0) {
673 IMAP_DM(pc, DEBUG_INFO, "unable to initialize gcrypt md5\n");
674 return 0;
676 DEFROB(PCU.password);
677 gcry_md_setkey(gmh, PCU.password, strlen(PCU.password));
678 ENFROB(PCU.password);
679 gcry_md_write(gmh, (unsigned char *) buf2, strlen(buf2));
680 gcry_md_final(gmh);
681 md5 = gcry_md_read(gmh, 0);
682 Bin2Hex(md5, 16, buf2);
683 gcry_md_close(gmh);
685 strcat(buf, buf2);
686 IMAP_DM(pc, DEBUG_INFO, "CRAM-MD5 response: %s\n", buf);
687 Encode_Base64(buf, buf2);
689 tlscomm_printf(scs, "%s\r\n", buf2);
690 if (tlscomm_expect(scs, "a007 ", buf, BUF_SIZE) == 0)
691 goto expect_failure;
693 if (!strncmp(buf, "a007 OK", 7))
694 return 1; /* AUTH successful */
696 IMAP_DM(pc, DEBUG_ERROR,
697 "CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
698 PCU.userName, PCU.serverName, PCU.serverPort);
699 IMAP_DM(pc, DEBUG_INFO, "It said %s", buf);
700 return 0;
702 expect_failure:
703 IMAP_DM(pc, DEBUG_ERROR,
704 "tlscomm_expect failed during cram-md5 auth: %s", buf);
705 IMAP_DM(pc, DEBUG_ERROR, "failed to authenticate using cram-md5.");
706 return 0;
708 #endif
710 static void ask_user_for_password( /*@notnull@ */ Pop3 pc, int bFlushCache)
712 /* see if we already have a password, as provided in the config file, or
713 already requested from the user. */
714 if (PCU.interactive_password) {
715 if (strlen(PCU.password) == 0) {
716 /* we need to grab the password from the user. */
717 char *password;
718 IMAP_DM(pc, DEBUG_INFO, "asking for password %d\n",
719 bFlushCache);
720 password =
721 passwordFor(PCU.userName, PCU.serverName, pc, bFlushCache);
722 if (password != NULL) {
723 if (strlen(password) + 1 > BUF_SMALL) {
724 DMA(DEBUG_ERROR, "Password is too long.\n");
725 memset(PCU.password, 0, BUF_SMALL - 1);
726 } else {
727 strncpy(PCU.password, password, BUF_SMALL - 1);
728 PCU.password_len = strlen(PCU.password);
730 free(password);
731 ENFROB(PCU.password);
737 /* vim:set ts=4: */
739 * Local Variables:
740 * tab-width: 4
741 * c-indent-level: 4
742 * c-basic-offset: 4
743 * End: