/* * imap.c -- IMAP2bis/IMAP4 protocol methods * * Copyright 1997 by Eric S. Raymond * For license terms, see the file COPYING in this directory. */ #include "config.h" #include #include #include #if defined(STDC_HEADERS) #include #include #include #endif #include "fetchmail.h" #include "socket.h" #include "i18n.h" /* imap_version values */ #define IMAP2 -1 /* IMAP2 or IMAP2BIS, RFC1176 */ #define IMAP4 0 /* IMAP4 rev 0, RFC1730 */ #define IMAP4rev1 1 /* IMAP4 rev 1, RFC2060 */ /* global variables: please reinitialize them explicitly for proper * working in daemon mode */ /* TODO: session variables to be initialized before server greeting */ static int preauth = FALSE; /* session variables initialized in capa_probe() or imap_getauth() */ static char capabilities[MSGBUFSIZE+1]; static int imap_version = IMAP4; static flag do_idle = FALSE, has_idle = FALSE; static int expunge_period = 1; /* mailbox variables initialized in imap_getrange() */ static int count = 0, oldcount = 0, recentcount = 0, unseen = 0, deletions = 0; static unsigned int startcount = 1; static int expunged = 0; static unsigned int *unseen_messages; /* for "IMAP> EXPUNGE" */ static int actual_deletions = 0; /* for "IMAP> IDLE" */ static int saved_timeout = 0, idle_timeout = 0; static time_t idle_start_time = 0; static int imap_untagged_response(int sock, const char *buf) /* interpret untagged status responses */ { /* For each individual check, use a BLANK before the word to avoid * confusion with the \Recent flag or similar */ if (stage == STAGE_GETAUTH && !strncmp(buf, "* CAPABILITY", 12)) { strlcpy(capabilities, buf + 12, sizeof(capabilities)); } else if (stage == STAGE_GETAUTH && !strncmp(buf, "* PREAUTH", 9)) { preauth = TRUE; } else if (stage != STAGE_LOGOUT && !strncmp(buf, "* BYE", 5)) { /* log the unexpected bye from server as we expect the * connection to be cut-off after this */ if (outlevel > O_SILENT) report(stderr, GT_("Received BYE response from IMAP server: %s"), buf + 5); } else if (strstr(buf, " EXISTS")) { char *t; unsigned long u; errno = 0; u = strtoul(buf+2, &t, 10); /* * Don't trust the message count passed by the server. * Without this check, it might be possible to do a * DNS-spoofing attack that would pass back a ridiculous * count, and allocate a malloc area that would overlap * a portion of the stack. */ if (errno /* strtoul failed */ || t == buf+2 /* no valid data */ || u > (unsigned long)(INT_MAX/sizeof(int)) /* too large */) { report(stderr, GT_("bogus message count in \"%s\"!"), buf); return(PS_PROTOCOL); } count = u; /* safe as long as count <= INT_MAX - checked above */ if ((recentcount = count - oldcount) < 0) recentcount = 0; /* * Nasty kluge to handle RFC2177 IDLE. If we know we're idling * we can't wait for the tag matching the IDLE; we have to tell the * server the IDLE is finished by shipping back a DONE when we * see an EXISTS. Only after that will a tagged response be * shipped. The idling flag also gets cleared on a timeout. */ if (stage == STAGE_IDLE) { /* If IDLE isn't supported, we were only sending NOOPs anyway. */ if (has_idle) { /* we do our own write and report here to disable tagging */ SockWrite(sock, "DONE\r\n", 6); if (outlevel >= O_MONITOR) report(stdout, "IMAP> DONE\n"); } mytimeout = saved_timeout; stage = STAGE_GETRANGE; } } /* we now compute recentcount as a difference between * new and old EXISTS, hence disable RECENT check */ # if 0 else if (strstr(buf, " RECENT")) { /* fixme: use strto[u]l and error checking */ recentcount = atoi(buf+2); } # endif else if (strstr(buf, " EXPUNGE")) { unsigned long u; char *t; /* the response "* 10 EXPUNGE" means that the currently * tenth (i.e. only one) message has been deleted */ errno = 0; u = strtoul(buf+2, &t, 10); if (errno /* conversion error */ || t == buf+2 /* no number found */) { report(stderr, GT_("bogus EXPUNGE count in \"%s\"!"), buf); return PS_PROTOCOL; } if (u > 0) { if (count > 0) count--; if (oldcount > 0) oldcount--; /* We do expect an EXISTS response immediately * after this, so this updation of recentcount is * just a precaution! */ if ((recentcount = count - oldcount) < 0) recentcount = 0; actual_deletions++; } } /* * The server may decide to make the mailbox read-only, * which causes fetchmail to go into a endless loop * fetching the same message over and over again. * * However, for check_only, we use EXAMINE which will * mark the mailbox read-only as per the RFC. * * This checks for the condition and aborts if * the mailbox is read-only. * * See RFC 2060 section 6.3.1 (SELECT). * See RFC 2060 section 6.3.2 (EXAMINE). */ else if (stage == STAGE_GETRANGE && !check_only && strstr(buf, "[READ-ONLY]")) { return(PS_LOCKBUSY); } else { return(PS_UNTAGGED); } return(PS_SUCCESS); } static int imap_response(int sock, char *argbuf) /* parse command response */ { char buf[MSGBUFSIZE+1]; do { int ok; char *cp; if ((ok = gen_recv(sock, buf, sizeof(buf)))) return(ok); /* all tokens in responses are caseblind */ for (cp = buf; *cp; cp++) if (islower((unsigned char)*cp)) *cp = toupper((unsigned char)*cp); /* untagged responses start with "* " */ if (buf[0] == '*' && buf[1] == ' ') { ok = imap_untagged_response(sock, buf); if (ok == PS_UNTAGGED) { if (argbuf && stage != STAGE_IDLE && tag[0] != '\0') { /* if there is an unmatched response, pass it back to * the calling function for further analysis. The * calling function should call imap_response() again * to read the remaining response */ strcpy(argbuf, buf); return(ok); } } else if (ok != PS_SUCCESS) return(ok); } if (stage == STAGE_IDLE) { /* reduce the timeout: servers may not reset their timeout * when they send some information asynchronously */ mytimeout = idle_timeout - (time((time_t *) NULL) - idle_start_time); if (mytimeout <= 0) return(PS_IDLETIMEOUT); } } while (tag[0] != '\0' && strncmp(buf, tag, strlen(tag))); if (tag[0] == '\0') { if (argbuf) strcpy(argbuf, buf); return(PS_SUCCESS); } else { char *cp; /* skip the tag */ for (cp = buf; !isspace((unsigned char)*cp); cp++) continue; while (isspace((unsigned char)*cp)) cp++; if (strncasecmp(cp, "OK", 2) == 0) { if (argbuf) strcpy(argbuf, cp); return(PS_SUCCESS); } else if (strncasecmp(cp, "BAD", 3) == 0) return(PS_ERROR); else if (strncasecmp(cp, "NO", 2) == 0) { if (stage == STAGE_GETAUTH) return(PS_AUTHFAIL); /* RFC2060, 6.2.2 */ else if (stage == STAGE_GETSIZES) return(PS_SUCCESS); /* see comments in imap_getpartialsizes() */ else return(PS_ERROR); } else return(PS_PROTOCOL); } } static int imap_ok(int sock, char *argbuf) /* parse command response */ { int ok; while ((ok = imap_response(sock, argbuf)) == PS_UNTAGGED) ; /* wait for the tagged response */ return(ok); } #ifdef NTLM_ENABLE #include "ntlm.h" /* * NTLM support by Grant Edwards. * * Handle MS-Exchange NTLM authentication method. This is the same * as the NTLM auth used by Samba for SMB related services. We just * encode the packets in base64 instead of sending them out via a * network interface. * * Much source (ntlm.h, smb*.c smb*.h) was borrowed from Samba. */ static int do_imap_ntlm(int sock, struct query *ctl) { tSmbNtlmAuthRequest request; tSmbNtlmAuthChallenge challenge; tSmbNtlmAuthResponse response; char msgbuf[2048]; int result,len; gen_send(sock, "AUTHENTICATE NTLM"); if ((result = gen_recv(sock, msgbuf, sizeof msgbuf))) return result; if (msgbuf[0] != '+') return PS_AUTHFAIL; buildSmbNtlmAuthRequest(&request,ctl->remotename,NULL); if (outlevel >= O_DEBUG) dumpSmbNtlmAuthRequest(stdout, &request); memset(msgbuf,0,sizeof msgbuf); to64frombits (msgbuf, &request, SmbLength(&request)); if (outlevel >= O_MONITOR) report(stdout, "IMAP> %s\n", msgbuf); strcat(msgbuf,"\r\n"); SockWrite (sock, msgbuf, strlen (msgbuf)); if ((gen_recv(sock, msgbuf, sizeof msgbuf))) return result; len = from64tobits (&challenge, msgbuf, sizeof(challenge)); if (outlevel >= O_DEBUG) dumpSmbNtlmAuthChallenge(stdout, &challenge); buildSmbNtlmAuthResponse(&challenge, &response,ctl->remotename,ctl->password); if (outlevel >= O_DEBUG) dumpSmbNtlmAuthResponse(stdout, &response); memset(msgbuf,0,sizeof msgbuf); to64frombits (msgbuf, &response, SmbLength(&response)); if (outlevel >= O_MONITOR) report(stdout, "IMAP> %s\n", msgbuf); strcat(msgbuf,"\r\n"); SockWrite (sock, msgbuf, strlen (msgbuf)); result = imap_ok (sock, NULL); if (result == PS_SUCCESS) return PS_SUCCESS; else return PS_AUTHFAIL; } #endif /* NTLM */ static void imap_canonicalize(char *result, char *raw, size_t maxlen) /* encode an IMAP password as per RFC1730's quoting conventions */ { size_t i, j; j = 0; for (i = 0; i < strlen(raw) && i < maxlen; i++) { if ((raw[i] == '\\') || (raw[i] == '"')) result[j++] = '\\'; result[j++] = raw[i]; } result[j] = '\0'; } static void capa_probe(int sock, struct query *ctl) /* set capability variables from a CAPA probe */ { int ok; /* probe to see if we're running IMAP4 and can use RFC822.PEEK */ capabilities[0] = '\0'; if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS) { char *cp; /* capability checks are supposed to be caseblind */ for (cp = capabilities; *cp; cp++) *cp = toupper((unsigned char)*cp); /* UW-IMAP server 10.173 notifies in all caps, but RFC2060 says we should expect a response in mixed-case */ if (strstr(capabilities, "IMAP4REV1")) { imap_version = IMAP4rev1; if (outlevel >= O_DEBUG) report(stdout, GT_("Protocol identified as IMAP4 rev 1\n")); } else { imap_version = IMAP4; if (outlevel >= O_DEBUG) report(stdout, GT_("Protocol identified as IMAP4 rev 0\n")); } } else if (ok == PS_ERROR) { imap_version = IMAP2; if (outlevel >= O_DEBUG) report(stdout, GT_("Protocol identified as IMAP2 or IMAP2BIS\n")); } /* * Handle idling. We depend on coming through here on startup * and after each timeout (including timeouts during idles). */ do_idle = ctl->idle; if (ctl->idle) { if (strstr(capabilities, "IDLE")) has_idle = TRUE; else has_idle = FALSE; if (outlevel >= O_VERBOSE) report(stdout, GT_("will idle after poll\n")); } peek_capable = (imap_version >= IMAP4); } static int do_authcert (int sock, char *command, const char *name) /* do authentication "external" (authentication provided by client cert) */ { char buf[256]; if (name && name[0]) { size_t len = strlen(name); if ((len / 3) + ((len % 3) ? 4 : 0) < sizeof(buf)) to64frombits (buf, name, strlen(name)); else return PS_AUTHFAIL; /* buffer too small. */ } else buf[0]=0; return gen_transact(sock, "%s EXTERNAL %s",command,buf); } static int imap_getauth(int sock, struct query *ctl, char *greeting) /* apply for connection authorization */ { int ok = 0; #ifdef SSL_ENABLE int got_tls = 0; #endif (void)greeting; /* * Assumption: expunges are cheap, so we want to do them * after every message unless user said otherwise. */ if (NUM_SPECIFIED(ctl->expunge)) expunge_period = NUM_VALUE_OUT(ctl->expunge); else expunge_period = 1; capa_probe(sock, ctl); /* * If either (a) we saw a PREAUTH token in the greeting, or * (b) the user specified ssh preauthentication, then we're done. */ if (preauth || ctl->server.authenticate == A_SSH) { preauth = FALSE; /* reset for the next session */ return(PS_SUCCESS); } #ifdef SSL_ENABLE if (maybe_tls(ctl)) { char *commonname; commonname = ctl->server.pollname; if (ctl->server.via) commonname = ctl->server.via; if (ctl->sslcommonname) commonname = ctl->sslcommonname; if (strstr(capabilities, "STARTTLS")) { /* Use "tls1" rather than ctl->sslproto because tls1 is the only * protocol that will work with STARTTLS. Don't need to worry * whether TLS is mandatory or opportunistic unless SSLOpen() fails * (see below). */ if (gen_transact(sock, "STARTTLS") == PS_SUCCESS && SSLOpen(sock, ctl->sslcert, ctl->sslkey, "tls1", ctl->sslcertck, ctl->sslcertpath, ctl->sslfingerprint, commonname, ctl->server.pollname, &ctl->remotename) != -1) { /* * RFC 2595 says this: * * "Once TLS has been started, the client MUST discard cached * information about server capabilities and SHOULD re-issue the * CAPABILITY command. This is necessary to protect against * man-in-the-middle attacks which alter the capabilities list prior * to STARTTLS. The server MAY advertise different capabilities * after STARTTLS." * * Now that we're confident in our TLS connection we can * guarantee a secure capability re-probe. */ got_tls = 1; capa_probe(sock, ctl); if (outlevel >= O_VERBOSE) { report(stdout, GT_("%s: upgrade to TLS succeeded.\n"), commonname); } } } if (!got_tls) { if (must_tls(ctl)) { /* Config required TLS but we couldn't guarantee it, so we must * stop. */ report(stderr, GT_("%s: upgrade to TLS failed.\n"), commonname); return PS_SOCKET; } else { if (outlevel >= O_VERBOSE) { report(stdout, GT_("%s: opportunistic upgrade to TLS failed, trying to continue\n"), commonname); } /* We don't know whether the connection is in a working state, so * test by issuing a NOOP. */ if (gen_transact(sock, "NOOP") != PS_SUCCESS) { /* Not usable. Empty sslproto to force an unencrypted * connection on the next attempt, and repoll. */ ctl->sslproto = xstrdup(""); return PS_REPOLL; } /* Usable. Proceed with authenticating insecurely. */ } } } #endif /* SSL_ENABLE */ /* * Time to authenticate the user. * Try the protocol variants that don't require passwords first. */ ok = PS_AUTHFAIL; /* Yahoo hack - we'll just try ID if it was offered by the server, * and IGNORE errors. */ { char *tmp = strstr(capabilities, " ID"); if (tmp && !isalnum((unsigned char)tmp[3]) && strstr(ctl->server.via ? ctl->server.via : ctl->server.pollname, "yahoo.com")) { (void)gen_transact(sock, "ID (\"guid\" \"1\")"); } } if ((ctl->server.authenticate == A_ANY || ctl->server.authenticate == A_EXTERNAL) && strstr(capabilities, "AUTH=EXTERNAL")) { ok = do_authcert(sock, "AUTHENTICATE", ctl->remotename); if (ok) { /* SASL cancellation of authentication */ gen_send(sock, "*"); if (ctl->server.authenticate != A_ANY) return ok; } else { return ok; } } #ifdef GSSAPI if ((ctl->server.authenticate == A_ANY || ctl->server.authenticate == A_GSSAPI) && strstr(capabilities, "AUTH=GSSAPI")) { if ((ok = do_gssauth(sock, "AUTHENTICATE", "imap", ctl->server.truename, ctl->remotename))) { /* SASL cancellation of authentication */ gen_send(sock, "*"); if (ctl->server.authenticate != A_ANY) return ok; } else { return ok; } } #endif /* GSSAPI */ #ifdef KERBEROS_V4 if ((ctl->server.authenticate == A_ANY || ctl->server.authenticate == A_KERBEROS_V4 || ctl->server.authenticate == A_KERBEROS_V5) && strstr(capabilities, "AUTH=KERBEROS_V4")) { if ((ok = do_rfc1731(sock, "AUTHENTICATE", ctl->server.truename))) { /* SASL cancellation of authentication */ gen_send(sock, "*"); if(ctl->server.authenticate != A_ANY) return ok; } else return ok; } #endif /* KERBEROS_V4 */ /* * No such luck. OK, now try the variants that mask your password * in a challenge-response. */ if ((ctl->server.authenticate == A_ANY && strstr(capabilities, "AUTH=CRAM-MD5")) || ctl->server.authenticate == A_CRAM_MD5) { if ((ok = do_cram_md5 (sock, "AUTHENTICATE", ctl, NULL))) { /* SASL cancellation of authentication
/*
 * netrc.c -- parse the .netrc file to get hosts, accounts, and passwords
 *
   Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
   Copyright assigned to Eric S. Raymond, October 2001.

   For license terms, see the file COPYING in this directory.

   Compile with -DSTANDALONE to test this module.
   (Makefile.am should have a rule so you can just type "make netrc")
*/

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"
#include "fetchmail.h"
#include "netrc.h"
#include "i18n.h"

