pop3: messages may be larger than what the server declares
[pop3.git] / pop3.c
blobf1143b5e41a2e2877d2fadb5e758672d821576c7
1 /*
2 * A NEAT POP3 MAIL CLIENT
4 * Copyright (C) 2010-2021 Ali Gholami Rudi
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <ctype.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <netdb.h>
22 #include <signal.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 #include <unistd.h>
29 #include "conf.h"
30 #include "uidl.h"
31 #include "conn.h"
33 #define BUFFSIZE (1 << 12)
34 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
35 #define MIN(a, b) ((a) < (b) ? (a) : (b))
37 static struct mailinfo {
38 char name[1 << 4];
39 char id[1 << 6];
40 int size;
41 } *mails; /* pop3 message list */
42 static int mails_n;
43 static int mails_sz;
44 static struct uidl *uidl;
46 static char buf[BUFFSIZE]; /* pop3 input buffer */
47 static int buf_len;
48 static int buf_pos;
49 static struct conn *conn; /* pop3 connection */
50 static char *mailbuf; /* message content */
51 static int mailbuf_sz;
52 static int mailbuf_maxsz; /* the maximum message length */
54 static int pop3_read(void)
56 if (buf_pos == buf_len) {
57 buf_len = conn_read(conn, buf, sizeof(buf));
58 buf_pos = 0;
60 return buf_pos < buf_len ? (unsigned char) buf[buf_pos++] : -1;
63 /* read a line from the server */
64 static int pop3_get(char *dst, int len)
66 int i = 0;
67 int c;
68 while (i < len - 1) {
69 c = pop3_read();
70 if (c < 0)
71 return -1;
72 dst[i++] = c;
73 if (c == '\n')
74 break;
76 dst[i] = '\0';
77 LOG(dst);
78 return i;
81 /* read a pop3 response line */
82 static int pop3_res(char *dst, int len)
84 pop3_get(dst, len);
85 if (dst[0] != '+')
86 printf("%s", dst);
87 return dst[0] != '+';
90 /* send a pop3 command */
91 static void pop3_cmd(char *cmd, ...)
93 static char buf[512];
94 va_list ap;
95 va_start(ap, cmd);
96 vsnprintf(buf, sizeof(buf), cmd, ap);
97 va_end(ap);
98 conn_write(conn, buf, strlen(buf));
99 LOG(buf);
102 static int pop3_iseoc(char *line, int len)
104 return len >= 2 && len <= 3 && line[0] == '.' &&
105 (line[1] == '\r' || line[1] == '\n');
108 static int pop3_login(char *user, char *pass)
110 char line[BUFFSIZE];
111 pop3_cmd("USER %s\r\n", user);
112 if (pop3_res(line, sizeof(line)))
113 return 1;
114 pop3_cmd("PASS %s\r\n", pass);
115 if (pop3_res(line, sizeof(line)))
116 return 1;
117 return 0;
120 static int pop3_stat(void)
122 char line[BUFFSIZE];
123 int len;
124 pop3_cmd("STAT\r\n");
125 if (pop3_res(line, sizeof(line)))
126 return 1;
127 printf("%s", line);
128 pop3_cmd("LIST\r\n");
129 if (pop3_res(line, sizeof(line)))
130 return 1;
131 while ((len = pop3_get(line, sizeof(line))) >= 0) {
132 struct mailinfo *mail;
133 if (pop3_iseoc(line, len))
134 break;
135 if (mails_n == mails_sz) {
136 struct mailinfo *mails_old = mails;
137 mails_sz = mails_sz + (mails_sz ? mails_sz : 256);
138 mails = malloc(mails_sz * sizeof(mails[0]));
139 memcpy(mails, mails_old, mails_n * sizeof(mails[0]));
140 free(mails_old);
142 mail = &mails[mails_n++];
143 sscanf(line, "%s %d", mail->name, &mail->size);
145 return 0;
148 static int pop3_uidl(void)
150 char line[BUFFSIZE];
151 char name[128];
152 int len;
153 int i = 0;
154 pop3_cmd("UIDL\r\n");
155 if (pop3_res(line, sizeof(line)))
156 return 1;
157 while ((len = pop3_get(line, sizeof(line))) > 0 && !pop3_iseoc(line, len))
158 sscanf(line, "%s %s", name, mails[i++].id);
159 return 0;
162 static void pop3_retr(int i)
164 pop3_cmd("RETR %s\r\n", mails[i].name);
167 static char *mail_dst(char *hdr, int len)
169 int i;
170 hdr[len] = '\0';
171 for (i = 0; i < ARRAY_SIZE(filters); i++)
172 if (!strncmp(filters[i].hdr, hdr, strlen(filters[i].hdr)) &&
173 strstr(hdr, filters[i].val))
174 return filters[i].dst;
175 return NULL;
178 static int xwrite(int fd, char *buf, int len)
180 int nw = 0;
181 while (nw < len) {
182 int ret = write(fd, buf + nw, len - nw);
183 if (ret == -1 && (errno == EAGAIN || errno == EINTR))
184 continue;
185 if (ret < 0)
186 break;
187 nw += ret;
189 return nw;
192 static int mail_write(char *dst, char *mail, int len)
194 int fd = open(dst, O_WRONLY | O_APPEND | O_CREAT, 0600);
195 if (fd < 0)
196 return 1;
197 if (xwrite(fd, mail, len) != len)
198 return 1;
199 close(fd);
200 return 0;
203 static int mail_from_(char *s)
205 char date[128];
206 time_t t;
207 time(&t);
208 strftime(date, sizeof(date), "%a %b %d %H:%M:%S %Y", localtime(&t));
209 return sprintf(s, "From %s %s\n", getenv("USER") ? getenv("USER") : "root", date);
212 static int pop3_lonefrom_(char *s)
214 while (*s == '>')
215 s++;
216 return !strncmp("From ", s, 5);
219 static int fetch_one(int i)
221 char line[BUFFSIZE];
222 char *dst = NULL;
223 char *s;
224 int hdr = 1;
225 int len, ret;
226 int maxsz = mails[i].size * 2 + 4096;
227 if (maxsz > mailbuf_sz) {
228 free(mailbuf);
229 mailbuf_sz = maxsz;
230 mailbuf = malloc(mailbuf_sz);
232 if (pop3_res(line, sizeof(line)))
233 return 1;
234 printf("%s", mails[i].name);
235 fflush(stdout);
236 s = mailbuf;
237 s += mail_from_(s);
238 while (1) {
239 len = pop3_get(line, sizeof(line));
240 if (len <= 0) /* end of stream or error */
241 return 1;
242 if (pop3_iseoc(line, len))
243 break;
244 if (len > 1 && line[len - 2] == '\r')
245 line[len-- - 2] = '\n';
246 if (line[0] == '\n')
247 hdr = 0;
248 if (hdr && !dst)
249 dst = mail_dst(line, len);
250 if (s + len + 2 >= mailbuf + mailbuf_sz)
251 return 1;
252 if (pop3_lonefrom_(line))
253 *s++ = '>';
254 memcpy(s, line, len);
255 s += len;
257 *s++ = '\n';
258 if (!dst)
259 dst = SPOOL;
260 ret = mail_write(dst, mailbuf, s - mailbuf);
261 printf(" -> %s%s\n", dst, ret ? " [failed]" : "");
262 return ret;
265 static void pop3_del(int i)
267 pop3_cmd("DELE %s\r\n", mails[i].name);
270 static int size_ok(int i)
272 return mailbuf_maxsz <= 0 || mails[i].size < mailbuf_maxsz;
275 static int uidl_new(int i)
277 return !uidl || !uidl_find(uidl, mails[i].id);
280 static int fetch_mails(int beg, int end, int del)
282 char line[BUFFSIZE];
283 int i;
284 for (i = beg; i < end; i++)
285 if (size_ok(i) && uidl_new(i))
286 pop3_retr(i);
287 for (i = beg; i < end; i++) {
288 if (size_ok(i) && uidl_new(i)) {
289 if (fetch_one(i))
290 return 1;
291 if (uidl)
292 uidl_add(uidl, mails[i].id);
295 if (del) {
296 for (i = beg; i < end; i++)
297 if ((!uidl && size_ok(i)) || (uidl && !uidl_new(i)))
298 pop3_del(i);
299 for (i = beg; i < end; i++)
300 if ((!uidl && size_ok(i)) || (uidl && !uidl_new(i)))
301 pop3_get(line, sizeof(line));
303 return 0;
306 static int fetch(struct account *account)
308 char line[BUFFSIZE];
309 int batch;
310 int failed = 0;
311 int i;
312 mails_n = 0;
313 conn = conn_connect(account->server, account->port);
314 if (!conn)
315 return 1;
316 if (account->stls) {
317 if (pop3_res(line, sizeof(line)))
318 return 1;
319 pop3_cmd("STLS\r\n");
320 if (pop3_res(line, sizeof(line)))
321 return 1;
323 if (conn_tls(conn, account->cert)) {
324 conn_close(conn);
325 return 1;
327 buf_pos = 0;
328 buf_len = 0;
329 if (account->uidl)
330 uidl = uidl_read(account->uidl);
331 printf("fetching %s@%s\n", account->user, account->server);
332 if (!account->stls)
333 if (pop3_res(line, sizeof(line)))
334 return 1;
335 if (pop3_login(account->user, account->pass))
336 return 1;
337 if (pop3_stat())
338 return 1;
339 if (account->uidl)
340 if (pop3_uidl())
341 return 1;
342 batch = account->nopipe ? 1 : mails_n;
343 for (i = 0; i < mails_n; i += batch)
344 if ((failed = fetch_mails(i, MIN(mails_n, i + batch), account->del)))
345 break;
346 if (!failed) {
347 pop3_cmd("QUIT\r\n");
348 pop3_get(line, sizeof(line));
350 conn_close(conn);
351 if (uidl)
352 uidl_save(uidl);
353 uidl = NULL;
354 return failed;
357 static void sigint(int sig)
359 if (uidl)
360 uidl_save(uidl);
361 exit(1);
364 int main(int argc, char *argv[])
366 int i;
367 signal(SIGINT, sigint);
368 mailbuf_sz = 1 << 21;
369 mailbuf = malloc(mailbuf_sz);
370 for (i = 0; i < ARRAY_SIZE(accounts); i++) {
371 mailbuf_maxsz = accounts[i].maxsize;
372 fetch(&accounts[i]);
374 free(mailbuf);
375 free(mails);
376 return 0;