aboutsummaryrefslogtreecommitdiffstats
path: root/rfc822.c
blob: 4fec994178f518ac8f4e31c1d50c9318ad011a24 (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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/*
 * rfc822.c -- code for slicing and dicing RFC822 mail headers
 *
 * Copyright 1997 by Eric S. Raymond
 * For license terms, see the file COPYING in this directory.
 */

#include  <stdio.h>
#include  <ctype.h>
#include  <string.h>
#if defined(STDC_HEADERS)
#include  <stdlib.h>
#endif

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

#define HEADER_END(p)	((p)[0] == '\n' && ((p)[1] != ' ' && (p)[1] != '\t'))

#ifdef TESTMAIN
static int verbose;
char *program_name = "rfc822";
#endif /* TESTMAIN */

unsigned char *reply_hack(buf, host)
/* hack message headers so replies will work properly */
unsigned char *buf;		/* header to be hacked */
const unsigned char *host;	/* server hostname */
{
    unsigned char *from, *cp, last_nws = '\0', *parens_from = NULL;
    int parendepth, state, has_bare_name_part, has_host_part;
#ifndef TESTMAIN
    int addresscount = 1;
#endif /* TESTMAIN */

    if (strncasecmp("From:", buf, 5)
	&& strncasecmp("To:", buf, 3)
	&& strncasecmp("Reply-To:", buf, 9)
	&& strncasecmp("Return-Path:", buf, 12)
	&& strncasecmp("Cc:", buf, 3)
	&& strncasecmp("Bcc:", buf, 4)
	&& strncasecmp("Resent-From:", buf, 12)
	&& strncasecmp("Resent-To:", buf, 10)
	&& strncasecmp("Resent-Cc:", buf, 10)
	&& strncasecmp("Resent-Bcc:", buf, 11)
	&& strncasecmp("Apparently-From:", buf, 16)
	&& strncasecmp("Apparently-To:", buf, 14)
	&& strncasecmp("Sender:", buf, 7)
	&& strncasecmp("Resent-Sender:", buf, 14)
       ) {
	return(buf);
    }

#ifndef TESTMAIN
    if (outlevel >= O_DEBUG)
	report_build(stdout, _("About to rewrite %s"), buf);

    /* make room to hack the address; buf must be malloced */
    for (cp = buf; *cp; cp++)
	if (*cp == ',' || isspace(*cp))
	    addresscount++;
    buf = (unsigned char *)xrealloc(buf, strlen(buf) + addresscount * strlen(host) + 1);
#endif /* TESTMAIN */

    /*
     * This is going to foo up on some ill-formed addresses.
     * Note that we don't rewrite the fake address <> in order to
     * avoid screwing up bounce suppression with a null Return-Path.
     */

    parendepth = state = 0;
    has_host_part = has_bare_name_part = FALSE;
    for (from = buf; *from; from++)
    {
#ifdef TESTMAIN
	if (verbose)
	{
	    printf("state %d: %s", state, buf);
	    printf("%*s^\n", from - buf + 10, " ");
	}
#endif /* TESTMAIN */
	if (state != 2)
	{
	    if (*from == '(')
		++parendepth;
	    else if (*from == ')')
		--parendepth;
	}

	if (!parendepth && !has_host_part)
	    switch (state)
	    {
	    case 0:	/* before header colon */
		if (*from == ':')
		    state = 1;
		break;

	    case 1:	/* we've seen the colon, we're looking for addresses */
		if (!isspace(*from))
		    last_nws = *from;
		if (*from == '<')
		    state = 3;
		else if (*from == '@')
		    has_host_part = TRUE;
		else if (*from == '"')
		    state = 2;
		/*
		 * Not expanding on last non-WS == ';' deals with groupnames,
		 * an obscure misfeature described in sections
		 * 6.1, 6.2.6, and A.1.5 of the RFC822 standard.
		 */
		else if ((*from == ',' || HEADER_END(from))
			 && has_bare_name_part
			 && !has_host_part
			 && last_nws != ';')
		{
		    int hostlen;
		    unsigned char *p;

		    p = from;
		    if (parens_from)
			from = parens_from;
		    while (isspace(*from) || (*from == ','))
			--from;
		    from++;
		    hostlen = strlen(host);
		    for (cp = from + strlen(from); cp >= from; --cp)
			cp[hostlen+1] = *cp;
		    *from++ = '@';
		    memcpy(from, host, hostlen);
		    from = p + hostlen + 1;
		    has_host_part = TRUE;
		} 
		else if (from[1] == '('
			 && has_bare_name_part
			 && !has_host_part
			 && last_nws != ';' && last_nws != ')')
		{
		    parens_from = from;
		} 
		else if (!isspace(*from))
		    has_bare_name_part = TRUE;
		break;

	    case 2:	/* we're in a string */
		if (*from == '"')
		{
		    char	*bp;
		    int		bscount;

		    bscount = 0;
		    for (bp = from - 1; *bp == '\\'; bp--)
			bscount++;
		    if (!(bscount % 2))
			state = 1;
		}
		break;

	    case 3:	/* we're in a <>-enclosed address */
		if (*from == '@')
		    has_host_part = TRUE;
		else if (*from == '>' && from[-1] != '<')
		{
		    state = 1;
		    if (!has_host_part)
		    {
			int hostlen;

			hostlen = strlen(host);
			for (cp = from + strlen(from); cp >= from; --cp)
			    cp[hostlen+1] = *cp;
			*from++ = '@';
			memcpy(from, host, hostlen);
			from += hostlen;
			has_host_part = TRUE;
		    }
		}
		break;
	    }

	/*
	 * If we passed a comma, reset everything.
	 */
	if (from[-1] == ',' && !parendepth) {
	  has_host_part = has_bare_name_part = FALSE;
	  parens_from = NULL;
	}
    }

#ifndef TESTMAIN
    if (outlevel >= O_DEBUG)
	report_complete(stdout, _("Rewritten version is %s\n"), buf);
#endif /* TESTMAIN */
    return(buf);
}

unsigned char *nxtaddr(hdr)
/* parse addresses in succession out of a specified RFC822 header */
const unsigned char *hdr;	/* header to be parsed, NUL to continue previous hdr */
{
    static unsigned char address[POPBUFSIZE+1];
    static int tp;
    static const unsigned char *hp;
    static int	state, oldstate;
#ifdef TESTMAIN
    static const unsigned char *orighdr;
#endif /* TESTMAIN */
    int parendepth = 0;

#define START_HDR	0	/* before header colon */
#define SKIP_JUNK	1	/* skip whitespace, \n, and junk */
#define BARE_ADDRESS	2	/* collecting address without delimiters */
#define INSIDE_DQUOTE	3	/* inside double quotes */
#define INSIDE_PARENS	4	/* inside parentheses */
#define INSIDE_BRACKETS	5	/* inside bracketed address */
#define ENDIT_ALL	6	/* after last address */

#define NEXTTP()	((tp < sizeof(address)-1) ? tp++ : tp)

    if (hdr)
    {
	hp = hdr;
	state = START_HDR;
#ifdef TESTMAIN
	orighdr = hdr;
#endif /* TESTMAIN */
	tp = 0;
    }

    for (; *hp; hp++)
    {
#ifdef TESTMAIN
	if (verbose)
	{
	    printf("state %d: %s", state, orighdr);
	    printf("%*s^\n", hp - orighdr + 10, " ");
	}
#endif /* TESTMAIN */

	if (state == ENDIT_ALL)		/* after last address */
	    return(NULL);
	else if (HEADER_END(hp))
	{
	    state = ENDIT_ALL;
	    if (tp)
	    {
		while (isspace(address[--tp]))
		    continue;
		address[++tp] = '\0';
		tp = 0;
		return (address);
	    }
	    return((unsigned char *)NULL);
	}
	else if (*hp == '\\')		/* handle RFC822 escaping */
	{
	    if (state != INSIDE_PARENS)
	    {
		address[NEXTTP()] = *hp++;	/* take the escape */
		address[NEXTTP()] = *hp;	/* take following unsigned char */
	    }
	}
	else switch (state)
	{
	case START_HDR:   /* before header colon */
	    if (*hp == ':')
		state = SKIP_JUNK;
	    break;

	case SKIP_JUNK:		/* looking for address start */
	    if (*hp == '"')	/* quoted string */
	    {
		oldstate = SKIP_JUNK;
	        state = INSIDE_DQUOTE;
		address[NEXTTP()] = *hp;
	    }
	    else if (*hp == '(')	/* address comment -- ignore */
	    {
		parendepth = 1;
		oldstate = SKIP_JUNK;
		state = INSIDE_PARENS;    
	    }
	    else if (*hp == '<')	/* begin <address> */
	    {
		state = INSIDE_BRACKETS;
		tp = 0;
	    }
	    else if (*hp != ',' && !isspace(*hp))
	    {
		--hp;
	        state = BARE_ADDRESS;
	    }
	    break;

	case BARE_ADDRESS:   	/* collecting address without delimiters */
	    if (*hp == ',')  	/* end of address */
	    {
		if (tp)
		{
		    address[NEXTTP()] = '\0';
		    state = SKIP_JUNK;
		    tp = 0;
		    return(address);
		}
	    }
	    else if (*hp == '(')  	/* beginning of comment */
	    {
		parendepth = 1;
		oldstate = BARE_ADDRESS;
		state = INSIDE_PARENS;    
	    }
	    else if (*hp == '<')  	/* beginning of real address */
	    {
		state = INSIDE_BRACKETS;
		tp = 0;
	    }
	    else if (!isspace(*hp)) 	/* just take it, ignoring whitespace */
		address[NEXTTP()] = *hp;
	    break;

	case INSIDE_DQUOTE:	/* we're in a quoted string, copy verbatim */
	    if (*hp != '"')
	        address[NEXTTP()] = *hp;
	    else
	    {
	        address[NEXTTP()] = *hp;
		state = oldstate;
	    }
	    break;

	case INSIDE_PARENS:	/* we're in a parenthesized comment, ignore */
	    if (*hp == '(')
		++parendepth;
	    else if (*hp == ')')
		--parendepth;
	    if (parendepth == 0)
		state = oldstate;
	    break;

	case INSIDE_BRACKETS:	/* possible <>-enclosed address */
	    if (*hp == '>')	/* end of address */
	    {
		address[NEXTTP()] = '\0';
		state = SKIP_JUNK;
		++hp;
		tp = 0;
		return(address);
	    }
	    else if (*hp == '<')	/* nested <> */
	        tp = 0;
	    else if (*hp == '"')	/* quoted address */
	    {
	        address[NEXTTP()] = *hp;
		oldstate = INSIDE_BRACKETS;
		state = INSIDE_DQUOTE;
	    }
	    else			/* just copy address */
		address[NEXTTP()] = *hp;
	    break;
	}
    }

    return(NULL);
}

#ifdef TESTMAIN
static void parsebuf(unsigned char *longbuf, int reply)
{
    unsigned char	*cp;

    if (reply)
    {
	reply_hack(longbuf, "HOSTNAME.NET");
	printf("Rewritten buffer: %s", longbuf);
    }
    else
	if ((cp = nxtaddr(longbuf)) != (unsigned char *)NULL)
	    do {
		printf("\t-> \"%s\"\n", cp);
	    } while
		((cp = nxtaddr((unsigned char *)NULL)) != (unsigned char *)NULL);
}



main(int argc, char *argv[])
{
    unsigned char	buf[MSGBUFSIZE], longbuf[BUFSIZ];
    int			ch, reply;
    
    verbose = reply = FALSE;
    while ((ch = getopt(argc, argv, "rv")) != EOF)
	switch(ch)
	{
	case 'r':
	    reply = TRUE;
	    break;

	case 'v':
	    verbose = TRUE;
	    break;
	}

    while (fgets(buf, sizeof(buf)-1, stdin))
    {
	if (buf[0] == ' ' || buf[0] == '\t')
	    strcat(longbuf, buf);
	else if (!strncasecmp("From: ", buf, 6)
		    || !strncasecmp("To: ", buf, 4)
		    || !strncasecmp("Reply-", buf, 6)
		    || !strncasecmp("Cc: ", buf, 4)
		    || !strncasecmp("Bcc: ", buf, 5))
	    strcpy(longbuf, buf);	
	else if (longbuf[0])
	{
	    if (verbose)
		fputs(longbuf, stdout);
	    parsebuf(longbuf, reply);
	    longbuf[0] = '\0';
	}
    }
    if (longbuf[0])
    {
	if (verbose)
	    fputs(longbuf, stdout);
	parsebuf(longbuf, reply);
    }
}
#endif /* TESTMAIN */

/* rfc822.c end */
e == A_NTLM) && strstr (capabilities, "AUTH=NTLM")) { if ((ok = do_imap_ntlm(sock, ctl))) { /* SASL cancellation of authentication */ gen_send(sock, "*"); if(ctl->server.authenticate != A_ANY) return ok; } else return(ok); } #else if (ctl->server.authenticate == A_NTLM) { report(stderr, _("Required NTLM capability not compiled into fetchmail\n")); } #endif /* NTLM_ENABLE */ #ifdef __UNUSED__ /* The Cyrus IMAP4rev1 server chokes on this */ /* this handles either AUTH=LOGIN or AUTH-LOGIN */ if ((imap_version >= IMAP4rev1) && (!strstr(capabilities, "LOGIN"))) { report(stderr, _("Required LOGIN capability not supported by server\n")); } #endif /* __UNUSED__ */ /* we're stuck with sending the password en clair */ if ((ctl->server.authenticate == A_ANY || ctl->server.authenticate == A_PASSWORD) && !strstr (capabilities, "LOGINDISABLED")) { /* these sizes guarantee no buffer overflow */ char remotename[NAMELEN*2+1], password[PASSWORDLEN*2+1]; imap_canonicalize(remotename, ctl->remotename, NAMELEN); imap_canonicalize(password, ctl->password, PASSWORDLEN); strcpy(shroud, ctl->password); ok = gen_transact(sock, "LOGIN \"%s\" \"%s\"", remotename, password); shroud[0] = '\0'; if (ok) { /* SASL cancellation of authentication */ gen_send(sock, "*"); if(ctl->server.authenticate != A_ANY) return ok; } else return(ok); } return(ok); } 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_idle(int sock) /* start an RFC2177 IDLE */ { stage = STAGE_IDLE; saved_timeout = mytimeout; mytimeout = 0; return (gen_transact(sock, "IDLE")); } 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; char buf[MSGBUFSIZE+1], *cp; /* find out how many messages are waiting */ *bytes = -1; if (pass > 1) { /* * We have to have an expunge here, otherwise the re-poll will * infinite-loop picking up un-expunged messages -- unless the * expunge period is one and we've been nuking each message * just after deletion. */ ok = 0; if (deletions && expunge_period != 1) ok = internal_expunge(sock); count = -1; if (do_idle) ok = imap_idle(sock); if (ok || gen_transact(sock, "NOOP")) { report(stderr, _("re-poll failed\n")); return(ok); } else if (count == -1) /* no EXISTS response to NOOP/IDLE */ { count = 0; } if (outlevel >= O_DEBUG) report(stdout, _("%d messages waiting after re-poll\n"), count); } else { ok = gen_transact(sock, check_only ? "EXAMINE \"%s\"" : "SELECT \"%s\"", folder ? folder : "INBOX"); if (ok != 0) { report(stderr, _("mailbox selection failed\n")); return(ok); } else if (outlevel >= O_DEBUG) report(stdout, _("%d messages waiting after first poll\n"), count); /* no messages? then we may need to idle until we get some */ if (count == 0 && do_idle) imap_idle(sock); } *countp = count; /* OK, now get a count of unseen messages and their indices */ if (!ctl->fetchall && count > 0) { if (unseen_messages) free(unseen_messages); unseen_messages = xmalloc(count * sizeof(unsigned int)); memset(unseen_messages, 0, count * sizeof(unsigned int)); unseen = 0; gen_send(sock, "SEARCH UNSEEN"); do { ok = gen_recv(sock, buf, sizeof(buf)); if (ok != 0) { report(stderr, _("search for unseen messages failed\n")); return(PS_PROTOCOL); } else if ((cp = strstr(buf, "* SEARCH"))) { char *ep; cp += 8; /* skip "* SEARCH" */ while (*cp && unseen < count) { /* skip whitespace */ while (*cp && isspace(*cp)) cp++; if (*cp) { /* * Message numbers are between 1 and 2^32 inclusive, * so unsigned int is large enough. */ unseen_messages[unseen]=(unsigned int)strtol(cp,&ep,10); if (outlevel >= O_DEBUG) report(stdout, _("%u is unseen\n"), unseen_messages[unseen]); unseen++; cp = ep; } } } } while (tag[0] != '\0' && strncmp(buf, tag, strlen(tag))); } else unseen = -1; *newp = unseen; 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. * * Microsoft Exchange (brain-dead piece of crap that it is) * sometimes gets its knickers in a knot about bodiless messages. * You may see responses like this: * * fetchmail: IMAP> A0004 FETCH 1:9 RFC822.SIZE * fetchmail: IMAP< * 2 FETCH (RFC822.SIZE 1187) * fetchmail: IMAP< * 3 FETCH (RFC822.SIZE 3954) * fetchmail: IMAP< * 4 FETCH (RFC822.SIZE 1944) * fetchmail: IMAP< * 5 FETCH (RFC822.SIZE 2933) * fetchmail: IMAP< * 6 FETCH (RFC822.SIZE 1854) * fetchmail: IMAP< * 7 FETCH (RFC822.SIZE 34054) * fetchmail: IMAP< * 8 FETCH (RFC822.SIZE 5561) * fetchmail: IMAP< * 9 FETCH (RFC822.SIZE 1101) * fetchmail: IMAP< A0004 NO The requested item could not be found. * * This means message 1 has only headers. For kicks and grins * you can telnet in and look: * A003 FETCH 1 FULL * A003 NO The requested item could not be found. * A004 fetch 1 rfc822.header * A004 NO The requested item could not be found. * A006 FETCH 1 BODY * * 1 FETCH (BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 35 3)) * A006 OK FETCH completed. * * To get around this, we terminate the read loop on a NO and count * on the fact that the sizes array has been preinitialized with a * known-bad size value. */ 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); else if (strstr(buf, "OK") || strstr(buf, "NO")) 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? */ { 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 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; if ((ok = gen_recv(sock, buf, sizeof(buf)))) return(ok); if (sscanf(buf+2, "%d FETCH (%*s {%d}", &num, lenp) == 2) break; else if (sscanf(buf+2, "%d NO", &num) == 1) return(PS_ERROR); else if (sscanf(buf+2, "%d BAD", &num) == 1) return(PS_ERROR); } 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 (and according * to RFC2060 p.43 this fetch should set Seen as a side effect). * * 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 */ 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 (!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. */ if ((cp = strchr(buf, '{'))) *lenp = atoi(cp + 1); 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; #ifdef __UNUSED__ /* * Any IMAP server that fails to set Seen on a BODY[TEXT] * fetch violates RFC2060 p.43 (top). This becomes an issue * when keep is on, because seen messages aren't deleted and * get refetched on each poll. As a workaround, if keep is on * we can set the Seen flag explicitly. * * This code isn't used yet because we don't know of any IMAP * servers broken in this way. */ if (ctl->keep) if ((ok = gen_transact(sock, imap_version == IMAP4 ? "STORE %d +FLAGS.SILENT (\\Seen)" : "STORE %d +FLAGS (\\Seen)", number))) return(ok); #endif /* __UNUSED__ */ } 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_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")); } const static struct method imap = { "IMAP", /* Internet Message Access Protocol */ #if INET6_ENABLE "imap", "imaps", #else /* INET6_ENABLE */ 143, /* standard IMAP2bis/IMAP4 port */ 993, /* ssl IMAP2bis/IMAP4 port */ #endif /* INET6_ENABLE */ 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_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 */