#ifdef STANDALONE
/* Normally defined in xstrdup.c. */
# define xstrdup strdup

/* Normally defined in xmalloc.c */
# define xmalloc malloc
# define xrealloc realloc

char *program_name = "netrc";
#endif

/* Maybe add NEWENTRY to the account information list, LIST.  NEWENTRY is
   set to a ready-to-use netrc_entry, in any event. */
static void
maybe_add_to_list (netrc_entry **newentry, netrc_entry **list)
{
    netrc_entry *a, *l;
    a = *newentry;
    l = *list;

    /* We need a login name in order to add the entry to the list. */
    if (a && ! a->login)
    {
	/* Free any allocated space. */
	if (a->host)
	    free (a->host);
	if (a->password)
	    free (a->password);
    }
    else
    {
	if (a)
	{
	    /* Add the current machine into our list. */
	    a->next = l;
	    l = a;
	}

	/* Allocate a new netrc_entry structure. */
	a = (netrc_entry *) xmalloc (sizeof (netrc_entry));
    }

    /* Zero the structure, so that it is ready to use. */
    memset (a, 0, sizeof(*a));

    /* Return the new pointers. */
    *newentry = a;
    *list = l;
    return;
}


/* Parse FILE as a .netrc file (as described in ftp(1)), and return a
   list of entries.  NULL is returned if the file could not be
   parsed. */
netrc_entry *
parse_netrc (char *file)
{
    FILE *fp;
    char buf[POPBUFSIZE+1], *p, *tok;
    const char *premature_token;
    netrc_entry *current, *retval;
    int ln;

    /* The latest token we've seen in the file. */
    enum
    {
	tok_nothing, tok_account, tok_login, tok_macdef, tok_machine, tok_password
    } last_token = tok_nothing;

    current = retval = NULL;

    fp = fopen (file, "r");
    if (!fp)
    {
	/* Just return NULL if we can't open the file. */
	return NULL;
    }

    /* Initialize the file data. */
    ln = 0;
    premature_token = NULL;

    /* While there are lines in the file... */
    while (fgets(buf, sizeof(buf) - 1, fp))
    {
	ln++;

	/* Strip trailing CRLF */
	for (p = buf + strlen(buf) - 1; (p >= buf) && isspace((unsigned char)*p); p--)
	    *p = '\0';

	/* Parse the line. */
	p = buf;

	/* If the line is empty... */
	if (!*p)
	{
	    if (last_token == tok_macdef)	/* end of macro */
		last_token = tok_nothing;
	    else
		continue;			/* otherwise ignore it */
	}

	/* If we are defining macros, then skip parsing the line. */
	while (*p && last_token != tok_macdef)
	{
	    char quote_char = 0;
	    char *pp;

	    /* Skip any whitespace. */
	    while (*p && isspace ((unsigned char)*p))
		p++;

	    /* Discard end-of-line comments. */
	    if (*p == '#')
		break;

	    tok = pp = p;

	    /* Find the end of the token. */
	    while (*p && (quote_char || !isspace ((unsigned char)*p)))
	    {
		if (