/* * 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 #endif #include "fetchmail.h" #include "socket.h" #ifdef KERBEROS_V4 #if defined (__bsdi__) #include #define krb_get_err_text(e) (krb_err_txt[e]) #endif #if defined(__NetBSD__) || (__FreeBSD__) || defined(__linux__) #define krb_get_err_text(e) (krb_err_txt[e]) #endif #include #endif /* KERBEROS_V4 */ #ifdef GSSAPI #include #include #endif #if OPIE #include #endif /* OPIE */ #ifndef strstr /* glibc-2.1 declares this as a macro */ extern char *strstr(); /* needed on sysV68 R3V7.1. */ #endif /* strstr */ /* 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, seen, recent, unseen, deletions, expunged, imap_version; static char capabilities[MSGBUFSIZE+1]; int imap_ok(int sock, char *argbuf) /* parse command response */ { char buf [MSGBUFSIZE+1]; seen = 0; 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(*cp)) *cp = toupper(*cp); /* interpret untagged status responses */ if (strstr(buf, "* CAPABILITY")) strncpy(capabilities, buf + 12, sizeof(capabilities)); if (strstr(buf, "EXISTS")) count = atoi(buf+2); if (strstr(buf, "RECENT")) recent = atoi(buf+2); if (strstr(buf, "UNSEEN")) { char *cp; /* * Handle both "* 42 UNSEEN" (if tha ever happens) and * "* OK [UNSEEN 42] 42". Note that what this gets us is * a minimum index, not a count. */ unseen = 0; for (cp = buf; *cp && !isdigit(*cp); cp++) continue; unseen = atoi(cp); } if (strstr(buf, "FLAGS")) seen = (strstr(buf, "SEEN") != (char *)NULL); } 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(*cp); cp++) continue; while (isspace(*cp)) cp++; if (strncmp(cp, "OK", 2) == 0) { if (argbuf) strcpy(argbuf, cp); return(PS_SUCCESS); } else if (strncmp(cp, "BAD", 3) == 0) return(PS_ERROR); else if (strncmp(cp, "NO", 2) == 0) return(PS_ERROR); else return(PS_PROTOCOL); } } #if OPIE static int do_otp(int sock, struct query *ctl) { int i, rval; char buffer[128]; char challenge[OPIE_CHALLENGE_MAX+1]; char response[OPIE_RESPONSE_MAX+1]; gen_send(sock, "AUTHENTICATE X-OTP"); if (rval = gen_recv(sock, buffer, sizeof(buffer))) return rval; if ((i = from64tobits(challenge, buffer)) < 0) { error(0, -1, "Could not decode initial BASE64 challenge"); return PS_AUTHFAIL; }; to64frombits(buffer, ctl->remotename, strlen(ctl->remotename)); if (outlevel >= O_MONITOR) error(0, 0, "IMAP> %s", buffer); SockWrite(sock, buffer, strlen(buffer)); SockWrite(sock, "\r\n", 2); if (rval = gen_recv(sock, buffer, sizeof(buffer))) return rval; if ((i = from64tobits(challenge, buffer)) < 0) { error(0, -1, "Could not decode OTP challenge"); return PS_AUTHFAIL; }; rval = opiegenerator(challenge, !strcmp(ctl->password, "opie") ? "" : ctl->password, response); if ((rval == -2) && !run.poll_interval) { char secret[OPIE_SECRET_MAX+1]; fprintf(stderr, "Secret pass phrase: "); if (opiereadpass(secret, sizeof(secret), 0)) rval = opiegenerator(challenge, secret, response); memset(secret, 0, sizeof(secret)); }; if (
#!/usr/bin/env python
#
# Collect statistics on current release.

import commands, os, string, ftplib

# Get version and date
date = commands.getoutput("LC_TIME=C date +'%Y-%m-%d'")
pid = os.getpid()
os.mkdir("/tmp/getstats.%d" % pid)

cmd = "git archive --format=tar HEAD | ( cd /tmp/getstats.%d && tar -xf -)" % pid

if os.system(cmd):
    print "git-archive FAILED"
    os.exit(1)

ln = commands.getoutput("cat /tmp/getstats.%d/*.[chly] 2>/dev/null | wc -l" % pid)
os.system("rm -rf /tmp/getstats.%d" % pid)
vers = commands.getoutput("sed -n -e '/AC_INIT/s/AC_INIT(\\[.\\+\\],\\[\\(.*\\)\\],.*)/\\1/p' <configure.ac")
print "fetchmail-" + vers + " (released " + date + ", " + string.strip(ln) + " LoC):"

