/* * 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 */ static int count = 0, recentcount = 0, unseen = 0, deletions = 0; static int recentcount_ok = 0; static unsigned int startcount = 1; static int expunged, expunge_period, saved_timeout = 0; static int imap_version, preauth; static flag do_idle, has_idle; static char capabilities[MSGBUFSIZE+1]; static unsigned int *unseen_messages; static int imap_ok(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); /* interpret untagged status responses */ if (strstr(buf, "* CAPABILITY")) { strlcpy(capabilities, buf + 12, sizeof(capabilities)); } else if (strstr(buf, "EXISTS")) { count = atoi(buf+2); /* * 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 (count > INT_MAX/sizeof(int)) { report(stderr, GT_("bogus message count!")); return(PS_PROTOCOL); } /* * 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_FETCH; } } /* a space is required to avoid confusion with the \Recent flag */ else if (strstr(buf, " RECENT")) { recentcount_ok = 1; recentcount = atoi(buf+2); } else if (strstr(buf, "EXPUNGE") && !strstr(buf, "OK")) { count -= atoi(buf+2); if (count < 0) count = 0; } else if (strstr(buf, "PREAUTH")) preauth = TRUE; /* * 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 (!check_only && strstr(buf, "[READ-ONLY]")) return(PS_LOCKBUSY); } 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 return(PS_ERROR); } else return(PS_PROTOCOL); } } #ifdef NTLM_ENABLE #include "ntlm.h" static tSmbNtlmAuthRequest request; static tSmbNtlmAuthChallenge challenge; static tSmbNtlmAuthResponse response; /* * 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) { 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, (unsigned char*)&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 ((char*)&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, (unsigned char*)&response, SmbLength(&response)); if (outlevel >= O_MONITOR) report(stdout, "IMAP> %s\n", msgbuf); strcat(msgbuf,"\r\n"); SockWrite (sock, msgbuf, strlen (msgbuf)); if ((result = gen_recv (sock, msgbuf, sizeof msgbuf))) return result; if (strstr (msgbuf, "OK")) return PS_SUCCESS; else return PS_AUTHFAIL; } #endif /* NTLM */ static int imap_canonicalize(char *result, char *raw, int maxlen) /* encode an IMAP password as per RFC1730's quoting conventions */ { int 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'; return(i); } 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(*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). */ if (ctl->idle) { do_idle = TRUE; if (strstr(capabilities, "IDLE")) { has_idle = TRUE; } if (outlevel >= O_VERBOSE) report(stdout, GT_("will idle after poll\n")); } peek_capable = (imap_version >= IMAP4); } static int imap_getauth(int sock, struct query *ctl, char *greeting) /* apply for connection authorization */ { int ok = 0; #ifdef SSL_ENABLE flag did_stls = FALSE; #endif /* SSL_ENABLE */ /* * 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 ((!ctl->sslproto || !strcmp(ctl->sslproto,"tls1")) && !ctl->use_ssl && strstr(capabilities, "STARTTLS")) { char *realhost; realhost = ctl->server.via ? ctl->server.via : ctl->server.pollname; ok = gen_transact(sock, "STARTTLS"); /* We use "tls1" instead of ctl->sslproto, as we want STARTTLS, * not other SSL protocols */ if (ok == PS_SUCCESS && SSLOpen(sock,ctl->sslcert,ctl->sslkey,"tls1",ctl->sslcertck, ctl->sslcertpath,ctl->sslfingerprint,realhost,ctl->server.pollname) == -1) { if (!ctl->sslproto && !ctl->wehaveauthed) { ctl->sslproto = xstrdup(""); /* repoll immediately */ return(PS_REPOLL); } report(stderr, GT_("SSL co
INSTALL Instructions for fetchmail
==================================

Building from Git repository: see README.git

Packagers and port/emerge maintainers: see README.packaging.


If you have installed binaries (e.g. from a Linux RPM or DPKG, Solaris
package or FreeBSD port), you can skip to step 5 below.

---------------------------------------------------------------------
The Frequently Asked Questions list, included as the file FAQ in this
distribution, answers the most common questions about configuring and
running fetchmail.
---------------------------------------------------------------------


1. PREPARATIONS: USEFUL THINGS TO INSTALL FIRST

1.1 OpenSSL

If you are installing OpenSSL yourself, it is recommended that you build
shared OpenSSL libraries, it works better and updating OpenSSL does not
then require you to reinstall all applications that use OpenSSL.

Try after unpacking OpenSSL:

	./config shared && make && make test && make install

1.2 gettext (internationalization)

Internationalization of fetchmail requires GNU gettext (libintl and
libiconv). Fetchmail, as of version 6.3.0, no longer ships its own
libintl copy.  Note that some systems include gettext in their libc.

1.3 OTP/OPIE

If you want support for RFC1938-compliant one-time passwords, you'll
need to install Craig Metz's OPIE libraries first and *make sure
they're on the normal library path* where configure will find them.  Then
configure with --enable-OPIE, and fetchmail build process will detect
them and compile appropriately.

Note: there is no point in doing this unless your server is
OTP-enabled.  To test this, telnet to the server port and give it
a valid USER id.  If the OK response includes the string "otp-",
you should install OPIE.  You need version 2.32 or better.

The OPIE library sources are available at http://www.inner.net/pub/opie/
You can also find OPIE and IPV6-capable servers there.

1.4 IPv6

Building in IPv6 support *requires* an up-to-date operating system.
Recent Linux versions with glibc 2.1.1 or newer, FreeBSD, Solaris should
be fine.

If you have trouble with intl or gettext functions, try using the
configure option '--with-included-gettext'.


2. CONFIGURE

2.1 Basic options

Installing fetchmail is easy.  From within this directory, type:

	./configure

The autoconfiguration script will spend a bit of time figuring out the
specifics of your system.  If you want to specify a particular compiler
(e.g. you have gcc but want to compile with cc), set the environment
variable CC before you run configure.

The configure script accepts certain standard configuration options.
These include --prefix, --exec-prefix, --bindir, --infodir, --mandir,
and --srcdir.  Run 'configure --help' for more.

POP2 support is no longer compiled in by default, as POP2 is way obsolete
and there don't seem to be any live servers for it anymore.  You can
configure it back in if you want with 'configure --enable-POP2', but
leaving it out cuts the executable's size slightly.

Support for CompuServe's RPA authentication method (rather similar to
APOP) is available but also not included in the standard build.  You
can compile it in with 'configure --enable-RPA'.

Support for Microsoft's NTLM authentication method is also available
but not included in the standard build either.  You can compile it in
with 'configure --enable-NTLM'.

Support for authentication using RFC1731 GSSAPI is available
but also not included by default.  You can compile it in with
'configure --with-gssapi', which looks for GSSAPI support in standard
locations (/usr, /usr/local).  If you set --with-GSSAPI=DIR
you can direct the build to look for GSSAPI support under DIR.

Hooks for the OpenSSL library (see http://www.openssl.org/) are
included in the distribution.  Fetchmail 6.4 enables these by default.
Fetchmail's configure script will query pkg-config (pkgconf) or failing that,
probe some default locations for the include/openssl/ssl.h file. If this
doesn't work (i. e. configure prints "SSL support enabled, but OpenSSL not
found" and aborts), you need to give the explicit prefix of your OpenSSL
installation (specify the directory that contains OpenSSL's "include"
subdirectory), for instance: "--with-ssl=/example/path" would assume that you
have an /example/path/include/openssl/ssl.h header file.

2.2 Advanced options

Specifying --with-kerberos=DIR or --with-kerberos5=DIR will tell the
fetchmail build process to look in DIR for Kerberos support.
Configure normally looks in /usr/kerberos and /usr/athena; if you
specify this option with an argument it will look in DIR first.

Unfortunately, there doesn't seem to be good standardization of where
Kerberos lives.  If your configuration doesn't match one of the four
that fetchmail's configure.in knows about, you may find you have to
hand-hack the Makefile a bit.

You may also want to hand-hack the Makefile if you're writing a custom
or bleeding-edge resolver library.  In that case you will probably
want to add -lresolv or whatever to the definition of LOADLIBS.

It is also possible to explicitly condition out the support for
POP3, IMAP, and ETRN (with configure arguments of --disable-POP3,
--disable-IMAP, and --disable-ETRN respectively).


3. BUILD

Run

	make

This should compile fetchmail for your system.  If fetchmail fails to build
properly, see the FAQ section B on build-time problems.

On multi-core computers, run

	make -j8

on a computer that supports 8 CPU threads at the same time (for instance,
Octocore computers or Quad-core computers supporting two threads per core).


4. INSTALL

Lastly, become root and run

	make install

This will install fetchmail.  By default, fetchmail will be installed
in /usr/local/bin, with the man page in /usr/local/man/man1.  You can
use the configure options --bindir and --mandir to change these.

If you are tight on disk space, you can run instead

	make install-strip

NOTE: If you are using an MTA other than sendmail (such as qmail,
exim, or smail), see the FAQ (section T) for discussion of any special
configuration steps that may be necessary.


5. SET UP A RUN CONTROL FILE

See the man page for a description of how to configure your individual
preferences.

If you're upgrading from popclient, see question F4 in the FAQ file.


6. TEST

I strongly recommend that your first fetchmail run use the -v, -a and -k
options, in case there is something not quite right with your server,
your local delivery configuration or your port 25 listener.  Also,
beware of aliases that direct your local mail back to the server host!

This software is known to work with the qpop/popper series of freeware
POP3 servers; also with the IMAP2bis and IMAP4 servers that are
distributed with Pine from the University of Washington; also with the
Cyrus IMAP server from CMU.  This covers all the servers commonly
hosted on Linux and *BSD systems.  It also works with the IMAP service
of Microsoft Exchange, despite the fact that Microsoft Exchange is
extremely broken (returns incorrect message lengths in LIST
responses).

See the FAQ, section S, for detailed advice on running with various
servers.


7. REPORTING BUGS

You should read the FAQ file question G3 before reporting a bug.


8. USE IT

Once you've verified your configuration, you can start fetchmail to
run in background and forget about it.  Enjoy!


END of text file INSTALL
, "FETCH %d:%d RFC822.SIZE", first, last); else /* no unseen messages! */ return(PS_SUCCESS); for (;;) { unsigned int num, size; int ok; char *cp; if ((ok = gen_recv(sock, buf, sizeof(buf)))) return(ok); /* we want response matching to be case-insensitive */ for (cp = buf; *cp; cp++) *cp = toupper(*cp); /* an untagged NO means that a message was not readable */ if (strstr(buf, "* NO")) ; else if (strstr(buf, "OK") || strstr(buf, "NO")) break; else if (sscanf(buf, "* %u FETCH (RFC822.SIZE %u)", &num, &size) == 2) { if (num >= first && num <= last) sizes[num - first] = size; else report(stderr, "Warning: ignoring bogus data for message sizes returned by the server.\n"); } } return(PS_SUCCESS); } static int imap_getsizes(int sock, int count, int *sizes) /* capture the sizes of all messages */ { return imap_getpartialsizes(sock, 1, count, sizes); } static int imap_is_old(int sock, struct query *ctl, int number) /* is the given message old? */ { flag seen = TRUE; int i; /* * Expunges change the fetch numbers, but unseen_messages contains * indices from before any expungees were done. So neither the * argument nor the values in message_sequence need to be decremented. */ seen = TRUE; for (i = 0; i < unseen; i++) if (unseen_messages[i] == number) { seen = FALSE; break; } return(seen); } static char *skip_token(char *ptr) { while(isspace((unsigned char)*ptr)) ptr++; while(!isspace((unsigned char)*ptr) && !iscntrl((unsigned char)*ptr)) ptr++; while(isspace((unsigned char)*ptr)) ptr++; return(ptr); } static int imap_fetch_headers(int sock, struct query *ctl,int number,int *lenp) /* request headers of nth message */ { char buf [MSGBUFSIZE+1]; int num; /* expunges change the fetch numbers */ number -= expunged; /* * This is blessed by RFC1176, RFC1730, RFC2060. * According to the RFCs, it should *not* set the \Seen flag. */ gen_send(sock, "FETCH %d RFC822.HEADER", number); /* looking for FETCH response */ for (;;) { int ok; char *ptr; if ((ok = gen_recv(sock, buf, sizeof(buf)))) return(ok); ptr = skip_token(buf); /* either "* " or "AXXXX " */ if (sscanf(ptr, "%d FETCH (%*s {%d}", &num, lenp) == 2) break; /* try to recover from chronically fucked-up M$ Exchange servers */ else if (!strncmp(ptr, "NO", 2)) { /* wait for a tagged response */ if (strstr (buf, "* NO")) imap_ok (sock, 0); return(PS_TRANSIENT); } else if (!strncmp(ptr, "BAD", 3)) { /* wait for a tagged response */ if (strstr (buf, "* BAD")) imap_ok (sock, 0); return(PS_TRANSIENT); } } if (num != number) return(PS_ERROR); else return(PS_SUCCESS); } static int imap_fetch_body(int sock, struct query *ctl, int number, int *lenp) /* request body of nth message */ { char buf [MSGBUFSIZE+1], *cp; int num; /* expunges change the fetch numbers */ number -= expunged; /* * If we're using IMAP4, we can fetch the message without setting its * seen flag. This is good! It means that if the protocol exchange * craps out during the message, it will still be marked `unseen' on * the server. * * According to RFC2060, and Mark Crispin the IMAP maintainer, * FETCH %d BODY[TEXT] and RFC822.TEXT are "functionally * equivalent". However, we know of at least one server that * treats them differently in the presence of MIME attachments; * the latter form downloads the attachment, the former does not. * The server is InterChange, and the fool who implemented this * misfeature ought to be strung up by his thumbs. * * When I tried working around this by disabling use of the 4rev1 form, * I found that doing this breaks operation with M$ Exchange. * Annoyingly enough, Exchange's refusal to cope is technically legal * under RFC2062. Trust Microsoft, the Great Enemy of interoperability * standards, to find a way to make standards compliance irritating.... */ switch (imap_version) { case IMAP4rev1: /* RFC 2060 */ gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number); break; case IMAP4: /* RFC 1730 */ gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number); break; default: /* RFC 1176 */ gen_send(sock, "FETCH %d RFC822.TEXT", number); break; } /* looking for FETCH response */ do { int ok; if ((ok = gen_recv(sock, buf, sizeof(buf)))) return(ok); } while (!strstr(buf+4, "FETCH") || sscanf(buf+2, "%d", &num) != 1); if (num != number) return(PS_ERROR); /* * Try to extract a length from the FETCH response. RFC2060 requires * it to be present, but at least one IMAP server (Novell GroupWise) * botches this. The overflow check is needed because of a broken * server called dbmail that returns huge garbage lengths. */ if ((cp = strchr(buf, '{'))) { errno = 0; *lenp = (int)strtol(cp + 1, (char **)NULL, 10); if (errno == ERANGE && (*lenp == LONG_MAX || *lenp == LONG_MIN)) *lenp = -1; /* length is too big/small for us to handle */ } else *lenp = -1; /* missing length part in FETCH reponse */ return(PS_SUCCESS); } static int imap_trail(int sock, struct query *ctl, int number) /* discard tail of FETCH response after reading message text */ { /* expunges change the fetch numbers */ /* number -= expunged; */ for (;;) { char buf[MSGBUFSIZE+1]; int ok; if ((ok = gen_recv(sock, buf, sizeof(buf)))) return(ok); /* UW IMAP returns "OK FETCH", Cyrus returns "OK Completed" */ if (strstr(buf, "OK")) break; } return(PS_SUCCESS); } static int imap_delete(int sock, struct query *ctl, int number) /* set delete flag for given message */ { int ok; /* expunges change the fetch numbers */ number -= expunged; /* * Use SILENT if possible as a minor throughput optimization. * Note: this has been dropped from IMAP4rev1. * * We set Seen because there are some IMAP servers (notably HP * OpenMail) that do message-receipt DSNs, but only when the seen * bit is set. This is the appropriate time -- we get here right * after the local SMTP response that says delivery was * successful. */ if ((ok = gen_transact(sock, imap_version == IMAP4 ? "STORE %d +FLAGS.SILENT (\\Seen \\Deleted)" : "STORE %d +FLAGS (\\Seen \\Deleted)", number))) return(ok); else deletions++; /* * We do an expunge after expunge_period messages, rather than * just before quit, so that a line hit during a long session * won't result in lots of messages being fetched again during * the next session. */ if (NUM_NONZERO(expunge_period) && (deletions % expunge_period) == 0) internal_expunge(sock); return(PS_SUCCESS); } static int imap_mark_seen(int sock, struct query *ctl, int number) /* mark the given message as seen */ { return(gen_transact(sock, imap_version == IMAP4 ? "STORE %d +FLAGS.SILENT (\\Seen)" : "STORE %d +FLAGS (\\Seen)", number)); } static int imap_logout(int sock, struct query *ctl) /* send logout command */ { /* if any un-expunged deletions remain, ship an expunge now */ if (deletions) internal_expunge(sock); #ifdef USE_SEARCH /* Memory clean-up */ if (unseen_messages) free(unseen_messages); #endif /* USE_SEARCH */ return(gen_transact(sock, "LOGOUT")); } static const struct method imap = { "IMAP", /* Internet Message Access Protocol */ "imap", "imaps", TRUE, /* this is a tagged protocol */ FALSE, /* no message delimiter */ imap_ok, /* parse command response */ imap_getauth, /* get authorization */ imap_getrange, /* query range of messages */ imap_getsizes, /* get sizes of messages (used for ESMTP SIZE option) */ imap_getpartialsizes, /* get sizes of subset of messages (used for ESMTP SIZE option) */ imap_is_old, /* no UID check */ imap_fetch_headers, /* request given message headers */ imap_fetch_body, /* request given message body */ imap_trail, /* eat message trailer */ imap_delete, /* delete the message */ imap_mark_seen, /* how to mark a message as seen */ imap_logout, /* expunge and exit */ TRUE, /* yes, we can re-poll */ }; int doIMAP(struct query *ctl) /* retrieve messages using IMAP Version 2bis or Version 4 */ { return(do_protocol(ctl, &imap)); } /* imap.c ends here */