Make AddMouseRegion's index unsigned
[dockapps.git] / wmbiff / wmbiff / Imap4Client.c
blob5b54ce4047aa57aea7353852d9d72dd82e5cd78f
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 free(connection_name);
204 return NULL;
207 /* build the connection using STARTTLS */
208 if (PCU.dossl != 0 && (PCU.serverPort == 143)) {
209 /* setup an unencrypted binding long enough to invoke STARTTLS */
210 scs = initialize_unencrypted(sd, connection_name, pc);
212 /* can we? */
213 tlscomm_printf(scs, "a000 CAPABILITY\r\n");
214 if (tlscomm_expect(scs, "* CAPABILITY", capabilities, BUF_SIZE) ==
216 goto communication_failure;
218 if (!strstr(capabilities, "STARTTLS")) {
219 IMAP_DM(pc, DEBUG_ERROR,
220 "server doesn't support ssl imap on port 143.");
221 goto communication_failure;
224 /* we sure can! */
225 IMAP_DM(pc, DEBUG_INFO, "Negotiating TLS within IMAP");
226 tlscomm_printf(scs, "a001 STARTTLS\r\n");
228 if (tlscomm_expect(scs, "a001 ", buf, BUF_SIZE) == 0)
229 goto communication_failure;
231 if (strstr(buf, "a001 OK") == 0) {
232 /* we didn't see the success message in the response */
233 IMAP_DM(pc, DEBUG_ERROR, "couldn't negotiate tls. :(\n");
234 goto communication_failure;
237 /* we don't need the unencrypted state anymore */
238 /* note that communication_failure will close the
239 socket and free via tls_close() */
240 free(scs); /* fall through will scs = initialize_gnutls(sd); */
243 /* either we've negotiated ssl from starttls, or
244 we're starting an encrypted connection now */
245 if (PCU.dossl != 0) {
246 scs = initialize_gnutls(sd, connection_name, pc, PCU.serverName);
247 if (scs == NULL) {
248 IMAP_DM(pc, DEBUG_ERROR, "Failed to initialize TLS\n");
249 return NULL;
251 } else {
252 scs = initialize_unencrypted(sd, connection_name, pc);
255 /* authenticate; first find out how */
256 /* note that capabilities may have changed since last
257 time we may have asked, if we called STARTTLS, my
258 server will allow plain password login within an
259 encrypted session. */
260 tlscomm_printf(scs, "a000 CAPABILITY\r\n");
261 if (tlscomm_expect(scs, "* CAPABILITY", capabilities, BUF_SIZE) == 0) {
262 IMAP_DM(pc, DEBUG_ERROR, "unable to query capability string\n");
263 goto communication_failure;
266 /* try each authentication method in turn. */
267 for (a = auth_methods; a->name != NULL; a++) {
268 /* was it specified or did the user leave it up to us? */
269 if (PCU.authList[0] == '\0'
270 || strstr(PCU.authList, a->name) != NULL)
271 /* try the authentication method */
272 if ((a->auth_callback(pc, scs, capabilities)) != 0) {
273 /* store this well setup connection in the cache */
274 bind_state_to_pcu(pc, scs);
275 complained_already = 0;
276 return NULL;
280 /* if authentication worked, we won't get here */
281 IMAP_DM(pc, DEBUG_ERROR,
282 "All authentication methods failed for '%s@%s:%d'\n",
283 PCU.userName, PCU.serverName, PCU.serverPort);
284 communication_failure:
285 tlscomm_printf(scs, "a002 LOGOUT\r\n");
286 tlscomm_close(scs);
287 return NULL;
291 void imap_cacheHeaders( /*@notnull@ */ Pop3 *pc);
293 int imap_checkmail( /*@notnull@ */ Pop3 *pc)
295 /* recover connection state from the cache */
296 struct connection_state *scs = state_for_pcu(pc);
297 char buf[BUF_SIZE];
298 char examine_expect[BUF_SIZE];
299 static int command_id;
301 /* if it's not in the cache, try to open */
302 if (scs == NULL) {
303 IMAP_DM(pc, DEBUG_INFO, "Need new connection to %s@%s\n",
304 PCU.userName, PCU.serverName);
305 (void) imap_open(pc);
306 scs = state_for_pcu(pc);
308 if (scs == NULL) {
309 return -1;
312 if (tlscomm_is_blacklisted(scs) != 0) {
313 /* unresponsive server, don't bother. */
314 return -1;
317 command_id++;
318 tlscomm_printf(scs, "a%03d EXAMINE %s\r\n", command_id, pc->path);
319 snprintf(examine_expect, BUF_SIZE, "a%03d OK", command_id);
320 if (tlscomm_expect(scs, examine_expect, buf, 127) == 0) {
321 tlscomm_close(unbind(scs));
322 return -1;
325 command_id++;
326 tlscomm_printf(scs, "a%03d CLOSE\r\n", command_id);
327 snprintf(examine_expect, BUF_SIZE, "a%03d OK", command_id);
328 if (tlscomm_expect(scs, examine_expect, buf, 127) == 0) {
329 tlscomm_close(unbind(scs));
330 return -1;
333 /* if we've got it by now, try the status query */
334 command_id++;
335 tlscomm_printf(scs, "a%03d STATUS %s (MESSAGES UNSEEN)\r\n",
336 command_id % 1000, pc->path);
337 if (tlscomm_expect(scs, "* STATUS", buf, 127) != 0) {
338 /* a valid response? */
339 // doesn't support spaces: (void) sscanf(buf, "* STATUS %*s (MESSAGES %d UNSEEN %d)",
340 const char *msg;
341 msg = strstr(buf, "(MESSAGES");
342 if (msg != NULL)
343 (void) sscanf(msg, "(MESSAGES %d UNSEEN %d)",
344 &(pc->TotalMsgs), &(pc->UnreadMsgs));
345 /* update the cached headers if evidence that change
346 has occurred; not necessarily complete. */
347 if (pc->UnreadMsgs != pc->OldUnreadMsgs ||
348 pc->TotalMsgs != pc->OldMsgs) {
349 if (PCU.wantCacheHeaders) {
350 imap_cacheHeaders(pc);
353 } else {
354 /* something went wrong. bail. */
355 tlscomm_close(unbind(scs));
356 return -1;
358 return 0;
361 void
362 imap_releaseHeaders(Pop3 *pc __attribute__((unused)), struct msglst *h)
364 assert(h != NULL);
365 /* allow the list to be released next time around */
366 if (h->in_use <= 0) {
367 /* free the old one */
368 while (h != NULL) {
369 struct msglst *n = h->next;
370 free(h);
371 h = n;
373 } else {
374 h->in_use--;
378 void imap_cacheHeaders( /*@notnull@ */ Pop3 *pc)
380 struct connection_state *scs = state_for_pcu(pc);
381 char *msgid;
382 char buf[BUF_SIZE];
384 if (scs == NULL) {
385 (void) imap_open(pc);
386 scs = state_for_pcu(pc);
388 if (scs == NULL) {
389 return;
391 if (tlscomm_is_blacklisted(scs) != 0) {
392 return;
395 if (pc->headerCache != NULL) {
396 /* decrement the reference count, and free our version */
397 imap_releaseHeaders(pc, pc->headerCache);
398 pc->headerCache = NULL;
401 IMAP_DM(pc, DEBUG_INFO, "working headers\n");
403 tlscomm_printf(scs, "a004 EXAMINE %s\r\n", pc->path);
404 if (tlscomm_expect(scs, "a004 OK", buf, 127) == 0) {
405 tlscomm_close(unbind(scs));
406 return;
408 IMAP_DM(pc, DEBUG_INFO, "examine ok\n");
410 /* if we've got it by now, try the status query */
411 tlscomm_printf(scs, "a005 SEARCH UNSEEN\r\n");
412 if (tlscomm_expect(scs, "* SEARCH", buf, 127) == 0) {
413 tlscomm_close(unbind(scs));
414 return;
416 IMAP_DM(pc, DEBUG_INFO, "search: %s", buf);
417 if (strlen(buf) < 9)
418 return; /* search turned up nothing */
419 msgid = strtok(buf + 9, " \r\n");
420 pc->headerCache = NULL;
421 /* the isdigit cruft is to deal with EOL */
422 if (msgid != NULL && isdigit(msgid[0]))
423 do {
424 struct msglst *m = malloc(sizeof(struct msglst));
425 char hdrbuf[128];
426 int fetch_command_done = FALSE;
427 tlscomm_printf(scs, "a04 FETCH %s (FLAGS "
428 "BODY[HEADER.FIELDS (FROM SUBJECT)])\r\n",
429 msgid);
430 if (tlscomm_expect(scs, "* ", hdrbuf, 127)) {
431 m->subj[0] = '\0';
432 m->from[0] = '\0';
433 while (m->subj[0] == '\0' || m->from[0] == '\0') {
434 if (tlscomm_expect(scs, "", hdrbuf, 127)) {
435 if (strncasecmp(hdrbuf, "Subject:", 8) == 0) {
436 strncpy(m->subj, hdrbuf + 9, SUBJ_LEN - 1);
437 m->subj[SUBJ_LEN - 1] = '\0';
438 } else if (strncasecmp(hdrbuf, "From: ", 5) == 0) {
439 strncpy(m->from, hdrbuf + 6, FROM_LEN - 1);
440 m->from[FROM_LEN - 1] = '\0';
441 } else if (strncasecmp
442 (hdrbuf, "a04 OK FETCH", 5) == 0) {
443 /* server says we're done getting this header, which
444 may occur if the message has no subject */
445 if (m->from[0] == '\0') {
446 strcpy(m->from, " ");
448 if (m->subj[0] == '\0') {
449 strcpy(m->subj, "(no subject)");
451 fetch_command_done = TRUE;
453 } else {
454 IMAP_DM(pc, DEBUG_ERROR,
455 "timedout looking for headers.: %s",
456 hdrbuf);
457 strcpy(m->from, "wmbiff");
458 strcpy(m->subj, "failure");
461 IMAP_DM(pc, DEBUG_INFO, "From: '%s' Subj: '%s'\n",
462 m->from, m->subj);
463 m->next = pc->headerCache;
464 pc->headerCache = m;
465 pc->headerCache->in_use = 0; /* initialize that it isn't locked */
466 } else {
467 free(m);
468 IMAP_DM(pc, DEBUG_ERROR, "error fetching: %s", hdrbuf);
470 if (!fetch_command_done) {
471 tlscomm_expect(scs, "a04 OK", hdrbuf, 127);
474 while ((msgid = strtok(NULL, " \r\n")) != NULL
475 && isdigit(msgid[0]));
477 tlscomm_printf(scs, "a06 CLOSE\r\n"); /* return to polling state */
478 /* may be unneeded tlscomm_expect(scs, "a06 OK CLOSE\r\n" ); see if it worked? */
479 IMAP_DM(pc, DEBUG_INFO, "worked headers\n");
482 /* a client is asking for the headers, hand em a reference, increase the
483 one-bit reference counter */
484 struct msglst *imap_getHeaders( /*@notnull@ */ Pop3 *pc)
486 if (pc->headerCache == NULL)
487 imap_cacheHeaders(pc);
488 if (pc->headerCache != NULL)
489 pc->headerCache->in_use = 1;
490 return pc->headerCache;
493 /* parse the config line to setup the Pop3 structure */
494 int imap4Create( /*@notnull@ */ Pop3 *pc, const char *const str)
496 int i;
497 int matchedchars;
498 /* special characters aren't allowed in hostnames, rfc 1034 */
499 const char *regexes[] = {
500 // type : username : password @ hostname (/ name)?(:port)?
501 ".*imaps?:([^: ]{1,255}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(/(\"[^\"]+\")|([^:@ ]+))?(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
502 ".*imaps?:([^: ]{1,255}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)(/(\"[^\"]+\")|([^: ]+))?( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
503 NULL
505 char *unaliased_str;
507 * regulo_atoi expects a pointer-to-int and pop_imap.serverPort is a
508 * uint16_t, so &pop_imap.serverPort is not compatible and we need to use an
509 * int temporary variable to avoid endianness problems.
511 int serverPort;
513 struct regulo regulos[] = {
514 {1, PCU.userName, regulo_strcpy},
515 {2, PCU.password, regulo_strcpy},
516 {3, PCU.serverName, regulo_strcpy},
517 {4, pc->path, regulo_strcpy_skip1},
518 {7, &serverPort, regulo_atoi},
519 {9, PCU.authList, regulo_strcpy_tolower},
520 {0, NULL, NULL}
523 if (Relax) {
524 regexes[0] =
525 ".*imaps?:([^: ]{1,256}):([^@]{0,32})@([^/: ]+)(/(\"[^\"]+\")|([^:@ ]+))?(:[0-9]+)?( *(.*))?$";
526 regexes[1] =
527 ".*imaps?:([^: ]{1,256}) ([^ ]{1,32}) ([^/: ]+)(/(\"[^\"]+\")|([^: ]+))?( [0-9]+)?( *(.*))?$";
531 /* IMAP4 format: imap:user:password@server/mailbox[:port] */
532 /* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
533 if (strncmp("sslimap:", str, 8) == 0 || strncmp("imaps:", str, 6) == 0) {
534 #ifdef HAVE_GNUTLS_GNUTLS_H
535 PCU.dossl = 1;
536 #else
537 printf("This copy of wmbiff was not compiled with gnutls;\n"
538 "imaps is unavailable. Exiting to protect your\n"
539 "passwords and privacy.\n");
540 exit(EXIT_FAILURE);
541 #endif
542 } else {
543 PCU.dossl = 0;
546 /* defaults */
547 serverPort = (PCU.dossl != 0) ? 993 : 143;
548 PCU.authList[0] = '\0';
550 /* argh, str and pc->path are aliases, so we can't just write the default
551 value into the string we're about to parse. */
552 unaliased_str = strdup(str);
553 strcpy(pc->path, "INBOX");
555 for (matchedchars = 0, i = 0;
556 regexes[i] != NULL && matchedchars <= 0; i++) {
557 matchedchars = regulo_match(regexes[i], unaliased_str, regulos);
560 /* failed to match either regex */
561 if (matchedchars <= 0) {
562 pc->label[0] = '\0';
563 IMAP_DM(pc, DEBUG_ERROR, "Couldn't parse line %s (%d)\n"
564 " If this used to work, run wmbiff with the -relax option, and\n"
565 " send mail to "PACKAGE_BUGREPORT" with the hostname\n"
566 " of your mail server.\n", unaliased_str, matchedchars);
567 return -1;
570 PCU.serverPort = serverPort;
572 PCU.password_len = strlen(PCU.password);
573 if (PCU.password[0] == '\0') {
574 PCU.interactive_password = 1;
575 } else {
576 ENFROB(PCU.password);
579 // grab_authList(unaliased_str + matchedchars, PCU.authList);
581 free(unaliased_str);
583 IMAP_DM(pc, DEBUG_INFO, "userName= '%s'\n", PCU.userName);
584 IMAP_DM(pc, DEBUG_INFO, "password is %zu characters long\n",
585 PCU.password_len);
586 IMAP_DM(pc, DEBUG_INFO, "serverName= '%s'\n", PCU.serverName);
587 IMAP_DM(pc, DEBUG_INFO, "serverPath= '%s'\n", pc->path);
588 IMAP_DM(pc, DEBUG_INFO, "serverPort= '%d'\n", PCU.serverPort);
589 IMAP_DM(pc, DEBUG_INFO, "authList= '%s'\n", PCU.authList);
591 if (strcmp(pc->action, "msglst") == 0 ||
592 strcmp(pc->fetchcmd, "msglst") == 0 ||
593 strcmp(pc->button2, "msglst") == 0) {
594 PCU.wantCacheHeaders = 1;
595 } else {
596 PCU.wantCacheHeaders = 0;
598 pc->checkMail = imap_checkmail;
599 pc->getHeaders = imap_getHeaders;
600 pc->releaseHeaders = imap_releaseHeaders;
601 pc->TotalMsgs = 0;
602 pc->UnreadMsgs = 0;
603 pc->OldMsgs = -1;
604 pc->OldUnreadMsgs = -1;
605 return 0;
608 static int authenticate_plaintext( /*@notnull@ */ Pop3 *pc,
609 struct connection_state *scs,
610 const char *capabilities)
612 char buf[BUF_SIZE];
613 /* is login prohibited? */
614 /* "An IMAP client which complies with [rfc2525, section 3.2]
615 * MUST NOT issue the LOGIN command if this capability is present.
617 if (strstr(capabilities, "LOGINDISABLED")) {
618 IMAP_DM(pc, DEBUG_ERROR,
619 "Plaintext auth prohibited by server: (LOGINDISABLED).\n");
620 goto plaintext_failed;
623 ask_user_for_password(pc, 0);
624 do {
625 /* login */
626 DEFROB(PCU.password);
627 tlscomm_printf(scs, "a001 LOGIN %s \"%s\"\r\n", PCU.userName,
628 PCU.password);
629 ENFROB(PCU.password);
630 if (tlscomm_expect(scs, "a001 ", buf, BUF_SIZE) == 0) {
631 IMAP_DM(pc, DEBUG_ERROR,
632 "Did not get a response to the LOGIN command.\n");
633 goto plaintext_failed;
636 if (buf[5] != 'O') {
637 IMAP_DM(pc, DEBUG_ERROR, "IMAP Login failed: %s\n", buf);
638 /* if we're prompting the user, ask again, else fail */
639 if (PCU.interactive_password) {
640 PCU.password[0] = '\0';
641 ask_user_for_password(pc, 1); /* 1=overwrite the cache */
642 } else {
643 goto plaintext_failed;
645 } else {
646 return (1);
649 while (1);
651 plaintext_failed:
652 return (0);
655 #ifdef HAVE_GCRYPT_H
656 static int
657 authenticate_md5(Pop3 *pc,
658 struct connection_state *scs, const char *capabilities)
660 char buf[BUF_SIZE];
661 char buf2[BUF_SIZE];
662 unsigned char *md5;
663 gcry_md_hd_t gmh;
664 gcry_error_t rc;
666 if (!strstr(capabilities, "AUTH=CRAM-MD5")) {
667 /* server doesn't support cram-md5. */
668 return 0;
671 tlscomm_printf(scs, "a007 AUTHENTICATE CRAM-MD5\r\n");
672 if (tlscomm_expect(scs, "+ ", buf, BUF_SIZE) == 0)
673 goto expect_failure;
675 Decode_Base64(buf + 2, buf2);
676 IMAP_DM(pc, DEBUG_INFO, "CRAM-MD5 challenge: %s\n", buf2);
678 strcpy(buf, PCU.userName);
679 strcat(buf, " ");
680 ask_user_for_password(pc, 0);
681 rc = gcry_md_open(&gmh, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
682 if (rc != 0) {
683 IMAP_DM(pc, DEBUG_INFO, "unable to initialize gcrypt md5\n");
684 return 0;
686 DEFROB(PCU.password);
687 gcry_md_setkey(gmh, PCU.password, strlen(PCU.password));
688 ENFROB(PCU.password);
689 gcry_md_write(gmh, (unsigned char *) buf2, strlen(buf2));
690 gcry_md_final(gmh);
691 md5 = gcry_md_read(gmh, 0);
692 Bin2Hex(md5, 16, buf2);
693 gcry_md_close(gmh);
695 strcat(buf, buf2);
696 IMAP_DM(pc, DEBUG_INFO, "CRAM-MD5 response: %s\n", buf);
697 Encode_Base64(buf, buf2);
699 tlscomm_printf(scs, "%s\r\n", buf2);
700 if (tlscomm_expect(scs, "a007 ", buf, BUF_SIZE) == 0)
701 goto expect_failure;
703 if (!strncmp(buf, "a007 OK", 7))
704 return 1; /* AUTH successful */
706 IMAP_DM(pc, DEBUG_ERROR,
707 "CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
708 PCU.userName, PCU.serverName, PCU.serverPort);
709 IMAP_DM(pc, DEBUG_INFO, "It said %s", buf);
710 return 0;
712 expect_failure:
713 IMAP_DM(pc, DEBUG_ERROR,
714 "tlscomm_expect failed during cram-md5 auth: %s", buf);
715 IMAP_DM(pc, DEBUG_ERROR, "failed to authenticate using cram-md5.");
716 return 0;
718 #endif
720 static void ask_user_for_password( /*@notnull@ */ Pop3 *pc, int bFlushCache)
722 /* see if we already have a password, as provided in the config file, or
723 already requested from the user. */
724 if (PCU.interactive_password) {
725 if (strlen(PCU.password) == 0) {
726 /* we need to grab the password from the user. */
727 char *password;
728 IMAP_DM(pc, DEBUG_INFO, "asking for password %d\n",
729 bFlushCache);
730 password =
731 passwordFor(PCU.userName, PCU.serverName, pc, bFlushCache);
732 if (password != NULL) {
733 if (strlen(password) + 1 > BUF_SMALL) {
734 DMA(DEBUG_ERROR, "Password is too long.\n");
735 memset(PCU.password, 0, BUF_SMALL - 1);
736 } else {
737 strncpy(PCU.password, password, BUF_SMALL - 1);
738 PCU.password_len = strlen(PCU.password);
740 free(password);
741 ENFROB(PCU.password);
747 /* vim:set ts=4: */
749 * Local Variables:
750 * tab-width: 4
751 * c-indent-level: 4
752 * c-basic-offset: 4
753 * End: