aboutsummaryrefslogtreecommitdiffstats
path: root/idlist.c
blob: 379073fd7f2f7b2e4fc01505fab2f1b017f3e5c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/**
 * \file idlist.c -- string list handling
 *
 * For license terms, see the file COPYING in this directory.
 */

#include "config.h"
#include "fetchmail.h"

#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <limits.h>
#if defined(STDC_HEADERS)
#include <stdlib.h>
#include <string.h>
#endif
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif


/** Save string \a str to idlist \a idl with status \a status.
 * \return Pointer to the last element of the list to help the quick,
 * constant-time addition to the list. */
/*@shared@*/ static
struct idlist **save_str_quick(/*@shared@*/ struct idlist **idl,
			       /*@only@*/ char *str /** caller-allocated string */, flag status)
/* save a number/UID pair on the given UID list */
{
    struct idlist **end;

    /* do it nonrecursively so the list is in the right order */
    for (end = idl; *end; end = &(*end)->next)
	continue;

    *end = (struct idlist *)xmalloc(sizeof(struct idlist));
    (*end)->id = str;
    (*end)->val.status.mark = status;
    (*end)->val.status.num = 0;
    (*end)->next = NULL;

    return end;
}

/** Save string \a str to idlist \a idl with status \a status.
 * \return the end list element for direct modification. */
struct idlist *save_str(struct idlist **idl, const char *str /** implicitly strdup()ed */, flag status)
{
    return *save_str_quick(idl, str ? xstrdup(str) : NULL, status);
}

/** Free string list \a idl and free each of the id members. */
void free_str_list(struct idlist **idl)
{
    struct idlist *i = *idl;

    while(i) {
	struct idlist *t = i->next;
	free(i->id);
	free(i);
	i = t;
    }
    *idl = 0;
}

/** Save an ID pair made of \a str1 and \a str2 on the given idlist \a idl. */
void save_str_pair(struct idlist **idl, const char *str1, const char *str2)
{
    struct idlist **end;

    /* do it nonrecursively so the list is in the right order */
    for (end = idl; *end; end = &(*end)->next)
	continue;

    *end = (struct idlist *)xmalloc(sizeof(struct idlist));
    (*end)->id = str1 ? xstrdup(str1) : (char *)NULL;
    if (str2)
	(*end)->val.id2 = xstrdup(str2);
    else
	(*end)->val.id2 = (char *)NULL;
    (*end)->next = (struct idlist *)NULL;
}

/** Check if ID \a str is in idlist \a idl. \return idlist entry if found,
 * NULL if not found. */
struct idlist *str_in_list(struct idlist **idl, const char *str,
const flag caseblind /** if true, use strcasecmp, if false, use strcmp */)
{
    struct idlist *walk;
    if (caseblind) {
	for( walk = *idl; walk; walk = walk->next )
	    if( strcasecmp( str, walk->id) == 0 )
		return walk;
    } else {
	for( walk = *idl; walk; walk = walk->next )
	    if( strcmp( str, walk->id) == 0 )
		return walk;
    }
    return NULL;
}

/** \return position of first occurrence of \a str in idlist \a idl */
int str_nr_in_list(struct idlist **idl, const char *str)
{
    int nr;
    struct idlist *walk;

    if (!str)
        return -1;
    for (walk = *idl, nr = 0; walk; nr ++, walk = walk->next)
        if (strcmp(str, walk->id) == 0)
	    return nr;
    return -1;
}

/** \return position of last occurrence of \a str in idlist \a idl */
int str_nr_last_in_list( struct idlist **idl, const char *str)
{
    int nr, ret = -1;
    struct idlist *walk;
    if ( !str )
        return -1;
    for( walk = *idl, nr = 0; walk; nr ++, walk = walk->next )
        if( strcmp( str, walk->id) == 0 )
	    ret = nr;
    return ret;
}

/** Update the mark of an id \a str in idlist \a idl to given value \a val. */
void str_set_mark( struct idlist **idl, const char *str, const flag val)
{
    int nr;
    struct idlist *walk;
    if (!str)
        return;
    for(walk = *idl, nr = 0; walk; nr ++, walk = walk->next)
        if (strcmp(str, walk->id) == 0)
	    walk->val.status.mark = val;
}

/** Count the number of elements in the idlist \a idl. 
 * \return number of elements */
int count_list(struct idlist **idl)
{
	int i = 0;
	struct idlist *it;

	for (it = *idl ; it ; it = it->next)
		++i;

	return i;
}

/** return the \a number'th id string on idlist \a idl */
/*@null@*/ char *str_from_nr_list(struct idlist **idl, long number)
{
    if( !*idl  || number < 0)
        return 0;
    if( number == 0 )
        return (*idl)->id;
    return str_from_nr_list(&(*idl)->next, number-1);
}


/** Search idlist \a idl for entry with given \a number.
 * \return id member of idlist entry. */
char *str_find(struct idlist **idl, long number)
{
    if (*idl == (struct idlist *) 0)
	return((char *) 0);
    else if (number == (*idl)->val.status.num)
	return((*idl)->id);
    else
	return(str_find(&(*idl)->next, number));
}

/** Search idlist \a idl for entry with given \a number.
 * \return idlist entry. */
struct idlist *id_find(struct idlist **idl, long number)
{
    struct idlist	*idp;
    for (idp = *idl; idp; idp = idp->next)
	if (idp->val.status.num == number)
	    return(idp);
    return(0);
}

/** Return the id of the given \a id in the given idlist \a idl, comparing
 * case insensitively. \returns the respective other \a idlist member (the one
 * that was not searched for). */