# end of getstats.py
result = gen_recv(sock, buf1, sizeof buf1)) { return result; } /* now start the security context initialisation loop... */ sec_token = GSS_C_NO_BUFFER; context = GSS_C_NO_CONTEXT; if (outlevel >= O_VERBOSE) error(0,0,"Sending credentials"); do { maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &context, target_name, NULL, 0, 0, NULL, sec_token, NULL, &send_token, &cflags, NULL); if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) { error(0, -1,"Error exchanging credentials"); gss_release_name(&min_stat, &target_name); /* wake up server and await NO response */ SockWrite(sock, "\r\n", 2); if (result = gen_recv(sock, buf1, sizeof buf1)) return result; return PS_AUTHFAIL; } to64frombits(buf1, send_token.value, send_token.length); gss_release_buffer(&min_stat, &send_token); SockWrite(sock, buf1, strlen(buf1)); SockWrite(sock, "\r\n", 2); if (outlevel >= O_MONITOR) error(0,0,"IMAP> %s", buf1); if (maj_stat == GSS_S_CONTINUE_NEEDED) { if (result = gen_recv(sock, buf1, sizeof buf1)) { gss_release_name(&min_stat, &target_name); return result; } request_buf.length = from64tobits(buf2, buf1 + 2); request_buf.value = buf2; sec_token = &request_buf; } } while (maj_stat == GSS_S_CONTINUE_NEEDED); gss_release_name(&min_stat, &target_name); /* get security flags and buffer size */ if (result = gen_recv(sock, buf1, sizeof buf1)) { return result; } request_buf.length = from64tobits(buf2, buf1 + 2); request_buf.value = buf2; maj_stat = gss_unwrap(&min_stat, context, &request_buf, &send_token, &cflags, &quality); if (maj_stat != GSS_S_COMPLETE) { error(0,-1,"Couldn't unwrap security level data"); gss_release_buffer(&min_stat, &send_token); return PS_AUTHFAIL; } if (outlevel >= O_DEBUG) error(0,0,"Credential exchange complete"); /* first octet is security levels supported. We want none, for now */ server_conf_flags = ((char *)send_token.value)[0]; if ( !(((char *)send_token.value)[0] & GSSAUTH_P_NONE) ) { error(0,-1,"Server requires integrity and/or privacy"); gss_release_buffer(&min_stat, &send_token); return PS_AUTHFAIL; } ((char *)send_token.value)[0] = 0; buf_size = ntohl(*((long *)send_token.value)); /* we don't care about buffer size if we don't wrap data */ gss_release_buffer(&min_stat, &send_token); if (outlevel >= O_DEBUG) { error(0,0,"Unwrapped security level flags: %s%s%s", server_conf_flags & GSSAUTH_P_NONE ? "N" : "-", server_conf_flags & GSSAUTH_P_INTEGRITY ? "I" : "-", server_conf_flags & GSSAUTH_P_PRIVACY ? "C" : "-"); error(0,0,"Maximum GSS token size is %ld",buf_size); } /* now respond in kind (hack!!!) */ buf_size = htonl(buf_size); /* do as they do... only matters if we do enc */ memcpy(buf1, &buf_size, 4); buf1[0] = GSSAUTH_P_NONE; strcpy(buf1+4, username); /* server decides if princ is user */ request_buf.length = 4 + strlen(username) + 1; request_buf.value = buf1; maj_stat = gss_wrap(&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf, &cflags, &send_token); if (maj_stat != GSS_S_COMPLETE) { error(0,-1,"Error creating security level request"); return PS_AUTHFAIL; } to64frombits(buf1, send_token.value, send_token.length); if (outlevel >= O_DEBUG) { error(0,0,"Requesting authorisation as %s", username); error(0,0,"IMAP> %s",buf1); } SockWrite(sock, buf1, strlen(buf1)); SockWrite(sock, "\r\n", 2); /* we should be done. Get status and finish up */ if (result = gen_recv(sock, buf1, sizeof buf1)) return result; if (strstr(buf1, "OK")) { /* flush security context */ if (outlevel >= O_DEBUG) error(0, 0, "Releasing GSS credentials"); maj_stat = gss_delete_sec_context(&min_stat, &context, &send_token); if (maj_stat != GSS_S_COMPLETE) { error(0, -1, "Error releasing credentials"); return PS_AUTHFAIL; } /* 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 PS_SUCCESS; } return PS_AUTHFAIL; } #endif /* GSSAPI */ int imap_canonicalize(char *result, char *passwd) /* encode an IMAP password as per RFC1730's quoting conventions */ { int i, j; j = 0; for (i = 0; i < strlen(passwd); i++) { if ((passwd[i] == '\\') || (passwd[i] == '"')) result[j++] = '\\'; result[j++] = passwd[i]; } result[j] = '\0'; return(i); } int imap_getauth(int sock, struct query *ctl, char *greeting) /* apply for connection authorization */ { int ok = 0; char password[PASSWORDLEN*2]; /* probe to see if we're running IMAP4 and can use RFC822.PEEK */ capabilities[0] = '\0'; if ((ok = gen_transact(sock, "CAPABILITY")) == PS_SUCCESS) { /* UW-IMAP server 10.173 notifies in all caps */ if (strstr(capabilities, "IMAP4REV1")) { imap_version = IMAP4rev1; if (outlevel >= O_DEBUG) error(0, 0, "Protocol identified as IMAP4 rev 1"); } else { imap_version = IMAP4; if (outlevel >= O_DEBUG) error(0, 0, "Protocol identified as IMAP4 rev 0"); } } else if (ok == PS_ERROR) { imap_version = IMAP2; if (outlevel >= O_DEBUG) error(0, 0, "Protocol identified as IMAP2 or IMAP2BIS"); } else return(ok); peek_capable = (imap_version >= IMAP4); #if OPIE if ((ctl->server.protocol == P_IMAP) && strstr(capabilities, "AUTH=X-OTP")) { if (outlevel >= O_DEBUG) error(0, 0, "OTP authentication is supported"); if (do_otp(sock, ctl) == PS_SUCCESS) return(PS_SUCCESS); }; #endif /* OPIE */ #ifdef GSSAPI if (strstr(capabilities, "AUTH=GSSAPI")) { if (ctl->server.protocol == P_IMAP_GSS) { if (outlevel >= O_DEBUG) error(0, 0, "GSS authentication is supported"); return do_gssauth(sock, ctl->server.truename, ctl->remotename); } } else if (ctl->server.protocol == P_IMAP_GSS) { error(0,-1, "Required GSS capability not supported by server"); return(PS_AUTHFAIL); } #endif /* GSSAPI */ #ifdef KERBEROS_V4 if (strstr(capabilities, "AUTH=KERBEROS_V4")) { if (outlevel >= O_DEBUG) error(0, 0, "KERBEROS_V4 authentication is supported"); if (ctl->server.protocol == P_IMAP_K4) { if ((ok = do_rfc1731(sock, ctl->server.truename))) { if (outlevel >= O_MONITOR) error(0, 0, "IMAP> *"); SockWrite(sock, "*\r\n", 3); } return(ok); } /* else fall through to ordinary AUTH=LOGIN case */ } else if (ctl->server.protocol == P_IMAP_K4) { error(0,-1, "Required KERBEROS_V4 capability not supported by server"); return(PS_AUTHFAIL); } #endif /* KERBEROS_V4 */ #ifdef __UNUSED__ /* The Cyrus IMAP4rev1 server chokes on this */ /* this handles either AUTH=LOGIN or AUTH-LOGIN */ if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN"))) { error(0,-1, "Required LOGIN capability not supported by server"); return PS_AUTHFAIL; }; #endif /* __UNUSED__ */ imap_canonicalize(password, ctl->password); ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", ctl->remotename, password); if (ok) return(ok); return(PS_SUCCESS); } static int internal_expunge(int sock) /* ship an expunge, resetting associated counters */ { int ok; if ((ok = gen_transact(sock, "EXPUNGE"))) return(ok); expunged += deletions; deletions = 0; #ifdef IMAP_UID /* not used */ expunge_uids(ctl); #endif /* IMAP_UID */ return(PS_SUCCESS); } static int imap_getrange(int sock, struct query *ctl, const char *folder, int *countp, int *newp, int *bytes) /* get range of messages to be fetched */ { int ok; /* find out how many messages are waiting */ *bytes = recent = unseen = -1; if (pass > 1) { /* * We have to have an expunge here, otherwise the re-poll will * infinite-loop picking up un-expunged message. */ ok = 0; if (deletions && ctl->expunge > 1) internal_expunge(sock); count = -1; if (ok || gen_transact(sock, "NOOP")) { error(0, 0, "re-poll failed"); return(ok); } else if (count == -1) /* no EXISTS response to NOOP */ { count = recent = 0; unseen = -1; } } else { if (!check_only) ok = gen_transact(sock, "SELECT %s", folder ? folder : "INBOX"); else ok = gen_transact(sock, "EXAMINE %s", folder ? folder : "INBOX"); if (ok != 0) { error(0, 0, "mailbox selection failed"); return(ok); } } *countp = count; /* * Note: because IMAP has an is_old method, this number is used * only for the "X messages (Y unseen)" notification. Accordingly * it doesn't matter much that it can be wrong (e.g. if we see an * UNSEEN response but not all messages above the first UNSEEN one * are likewise). */ if (unseen >= 0) /* optional, but better if we see it */ *newp = count - unseen + 1; else if (recent >= 0) /* mandatory */ *newp = recent; else *newp = -1; /* should never happen, RECENT is mandatory */ expunged = 0; return(PS_SUCCESS); } static int imap_getsizes(int sock, int count, int *sizes) /* capture the sizes of all messages */ { char buf [MSGBUFSIZE+1]; /* * Some servers (as in, PMDF5.1-9.1 under OpenVMS 6.1) * won't accept 1:1 as valid set syntax. Some implementors * should be taken out and shot for excessive anality. */ if (count == 1) gen_send(sock, "FETCH 1 RFC822.SIZE", count); else gen_send(sock, "FETCH 1:%d RFC822.SIZE", count); for (;;) { int num, size, ok; if ((ok = gen_recv(sock, buf, sizeof(buf)))) return(ok); if (strstr(buf, "OK")) break; else if (sscanf(buf, "* %d FETCH (RFC822.SIZE %d)", &num, &size) == 2) sizes[num - 1] = size; } return(PS_SUCCESS); } static int imap_is_old(int sock, struct query *ctl, int number) /* is the given message old? */ { int ok; /* expunges change the fetch numbers */ number -= expunged; if ((ok = gen_transact(sock, "FETCH %d FLAGS", number)) != 0) return(PS_ERROR); return(seen); } 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 RFC 1176, 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 */ do { int ok; if ((ok = gen_recv(sock, buf, sizeof(buf)))) return(ok); } while (sscanf(buf+2, "%d FETCH (%*s {%d}", &num, lenp) != 2); 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. * * However...*don't* do this if we're using keep to suppress deletion! * In that case, marking the seen flag is the only way to prevent the * message from being re-fetched on subsequent runs. */ switch (imap_version) { case IMAP4rev1: /* RFC 2060 */ if (!ctl->keep) gen_send(sock, "FETCH %d BODY.PEEK[TEXT]", number); else gen_send(sock, "FETCH %d BODY[TEXT]", number); break; case IMAP4: /* RFC 1730 */ if (!ctl->keep) gen_send(sock, "FETCH %d RFC822.TEXT.PEEK", number); else gen_send(sock, "FETCH %d RFC822.TEXT", 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 (sscanf(buf+2, "%d FETCH", &num) != 1); if (num != number) return(PS_ERROR); /* try to extract a length */ if ((cp = strchr(buf, '{'))) *lenp = atoi(cp + 1); else *lenp = 0; 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. */ if ((ok = gen_transact(sock, imap_version == IMAP4 ? "STORE %d +FLAGS.SILENT (\\Deleted)" : "STORE %d +FLAGS (\\Deleted)", number))) return(ok); else deletions++; /* * We do an expunge after ctl->expunge 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(ctl->expunge) && (deletions % ctl->expunge) == 0) internal_expunge(sock); return(PS_SUCCESS); } static int imap_logout(int sock, struct query *ctl) /* send logout command */ { /* if expunges after deletion have been suppressed, ship one now */ if (NUM_SPECIFIED(ctl->expunge) && NUM_ZERO(ctl->expunge) && deletions) internal_expunge(sock); return(gen_transact(sock, "LOGOUT")); } const static struct method imap = { "IMAP", /* Internet Message Access Protocol */ #if INET6 "imap", #else /* INET6 */ 143, /* standard IMAP2bis/IMAP4 port */ #endif /* INET6 */ TRUE, /* this is a tagged protocol */ FALSE, /* no message delimiter */ imap_ok, /* parse command response */ imap_canonicalize, /* deal with embedded slashes and spaces */ imap_getauth, /* get authorization */ imap_getrange, /* query range of messages */ imap_getsizes, /* get sizes of messages (used for --limit 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_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 */