/* this is basically a stripped-down version of the cram-md5 method. */ imap_auth_res_t imap_auth_anon (IMAP_DATA* idata, const char* method) { int rc; if (!mutt_bit_isset (idata->capabilities, AUTH_ANON)) return IMAP_AUTH_UNAVAIL; if (mutt_account_getuser (&idata->conn->account)) return IMAP_AUTH_FAILURE; if (idata->conn->account.user[0] != '\0') return IMAP_AUTH_UNAVAIL; mutt_message _("Authenticating (anonymous)..."); imap_cmd_start (idata, "AUTHENTICATE ANONYMOUS"); do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { dprint (1, (debugfile, "Invalid response from server.\n")); goto bail; } mutt_socket_write (idata->conn, "ZHVtbXkK\r\n"); /* base64 ("dummy") */ do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_OK) { dprint (1, (debugfile, "Error receiving server response.\n")); goto bail; } if (imap_code (idata->buf)) return IMAP_AUTH_SUCCESS; bail: mutt_error _("Anonymous authentication failed."); mutt_sleep (2); return IMAP_AUTH_FAILURE; }
static int browse_add_list_result (IMAP_DATA* idata, const char* cmd, struct browser_state* state, short isparent) { char *name; int noselect; int noinferiors; IMAP_MBOX mx; if (imap_parse_path (state->folder, &mx)) { dprint (2, (debugfile, "browse_add_list_result: current folder %s makes no sense\n", state->folder)); return -1; } imap_cmd_start (idata, cmd); do { if (imap_parse_list_response(idata, &name, &noselect, &noinferiors, &idata->delim) != 0) { FREE (&mx.mbox); return -1; } if (name) { /* Let a parent folder never be selectable for navigation */ if (isparent) noselect = 1; /* prune current folder from output */ if (isparent || mutt_strncmp (name, mx.mbox, strlen (name))) imap_add_folder (idata->delim, name, noselect, noinferiors, state, isparent); } } while ((ascii_strncmp (idata->cmd.buf, idata->cmd.seq, SEQLEN) != 0)); FREE (&mx.mbox); return 0; }
/* Check which namespaces have contents */ static int browse_verify_namespace (IMAP_DATA* idata, IMAP_NAMESPACE_INFO *nsi, int nns) { char buf[LONG_STRING]; int i = 0; char *name; char delim; for (i = 0; i < nns; i++, nsi++) { /* Cyrus gives back nothing if the % isn't added. This may return lots * of data in some cases, I guess, but I currently feel that's better * than invisible namespaces */ if (nsi->delim) snprintf (buf, sizeof (buf), "%s \"\" \"%s%c%%\"", option (OPTIMAPLSUB) ? "LSUB" : "LIST", nsi->prefix, nsi->delim); else snprintf (buf, sizeof (buf), "%s \"\" \"%s%%\"", option (OPTIMAPLSUB) ? "LSUB" : "LIST", nsi->prefix); imap_cmd_start (idata, buf); nsi->listable = 0; nsi->home_namespace = 0; do { if (imap_parse_list_response(idata, &name, &nsi->noselect, &nsi->noinferiors, &delim) != 0) return -1; nsi->listable |= (name != NULL); } while ((ascii_strncmp (idata->cmd.buf, idata->cmd.seq, SEQLEN) != 0)); } return 0; }
/* imap_auth_sasl: Default authenticator if available. */ imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata, const char* method) { sasl_conn_t* saslconn; sasl_interact_t* interaction = NULL; int rc, irc; char buf[HUGE_STRING]; const char* mech; const char *pc = NULL; unsigned int len, olen; unsigned char client_start; if (mutt_sasl_client_new (idata->conn, &saslconn) < 0) { dprint (1, (debugfile, "imap_auth_sasl: Error allocating SASL connection.\n")); return IMAP_AUTH_FAILURE; } rc = SASL_FAIL; /* If the user hasn't specified a method, use any available */ if (!method) { method = idata->capstr; /* hack for SASL ANONYMOUS support: * 1. Fetch username. If it's "" or "anonymous" then * 2. attempt sasl_client_start with only "AUTH=ANONYMOUS" capability * 3. if sasl_client_start fails, fall through... */ if (mutt_account_getuser (&idata->conn->account)) return IMAP_AUTH_FAILURE; if (mutt_bit_isset (idata->capabilities, AUTH_ANON) && (!idata->conn->account.user[0] || !ascii_strncmp (idata->conn->account.user, "anonymous", 9))) rc = sasl_client_start (saslconn, "AUTH=ANONYMOUS", NULL, &pc, &olen, &mech); } else if (!ascii_strcasecmp ("login", method) && !strstr (NONULL (idata->capstr), "AUTH=LOGIN")) /* do not use SASL login for regular IMAP login (#3556) */ return IMAP_AUTH_UNAVAIL; if (rc != SASL_OK && rc != SASL_CONTINUE) do { rc = sasl_client_start (saslconn, method, &interaction, &pc, &olen, &mech); if (rc == SASL_INTERACT) mutt_sasl_interact (interaction); } while (rc == SASL_INTERACT); client_start = (olen > 0); if (rc != SASL_OK && rc != SASL_CONTINUE) { if (method) dprint (2, (debugfile, "imap_auth_sasl: %s unavailable\n", method)); else dprint (1, (debugfile, "imap_auth_sasl: Failure starting authentication exchange. No shared mechanisms?\n")); /* SASL doesn't support LOGIN, so fall back */ return IMAP_AUTH_UNAVAIL; } mutt_message (_("Authenticating (%s)..."), mech); snprintf (buf, sizeof (buf), "AUTHENTICATE %s", mech); if (mutt_bit_isset (idata->capabilities, SASL_IR) && client_start) { len = mutt_strlen (buf); buf[len++] = ' '; if (sasl_encode64 (pc, olen, buf + len, sizeof (buf) - len, &olen) != SASL_OK) { dprint (1, (debugfile, "imap_auth_sasl: error base64-encoding client response.\n")); goto bail; } client_start = olen = 0; } imap_cmd_start (idata, buf); irc = IMAP_CMD_CONTINUE; /* looping protocol */ while (rc == SASL_CONTINUE || olen > 0) { do irc = imap_cmd_step (idata); while (irc == IMAP_CMD_CONTINUE); if (irc == IMAP_CMD_BAD || irc == IMAP_CMD_NO) goto bail; if (irc == IMAP_CMD_RESPOND) { /* Exchange incorrectly returns +\r\n instead of + \r\n */ if (idata->buf[1] == '\0') { buf[0] = '\0'; len = 0; } else if (sasl_decode64 (idata->buf+2, strlen (idata->buf+2), buf, LONG_STRING-1, &len) != SASL_OK) { dprint (1, (debugfile, "imap_auth_sasl: error base64-decoding server response.\n")); goto bail; } } /* client-start is only available with the SASL-IR extension, but * SASL 2.1 seems to want to use it regardless, at least for DIGEST * fast reauth. Override if the server sent an initial continuation */ if (!client_start || buf[0]) { do { rc = sasl_client_step (saslconn, buf, len, &interaction, &pc, &olen); if (rc == SASL_INTERACT) mutt_sasl_interact (interaction); } while (rc == SASL_INTERACT); } else client_start = 0; /* send out response, or line break if none needed */ if (olen) { if (sasl_encode64 (pc, olen, buf, sizeof (buf), &olen) != SASL_OK) { dprint (1, (debugfile, "imap_auth_sasl: error base64-encoding client response.\n")); goto bail; } } if (irc == IMAP_CMD_RESPOND) { strfcpy (buf + olen, "\r\n", sizeof (buf) - olen); mutt_socket_write (idata->conn, buf); } /* If SASL has errored out, send an abort string to the server */ if (rc < 0) { mutt_socket_write (idata->conn, "*\r\n"); dprint (1, (debugfile, "imap_auth_sasl: sasl_client_step error %d\n",rc)); } olen = 0; } while (irc != IMAP_CMD_OK) if ((irc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE) break; if (rc != SASL_OK) goto bail; if (imap_code (idata->buf)) { mutt_sasl_setup_conn (idata->conn, saslconn); return IMAP_AUTH_SUCCESS; } bail: sasl_dispose (&saslconn); if (method) { dprint (2, (debugfile, "imap_auth_sasl: %s failed\n", method)); return IMAP_AUTH_UNAVAIL; } mutt_error _("SASL authentication failed."); mutt_sleep(2); return IMAP_AUTH_FAILURE; }
int imap_append_message (CONTEXT *ctx, MESSAGE *msg) { IMAP_DATA* idata; FILE *fp; char buf[LONG_STRING]; char mbox[LONG_STRING]; char mailbox[LONG_STRING]; char internaldate[IMAP_DATELEN]; char imap_flags[SHORT_STRING]; size_t len; progress_t progressbar; size_t sent; int c, last; IMAP_MBOX mx; int rc; idata = (IMAP_DATA*) ctx->data; if (imap_parse_path (ctx->path, &mx)) return -1; imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox)); if (!*mailbox) strfcpy (mailbox, "INBOX", sizeof (mailbox)); if ((fp = fopen (msg->path, "r")) == NULL) { mutt_perror (msg->path); goto fail; } /* currently we set the \Seen flag on all messages, but probably we * should scan the message Status header for flag info. Since we're * already rereading the whole file for length it isn't any more * expensive (it'd be nice if we had the file size passed in already * by the code that writes the file, but that's a lot of changes. * Ideally we'd have a HEADER structure with flag info here... */ for (last = EOF, len = 0; (c = fgetc(fp)) != EOF; last = c) { if(c == '\n' && last != '\r') len++; len++; } rewind (fp); mutt_progress_init (&progressbar, _("Uploading message..."), MUTT_PROGRESS_SIZE, NetInc, len); imap_munge_mbox_name (idata, mbox, sizeof (mbox), mailbox); imap_make_date (internaldate, msg->received); imap_flags[0] = imap_flags[1] = 0; if (msg->flags.read) safe_strcat (imap_flags, sizeof (imap_flags), " \\Seen"); if (msg->flags.replied) safe_strcat (imap_flags, sizeof (imap_flags), " \\Answered"); if (msg->flags.flagged) safe_strcat (imap_flags, sizeof (imap_flags), " \\Flagged"); if (msg->flags.draft) safe_strcat (imap_flags, sizeof (imap_flags), " \\Draft"); snprintf (buf, sizeof (buf), "APPEND %s (%s) \"%s\" {%lu}", mbox, imap_flags + 1, internaldate, (unsigned long) len); imap_cmd_start (idata, buf); do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { char *pc; dprint (1, (debugfile, "imap_append_message(): command failed: %s\n", idata->buf)); pc = idata->buf + SEQLEN; SKIPWS (pc); pc = imap_next_word (pc); mutt_error ("%s", pc); mutt_sleep (1); safe_fclose (&fp); goto fail; } for (last = EOF, sent = len = 0; (c = fgetc(fp)) != EOF; last = c) { if (c == '\n' && last != '\r') buf[len++] = '\r'; buf[len++] = c; if (len > sizeof(buf) - 3) { sent += len; flush_buffer(buf, &len, idata->conn); mutt_progress_update (&progressbar, sent, -1); } } if (len) flush_buffer(buf, &len, idata->conn); mutt_socket_write (idata->conn, "\r\n"); safe_fclose (&fp); do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); if (!imap_code (idata->buf)) { char *pc; dprint (1, (debugfile, "imap_append_message(): command failed: %s\n", idata->buf)); pc = idata->buf + SEQLEN; SKIPWS (pc); pc = imap_next_word (pc); mutt_error ("%s", pc); mutt_sleep (1); goto fail; } FREE (&mx.mbox); return 0; fail: FREE (&mx.mbox); return -1; }
/* imap_read_headers: * Changed to read many headers instead of just one. It will return the * msgno of the last message read. It will return a value other than * msgend if mail comes in while downloading headers (in theory). */ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend) { CONTEXT* ctx; char *hdrreq = NULL; FILE *fp; char tempfile[_POSIX_PATH_MAX]; int msgno, idx = msgbegin - 1; IMAP_HEADER h; IMAP_STATUS* status; int rc, mfhrc, oldmsgcount; int fetchlast = 0; int maxuid = 0; static const char * const want_headers = "DATE FROM SUBJECT TO CC MESSAGE-ID REFERENCES CONTENT-TYPE CONTENT-DESCRIPTION IN-REPLY-TO REPLY-TO LINES LIST-POST X-LABEL"; progress_t progress; int retval = -1; #if USE_HCACHE char buf[LONG_STRING]; unsigned int *uid_validity = NULL; unsigned int *puidnext = NULL; unsigned int uidnext = 0; int evalhc = 0; #endif /* USE_HCACHE */ ctx = idata->ctx; if (mutt_bit_isset (idata->capabilities,IMAP4REV1)) { safe_asprintf (&hdrreq, "BODY.PEEK[HEADER.FIELDS (%s%s%s)]", want_headers, ImapHeaders ? " " : "", NONULL (ImapHeaders)); } else if (mutt_bit_isset (idata->capabilities,IMAP4)) { safe_asprintf (&hdrreq, "RFC822.HEADER.LINES (%s%s%s)", want_headers, ImapHeaders ? " " : "", NONULL (ImapHeaders)); } else { /* Unable to fetch headers for lower versions */ mutt_error _("Unable to fetch headers from this IMAP server version."); mutt_sleep (2); /* pause a moment to let the user see the error */ goto error_out_0; } /* instead of downloading all headers and then parsing them, we parse them * as they come in. */ mutt_mktemp (tempfile, sizeof (tempfile)); if (!(fp = safe_fopen (tempfile, "w+"))) { mutt_error (_("Could not create temporary file %s"), tempfile); mutt_sleep (2); goto error_out_0; } unlink (tempfile); /* make sure context has room to hold the mailbox */ while ((msgend) >= idata->ctx->hdrmax) mx_alloc_memory (idata->ctx); oldmsgcount = ctx->msgcount; idata->reopen &= ~(IMAP_REOPEN_ALLOW|IMAP_NEWMAIL_PENDING); idata->newMailCount = 0; #if USE_HCACHE idata->hcache = imap_hcache_open (idata, NULL); if (idata->hcache && !msgbegin) { uid_validity = mutt_hcache_fetch_raw (idata->hcache, "/UIDVALIDITY", imap_hcache_keylen); puidnext = mutt_hcache_fetch_raw (idata->hcache, "/UIDNEXT", imap_hcache_keylen); if (puidnext) { uidnext = *puidnext; FREE (&puidnext); } if (uid_validity && uidnext && *uid_validity == idata->uid_validity) evalhc = 1; FREE (&uid_validity); } if (evalhc) { /* L10N: Comparing the cached data with the IMAP server's data */ mutt_progress_init (&progress, _("Evaluating cache..."), MUTT_PROGRESS_MSG, ReadInc, msgend + 1); snprintf (buf, sizeof (buf), "UID FETCH 1:%u (UID FLAGS)", uidnext - 1); imap_cmd_start (idata, buf); rc = IMAP_CMD_CONTINUE; for (msgno = msgbegin; rc == IMAP_CMD_CONTINUE; msgno++) { mutt_progress_update (&progress, msgno + 1, -1); memset (&h, 0, sizeof (h)); h.data = safe_calloc (1, sizeof (IMAP_HEADER_DATA)); do { mfhrc = 0; rc = imap_cmd_step (idata); if (rc != IMAP_CMD_CONTINUE) { imap_free_header_data (&h.data); break; } /* hole in the header cache */ if (!evalhc) continue; if ((mfhrc = msg_fetch_header (ctx, &h, idata->buf, NULL)) == -1) continue; else if (mfhrc < 0) { imap_free_header_data (&h.data); break; } if (!h.data->uid) { dprint (2, (debugfile, "imap_read_headers: skipping hcache FETCH " "response for unknown message number %d\n", h.sid)); mfhrc = -1; continue; } idx++; ctx->hdrs[idx] = imap_hcache_get (idata, h.data->uid); if (ctx->hdrs[idx]) { ctx->hdrs[idx]->index = idx; /* messages which have not been expunged are ACTIVE (borrowed from mh * folders) */ ctx->hdrs[idx]->active = 1; ctx->hdrs[idx]->read = h.data->read; ctx->hdrs[idx]->old = h.data->old; ctx->hdrs[idx]->deleted = h.data->deleted; ctx->hdrs[idx]->flagged = h.data->flagged; ctx->hdrs[idx]->replied = h.data->replied; ctx->hdrs[idx]->changed = h.data->changed; /* ctx->hdrs[msgno]->received is restored from mutt_hcache_restore */ ctx->hdrs[idx]->data = (void *) (h.data); ctx->msgcount++; ctx->size += ctx->hdrs[idx]->content->length; } else { /* bad header in the cache, we'll have to refetch. */ dprint (3, (debugfile, "bad cache entry at %d, giving up\n", h.sid - 1)); imap_free_header_data(&h.data); evalhc = 0; idx--; } } while (rc != IMAP_CMD_OK && mfhrc == -1); if (rc == IMAP_CMD_OK) break; if ((mfhrc < -1) || ((rc != IMAP_CMD_CONTINUE) && (rc != IMAP_CMD_OK))) { imap_free_header_data (&h.data); imap_hcache_close (idata); goto error_out_1; } } /* could also look for first null header in case hcache is holey */ msgbegin = ctx->msgcount; } #endif /* USE_HCACHE */ mutt_progress_init (&progress, _("Fetching message headers..."), MUTT_PROGRESS_MSG, ReadInc, msgend + 1); for (msgno = msgbegin; msgno <= msgend ; msgno++) { mutt_progress_update (&progress, msgno + 1, -1); /* we may get notification of new mail while fetching headers */ if (msgno + 1 > fetchlast) { char *cmd; fetchlast = msgend + 1; safe_asprintf (&cmd, "FETCH %d:%d (UID FLAGS INTERNALDATE RFC822.SIZE %s)", msgno + 1, fetchlast, hdrreq); imap_cmd_start (idata, cmd); FREE (&cmd); } rewind (fp); memset (&h, 0, sizeof (h)); h.data = safe_calloc (1, sizeof (IMAP_HEADER_DATA)); /* this DO loop does two things: * 1. handles untagged messages, so we can try again on the same msg * 2. fetches the tagged response at the end of the last message. */ do { mfhrc = 0; rc = imap_cmd_step (idata); if (rc != IMAP_CMD_CONTINUE) break; if ((mfhrc = msg_fetch_header (ctx, &h, idata->buf, fp)) == -1) continue; else if (mfhrc < 0) break; if (!ftello (fp)) { dprint (2, (debugfile, "msg_fetch_header: ignoring fetch response with no body\n")); mfhrc = -1; msgend--; continue; } /* make sure we don't get remnants from older larger message headers */ fputs ("\n\n", fp); idx++; if (idx > msgend) { dprint (1, (debugfile, "imap_read_headers: skipping FETCH response for " "unknown message number %d\n", h.sid)); mfhrc = -1; idx--; continue; } /* May receive FLAGS updates in a separate untagged response (#2935) */ if (idx < ctx->msgcount) { dprint (2, (debugfile, "imap_read_headers: message %d is not new\n", h.sid)); idx--; continue; } ctx->hdrs[idx] = mutt_new_header (); ctx->hdrs[idx]->index = h.sid - 1; /* messages which have not been expunged are ACTIVE (borrowed from mh * folders) */ ctx->hdrs[idx]->active = 1; ctx->hdrs[idx]->read = h.data->read; ctx->hdrs[idx]->old = h.data->old; ctx->hdrs[idx]->deleted = h.data->deleted; ctx->hdrs[idx]->flagged = h.data->flagged; ctx->hdrs[idx]->replied = h.data->replied; ctx->hdrs[idx]->changed = h.data->changed; ctx->hdrs[idx]->received = h.received; ctx->hdrs[idx]->data = (void *) (h.data); if (maxuid < h.data->uid) maxuid = h.data->uid; rewind (fp); /* NOTE: if Date: header is missing, mutt_read_rfc822_header depends * on h.received being set */ ctx->hdrs[idx]->env = mutt_read_rfc822_header (fp, ctx->hdrs[idx], 0, 0); /* content built as a side-effect of mutt_read_rfc822_header */ ctx->hdrs[idx]->content->length = h.content_length; ctx->size += h.content_length; #if USE_HCACHE imap_hcache_put (idata, ctx->hdrs[idx]); #endif /* USE_HCACHE */ ctx->msgcount++; } while ((rc != IMAP_CMD_OK) && ((mfhrc == -1) || ((msgno + 1) >= fetchlast))); if ((mfhrc < -1) || ((rc != IMAP_CMD_CONTINUE) && (rc != IMAP_CMD_OK))) { imap_free_header_data (&h.data); #if USE_HCACHE imap_hcache_close (idata); #endif goto error_out_1; } /* in case we get new mail while fetching the headers */ if (idata->reopen & IMAP_NEWMAIL_PENDING) { msgend = idata->newMailCount - 1; while ((msgend) >= ctx->hdrmax) mx_alloc_memory (ctx); idata->reopen &= ~IMAP_NEWMAIL_PENDING; idata->newMailCount = 0; } } if (maxuid && (status = imap_mboxcache_get (idata, idata->mailbox, 0)) && (status->uidnext < maxuid + 1)) status->uidnext = maxuid + 1; #if USE_HCACHE mutt_hcache_store_raw (idata->hcache, "/UIDVALIDITY", &idata->uid_validity, sizeof (idata->uid_validity), imap_hcache_keylen); if (maxuid && idata->uidnext < maxuid + 1) { dprint (2, (debugfile, "Overriding UIDNEXT: %u -> %u\n", idata->uidnext, maxuid + 1)); idata->uidnext = maxuid + 1; } if (idata->uidnext > 1) mutt_hcache_store_raw (idata->hcache, "/UIDNEXT", &idata->uidnext, sizeof (idata->uidnext), imap_hcache_keylen); imap_hcache_close (idata); #endif /* USE_HCACHE */ if (ctx->msgcount > oldmsgcount) { mx_alloc_memory(ctx); mx_update_context (ctx, ctx->msgcount - oldmsgcount); } idata->reopen |= IMAP_REOPEN_ALLOW; retval = msgend; error_out_1: safe_fclose (&fp); error_out_0: FREE (&hdrreq); return retval; }
int imap_fetch_message (CONTEXT *ctx, MESSAGE *msg, int msgno) { IMAP_DATA* idata; HEADER* h; ENVELOPE* newenv; char buf[LONG_STRING]; char path[_POSIX_PATH_MAX]; char *pc; long bytes; progress_t progressbar; int uid; int cacheno; IMAP_CACHE *cache; int read; int rc; /* Sam's weird courier server returns an OK response even when FETCH * fails. Thanks Sam. */ short fetched = 0; idata = (IMAP_DATA*) ctx->data; h = ctx->hdrs[msgno]; if ((msg->fp = msg_cache_get (idata, h))) { if (HEADER_DATA(h)->parsed) return 0; else goto parsemsg; } /* we still do some caching even if imap_cachedir is unset */ /* see if we already have the message in our cache */ cacheno = HEADER_DATA(h)->uid % IMAP_CACHE_LEN; cache = &idata->cache[cacheno]; if (cache->path) { /* don't treat cache errors as fatal, just fall back. */ if (cache->uid == HEADER_DATA(h)->uid && (msg->fp = fopen (cache->path, "r"))) return 0; else { unlink (cache->path); FREE (&cache->path); } } if (!isendwin()) mutt_message _("Fetching message..."); if (!(msg->fp = msg_cache_put (idata, h))) { cache->uid = HEADER_DATA(h)->uid; mutt_mktemp (path, sizeof (path)); cache->path = safe_strdup (path); if (!(msg->fp = safe_fopen (path, "w+"))) { FREE (&cache->path); return -1; } } /* mark this header as currently inactive so the command handler won't * also try to update it. HACK until all this code can be moved into the * command handler */ h->active = 0; snprintf (buf, sizeof (buf), "UID FETCH %u %s", HEADER_DATA(h)->uid, (mutt_bit_isset (idata->capabilities, IMAP4REV1) ? (option (OPTIMAPPEEK) ? "BODY.PEEK[]" : "BODY[]") : "RFC822")); imap_cmd_start (idata, buf); do { if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE) break; pc = idata->buf; pc = imap_next_word (pc); pc = imap_next_word (pc); if (!ascii_strncasecmp ("FETCH", pc, 5)) { while (*pc) { pc = imap_next_word (pc); if (pc[0] == '(') pc++; if (ascii_strncasecmp ("UID", pc, 3) == 0) { pc = imap_next_word (pc); uid = atoi (pc); if (uid != HEADER_DATA(h)->uid) mutt_error (_("The message index is incorrect. Try reopening the mailbox.")); } else if ((ascii_strncasecmp ("RFC822", pc, 6) == 0) || (ascii_strncasecmp ("BODY[]", pc, 6) == 0)) { pc = imap_next_word (pc); if (imap_get_literal_count(pc, &bytes) < 0) { imap_error ("imap_fetch_message()", buf); goto bail; } mutt_progress_init (&progressbar, _("Fetching message..."), MUTT_PROGRESS_SIZE, NetInc, bytes); if (imap_read_literal (msg->fp, idata, bytes, &progressbar) < 0) goto bail; /* pick up trailing line */ if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE) goto bail; pc = idata->buf; fetched = 1; } /* UW-IMAP will provide a FLAGS update here if the FETCH causes a * change (eg from \Unseen to \Seen). * Uncommitted changes in mutt take precedence. If we decide to * incrementally update flags later, this won't stop us syncing */ else if ((ascii_strncasecmp ("FLAGS", pc, 5) == 0) && !h->changed) { if ((pc = imap_set_flags (idata, h, pc)) == NULL) goto bail; } } } } while (rc == IMAP_CMD_CONTINUE); /* see comment before command start. */ h->active = 1; fflush (msg->fp); if (ferror (msg->fp)) { mutt_perror (cache->path); goto bail; } if (rc != IMAP_CMD_OK) goto bail; if (!fetched || !imap_code (idata->buf)) goto bail; msg_cache_commit (idata, h); parsemsg: /* Update the header information. Previously, we only downloaded a * portion of the headers, those required for the main display. */ rewind (msg->fp); /* It may be that the Status header indicates a message is read, but the * IMAP server doesn't know the message has been \Seen. So we capture * the server's notion of 'read' and if it differs from the message info * picked up in mutt_read_rfc822_header, we mark the message (and context * changed). Another possibility: ignore Status on IMAP?*/ read = h->read; newenv = mutt_read_rfc822_header (msg->fp, h, 0, 0); mutt_merge_envelopes(h->env, &newenv); /* see above. We want the new status in h->read, so we unset it manually * and let mutt_set_flag set it correctly, updating context. */ if (read != h->read) { h->read = read; mutt_set_flag (ctx, h, MUTT_NEW, read); } h->lines = 0; fgets (buf, sizeof (buf), msg->fp); while (!feof (msg->fp)) { h->lines++; fgets (buf, sizeof (buf), msg->fp); } h->content->length = ftell (msg->fp) - h->content->offset; /* This needs to be done in case this is a multipart message */ #if defined(HAVE_PGP) || defined(HAVE_SMIME) h->security = crypt_query (h->content); #endif mutt_clear_error(); rewind (msg->fp); HEADER_DATA(h)->parsed = 1; return 0; bail: safe_fclose (&msg->fp); imap_cache_del (idata, h); if (cache->path) { unlink (cache->path); FREE (&cache->path); } return -1; }
/* imap_auth_cram_md5: AUTH=CRAM-MD5 support. */ ImapResult imap_auth_cram(ImapMboxHandle* handle) { char ibuf[LONG_STRING*2], obuf[LONG_STRING]; unsigned char hmac_response[MD5_DIGEST_LEN]; unsigned cmdno; int len, rc, ok; char *user = NULL, *pass = NULL; if (!imap_mbox_handle_can_do(handle, IMCAP_ACRAM_MD5)) return IMAP_AUTH_UNAVAIL; ok = 0; if(!ok && handle->user_cb) handle->user_cb(IME_GET_USER_PASS, handle->user_arg, "CRAM-MD5", &user, &pass, &ok); if(!ok || user == NULL || pass == NULL) { imap_mbox_handle_set_msg(handle, "Authentication cancelled"); return IMAP_AUTH_CANCELLED; } /* start the interaction */ if(imap_cmd_start(handle, "AUTHENTICATE CRAM-MD5", &cmdno) <0) return IMAP_AUTH_FAILURE; /* From RFC 2195: * The data encoded in the first ready response contains a presumptively * arbitrary string of random digits, a timestamp, and the fully-qualified * primary host name of the server. The syntax of the unencoded form must * correspond to that of an RFC 822 'msg-id' [RFC822] as described in [POP3]. */ imap_handle_flush(handle); do rc = imap_cmd_step(handle, cmdno); while(rc == IMR_UNTAGGED); if (rc != IMR_RESPOND) { g_warning("cram-md5: unexpected response:\n"); return IMAP_AUTH_FAILURE; } imap_mbox_gets(handle, ibuf, sizeof(ibuf)); /* check error */ if ((len = lit_conv_from_base64(obuf, ibuf)) <0) { g_warning("Error decoding base64 response(%s), digit=%d:%d[%c]).\n", ibuf, len, ibuf[-len-1],ibuf[-len-1]); return IMAP_AUTH_FAILURE; } obuf[len] = '\0'; /* The client makes note of the data and then responds with a string * consisting of the user name, a space, and a 'digest'. The latter is * computed by applying the keyed MD5 algorithm from [KEYED-MD5] where the * key is a shared secret and the digested text is the timestamp (including * angle-brackets). * * Note: The user name shouldn't be quoted. Since the digest can't contain * spaces, there is no ambiguity. Some servers get this wrong, we'll work * around them when the bug report comes in. Until then, we'll remain * blissfully RFC-compliant. */ hmac_md5 (pass, obuf, hmac_response); g_snprintf (obuf, sizeof (obuf), "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", user, hmac_response[0], hmac_response[1], hmac_response[2], hmac_response[3], hmac_response[4], hmac_response[5], hmac_response[6], hmac_response[7], hmac_response[8], hmac_response[9], hmac_response[10], hmac_response[11], hmac_response[12], hmac_response[13], hmac_response[14], hmac_response[15]); /* XXX - ibuf must be long enough to store the base64 encoding of obuf, * plus the additional debris */ lit_conv_to_base64(ibuf, obuf, strlen (obuf), sizeof(ibuf)-2); strncat (ibuf, "\r\n", sizeof (ibuf) - strlen(ibuf) - 1); imap_handle_write(handle, ibuf, strlen(ibuf)); imap_handle_flush(handle); g_free(user); g_free(pass); /* FIXME: clean passwd first */ do rc = imap_cmd_step (handle, cmdno); while (rc == IMR_UNTAGGED); return rc == IMR_OK ? IMAP_SUCCESS : IMAP_AUTH_FAILURE; }
/** * imap_auth_gss - GSS Authentication support * @param adata Imap Account data * @param method Name of this authentication method * @retval enum Result, e.g. #IMAP_AUTH_SUCCESS */ enum ImapAuthRes imap_auth_gss(struct ImapAccountData *adata, const char *method) { gss_buffer_desc request_buf, send_token; gss_buffer_t sec_token; gss_name_t target_name; gss_ctx_id_t context; gss_OID mech_name; char server_conf_flags; gss_qop_t quality; int cflags; OM_uint32 maj_stat, min_stat; unsigned long buf_size; int rc, retval = IMAP_AUTH_FAILURE; if (!(adata->capabilities & IMAP_CAP_AUTH_GSSAPI)) return IMAP_AUTH_UNAVAIL; if (mutt_account_getuser(&adata->conn->account) < 0) return IMAP_AUTH_FAILURE; struct Buffer *buf1 = mutt_buffer_pool_get(); struct Buffer *buf2 = mutt_buffer_pool_get(); /* get an IMAP service ticket for the server */ mutt_buffer_printf(buf1, "imap@%s", adata->conn->account.host); request_buf.value = buf1->data; request_buf.length = mutt_buffer_len(buf1); maj_stat = gss_import_name(&min_stat, &request_buf, gss_nt_service_name, &target_name); if (maj_stat != GSS_S_COMPLETE) { mutt_debug(LL_DEBUG2, "Couldn't get service name for [%s]\n", buf1); retval = IMAP_AUTH_UNAVAIL; goto cleanup; } else if (C_DebugLevel >= 2) { gss_display_name(&min_stat, target_name, &request_buf, &mech_name); mutt_debug(LL_DEBUG2, "Using service name [%s]\n", (char *) request_buf.value); gss_release_buffer(&min_stat, &request_buf); } /* Acquire initial credentials - without a TGT GSSAPI is UNAVAIL */ sec_token = GSS_C_NO_BUFFER; context = GSS_C_NO_CONTEXT; /* build token */ maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &context, target_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL, &send_token, (unsigned int *) &cflags, NULL); if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) { print_gss_error(maj_stat, min_stat); mutt_debug(LL_DEBUG1, "Error acquiring credentials - no TGT?\n"); gss_release_name(&min_stat, &target_name); retval = IMAP_AUTH_UNAVAIL; goto cleanup; } /* now begin login */ mutt_message(_("Authenticating (GSSAPI)...")); imap_cmd_start(adata, "AUTHENTICATE GSSAPI"); /* expect a null continuation response ("+") */ do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { mutt_debug(LL_DEBUG2, "Invalid response from server: %s\n", buf1); gss_release_name(&min_stat, &target_name); goto bail; } /* now start the security context initialisation loop... */ mutt_debug(LL_DEBUG2, "Sending credentials\n"); mutt_b64_buffer_encode(buf1, send_token.value, send_token.length); gss_release_buffer(&min_stat, &send_token); mutt_buffer_addstr(buf1, "\r\n"); mutt_socket_send(adata->conn, mutt_b2s(buf1)); while (maj_stat == GSS_S_CONTINUE_NEEDED) { /* Read server data */ do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { mutt_debug(LL_DEBUG1, "#1 Error receiving server response\n"); gss_release_name(&min_stat, &target_name); goto bail; } if (mutt_b64_buffer_decode(buf2, adata->buf + 2) < 0) { mutt_debug(LL_DEBUG1, "Invalid base64 server response\n"); gss_release_name(&min_stat, &target_name); goto err_abort_cmd; } request_buf.value = buf2->data; request_buf.length = mutt_buffer_len(buf2); sec_token = &request_buf; /* Write client data */ maj_stat = gss_init_sec_context( &min_stat, GSS_C_NO_CREDENTIAL, &context, target_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL, &send_token, (unsigned int *) &cflags, NULL); if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) { print_gss_error(maj_stat, min_stat); mutt_debug(LL_DEBUG1, "Error exchanging credentials\n"); gss_release_name(&min_stat, &target_name); goto err_abort_cmd; } mutt_b64_buffer_encode(buf1, send_token.value, send_token.length); gss_release_buffer(&min_stat, &send_token); mutt_buffer_addstr(buf1, "\r\n"); mutt_socket_send(adata->conn, mutt_b2s(buf1)); } gss_release_name(&min_stat, &target_name); /* get security flags and buffer size */ do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { mutt_debug(LL_DEBUG1, "#2 Error receiving server response\n"); goto bail; } if (mutt_b64_buffer_decode(buf2, adata->buf + 2) < 0) { mutt_debug(LL_DEBUG1, "Invalid base64 server response\n"); goto err_abort_cmd; } request_buf.value = buf2->data; request_buf.length = mutt_buffer_len(buf2); maj_stat = gss_unwrap(&min_stat, context, &request_buf, &send_token, &cflags, &quality); if (maj_stat != GSS_S_COMPLETE) { print_gss_error(maj_stat, min_stat); mutt_debug(LL_DEBUG2, "Couldn't unwrap security level data\n"); gss_release_buffer(&min_stat, &send_token); goto err_abort_cmd; } mutt_debug(LL_DEBUG2, "Credential exchange complete\n"); /* first octet is security levels supported. We want NONE */ server_conf_flags = ((char *) send_token.value)[0]; if (!(((char *) send_token.value)[0] & GSS_AUTH_P_NONE)) { mutt_debug(LL_DEBUG2, "Server requires integrity or privacy\n"); gss_release_buffer(&min_stat, &send_token); goto err_abort_cmd; } /* we don't care about buffer size if we don't wrap content. But here it is */ ((char *) send_token.value)[0] = '\0'; buf_size = ntohl(*((long *) send_token.value)); gss_release_buffer(&min_stat, &send_token); mutt_debug(LL_DEBUG2, "Unwrapped security level flags: %c%c%c\n", (server_conf_flags & GSS_AUTH_P_NONE) ? 'N' : '-', (server_conf_flags & GSS_AUTH_P_INTEGRITY) ? 'I' : '-', (server_conf_flags & GSS_AUTH_P_PRIVACY) ? 'P' : '-'); mutt_debug(LL_DEBUG2, "Maximum GSS token size is %ld\n", buf_size); /* agree to terms (hack!) */ buf_size = htonl(buf_size); /* not relevant without integrity/privacy */ mutt_buffer_reset(buf1); mutt_buffer_addch(buf1, GSS_AUTH_P_NONE); mutt_buffer_addstr_n(buf1, ((char *) &buf_size) + 1, 3); /* server decides if principal can log in as user */ mutt_buffer_addstr(buf1, adata->conn->account.user); request_buf.value = buf1->data; request_buf.length = mutt_buffer_len(buf1); maj_stat = gss_wrap(&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf, &cflags, &send_token); if (maj_stat != GSS_S_COMPLETE) { mutt_debug(LL_DEBUG2, "Error creating login request\n"); goto err_abort_cmd; } mutt_b64_buffer_encode(buf1, send_token.value, send_token.length); mutt_debug(LL_DEBUG2, "Requesting authorisation as %s\n", adata->conn->account.user); mutt_buffer_addstr(buf1, "\r\n"); mutt_socket_send(adata->conn, mutt_b2s(buf1)); /* Joy of victory or agony of defeat? */ do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); if (rc == IMAP_CMD_RESPOND) { mutt_debug(LL_DEBUG1, "Unexpected server continuation request\n"); goto err_abort_cmd; } if (imap_code(adata->buf)) { /* flush the security context */ mutt_debug(LL_DEBUG2, "Releasing GSS credentials\n"); maj_stat = gss_delete_sec_context(&min_stat, &context, &send_token); if (maj_stat != GSS_S_COMPLETE) mutt_debug(LL_DEBUG1, "Error releasing credentials\n"); /* send_token may contain a notification to the server to flush * credentials. RFC1731 doesn't specify what to do, and since this * support is only for authentication, we'll assume the server knows * enough to flush its own credentials */ gss_release_buffer(&min_stat, &send_token); retval = IMAP_AUTH_SUCCESS; goto cleanup; } else goto bail; err_abort_cmd: mutt_socket_send(adata->conn, "*\r\n"); do rc = imap_cmd_step(adata); while (rc == IMAP_CMD_CONTINUE); bail: mutt_error(_("GSSAPI authentication failed")); retval = IMAP_AUTH_FAILURE; cleanup: mutt_buffer_pool_release(&buf1); mutt_buffer_pool_release(&buf2); return retval; }
/* imap_browse: IMAP hook into the folder browser, fills out browser_state, * given a current folder to browse */ int imap_browse (char* path, struct browser_state* state) { IMAP_DATA* idata; char buf[LONG_STRING]; char buf2[LONG_STRING]; char nsbuf[LONG_STRING]; char mbox[LONG_STRING]; char list_cmd[5]; IMAP_NAMESPACE_INFO nsi[16]; int home_namespace = 0; int n; int i; int nsup; char ctmp; int nns; char *cur_folder; short showparents = 0; int noselect; int noinferiors; IMAP_MBOX mx; if (imap_parse_path (path, &mx)) { mutt_error (_("%s is an invalid IMAP path"), path); return -1; } strfcpy (list_cmd, option (OPTIMAPLSUB) ? "LSUB" : "LIST", sizeof (list_cmd)); if (!(idata = imap_conn_find (&(mx.account), 0))) goto fail; if (!mx.mbox) { home_namespace = 1; mbox[0] = '\0'; /* Do not replace "" with "INBOX" here */ mx.mbox = safe_strdup(ImapHomeNamespace); nns = 0; if (mutt_bit_isset(idata->capabilities,NAMESPACE)) { mutt_message _("Getting namespaces..."); if (browse_get_namespace (idata, nsbuf, sizeof (nsbuf), nsi, sizeof (nsi), &nns) != 0) goto fail; if (browse_verify_namespace (idata, nsi, nns) != 0) goto fail; } } mutt_message _("Getting folder list..."); /* skip check for parents when at the root */ if (mx.mbox && mx.mbox[0] != '\0') { imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox)); imap_munge_mbox_name (buf, sizeof (buf), mbox); imap_unquote_string(buf); /* As kludgy as it gets */ mbox[sizeof (mbox) - 1] = '\0'; strncpy (mbox, buf, sizeof (mbox) - 1); n = mutt_strlen (mbox); dprint (3, (debugfile, "imap_browse: mbox: %s\n", mbox)); /* if our target exists and has inferiors, enter it if we * aren't already going to */ if (mbox[n-1] != idata->delim) { snprintf (buf, sizeof (buf), "%s \"\" \"%s\"", list_cmd, mbox); imap_cmd_start (idata, buf); do { if (imap_parse_list_response (idata, &cur_folder, &noselect, &noinferiors, &idata->delim) != 0) goto fail; if (cur_folder) { imap_unmunge_mbox_name (cur_folder); if (!noinferiors && cur_folder[0] && (n = strlen (mbox)) < LONG_STRING-1) { mbox[n++] = idata->delim; mbox[n] = '\0'; } } } while (ascii_strncmp (idata->cmd.buf, idata->cmd.seq, SEQLEN)); } /* if we're descending a folder, mark it as current in browser_state */ if (mbox[n-1] == idata->delim) { /* don't show parents in the home namespace */ if (!home_namespace) showparents = 1; imap_qualify_path (buf, sizeof (buf), &mx, mbox); state->folder = safe_strdup (buf); n--; } /* Find superiors to list * Note: UW-IMAP servers return folder + delimiter when asked to list * folder + delimiter. Cyrus servers don't. So we ask for folder, * and tack on delimiter ourselves. * Further note: UW-IMAP servers return nothing when asked for * NAMESPACES without delimiters at the end. Argh! */ for (n--; n >= 0 && mbox[n] != idata->delim ; n--); if (n > 0) /* "aaaa/bbbb/" -> "aaaa" */ { /* forget the check, it is too delicate (see above). Have we ever * had the parent not exist? */ ctmp = mbox[n]; mbox[n] = '\0'; if (showparents) { dprint (3, (debugfile, "imap_init_browse: adding parent %s\n", mbox)); imap_add_folder (idata->delim, mbox, 1, 0, state, 1); } /* if our target isn't a folder, we are in our superior */ if (!state->folder) { /* store folder with delimiter */ mbox[n++] = ctmp; ctmp = mbox[n]; mbox[n] = '\0'; imap_qualify_path (buf, sizeof (buf), &mx, mbox); state->folder = safe_strdup (buf); } mbox[n] = ctmp; } /* "/bbbb/" -> add "/", "aaaa/" -> add "" */ else { char relpath[2]; /* folder may be "/" */ snprintf (relpath, sizeof (relpath), "%c" , n < 0 ? '\0' : idata->delim); if (showparents) imap_add_folder (idata->delim, relpath, 1, 0, state, 1); if (!state->folder) { imap_qualify_path (buf, sizeof (buf), &mx, relpath); state->folder = safe_strdup (buf); } } } /* no namespace, no folder: set folder to host only */ if (!state->folder) { imap_qualify_path (buf, sizeof (buf), &mx, NULL); state->folder = safe_strdup (buf); } if (home_namespace && mbox[0] != '\0') { /* Listing the home namespace, so INBOX should be included. Home * namespace is not "", so we have to list it explicitly. We ask the * server to see if it has descendants. */ dprint (2, (debugfile, "imap_init_browse: adding INBOX\n")); if (browse_add_list_result (idata, "LIST \"\" \"INBOX\"", state, 0)) goto fail; if (!state->entrylen) { mutt_error _("No such folder"); goto fail; } } nsup = state->entrylen; dprint (3, (debugfile, "imap_browse: Quoting mailbox scan: %s -> ", mbox)); snprintf (buf, sizeof (buf), "%s%%", mbox); imap_quote_string (buf2, sizeof (buf2), buf); dprint (3, (debugfile, "%s\n", buf2)); snprintf (buf, sizeof (buf), "%s \"\" %s", list_cmd, buf2); if (browse_add_list_result (idata, buf, state, 0)) goto fail; mutt_clear_error (); qsort(&(state->entry[nsup]),state->entrylen-nsup,sizeof(state->entry[0]), (int (*)(const void*,const void*)) compare_names); if (home_namespace) { /* List additional namespaces */ for (i = 0; i < nns; i++) if (nsi[i].listable && !nsi[i].home_namespace) { imap_add_folder(nsi[i].delim, nsi[i].prefix, nsi[i].noselect, nsi[i].noinferiors, state, 0); dprint (3, (debugfile, "imap_init_browse: adding namespace: %s\n", nsi[i].prefix)); } } FREE (&mx.mbox); return 0; fail: FREE (&mx.mbox); return -1; }
static int browse_get_namespace (IMAP_DATA* idata, char* nsbuf, int nsblen, IMAP_NAMESPACE_INFO* nsi, int nsilen, int* nns) { char *s; int n; char ns[LONG_STRING]; char delim = '/'; int type; int nsbused = 0; int rc; *nns = 0; nsbuf[nsblen-1] = '\0'; imap_cmd_start (idata, "NAMESPACE"); do { if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE) break; s = imap_next_word (idata->cmd.buf); if (ascii_strncasecmp ("NAMESPACE", s, 9) == 0) { /* There are three sections to the response, User, Other, Shared, * and maybe more by extension */ for (type = IMAP_NS_PERSONAL; *s; type++) { s = imap_next_word (s); if (*s && ascii_strncmp (s, "NIL", 3)) { s++; while (*s && *s != ')') { s++; /* skip ( */ /* copy namespace */ n = 0; delim = '\0'; if (*s == '\"') { s++; while (*s && *s != '\"') { if (*s == '\\') s++; ns[n++] = *s; s++; } /* skip trailing ", if we found one, otherwise * imap_next_word will get confused */ if (*s) s++; } else while (*s && !ISSPACE (*s)) { ns[n++] = *s; s++; } ns[n] = '\0'; /* delim? */ s = imap_next_word (s); /* delimiter is meaningless if namespace is "". Why does * Cyrus provide one?! */ if (n && *s && *s == '\"') { if (s[1] && s[2] == '\"') delim = s[1]; else if (s[1] && s[1] == '\\' && s[2] && s[3] == '\"') delim = s[2]; } /* skip "" namespaces, they are already listed at the root */ if ((ns[0] != '\0') && (nsbused < nsblen) && (*nns < nsilen)) { dprint (3, (debugfile, "browse_get_namespace: adding %s\n", ns)); nsi->type = type; /* Cyrus doesn't append the delimiter to the namespace, * but UW-IMAP does. We'll strip it here and add it back * as if it were a normal directory, from the browser */ if (n && (ns[n-1] == delim)) ns[--n] = '\0'; strncpy (nsbuf+nsbused,ns,nsblen-nsbused-1); nsi->prefix = nsbuf+nsbused; nsbused += n+1; nsi->delim = delim; nsi++; (*nns)++; } while (*s && *s != ')') s++; if (*s) s++; } } } } } while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_OK) return -1; return 0; }
/* imap_auth_gss: AUTH=GSSAPI support. */ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method) { gss_buffer_desc request_buf, send_token; gss_buffer_t sec_token; gss_name_t target_name; gss_ctx_id_t context; #ifdef DEBUG gss_OID mech_name; #endif gss_qop_t quality; int cflags; OM_uint32 maj_stat, min_stat; char buf1[GSS_BUFSIZE], buf2[GSS_BUFSIZE], server_conf_flags; unsigned long buf_size; int rc; if (!mutt_bit_isset (idata->capabilities, AGSSAPI)) return IMAP_AUTH_UNAVAIL; if (mutt_account_getuser (&idata->conn->account)) return IMAP_AUTH_FAILURE; /* get an IMAP service ticket for the server */ snprintf (buf1, sizeof (buf1), "imap@%s", idata->conn->account.host); request_buf.value = buf1; request_buf.length = strlen (buf1) + 1; maj_stat = gss_import_name (&min_stat, &request_buf, gss_nt_service_name, &target_name); if (maj_stat != GSS_S_COMPLETE) { dprint (2, (debugfile, "Couldn't get service name for [%s]\n", buf1)); return IMAP_AUTH_UNAVAIL; } #ifdef DEBUG else if (debuglevel >= 2) { maj_stat = gss_display_name (&min_stat, target_name, &request_buf, &mech_name); dprint (2, (debugfile, "Using service name [%s]\n", (char*) request_buf.value)); maj_stat = gss_release_buffer (&min_stat, &request_buf); } #endif /* Acquire initial credentials - without a TGT GSSAPI is UNAVAIL */ sec_token = GSS_C_NO_BUFFER; context = GSS_C_NO_CONTEXT; /* build token */ maj_stat = gss_init_sec_context (&min_stat, GSS_C_NO_CREDENTIAL, &context, target_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL, &send_token, (unsigned int*) &cflags, NULL); if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { print_gss_error(maj_stat, min_stat); dprint (1, (debugfile, "Error acquiring credentials - no TGT?\n")); gss_release_name (&min_stat, &target_name); return IMAP_AUTH_UNAVAIL; } /* now begin login */ mutt_message _("Authenticating (GSSAPI)..."); imap_cmd_start (idata, "AUTHENTICATE GSSAPI"); /* expect a null continuation response ("+") */ do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { dprint (2, (debugfile, "Invalid response from server: %s\n", buf1)); gss_release_name (&min_stat, &target_name); goto bail; } /* now start the security context initialisation loop... */ dprint (2, (debugfile, "Sending credentials\n")); mutt_to_base64 ((unsigned char*) buf1, send_token.value, send_token.length, sizeof (buf1) - 2); gss_release_buffer (&min_stat, &send_token); safe_strcat (buf1, sizeof (buf1), "\r\n"); mutt_socket_write (idata->conn, buf1); while (maj_stat == GSS_S_CONTINUE_NEEDED) { /* Read server data */ do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { dprint (1, (debugfile, "Error receiving server response.\n")); gss_release_name (&min_stat, &target_name); goto bail; } request_buf.length = mutt_from_base64 (buf2, idata->buf + 2); request_buf.value = buf2; sec_token = &request_buf; /* Write client data */ maj_stat = gss_init_sec_context (&min_stat, GSS_C_NO_CREDENTIAL, &context, target_name, GSS_C_NO_OID, GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, sec_token, NULL, &send_token, (unsigned int*) &cflags, NULL); if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { print_gss_error(maj_stat, min_stat); dprint (1, (debugfile, "Error exchanging credentials\n")); gss_release_name (&min_stat, &target_name); goto err_abort_cmd; } mutt_to_base64 ((unsigned char*) buf1, send_token.value, send_token.length, sizeof (buf1) - 2); gss_release_buffer (&min_stat, &send_token); safe_strcat (buf1, sizeof (buf1), "\r\n"); mutt_socket_write (idata->conn, buf1); } gss_release_name (&min_stat, &target_name); /* get security flags and buffer size */ do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); if (rc != IMAP_CMD_RESPOND) { dprint (1, (debugfile, "Error receiving server response.\n")); goto bail; } request_buf.length = mutt_from_base64 (buf2, idata->buf + 2); request_buf.value = buf2; maj_stat = gss_unwrap (&min_stat, context, &request_buf, &send_token, &cflags, &quality); if (maj_stat != GSS_S_COMPLETE) { print_gss_error(maj_stat, min_stat); dprint (2, (debugfile, "Couldn't unwrap security level data\n")); gss_release_buffer (&min_stat, &send_token); goto err_abort_cmd; } dprint (2, (debugfile, "Credential exchange complete\n")); /* first octet is security levels supported. We want NONE */ server_conf_flags = ((char*) send_token.value)[0]; if ( !(((char*) send_token.value)[0] & GSS_AUTH_P_NONE) ) { dprint (2, (debugfile, "Server requires integrity or privacy\n")); gss_release_buffer (&min_stat, &send_token); goto err_abort_cmd; } /* we don't care about buffer size if we don't wrap content. But here it is */ ((char*) send_token.value)[0] = 0; buf_size = ntohl (*((long *) send_token.value)); gss_release_buffer (&min_stat, &send_token); dprint (2, (debugfile, "Unwrapped security level flags: %c%c%c\n", server_conf_flags & GSS_AUTH_P_NONE ? 'N' : '-', server_conf_flags & GSS_AUTH_P_INTEGRITY ? 'I' : '-', server_conf_flags & GSS_AUTH_P_PRIVACY ? 'P' : '-')); dprint (2, (debugfile, "Maximum GSS token size is %ld\n", buf_size)); /* agree to terms (hack!) */ buf_size = htonl (buf_size); /* not relevant without integrity/privacy */ memcpy (buf1, &buf_size, 4); buf1[0] = GSS_AUTH_P_NONE; /* server decides if principal can log in as user */ strncpy (buf1 + 4, idata->conn->account.user, sizeof (buf1) - 4); request_buf.value = buf1; request_buf.length = 4 + strlen (idata->conn->account.user) + 1; maj_stat = gss_wrap (&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf, &cflags, &send_token); if (maj_stat != GSS_S_COMPLETE) { dprint (2, (debugfile, "Error creating login request\n")); goto err_abort_cmd; } mutt_to_base64 ((unsigned char*) buf1, send_token.value, send_token.length, sizeof (buf1) - 2); dprint (2, (debugfile, "Requesting authorisation as %s\n", idata->conn->account.user)); safe_strcat (buf1, sizeof (buf1), "\r\n"); mutt_socket_write (idata->conn, buf1); /* Joy of victory or agony of defeat? */ do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); if (rc == IMAP_CMD_RESPOND) { dprint (1, (debugfile, "Unexpected server continuation request.\n")); goto err_abort_cmd; } if (imap_code (idata->buf)) { /* flush the security context */ dprint (2, (debugfile, "Releasing GSS credentials\n")); maj_stat = gss_delete_sec_context (&min_stat, &context, &send_token); if (maj_stat != GSS_S_COMPLETE) dprint (1, (debugfile, "Error releasing credentials\n")); /* send_token may contain a notification to the server to flush * credentials. RFC 1731 doesn't specify what to do, and since this * support is only for authentication, we'll assume the server knows * enough to flush its own credentials */ gss_release_buffer (&min_stat, &send_token); return IMAP_AUTH_SUCCESS; } else goto bail; err_abort_cmd: mutt_socket_write (idata->conn, "*\r\n"); do rc = imap_cmd_step (idata); while (rc == IMAP_CMD_CONTINUE); bail: mutt_error _("GSSAPI authentication failed."); mutt_sleep (2); return IMAP_AUTH_FAILURE; }