diff options
author | Thomas Roessler <roessler@does-not-exist.org> | 1999-08-20 08:24:01 +0000 |
---|---|---|
committer | Thomas Roessler <roessler@does-not-exist.org> | 1999-08-20 08:24:01 +0000 |
commit | 9febf2c9a98ebcebded37065bc6058adfb0847a9 (patch) | |
tree | c6f06f934b0b32f38486a743e0596da140ee08d6 /imap | |
parent | d79ed35520d7d5a3658e309af0e3e00d30ddf45d (diff) |
Brendan Cully's latest IMAP clean-up.
Diffstat (limited to 'imap')
-rw-r--r-- | imap/Makefile.am | 8 | ||||
-rw-r--r-- | imap/auth.c | 291 | ||||
-rw-r--r-- | imap/browse.c | 486 | ||||
-rw-r--r-- | imap/imap.c | 2274 | ||||
-rw-r--r-- | imap/imap.h | 41 | ||||
-rw-r--r-- | imap/imap_private.h | 184 | ||||
-rw-r--r-- | imap/imap_socket.h | 44 | ||||
-rw-r--r-- | imap/md5.h | 49 | ||||
-rw-r--r-- | imap/md5c.c | 330 | ||||
-rw-r--r-- | imap/socket.c | 139 |
10 files changed, 3846 insertions, 0 deletions
diff --git a/imap/Makefile.am b/imap/Makefile.am new file mode 100644 index 00000000..70ed6542 --- /dev/null +++ b/imap/Makefile.am @@ -0,0 +1,8 @@ +## Process this file with automake to produce Makefile.in + +AUTOMAKE_OPTIONS = foreign + +noinst_LIBRARIES = libimap.a +noinst_HEADERS = imap_private.h imap_socket.h md5.h + +libimap_a_SOURCES = imap.c imap.h auth.c browse.c md5c.c socket.c diff --git a/imap/auth.c b/imap/auth.c new file mode 100644 index 00000000..e04c6212 --- /dev/null +++ b/imap/auth.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * Copyright (C) 1996-9 Brandon Long <blong@fiction.net> + * Copyright (C) 1999 Brendan Cully <brendan@kublai.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* IMAP login/authentication code */ + +#include "mutt.h" +#include "imap_private.h" +#include "md5.h" + +#define MD5_BLOCK_LEN 64 +#define MD5_DIGEST_LEN 16 + +/* forward declarations */ +static void hmac_md5 (const char* password, char* challenge, + unsigned char* response); +static int imap_auth_cram_md5 (IMAP_DATA* idata, const char* user, + const char* pass); + +/* hmac_md5: produce CRAM-MD5 challenge response. */ +static void hmac_md5 (const char* password, char* challenge, + unsigned char* response) +{ + MD5_CTX ctx; + unsigned char ipad[MD5_BLOCK_LEN], opad[MD5_BLOCK_LEN]; + unsigned char secret[MD5_BLOCK_LEN+1]; + unsigned char hash_passwd[MD5_DIGEST_LEN]; + unsigned int secret_len, chal_len; + int i; + + secret_len = strlen (password); + chal_len = strlen (challenge); + + /* passwords longer than MD5_BLOCK_LEN bytes are substituted with their MD5 + * digests */ + if (secret_len > MD5_BLOCK_LEN) + { + MD5Init (&ctx); + MD5Update (&ctx, (unsigned char*) password, secret_len); + MD5Final (hash_passwd, &ctx); + strfcpy ((char*) secret, (char*) hash_passwd, MD5_DIGEST_LEN); + secret_len = MD5_DIGEST_LEN; + } + else + strfcpy (secret, password, sizeof (secret)); + + memset (ipad, 0, sizeof (ipad)); + memset (opad, 0, sizeof (opad)); + memcpy (ipad, secret, secret_len); + memcpy (opad, secret, secret_len); + + for (i = 0; i < MD5_BLOCK_LEN; i++) + { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + /* inner hash: challenge and ipadded secret */ + MD5Init (&ctx); + MD5Update (&ctx, ipad, MD5_BLOCK_LEN); + MD5Update (&ctx, (unsigned char*) challenge, chal_len); + MD5Final (response, &ctx); + + /* outer hash: inner hash and opadded secret */ + MD5Init (&ctx); + MD5Update (&ctx, opad, MD5_BLOCK_LEN); + MD5Update (&ctx, response, MD5_DIGEST_LEN); + MD5Final (response, &ctx); +} + +/* imap_auth_cram_md5: AUTH=CRAM-MD5 support. Used unconditionally if the + * server supports it */ +static int imap_auth_cram_md5 (IMAP_DATA* idata, const char* user, + const char* pass) +{ + char ibuf[LONG_STRING], obuf[LONG_STRING]; + unsigned char hmac_response[MD5_DIGEST_LEN]; + int len; + char seq[16]; + + dprint (2, (debugfile, "Attempting CRAM-MD5 login...\n")); + mutt_message _("Logging in (CRAM-MD5)..."); + imap_make_sequence (seq, sizeof (seq)); + snprintf (obuf, LONG_STRING, "%s AUTHENTICATE CRAM-MD5\r\n", seq); + mutt_socket_write (idata->conn, obuf); + + /* 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]. + */ + if (mutt_socket_read_line_d (ibuf, LONG_STRING, idata->conn) < 0) + { + dprint (1, (debugfile, "Error receiving server response.\n")); + + return -1; + } + + if (ibuf[0] != '+') + { + dprint (1, (debugfile, "Invalid response from server: %s\n", ibuf)); + + return -1; + } + + if ((len = mutt_from_base64 (obuf, ibuf + 2)) == -1) + { + dprint (1, (debugfile, "Error decoding base64 response.\n")); + + return -1; + } + + obuf[len] = '\0'; + dprint (2, (debugfile, "CRAM challenge: %s\n", obuf)); + + /* 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: it's unspecified whether the user name needs IMAP quoting.) + */ + hmac_md5 (pass, obuf, hmac_response); + dprint (2, (debugfile, "CRAM response: %s,[%s]->", obuf, pass)); + /* dubious optimisation I saw elsewhere: make the whole string in one call */ + 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]); + dprint(2, (debugfile, "%s\n", obuf)); + + mutt_to_base64 ((unsigned char*) ibuf, (unsigned char*) obuf, strlen (obuf)); + strcpy (ibuf + strlen (ibuf), "\r\n"); + mutt_socket_write (idata->conn, ibuf); + + if (mutt_socket_read_line_d (ibuf, LONG_STRING, idata->conn) < 0) + { + dprint (1, (debugfile, "Error receiving server response.\n")); + + return -1; + } + + if (imap_code (ibuf)) + { + dprint (2, (debugfile, "CRAM login complete.\n")); + + return 0; + } + + dprint (2, (debugfile, "CRAM login failed.\n")); + return -1; +} + +/* imap_authenticate: loop until success or user abort. At each loop, all + * supported authentication methods are tried, from strongest to weakest. + * Currently available: + * CRAM-MD5: like APOP or CHAP. Safe against replay and sniffing, but + * requires that your key be stored on the server, readable by the + * server account. UW-IMAP supports this method since at least 4.5, if + * the key file exists on the server. + * LOGIN: ugh. Don't use this if you can help it. Uses cleartext password + * exchange, furthermore uses unix login techniques so this same password + * can be used to log in to the server or others that share the + * credentials database. + * Pending: + * GSSAPI: strongest available form, requires Kerberos V infrastructure, + * or possibly alternatively Heimdal. + * Unavailable: + * KERBEROS_V4. Superceded by GSSAPI. + */ +int imap_authenticate (IMAP_DATA *idata, CONNECTION *conn) +{ + char buf[LONG_STRING]; + char user[SHORT_STRING], q_user[SHORT_STRING]; + char ckey[SHORT_STRING]; + char pass[SHORT_STRING], q_pass[SHORT_STRING]; + char seq[16]; + + int r = 1; + + while (r != 0) + { + if (!ImapUser) + { + strfcpy (user, NONULL(Username), sizeof (user)); + if (mutt_get_field (_("IMAP Username: "), user, sizeof (user), 0) != 0 || + !user[0]) + { + user[0] = 0; + return (-1); + } + } + else + strfcpy (user, ImapUser, sizeof (user)); + + /* attempt CRAM-MD5 if available */ + if (mutt_bit_isset (idata->capabilities, ACRAM_MD5)) + { + if (!ImapCRAMKey) + { + ckey[0] = '\0'; + snprintf (buf, sizeof (buf), _("CRAM key for %s@%s: "), user, + conn->server); + if (mutt_get_field (buf, ckey, sizeof (ckey), M_PASS) != 0 || + !ckey[0]) + return -1; + else + /* strip CR */ + ckey[strlen (ckey)] = '\0'; + } + else + strfcpy (ckey, ImapCRAMKey, sizeof (ckey)); + + if ((r = imap_auth_cram_md5 (idata, user, ckey))) + { + mutt_error _("CRAM-MD5 login failed."); + sleep (1); + } + else + return 0; + } + else + dprint (2, (debugfile, "CRAM-MD5 authentication is not supported\n")); + + if (!ImapPass) + { + pass[0]=0; + snprintf (buf, sizeof (buf), _("Password for %s@%s: "), user, conn->server); + if (mutt_get_field (buf, pass, sizeof (pass), M_PASS) != 0 || + !pass[0]) + { + return (-1); + } + } + else + strfcpy (pass, ImapPass, sizeof (pass)); + + imap_quote_string (q_user, sizeof (q_user), user); + imap_quote_string (q_pass, sizeof (q_pass), pass); + + mutt_message _("Logging in..."); + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s LOGIN %s %s\r\n", seq, q_user, q_pass); + r = imap_exec (buf, sizeof (buf), idata, seq, buf, IMAP_OK_FAIL); + if (r == -1) + { + /* connection or protocol problem */ + imap_error ("imap_open_connection()", buf); + return (-1); + } + else if (r == -2) + { + /* Login failed, try again */ + mutt_error _("Login failed."); + sleep (1); + FREE (&ImapUser); + FREE (&ImapPass); + } + else + { + /* If they have a successful login, we may as well cache the + * user/password. */ + if (!ImapUser) + ImapUser = safe_strdup (user); + if (!ImapPass) + ImapPass = safe_strdup (pass); + } + } + return 0; +} diff --git a/imap/browse.c b/imap/browse.c new file mode 100644 index 00000000..b6213f17 --- /dev/null +++ b/imap/browse.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) 1996-9 Brandon Long <blong@fiction.net> + * Copyright (C) 1999 Brendan Cully <brendan@kublai.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Mutt browser support routines */ + +#include <stdlib.h> +#include <ctype.h> + +#include "mutt.h" +#include "imap.h" +#include "imap_private.h" +#include "imap_socket.h" + +/* -- forward declarations -- */ +static int add_list_result (CONNECTION *conn, const char *seq, const char *cmd, + struct browser_state *state, short isparent); +static void imap_add_folder (char delim, char *folder, int noselect, + int noinferiors, struct browser_state *state, short isparent); +static int compare_names(struct folder_file *a, struct folder_file *b); +static int get_namespace (IMAP_DATA *idata, char *nsbuf, int nsblen, + IMAP_NAMESPACE_INFO *nsi, int nsilen, int *nns); +static int verify_namespace (CONNECTION *conn, IMAP_NAMESPACE_INFO *nsi, + int nns); + +int imap_init_browse (char *path, struct browser_state *state) +{ + CONNECTION *conn; + IMAP_DATA *idata; + char buf[LONG_STRING]; + char nsbuf[LONG_STRING]; + char mbox[LONG_STRING]; + char host[SHORT_STRING]; + int port; + char list_cmd[5]; + char seq[16]; + char *ipath = NULL; + 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; + + if (imap_parse_path (path, host, sizeof (host), &port, &ipath)) + return (-1); + + strfcpy (list_cmd, option (OPTIMAPLSUB) ? "LSUB" : "LIST", sizeof (list_cmd)); + + conn = mutt_socket_select_connection (host, port, 0); + idata = CONN_DATA; + + if (!idata || (idata->state == IMAP_DISCONNECTED)) + { + if (!idata) + { + /* The current connection is a new connection */ + idata = safe_calloc (1, sizeof (IMAP_DATA)); + conn->data = idata; + idata->conn = conn; + } + if (imap_open_connection (idata, conn)) + return (-1); + } + + if (ipath[0] == '\0') + { + home_namespace = 1; + mbox[0] = 0; /* Do not replace "" with "INBOX" here */ + ipath = ImapHomeNamespace; + nns = 0; + if (mutt_bit_isset(idata->capabilities,NAMESPACE)) + { + if (get_namespace (idata, nsbuf, sizeof (nsbuf), + nsi, sizeof (nsi), &nns) != 0) + return (-1); + if (verify_namespace (conn, nsi, nns) != 0) + return (-1); + } + if (!ipath) /* Any explicitly set imap_home_namespace wins */ + { + for (i = 0; i < nns; i++) + if (nsi[i].listable && + (nsi[i].type == IMAP_NS_PERSONAL || nsi[i].type == IMAP_NS_SHARED)) + { + ipath = nsi->prefix; + nsi->home_namespace = 1; + break; + } + } + } + + mutt_message _("Contacted server, getting folder list..."); + + if (ipath && ipath[0] != '\0') + { + imap_fix_path (idata, ipath, mbox, sizeof (mbox)); + n = mutt_strlen (mbox); + + dprint (3, (debugfile, "imap_init_browse: mbox: %s\n", mbox)); + + /* if our target exists, has inferiors and isn't selectable, enter it if we + * aren't already going to */ + if (mbox[n-1] != idata->delim) + { + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s %s \"\" \"%s\"\r\n", seq, list_cmd, + mbox); + mutt_socket_write (conn, buf); + do + { + if (imap_parse_list_response(conn, buf, sizeof(buf), &cur_folder, + &noselect, &noinferiors, &(idata->delim)) != 0) + return -1; + + if (cur_folder) + { + imap_unquote_string (cur_folder); + + if (noselect && !noinferiors && cur_folder[0] && + (n = strlen (mbox)) < LONG_STRING-1) + { + mbox[n++] = idata->delim; + mbox[n] = '\0'; + } + } + } + while ((mutt_strncmp (buf, seq, SEQLEN) != 0)); + } + + /* if we're descending a folder, mark it as current in browser_state */ + if (mbox[n-1] == idata->delim) + { + showparents = 1; + imap_qualify_path (buf, sizeof (buf), host, port, mbox, NULL); + state->folder = safe_strdup (buf); + n--; + } + + /* Find superiors to list */ + for (n--; n >= 0 && mbox[n] != idata->delim ; n--); + if (n > 0) /* "aaaa/bbbb/" -> "aaaa/" */ + { + n++; + ctmp = mbox[n]; + mbox[n] = '\0'; + /* List it to see if it can be selected */ + dprint (2, (debugfile, "imap_init_browse: listing %s\n", mbox)); + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s %s \"\" \"%s\"\r\n", seq, + list_cmd, mbox); + /* add this entry as a superior, if we aren't tab-completing */ + if (showparents && add_list_result (conn, seq, buf, state, 1)) + return -1; + /* if our target isn't a folder, we are in our superior */ + if (!state->folder) + { + imap_qualify_path (buf, sizeof (buf), host, port, mbox, NULL); + 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), host, port, relpath, NULL); + state->folder = safe_strdup (buf); + } + } + } + + /* no namespace, no folder: set folder to host only */ + if (!state->folder) + { + imap_qualify_path (buf, sizeof (buf), host, port, NULL, 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. */ + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s LIST \"\" \"INBOX\"\r\n", seq); + if (add_list_result (conn, seq, buf, state, 0)) + return -1; + } + + nsup = state->entrylen; + + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s %s \"\" \"%s%%\"\r\n", seq, + list_cmd, mbox); + if (add_list_result (conn, seq, buf, state, 0)) + return -1; + + 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); + } + + mutt_clear_error (); + return 0; +} + +static int add_list_result (CONNECTION *conn, const char *seq, const char *cmd, + struct browser_state *state, short isparent) +{ + IMAP_DATA *idata = CONN_DATA; + char buf[LONG_STRING]; + char host[SHORT_STRING]; + int port; + char *curfolder; + char *name; + int noselect; + int noinferiors; + + if (imap_parse_path (state->folder, host, sizeof (host), &port, &curfolder)) + { + dprint (2, (debugfile, + "add_list_result: current folder %s makes no sense\n", state->folder)); + return -1; + } + + mutt_socket_write (conn, cmd); + + do + { + if (imap_parse_list_response(conn, buf, sizeof(buf), &name, + &noselect, &noinferiors, &(idata->delim)) != 0) + return -1; + + if (name) + { + imap_unquote_string (name); + /* prune current folder from output */ + if (isparent || strncmp (name, curfolder, strlen (name))) + imap_add_folder (idata->delim, name, noselect, noinferiors, state, + isparent); + } + } + while ((mutt_strncmp (buf, seq, SEQLEN) != 0)); + return (0); +} + +/* imap_add_folder: add a folder name to the browser list, formatting it as + * necessary. NOTE: check for duplicate folders removed, believed to be + * useless. Tell me if otherwise (brendan@kublai.com) */ +static void imap_add_folder (char delim, char *folder, int noselect, + int noinferiors, struct browser_state *state, short isparent) +{ + char tmp[LONG_STRING]; + char relpath[LONG_STRING]; + char host[SHORT_STRING]; + int port; + char *curfolder; + int flen = strlen (folder); + + if (imap_parse_path (state->folder, host, sizeof (host), &port, &curfolder)) + return; + + imap_unquote_string (folder); + + /* plus 2: folder may be selectable AND have inferiors */ + if (state->entrylen + 2 == state->entrymax) + { + safe_realloc ((void **) &state->entry, + sizeof (struct folder_file) * (state->entrymax += 256)); + } + + /* render superiors as unix-standard ".." */ + if (isparent) + strfcpy (relpath, "../", sizeof (relpath)); + /* strip current folder from target, to render a relative path */ + else if (!strncmp (curfolder, folder, strlen (curfolder))) + strfcpy (relpath, folder + strlen (curfolder), sizeof (relpath)); + else + strfcpy (relpath, folder, sizeof (relpath)); + + /* apply filemask filter. This should really be done at menu setup rather + * than at scan, since it's so expensive to scan. But that's big changes + * to browser.c */ + if (!((regexec (Mask.rx, relpath, 0, NULL, 0) == 0) ^ Mask.not)) + return; + + if (!noselect) + { + imap_qualify_path (tmp, sizeof (tmp), host, port, folder, NULL); + (state->entry)[state->entrylen].name = safe_strdup (tmp); + + snprintf (tmp, sizeof (tmp), "IMAP %-25s %-25s", host, relpath); + (state->entry)[state->entrylen].desc = safe_strdup (tmp); + + (state->entry)[state->entrylen].notfolder = 0; + (state->entrylen)++; + } + if (!noinferiors) + { + char trailing_delim[2]; + + /* create trailing delimiter if necessary */ + trailing_delim[1] = '\0'; + trailing_delim[0] = (flen && folder[flen - 1] != delim) ? delim : '\0'; + + imap_qualify_path (tmp, sizeof (tmp), host, port, folder, trailing_delim); + (state->entry)[state->entrylen].name = safe_strdup (tmp); + + if (strlen (relpath) < sizeof (relpath) - 2) + strcat (relpath, trailing_delim); + snprintf (tmp, sizeof (tmp), "IMAP %-25s %-25s", host, relpath); + (state->entry)[state->entrylen].desc = safe_strdup (tmp); + + (state->entry)[state->entrylen].notfolder = 1; + (state->entrylen)++; + } +} + +static int compare_names(struct folder_file *a, struct folder_file *b) +{ + return mutt_strcmp(a->name, b->name); +} + +static int get_namespace (IMAP_DATA *idata, char *nsbuf, int nsblen, + IMAP_NAMESPACE_INFO *nsi, int nsilen, int *nns) +{ + char buf[LONG_STRING]; + char seq[16]; + char *s; + int n; + char ns[LONG_STRING]; + char delim = '/'; + int type; + int nsbused = 0; + + *nns = 0; + nsbuf[nsblen-1] = '\0'; + + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s NAMESPACE\r\n", seq); + + mutt_socket_write (idata->conn, buf); + do + { + if (mutt_socket_read_line_d (buf, sizeof (buf), idata->conn) < 0) + { + return (-1); + } + + if (buf[0] == '*') + { + s = imap_next_word (buf); + if (mutt_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 && strncmp (s, "NIL", 3)) + { + s++; + while (*s && *s != ')') + { + s++; /* skip ( */ + /* copy namespace */ + n = 0; + if (*s == '\"') + { + s++; + while (*s && *s != '\"') + { + if (*s == '\\') + s++; + ns[n++] = *s; + s++; + } + } + else + while (*s && !ISSPACE (*s)) + { + ns[n++] = *s; + s++; + } + ns[n] = '\0'; + /* delim? */ + s = imap_next_word (s); + if (*s && *s == '\"') + { + if (s[1] && s[2] == '\"') + delim = s[1]; + else if (s[1] && s[1] == '\\' && s[2] && s[3] == '\"') + delim = s[2]; + } + /* Save result (if space) */ + if ((nsbused < nsblen) && (*nns < nsilen)) + { + nsi->type = type; + strncpy(nsbuf+nsbused,ns,nsblen-nsbused-1); + nsi->prefix = nsbuf+nsbused; + nsbused += n+1; + nsi->delim = delim; + nsi++; + (*nns)++; + } + while (*s && *s != ')') s++; + s++; + } + } + } + } + else + { + if (imap_handle_untagged (idata, buf) != 0) + return (-1); + } + } + } + while ((mutt_strncmp (buf, seq, SEQLEN) != 0)); + return 0; +} + +/* Check which namespaces actually exist */ +static int verify_namespace (CONNECTION *conn, IMAP_NAMESPACE_INFO *nsi, + int nns) +{ + char buf[LONG_STRING]; + char seq[16]; + int i = 0; + char *name; + char delim; + + for (i = 0; i < nns; i++, nsi++) + { + imap_make_sequence (seq, sizeof (seq)); + snprintf (buf, sizeof (buf), "%s %s \"\" \"%s\"\r\n", seq, + option (OPTIMAPLSUB) ? "LSUB" : "LIST", nsi->prefix); + mutt_socket_write (conn, buf); + + nsi->listable = 0; + nsi->home_namespace = 0; + do + { + if (imap_parse_list_response(conn, buf, sizeof(buf), &name, + &(nsi->noselect), &(nsi->noinferiors), + &delim) != 0) + return (-1); + nsi->listable |= (name != NULL); + } + while ((mutt_strncmp (buf, seq, SEQLEN) != 0)); + } + return (0); +} + diff --git a/imap/imap.c b/imap/imap.c new file mode 100644 index 00000000..4d0fde52 --- /dev/null +++ b/imap/imap.c @@ -0,0 +1,2274 @@ +/* + * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu> + * Copyright (C) 1996-9 Brandon Long <blong@fiction.net> + * Copyright (C) 1999 Brendan Cully <brendan@kublai.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Support for IMAP4rev1, with the occasional nod to IMAP 4. */ + +#include "mutt.h" +#include "mutt_curses.h" +#include "mx.h" +#include "mailbox.h" +#include "globals.h" +#include "sort.h" +#include "browser.h" +#include "imap.h" +#include "imap_private.h" +#include "imap_socket.h" + +#include <unistd.h> +#include <ctype.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + + +#ifdef _PGPPATH +#include "pgp.h" +#endif + + +static char *Capabilities[] = {"IMAP4", "IMAP4rev1", "STATUS", "ACL", + "NAMESPACE", "AUTH=CRAM-MD5", "AUTH=KERBEROS_V4", "AUTH=GSSAPI", + "AUTH=LOGIN", "AUTH-LOGIN", "AUTH=PLAIN", "AUTH=SKEY", "IDLE", + "LOGIN-REFERRALS", "MAILBOX-REFERRALS", "QUOTA", "SCAN", "SORT", + "THREAD=ORDEREDSUBJECT", "UIDPLUS", NULL}; + +/* imap forward declarations */ +static time_t imap_parse_date (char *s); +static int imap_parse_fetch (IMAP_HEADER_INFO *h, char *s); +static int imap_read_bytes (FILE *fp, CONNECTION *conn, long bytes); +static int imap_wordcasecmp(const char *a, const char *b); +static void imap_parse_capabilities (IMAP_DATA *idata, char *s); +static int get_literal_count(const char *buf, long *bytes); +static int imap_read_headers (CONTEXT *ctx, int msgbegin, int msgend); +static int imap_reopen_mailbox (CONTEXT *ctx, int *index_hint); +static int imap_get_delim (IMAP_DATA *idata, CONNECTION *conn); +static int imap_check_acl (IMAP_DATA *idata); +static int imap_check_capabilities (IMAP_DATA *idata); +static int imap_create_mailbox (IMAP_DATA *idata, char *mailbox); + +/* everything above should be moved into a separate imap header file */ + +void imap_make_sequence (char *buf, size_t buflen) +{ + static int sequence = 0; + + snprintf (buf, buflen, "a%04d", sequence++); + + if (sequence > 9999) + sequence = 0; +} + +void imap_error (const char *where, const char *msg) +{ + mutt_error (_("imap_error(): unexpected response in %s: %s\n"), where, msg); +} + +/* date is of the form: DD-MMM-YYYY HH:MM:SS +ZZzz */ +static time_t imap_parse_date (char *s) +{ + struct tm t; + time_t tz; + + t.tm_mday = (s[0] == ' '? s[1] - '0' : (s[0] - '0') * 10 + (s[1] - '0')); + s += 2; + if (*s != '-') + return 0; + s++; + t.tm_mon = mutt_check_month (s); + s += 3; + if (*s != '-') + return 0; + s++; + t.tm_year = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0') - 1900; + s += 4; + if (*s != ' ') + return 0; + s++; + + /* time */ + t.tm_hour = (s[0] - '0') * 10 + (s[1] - '0'); + s += 2; + if (*s != ':') + return 0; + s++; + t.tm_min = (s[0] - '0') * 10 + (s[1] - '0'); + s += 2; + if (*s != ':') + return 0; + s++; + t.tm_sec = (s[0] - '0') * 10 + (s[1] - '0'); + s += 2; + if (*s != ' ') + return 0; + s++; + + /* timezone */ + tz = ((s[1] - '0') * 10 + (s[2] - '0')) * 3600 + + ((s[3] - '0') * 10 + (s[4] - '0')) * 60; + if (s[0] == '+') + tz = -tz; + + return (mutt_mktime (&t, 0) + tz); +} + +static int imap_parse_fetch (IMAP_HEADER_INFO *h, char *s) +{ + char tmp[SHORT_STRING]; + char *ptmp; + int state = 0; + int recent = 0; + + if (!s) + return (-1); + + h->old = 0; + + while (*s) + { + SKIPWS (s); + + switch (state) + { + case 0: + if (mutt_strncasecmp ("FLAGS", s, 5) == 0) + { + s += 5; + SKIPWS (s); + if (*s != '(') + { + dprint (1, (debugfile, "imap_parse_fetch(): bogus FLAGS entry: %s\n", s)); + return (-1); /* parse error */ + } + /* we're about to get a new set of headers, so clear the old ones. */ + h->deleted = 0; + h->flagged = 0; + h->replied = 0; + h->read = 0; + h->old = 0; + h->changed = 0; + recent = 0; + s++; + state = 1; + } + else if (mutt_strncasecmp ("INTERNALDATE", s, 12) == 0) + { + s += 12; + SKIPWS (s); + if (*s != '\"') + { + dprint (1, (debugfile, "imap_parse_fetch(): bogus INTERNALDATE entry: %s\n", s)); + return (-1); + } + s++; + ptmp = tmp; + while (*s && *s != '\"') + *ptmp++ = *s++; + if (*s != '\"') + return (-1); + s++; /* skip past the trailing " */ + *ptmp = 0; + h->received = imap_parse_date (tmp); + } + else if (mutt_strncasecmp ("RFC822.SIZE", s, 11) == 0) + { + s += 11; + SKIPWS (s); + ptmp = tmp; + while (isdigit (*s)) + *ptmp++ = *s++; + *ptmp = 0; + h->content_length += atoi (tmp); + } + else if (*s == ')') + s++; /* end of request */ + else if (*s) + { + /* got something i don't understand */ + imap_error ("imap_parse_fetch()", s); + return (-1); + } + break; + case 1: /* flags */ + if (*s == ')') + { + s++; + /* if a message is neither seen nor recent, it is OLD. */ + if (option (OPTMARKOLD) && !recent && !(h->read)) + h->old = 1; + state = 0; + } + else if (mutt_strncasecmp ("\\deleted", s, 8) == 0) + { + s += 8; + h->deleted = 1; + } + else if (mutt_strncasecmp ("\\flagged", s, 8) == 0) + { + s += 8; + h->flagged = 1; + } + else if (mutt_strncasecmp ("\\answered", s, 9) == 0) + { + s += 9; + h->replied = 1; + } + else if (mutt_strncasecmp ("\\seen", s, 5) == 0) + { + s += 5; + h->read = 1; + } + else if (mutt_strncasecmp ("\\recent", s, 5) == 0) + { + s += 7; + recent = 1; + } + else + { + while (*s && !ISSPACE (*s) && *s != ')') + s++; + } + break; + } + } + return 0; +} + +void imap_quote_string (char *dest, size_t slen, const char *src) +{ + char quote[] = "\"\\", *pt; + const char *s; + size_t len = slen; + + pt = dest; + s = src; + + *pt++ = '"'; + /* save room for trailing quote-char */ + len -= 2; + + for (; *s && len; s++) + { + if (strchr (quote, *s)) + { + len -= 2; + if (!len) |