char *idpair_find(struct idlist **idl, const char *id)
{
    if (*idl == (struct idlist *) 0)
	return((char *) 0);
    else if (strcasecmp(id, (*idl)->id) == 0)
	return((*idl)->val.id2 ? (*idl)->val.id2 : (*idl)->id);
    else
	return(idpair_find(&(*idl)->next, id));
}

/** Mark message number \a num on given idlist \a idl as deleted.
 * \return 1 if found, 0 if not found. */
int delete_str(struct idlist **idl, long num)
{
    struct idlist	*idp;

    for (idp = *idl; idp; idp = idp->next)
	if (idp->val.status.num == num)
	{
	    idp->val.status.mark = UID_DELETED;
	    return(1);
	}
    return(0);
}

/** Copy the given UID list \a idl. \return A newly malloc()ed copy of the list. */
struct idlist *copy_str_list(struct idlist *idl)
{
    struct idlist *newnode ;

    if (idl == (struct idlist *)NULL)
	return(NULL);
    else
    {
	newnode = (struct idlist *)xmalloc(sizeof(struct idlist));
	memcpy(newnode, idl, sizeof(struct idlist));
	newnode->next = copy_str_list(idl->next);
	return(newnode);
    }
}

/** Append \a nidl to \a idl (does not copy *) */
void append_str_list(struct idlist **idl, struct idlist **nidl)
{
    if ((*nidl) == (struct idlist *)NULL || *nidl == *idl)
	return;
    else if ((*idl) == (struct idlist *)NULL)
	*idl = *nidl;
    else if ((*idl)->next == (struct idlist *)NULL)
	(*idl)->next = *nidl;
    else if ((*idl)->next != *nidl)
	append_str_list(&(*idl)->next, nidl);
}

/* idlist.c ends here */
w"> challenge; tSmbNtlmAuthResponse response; char msgbuf[2048]; int result,len; gen_send(sock, msn_instead ? "AUTH MSN" : "AUTH 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, "POP3> %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(msgbuf)); 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, "POP3> %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 */ #define DOTLINE(s) (s[0] == '.' && (s[1]=='\r'||s[1]=='\n'||s[1]=='\0')) static int pop3_ok (int sock, char *argbuf) /* parse command response */ { int ok; char buf [POPBUFSIZE+1]; char *bufp; if ((ok = gen_recv(sock, buf, sizeof(buf))) == 0) { bufp = buf; if (*bufp == '+' || *bufp == '-') bufp++; else return(PS_PROTOCOL); while (isalpha((unsigned char)*bufp)) bufp++; if (*bufp) *(bufp++) = '\0'; if (strcmp(buf,"+OK") == 0) { #ifdef OPIE_ENABLE strcpy(lastok, bufp); #endif /* OPIE_ENABLE */ ok = 0; } else if (strncmp(buf,"-ERR", 4) == 0) { if (stage == STAGE_FETCH) ok = PS_TRANSIENT; else if (stage > STAGE_GETAUTH) ok = PS_PROTOCOL; /* * We're checking for "lock busy", "unable to lock", * "already locked", "wait a few minutes" etc. here. * This indicates that we have to wait for the server to * unwedge itself before we can poll again. * * PS_LOCKBUSY check empirically verified with two recent * versions of the Berkeley popper; QPOP (version 2.2) and * QUALCOMM Pop server derived from UCB (version 2.1.4-R3) * These are caught by the case-indifferent "lock" check. * The "wait" catches "mail storage services unavailable, * wait a few minutes and try again" on the InterMail server. * * If these aren't picked up on correctly, fetchmail will * think there is an authentication failure and wedge the * connection in order to prevent futile polls. * * Gad, what a kluge. */ else if (strstr(bufp,"lock") || strstr(bufp,"Lock") || strstr(bufp,"LOCK") || strstr(bufp,"wait") /* these are blessed by RFC 2449 */ || strstr(bufp,"[IN-USE]")||strstr(bufp,"[LOGIN-DELAY]")) ok = PS_LOCKBUSY; else if ((strstr(bufp,"Service") || strstr(bufp,"service")) && (strstr(bufp,"unavailable"))) ok = PS_SERVBUSY; else ok = PS_AUTHFAIL; /* * We always want to pass the user lock-busy messages, because * they're red flags. Other stuff (like AUTH failures on non- * RFC1734 servers) only if we're debugging. */ if (*bufp && (ok == PS_LOCKBUSY || outlevel >= O_MONITOR)) report(stderr, "%s\n", bufp); } else ok = PS_PROTOCOL; #if POPBUFSIZE > MSGBUFSIZE #error "POPBUFSIZE must not be larger than MSGBUFSIZE" #endif if (argbuf != NULL) strcpy(argbuf,bufp); } return(ok); } static int capa_probe(int sock) /* probe the capabilities of the remote server */ { int ok; if (done_capa) { return PS_SUCCESS; } #if defined(GSSAPI) has_gssapi = FALSE; #endif /* defined(GSSAPI) */ #if defined(KERBEROS_V4) || defined(KERBEROS_V5) has_kerberos = FALSE; #endif /* defined(KERBEROS_V4) || defined(KERBEROS_V5) */ has_cram = FALSE; #ifdef OPIE_ENABLE has_otp = FALSE; #endif /* OPIE_ENABLE */ ok = gen_transact(sock, "CAPA"); if (ok == PS_SUCCESS) { char buffer[64]; /* determine what authentication methods we have available */ while ((ok = gen_recv(sock, buffer, sizeof(buffer))) == 0) { if (DOTLINE(buffer)) break; #ifdef SSL_ENABLE if (strstr(buffer, "STLS")) has_stls = TRUE; #endif /* SSL_ENABLE */ #if defined(GSSAPI) if (strstr(buffer, "GSSAPI")) has_gssapi = TRUE; #endif /* defined(GSSAPI) */ #if defined(KERBEROS_V4) if (strstr(buffer, "KERBEROS_V4")) has_kerberos = TRUE; #endif /* defined(KERBEROS_V4) */ #ifdef OPIE_ENABLE if (strstr(buffer, "X-OTP")) has_otp = TRUE; #endif /* OPIE_ENABLE */ if (strstr(buffer, "CRAM-MD5")) has_cram = TRUE; } } done_capa = TRUE; return(ok); } static void set_peek_capable(struct query *ctl) { /* we're peek-capable means that the use of TOP is enabled, * see pop3_fetch for details - short story, we can use TOP if * we have a means of reliably tracking which mail we need to * refetch should the connection abort in the middle. * fetchall forces RETR, as does keep without UIDL */ peek_capable = !ctl->fetchall && (!ctl->keep || ctl->server.uidl); } static int pop3_getauth(int sock, struct query *ctl, char *greeting) /* apply for connection authorization */ { int ok; char *start,*end; char *msg; #ifdef OPIE_ENABLE char *challenge; #endif /* OPIE_ENABLE */ #ifdef SSL_ENABLE flag connection_may_have_tls_errors = FALSE; flag got_tls = FALSE; #endif /* SSL_ENABLE */ done_capa = FALSE; #if defined(GSSAPI) has_gssapi = FALSE; #endif /* defined(GSSAPI) */ #if defined(KERBEROS_V4) || defined(KERBEROS_V5) has_kerberos = FALSE; #endif /* defined(KERBEROS_V4) || defined(KERBEROS_V5) */ has_cram = FALSE; #ifdef OPIE_ENABLE has_otp = FALSE; #endif /* OPIE_ENABLE */ #ifdef SSL_ENABLE has_stls = FALSE; #endif /* SSL_ENABLE */ /* Set this up before authentication quits early. */ set_peek_capable(ctl); /* Hack: allow user to force RETR. */ if (peek_capable && getenv("FETCHMAIL_POP3_FORCE_RETR")) { peek_capable = 0; } /* * The "Maillennium POP3/PROXY server" deliberately truncates * TOP replies after c. 64 or 80 kByte (we have varying reports), so * disable TOP. Comcast once spewed marketing babble to the extent * of protecting Outlook -- pretty overzealous to break a protocol * for that that Microsoft could have read, too. Comcast aren't * alone in using this software though. * <http://lists.ccil.org/pipermail/fetchmail-friends/2004-April/008523.html> * (Thanks to Ed Wilts for reminding me of that.) * * The warning is printed once per server, until fetchmail exits. * It will be suppressed when --fetchall or other circumstances make * us use RETR anyhow. * * Matthias Andree */ if (peek_capable && strstr(greeting, "Maillennium POP3/PROXY server")) { if ((ctl->server.workarounds & WKA_TOP) == 0) { report(stdout, GT_("Warning: \"Maillennium POP3/PROXY server\" found, using RETR command instead of TOP.\n")); ctl->server.workarounds |= WKA_TOP; } peek_capable = 0; } if (ctl->server.authenticate == A_SSH) { return PS_SUCCESS; } #ifdef SDPS_ENABLE /* * This needs to catch both demon.co.uk and demon.net. * If we see either, and we're in multidrop mode, try to use * the SDPS *ENV extension. */ if (!(ctl->server.sdps) && MULTIDROP(ctl) && strstr(greeting, "demon.")) ctl->server.sdps = TRUE; #endif /* SDPS_ENABLE */ #ifdef NTLM_ENABLE /* MSN servers require the use of NTLM (MSN) authentication */ if (!strcasecmp(ctl->server.pollname, "pop3.email.msn.com") || ctl->server.authenticate == A_MSN) return (do_pop3_ntlm(sock, ctl, 1) == 0) ? PS_SUCCESS : PS_AUTHFAIL; if (ctl->server.authenticate == A_NTLM) return (do_pop3_ntlm(sock, ctl, 0) == 0) ? PS_SUCCESS : PS_AUTHFAIL; #else if (ctl->server.authenticate == A_NTLM || ctl->server.authenticate == A_MSN) { report(stderr, GT_("Required NTLM capability not compiled into fetchmail\n")); } #endif switch (ctl->server.protocol) { case P_POP3: #ifdef RPA_ENABLE /* XXX FIXME: AUTH probing (RFC1734) should become global */ /* CompuServe POP3 Servers as of 990730 want AUTH first for RPA */ if (strstr(ctl->remotename, "@compuserve.com")) { /* AUTH command should return a list of available mechanisms */ if (gen_transact(sock, "AUTH") == 0) { char buffer[10]; flag has_rpa = FALSE; while ((ok = gen_recv(sock, buffer, sizeof(buffer))) == 0) { if (DOTLINE(buffer)) break; if (strncasecmp(buffer, "rpa", 3) == 0) has_rpa = TRUE; } if (has_rpa && !POP3_auth_rpa(ctl->remotename, ctl->password, sock)) return(PS_SUCCESS); } return(PS_AUTHFAIL); } #endif /* RPA_ENABLE */ /* * CAPA command may return a list including available * authentication mechanisms and STLS capability. * * If it doesn't, no harm done, we just fall back to a plain * login -- if the user allows it. * * Note that this code latches the server's authentication type, * so that in daemon mode the CAPA check only needs to be done * once at start of run. * * If CAPA fails, then force the authentication method to * PASSWORD, switch off opportunistic and repoll immediately. * If TLS is mandatory, fail up front. */ if ((ctl->server.authenticate == A_ANY) || (ctl->server.authenticate == A_GSSAPI) || (ctl->server.authenticate == A_KERBEROS_V4) || (ctl->server.authenticate == A_KERBEROS_V5) || (ctl->server.authenticate == A_OTP) || (ctl->server.authenticate == A_CRAM_MD5) || maybe_tls(ctl)) { if ((ok = capa_probe(sock)) != PS_SUCCESS) /* we are in STAGE_GETAUTH => failure is PS_AUTHFAIL! */ if (ok == PS_AUTHFAIL || /* Some servers directly close the socket. However, if we * have already authenticated before, then a previous CAPA * must have succeeded. In that case, treat this as a * genuine socket error and do not change the auth method. */ (ok == PS_SOCKET && !ctl->wehaveauthed)) { #ifdef SSL_ENABLE if (must_tls(ctl)) { /* fail with mandatory STLS without repoll */ report(stderr, GT_("TLS is mandatory for this session, but server refused CAPA command.\n")); report(stderr, GT_("The CAPA command is however necessary for TLS.\n")); return ok; } else if (maybe_tls(ctl)) { /* defeat opportunistic STLS */ xfree(ctl->sslproto); ctl->sslproto = xstrdup(""); } #endif /* If strong authentication was opportunistic, retry without, else fail. */ switch (ctl->server.authenticate) { case A_ANY: ctl->server.authenticate = A_PASSWORD; /* FALLTHROUGH */ case A_PASSWORD: /* this should only happen with TLS enabled */ return PS_REPOLL; default: return PS_AUTHFAIL; } } } #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 (has_stls) { /* 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, "STLS") == 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 = TRUE; done_capa = FALSE; ok = capa_probe(sock); if (ok != PS_SUCCESS) { return ok; } 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 { /* We don't know whether the connection is usable, and there's * no command we can reasonably issue to test it (NOOP isn't * allowed til post-authentication), so leave it in an unknown * state, mark it as such, and check more carefully if things * go wrong when we try to authenticate. */ connection_may_have_tls_errors = TRUE; if (outlevel >= O_VERBOSE) { report(stdout, GT_("%s: opportunistic upgrade to TLS failed, trying to continue.\n"), commonname); } } } } /* maybe_tls() */ #endif /* SSL_ENABLE */ /* * OK, we have an authentication type now. */ #if defined(KERBEROS_V4) /* * Servers doing KPOP have to go through a dummy login sequence * rather than doing SASL. */ if (has_kerberos && ctl->server.service && (strcmp(ctl->server.service, KPOP_PORT)!=0) && (ctl->server.authenticate == A_KERBEROS_V4 || ctl->server.authenticate == A_KERBEROS_V5 || ctl->server.authenticate == A_ANY)) { ok = do_rfc1731(sock, "AUTH", ctl->server.truename); if (ok == PS_SUCCESS || ctl->server.authenticate != A_ANY) break; } #endif /* defined(KERBEROS_V4) || defined(KERBEROS_V5) */ #if defined(GSSAPI) if (has_gssapi && (ctl->server.authenticate == A_GSSAPI || ctl->server.authenticate == A_ANY)) { ok = do_gssauth(sock,"AUTH","pop",ctl->server.truename,ctl->remotename); if (ok == PS_SUCCESS || ctl->server.authenticate != A_ANY) break; } #endif /* defined(GSSAPI) */ #ifdef OPIE_ENABLE if (has_otp && (ctl->server.authenticate == A_OTP || ctl->server.authenticate == A_ANY)) { ok = do_otp(sock, "AUTH", ctl); if (ok == PS_SUCCESS || ctl->server.authenticate != A_ANY) break; } #endif /* OPIE_ENABLE */ if (ctl->server.authenticate == A_CRAM_MD5 || (has_cram && ctl->server.authenticate == A_ANY)) { ok = do_cram_md5(sock, "AUTH", ctl, NULL); if (ok == PS_SUCCESS || ctl->server.authenticate != A_ANY) break; } /* ordinary validation, no one-time password or RPA */ if ((ok = gen_transact(sock, "USER %s", ctl->remotename))) break; #ifdef OPIE_ENABLE /* see RFC1938: A One-Time Password System */ if ((challenge = strstr(lastok, "otp-"))) { char response[OPIE_RESPONSE_MAX+1]; int i; char *n = xstrdup(""); i = opiegenerator(challenge, !strcmp(ctl->password, "opie") ? n : ctl->password, response); free(n); if ((i == -2) && !run.poll_interval) { char secret[OPIE_SECRET_MAX+1]; fprintf(stderr, GT_("Secret pass phrase: ")); if (opiereadpass(secret, sizeof(secret), 0)) i = opiegenerator(challenge, secret, response); memset(secret, 0, sizeof(secret)); }; if (i) { ok = PS_ERROR; break; }; ok = gen_transact(sock, "PASS %s", response); break; } #endif /* OPIE_ENABLE */ /* KPOP uses out-of-band authentication and does not check what * we send here, so send some random fixed string, to avoid * users switching *to* KPOP accidentally revealing their * password */ if ((ctl->server.authenticate == A_ANY || ctl->server.authenticate == A_KERBEROS_V4 || ctl->server.authenticate == A_KERBEROS_V5) && (ctl->server.service != NULL && strcmp(ctl->server.service, KPOP_PORT) == 0)) { ok = gen_transact(sock, "PASS krb_ticket"); break; } /* check if we are actually allowed to send the password */ if (ctl->server.authenticate == A_ANY || ctl->server.authenticate == A_PASSWORD) { strlcpy(shroud, ctl->password, sizeof(shroud)); ok = gen_transact(sock, "PASS %s", ctl->password); } else { report(stderr, GT_("We've run out of allowed authenticators and cannot continue.\n")); ok = PS_AUTHFAIL; } memset(shroud, 0x55, sizeof(shroud)); shroud[0] = '\0'; break; case P_APOP: /* build MD5 digest from greeting timestamp + password */ /* find start of timestamp */ for (start = greeting; *start != 0 && *start != '<'; start++) continue; if (*start == 0) { report(stderr, GT_("Required APOP timestamp not found in greeting\n")); return(PS_AUTHFAIL); } /* find end of timestamp */ for (end = start; *end != 0 && *end != '>'; end++) continue; if (*end == 0 || end == start + 1) { report(stderr, GT_("Timestamp syntax error in greeting\n")); return(PS_AUTHFAIL); } else *++end = '\0'; /* SECURITY: 2007-03-17 * Strictly validating the presented challenge for RFC-822 * conformity (it must be a msg-id in terms of that standard) is * supposed to make attacks against the MD5 implementation * harder[1] * * [1] "Security vulnerability in APOP authentication", * Gaëtan Leurent, fetchmail-devel, 2007-03-17 */ if (!rfc822_valid_msgid((unsigned char *)start)) { report(stderr, GT_("Invalid APOP timestamp.\n")); return PS_AUTHFAIL; } /* copy timestamp and password into digestion buffer */ msg = (char *)xmalloc((end-start+1) + strlen(ctl->password) + 1); strcpy(msg,start); strcat(msg,ctl->password); strcpy((char *)ctl->digest, (char *)MD5Digest((unsigned char *)msg)); free(msg); ok = gen_transact(sock, "APOP %s %s", ctl->remotename, ctl->digest); break; case P_RPOP: if ((ok = gen_transact(sock,"USER %s", ctl->remotename)) == 0) { strlcpy(shroud, ctl->password, sizeof(shroud)); ok = gen_transact(sock, "RPOP %s", ctl->password); memset(shroud, 0x55, sizeof(shroud)); shroud[0] = '\0'; } break; default: report(stderr, GT_("Undefined protocol request in POP3_auth\n")); ok = PS_ERROR; } #ifdef SSL_ENABLE /* this is for servers which claim to support TLS, but actually * don't! */ if (connection_may_have_tls_errors && (ok == PS_SOCKET || ok == PS_PROTOCOL)) { xfree(ctl->sslproto); ctl->sslproto = xstrdup(""); /* repoll immediately without TLS */ ok = PS_REPOLL; } #endif if (ok != 0) { /* maybe we detected a lock-busy condition? */ if (ok == PS_LOCKBUSY) report(stderr, GT_("lock busy! Is another session active?\n")); return(ok); } /* Disable the sleep. Based on patch by Brian Candler 2004-04-19/2004-11-08, * accepted by Matthias Andree. * * Rationale: the server must have locked the spool before returning +OK; * this sleep just wastes time and hence, for modem and GSM CSD users, money. */ #ifdef WANT_BOGUS /* * Empirical experience shows some server/OS combinations * may need a brief pause even after any lockfiles on the * server are released, to give the server time to finish * copying back very large mailfolders from the temp-file... * this is only ever an issue with extremely large mailboxes. */ sleep(3); /* to be _really_ safe, probably need sleep(5)! */ #endif /* we're approved */ return(PS_SUCCESS); } /* cut off C string at first POSIX space */ static void trim(char *s) { s += strcspn(s, POSIX_space); s[0] = '\0'; } /* XXX FIXME: using the Message-ID is unsafe, some messages (spam, * broken messages) do not have Message-ID headers, and messages without * those appear to break this code and cause fetchmail (at least version * 6.2.3) to not delete such messages properly after retrieval. * See Sourceforge Bug #780933. * * The other problem is that the TOP command itself is optional, too... */ static int pop3_gettopid(int sock, int num , char *id, size_t idsize) { int ok; int got_it; char buf [POPBUFSIZE+1]; snprintf(buf, sizeof(buf), "TOP %d 1", num); if ((ok = gen_transact(sock, "%s", buf)) != 0) return ok; got_it = 0; while (gen_recv(sock, buf, sizeof(buf)) == 0) { if (DOTLINE(buf)) break; if (!got_it && 0 == strncasecmp("Message-Id:", buf, 11)) { char *p = buf + 11; got_it = 1; p += strspn(p, POSIX_space); strlcpy(id, p, idsize); trim(id); } } /* XXX FIXME: do not return success here if no Message-ID header was * found. */ return 0; } /** Parse the UID response (leading +OK must have been * stripped off) in buf, store the number in gotnum, and store the ID * into the caller-provided buffer "id" of size "idsize". * Returns PS_SUCCESS or PS_PROTOCOL for failure. */ static int parseuid(const char *buf, unsigned long *gotnum, char *id, size_t idsize) { const char *i; char *j; /* skip leading blanks ourselves */ i = buf; i += strspn(i, POSIX_space); errno = 0; *gotnum = strtoul(i, &j, 10); if (j == i || !*j || errno || NULL == strchr(POSIX_space, *j)) { report(stderr, GT_("Cannot handle UIDL response from upstream server.\n")); return PS_PROTOCOL; } j += strspn(j, POSIX_space); strlcpy(id, j, idsize); trim(id); return PS_SUCCESS; } /** request UIDL for single message \a num and stuff the result into the * buffer \a id which can hold \a idsize bytes */ static int pop3_getuidl(int sock, int num, char *id /** output */, size_t idsize) { int ok; char buf [POPBUFSIZE+1]; unsigned long gotnum; gen_send(sock, "UIDL %d", num); if ((ok = pop3_ok(sock, buf)) != 0) return(ok); if ((ok = parseuid(buf, &gotnum, id, idsize))) return ok; if (gotnum != (unsigned long)num) { report(stderr, GT_("Server responded with UID for wrong message.\n")); return PS_PROTOCOL; } return(PS_SUCCESS); } static int pop3_fastuidl( int sock, struct query *ctl, unsigned int count, int *newp) { int ok; unsigned int first_nr, last_nr, try_nr; char id [IDLEN+1]; first_nr = 0; last_nr = count + 1; while (first_nr < last_nr - 1) { struct idlist *newl; try_nr = (first_nr + last_nr) / 2; if ((ok = pop3_getuidl(sock, try_nr, id, sizeof(id))) != 0) return ok; if ((newl = str_in_list(&ctl->oldsaved, id, FALSE))) { flag mark = newl->val.status.mark; if (mark == UID_DELETED || mark == UID_EXPUNGED) { if (outlevel >= O_VERBOSE) report(stderr, GT_("id=%s (num=%d) was deleted, but is still present!\n"), id, try_nr); /* just mark it as seen now! */ newl->val.status.mark = mark = UID_SEEN; } /* narrow the search region! */ if (mark == UID_UNSEEN) { if (outlevel >= O_DEBUG) report(stdout, GT_("%u is unseen\n"), try_nr); last_nr = try_nr; } else first_nr = try_nr; /* save the number */ newl->val.status.num = try_nr; } else { if (outlevel >= O_DEBUG) report(stdout, GT_("%u is unseen\n"), try_nr); last_nr = try_nr; /* save it */ newl = save_str(&ctl->oldsaved, id, UID_UNSEEN); newl->val.status.num = try_nr; } } if (outlevel >= O_DEBUG && last_nr <= count) report(stdout, GT_("%u is first unseen\n"), last_nr); /* update last! */ *newp = count - first_nr; last = first_nr; return 0; } static int pop3_slowuidl( int sock, struct query *ctl, int *countp, int *newp) { /* XXX FIXME: this code is severely broken. A Cc:d mailing list * message will arrive twice with the same Message-ID, so this * slowuidl code will break. Same goes for messages without * Message-ID headers at all. This code would best be removed. */ /* This approach tries to get the message headers from the * remote hosts and compares the message-id to the already known * ones: * + if the first message containes a new id, all messages on * the server will be new * + if the first is known, try to estimate the last known message * on the server and check. If this works you know the total number * of messages to get. * + Otherwise run a binary search to determine the last known message */ int ok, nolinear = 0; int first_nr, list_len, try_id, try_nr, add_id; int num; char id [IDLEN+1]; if ((ok = pop3_gettopid(sock, 1, id, sizeof(id))) != 0) return ok; if( ( first_nr = str_nr_in_list(&ctl->oldsaved, id) ) == -1 ) { /* the first message is unknown -> all messages are new */ *newp = *countp; return 0; } /* check where we expect the latest known message */ list_len = count_list( &ctl->oldsaved ); try_id = list_len - first_nr; /* -1 + 1 */ if( try_id > 1 ) { if( try_id <= *countp ) { if ((ok = pop3_gettopid(sock, try_id, id, sizeof(id))) != 0) return ok; try_nr = str_nr_last_in_list(&ctl->oldsaved, id); } else { try_id = *countp+1; try_nr = -1; } if( try_nr != list_len -1 ) { /* some messages inbetween have been deleted... */ if( try_nr == -1 ) { nolinear = 1; for( add_id = 1<<30; add_id > try_id-1; add_id >>= 1 ) ; for( ; add_id; add_id >>= 1 ) { if( try_nr == -1 ) { if( try_id - add_id <= 1 ) { continue; } try_id -= add_id; } else try_id += add_id; if ((ok = pop3_gettopid(sock, try_id, id, sizeof(id))) != 0) return ok; try_nr = str_nr_in_list(&ctl->oldsaved, id); } if( try_nr == -1 ) { try_id--; } } else { report(stderr, GT_("Messages inserted into list on server. Cannot handle this.\n")); return -1; } } } /* the first try_id messages are known -> copy them to the newsaved list */ for( num = first_nr; num < list_len; num++ ) { struct idlist *newl = save_str(&ctl->newsaved, str_from_nr_list(&ctl->oldsaved, num), UID_UNSEEN); newl->val.status.num = num - first_nr + 1; } if( nolinear ) { free_str_list(&ctl->oldsaved); ctl->oldsaved = 0; last = try_id; } *newp = *countp - try_id; return 0; } static int pop3_getrange(int sock, struct query *ctl, const char *folder, int *countp, int *newp, int *bytes) /* get range of messages to be fetched */ { int ok; char buf [POPBUFSIZE+1]; (void)folder; /* Ensure that the new list is properly empty */ ctl->newsaved = (struct idlist *)NULL; #ifdef MBOX /* Alain Knaff suggests this, but it's not RFC standard */ if (folder) if ((ok = gen_transact(sock, "MBOX %s", folder))) return ok; #endif /* MBOX */ /* get the total message count */ gen_send(sock, "STAT"); ok = pop3_ok(sock, buf); if (ok == 0) sscanf(buf,"%d %d", countp, bytes); else return(ok); /* * Newer, RFC-1725/1939-conformant POP servers may not have the LAST * command. We work as hard as possible to hide this, but it makes * counting new messages intrinsically quadratic in the worst case. */ last = 0; *newp = -1; /* if there are messages, and UIDL is desired, use UIDL * also use UIDL if fetchall is unset */ if (*countp > 0 && (!ctl->fetchall || ctl->server.uidl)) { int fastuidl; char id [IDLEN+1]; /* should we do fast uidl this time? */ fastuidl = ctl->fastuidl; if (*countp > 7 && /* linear search is better if there are few mails! */ !ctl->fetchall && /* with fetchall, all uids are required */ !ctl->flush && /* with flush, it is safer to disable fastuidl */ NUM_NONZERO (fastuidl)) { if (fastuidl == 1) dofastuidl = 1; else dofastuidl = ctl->fastuidlcount != 0; } else dofastuidl = 0; if (!ctl->server.uidl) { gen_send(sock, "LAST"); ok = pop3_ok(sock, buf); } else ok = 1; if (ok == 0) { /* scan LAST reply */ if (sscanf(buf, "%d", &last) == 0) { report(stderr, GT_("protocol error\n")); return(PS_ERROR); } *newp = (*countp - last); } else { /* do UIDL */ if (dofastuidl) return(pop3_fastuidl( sock, ctl, *countp, newp)); /* grab the mailbox's UID list */ if (gen_transact(sock, "UIDL") != 0) { /* don't worry, yet! do it the slow way */ if (pop3_slowuidl(sock, ctl, countp, newp)) { report(stderr, GT_("protocol error while fetching UIDLs\n")); return(PS_ERROR); } } else { /* UIDL worked - parse reply */ unsigned long unum; *newp = 0; while (gen_recv(sock, buf, sizeof(buf)) == PS_SUCCESS) { if (DOTLINE(buf)) break; if (parseuid(buf, &unum, id, sizeof(id)) == PS_SUCCESS) { struct idlist *old, *newl; newl = save_str(&ctl->newsaved, id, UID_UNSEEN); newl->val.status.num = unum; if ((old = str_in_list(&ctl->oldsaved, id, FALSE))) { flag mark = old->val.status.mark; if (mark == UID_DELETED || mark == UID_EXPUNGED) { /* XXX FIXME: switch 3 occurrences from * (int)unum or (unsigned int)unum to * remove the cast and use %lu - not now * though, time for new release */ if (outlevel >= O_VERBOSE) report(stderr, GT_("id=%s (num=%d) was deleted, but is still present!\n"), id, (int)unum); /* just mark it as seen now! */ old->val.status.mark = mark = UID_SEEN; } newl->val.status.mark = mark; if (mark == UID_UNSEEN) { (*newp)++; if (outlevel >= O_DEBUG) report(stdout, GT_("%u is unseen\n"), (unsigned int)unum); } } else { (*newp)++; if (outlevel >= O_DEBUG) report(stdout, GT_("%u is unseen\n"), (unsigned int)unum); /* add it to oldsaved also! In case, we do not * swap the lists (say, due to socket error), * the same mail will not be downloaded again. */ old = save_str(&ctl->oldsaved, id, UID_UNSEEN); } /* save the number */ old->val.status.num = unum; } else return PS_ERROR; } /* multi-line loop for UIDL reply */ } /* UIDL parser */ } /* do UIDL */ } return(PS_SUCCESS); } static int pop3_getpartialsizes(int sock, int first, int last, int *sizes) /* capture the size of message #first */ { int ok = 0, i, num; char buf [POPBUFSIZE+1]; unsigned int size; for (i = first; i <= last; i++) { gen_send(sock, "LIST %d", i); if ((ok = pop3_ok(sock, buf)) != 0) return(ok); if (sscanf(buf, "%d %u", &num, &size) == 2) { if (num == i) sizes[i - first] = size; else /* warn about possible attempt to induce buffer overrun * * we expect server reply message number and requested * message number to match */ report(stderr, "Warning: ignoring bogus data for message sizes returned by server.\n"); } } return(ok); } static int pop3_getsizes(int sock, int count, int *sizes) /* capture the sizes of all messages */ { int ok; if ((ok = gen_transact(sock, "LIST")) != 0) return(ok); else { char buf [POPBUFSIZE+1]; while ((ok = gen_recv(sock, buf, sizeof(buf))) == 0) { unsigned int num, size; if (DOTLINE(buf)) break; else if (sscanf(buf, "%u %u", &num, &size) == 2) { if (num > 0 && num <= (unsigned)count) sizes[num - 1] = size; else /* warn about possible attempt to induce buffer overrun */ report(stderr, "Warning: ignoring bogus data for message sizes returned by server.\n"); } } return(ok); } } static int pop3_is_old(int sock, struct query *ctl, int num) /* is the given message old? */ { struct idlist *newl; if (!ctl->oldsaved) return (num <= last); else if (dofastuidl) { char id [IDLEN+1]; if (num <= last) return(TRUE); /* in fast uidl, we manipulate the old list only! */ if ((newl = id_find(&ctl->oldsaved, num))) { /* we already have the id! */ return(newl->val.status.mark != UID_UNSEEN); } /* get the uidl first! */ if (pop3_getuidl(sock, num, id, sizeof(id)) != PS_SUCCESS) return(TRUE); if ((newl = str_in_list(&ctl->oldsaved, id, FALSE))) { /* we already have the id! */ newl->val.status.num = num; return(newl->val.status.mark != UID_UNSEEN); } /* save it */ newl = save_str(&ctl->oldsaved, id, UID_UNSEEN); newl->val.status.num = num; return(FALSE); } else return ((newl = id_find(&ctl->newsaved, num)) != NULL && newl->val.status.mark != UID_UNSEEN); } #ifdef UNUSED /* * We could use this to fetch headers only as we do for IMAP. The trouble * is that there's no way to fetch the body only. So the following RETR * would have to re-fetch the header. Enough messages have longer headers * than bodies to make this a net loss. */ static int pop_fetch_headers(int sock, struct query *ctl,int number,int *lenp) /* request headers of nth message */ { int ok; char buf[POPBUFSIZE+1]; gen_send(sock, "TOP %d 0", number); if ((ok = pop3_ok(sock, buf)) != 0) return(ok); *lenp = -1; /* we got sizes from the LIST response */ return(PS_SUCCESS); } #endif /* UNUSED */ static int pop3_fetch(int sock, struct query *ctl, int number, int *lenp) /* request nth message */ { int ok; char buf[POPBUFSIZE+1]; #ifdef SDPS_ENABLE /* * See http://www.demon.net/helpdesk/producthelp/mail/sdps-tech.html/ * for a description of what we're parsing here. * -- updated 2006-02-22 */ if (ctl->server.sdps) { int linecount = 0; sdps_envfrom = (char *)NULL; sdps_envto = (char *)NULL; gen_send(sock, "*ENV %d", number); do { if (gen_recv(sock, buf, sizeof(buf))) { break; } linecount++; switch (linecount) { case 4: /* No need to wrap envelope from address */ /* FIXME: some parts of fetchmail don't handle null * envelope senders, so use <> to mark null sender * as a workaround. */ if (strspn(buf, " \t") == strlen(buf)) strcpy(buf, "<>"); sdps_envfrom = (char *)xmalloc(strlen(buf)+1); strcpy(sdps_envfrom,buf); break; case 5: /* Wrap address with To: <> so nxtaddr() likes it */ sdps_envto = (char *)xmalloc(strlen(buf)+7); sprintf(sdps_envto,"To: <%s>",buf); break; } } while (!(buf[0] == '.' && (buf[1] == '\r' || buf[1] == '\n' || buf[1] == '\0'))); } #else (void)ctl; #endif /* SDPS_ENABLE */ /* * Though the POP RFCs don't document this fact, on almost every * POP3 server I know of messages are marked "seen" only at the * time the OK response to a RETR is issued. * * This means we can use TOP to 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. (Exception: in early 1999 SpryNet's POP3 servers were * reported to mark messages seen on a TOP fetch.) * * 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. * * Also use RETR (that means no TOP, no peek) if fetchall is on. * This gives us a workaround for servers like usa.net's that bungle * TOP. It's pretty harmless because fetchall guarantees that any * message dropped by an interrupted RETR will be picked up on the * next poll of the site. * * We take advantage here of the fact that, according to all the * POP RFCs, "if the number of lines requested by the POP3 client * is greater than than the number of lines in the body, then the * POP3 server sends the entire message."). * * The line count passed (99999999) is the maximum value CompuServe will * accept; it's much lower than the natural value 2147483646 (the maximum * twos-complement signed 32-bit integer minus 1) */ if (!peek_capable) gen_send(sock, "RETR %d", number); else gen_send(sock, "TOP %d 99999999", number); if ((ok = pop3_ok(sock, buf)) != 0) return(ok); *lenp = -1; /* we got sizes from the LIST response */ return(PS_SUCCESS); } static void mark_uid_seen(struct query *ctl, int number) /* Tell the UID code we've seen this. */ { struct idlist *sdp; if ((sdp = id_find(&ctl->newsaved, number))) sdp->val.status.mark = UID_SEEN; /* mark it as seen in oldsaved also! In case, we do not swap the lists * (say, due to socket error), the same mail will not be downloaded * again. */ if ((sdp = id_find(&ctl->oldsaved, number))) sdp->val.status.mark = UID_SEEN; } static int pop3_delete(int sock, struct query *ctl, int number) /* delete a given message */ { int ok; mark_uid_seen(ctl, number); /* actually, mark for deletion -- doesn't happen until QUIT time */ ok = gen_transact(sock, "DELE %d", number); if (ok != PS_SUCCESS) return(ok); delete_str(dofastuidl ? &ctl->oldsaved : &ctl->newsaved, number); return(PS_SUCCESS); } static int pop3_mark_seen(int sock, struct query *ctl, int number) /* mark a given message as seen */ { (void)sock; mark_uid_seen(ctl, number); return(PS_SUCCESS); } static int pop3_logout(int sock, struct query *ctl) /* send logout command */ { int ok; #ifdef __UNUSED__ /* * We used to do this in case the server marks messages deleted when seen. * (Yes, this has been reported, in the MercuryP/NLM server. * It's even legal under RFC 1939 (section 8) as a site policy.) * It interacted badly with UIDL, though. Thomas Zajic wrote: * "Running 'fetchmail -F -v' and checking the logs, I found out * that fetchmail did in fact flush my mailbox properly, but sent * a RSET just before sending QUIT to log off. This caused the * POP3 server to undo/forget about the previous DELEs, resetting * my mailbox to its original (ie. unflushed) state. The * ~/.fetchids file did get flushed though, so the next time * fetchmail was run it saw all the old messages as new ones ..." */ if (ctl->keep) gen_transact(sock, "RSET"); #endif /* __UNUSED__ */ ok = gen_transact(sock, "QUIT"); if (!ok) expunge_uids(ctl); return(ok); } static const struct method pop3 = { "POP3", /* Post Office Protocol v3 */ "pop3", /* port for plain and TLS POP3 */ "pop3s", /* port for SSL POP3 */ FALSE, /* this is not a tagged protocol */ TRUE, /* this uses a message delimiter */ pop3_ok, /* parse command response */ pop3_getauth, /* get authorization */ pop3_getrange, /* query range of messages */ pop3_getsizes, /* we can get a list of sizes */ pop3_getpartialsizes, /* we can get the size of 1 mail */ pop3_is_old, /* how do we tell a message is old? */ pop3_fetch, /* request given message */ NULL, /* no way to fetch body alone */ NULL, /* no message trailer */ pop3_delete, /* how to delete a message */ pop3_mark_seen, /* how to mark a message as seen */ NULL, /* no action at end of mailbox */ pop3_logout, /* log out, we're done */ FALSE, /* no, we can't re-poll */ }; int doPOP3 (struct query *ctl) /* retrieve messages using POP3 */ { #ifndef MBOX if (ctl->mailboxes->id) { fprintf(stderr,GT_("Option --folder is not supported with POP3\n")); return(PS_SYNTAX); } #endif /* MBOX */ return(do_protocol(ctl, &pop3)); } #endif /* POP3_ENABLE */ /* pop3.c ends here */