support skipping mails
[pop3.git] / pop3.c
blobb4f651d10879a0c4db26962af327e68f26998f7c
1 /*
2 * pop3 - a simple pop3 mail client
4 * Copyright (C) 2010 Ali Gholami Rudi
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License, as published by the
8 * Free Software Foundation.
9 */
10 #include <arpa/inet.h>
11 #include <ctype.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <netdb.h>
15 #include <netinet/in.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/socket.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 #include <time.h>
23 #include <unistd.h>
24 #include "config.h"
26 #define BUFFSIZE (1 << 12)
27 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
28 #define MIN(a, b) ((a) < (b) ? (a) : (b))
30 struct mailinfo {
31 char name[1 << 4];
32 int size;
33 } mails[MAXMAILS];
34 int nmails;
36 static int fd;
37 static char buf[BUFFSIZE];
38 static char *buf_cur;
39 static char *buf_end;
41 #ifdef SSL
42 #include <polarssl/ssl.h>
43 #include <polarssl/havege.h>
45 static ssl_context ssl;
46 static ssl_session ssn;
47 static havege_state hs;
49 static int ps_send(void *ctx, unsigned char *buf, int len)
51 return write(*(int *) ctx, buf, len);
54 static int ps_recv(void *ctx, unsigned char *buf, int len)
56 return read(*(int *) ctx, buf, len);
59 #endif
61 static int pop3_connect(char *addr, char *port)
63 struct addrinfo hints, *addrinfo;
64 int fd;
66 memset(&hints, 0, sizeof(hints));
67 hints.ai_family = AF_UNSPEC;
68 hints.ai_socktype = SOCK_STREAM;
69 hints.ai_flags = AI_PASSIVE;
71 getaddrinfo(addr, port, &hints, &addrinfo);
72 fd = socket(addrinfo->ai_family, addrinfo->ai_socktype,
73 addrinfo->ai_protocol);
75 if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) == -1) {
76 close(fd);
77 freeaddrinfo(addrinfo);
78 return -1;
80 freeaddrinfo(addrinfo);
81 return fd;
84 static void print(char *buf, int len)
86 write(STDOUT_FILENO, buf, len);
89 static int reply_line(char *dst, int len)
91 int nr = 0;
92 char *nl;
93 while (nr < len) {
94 int ml;
95 if (!buf_cur || buf_cur >= buf_end) {
96 #ifdef SSL
97 int buf_len = ssl_read(&ssl, (unsigned char *) buf,
98 sizeof(buf));
99 #else
100 int buf_len = read(fd, buf, sizeof(buf));
101 #endif
102 if (buf_len <= 0)
103 return -1;
104 #ifdef DEBUG
105 print(buf, buf_len);
106 #endif
107 buf_cur = buf;
108 buf_end = buf + buf_len;
110 ml = MIN(buf_end - buf_cur, len - nr);
111 if ((nl = memchr(buf_cur, '\n', ml))) {
112 nl++;
113 memcpy(dst + nr, buf_cur, nl - buf_cur);
114 nr += nl - buf_cur;
115 buf_cur = nl;
116 return nr;
118 memcpy(dst + nr, buf_cur, ml);
119 nr += ml;
120 buf_cur += ml;
122 return nr;
125 static void send_cmd(char *cmd)
127 #ifdef SSL
128 ssl_write(&ssl, (unsigned char *) cmd, strlen(cmd));
129 #else
130 write(fd, cmd, strlen(cmd));
131 #endif
132 fsync(fd);
133 #ifdef DEBUG
134 print(cmd, strlen(cmd));
135 #endif
138 static int is_eoc(char *line, int len)
140 return len < 4 && line[0] == '.' &&
141 (line[1] == '\r' || line[1] == '\n');
144 static char *putmem(char *dst, char *src, int len)
146 memcpy(dst, src, len);
147 return dst + len;
150 static char *cutword(char *dst, char *s)
152 while (*s && isspace(*s))
153 s++;
154 while (*s && !isspace(*s))
155 *dst++ = *s++;
156 *dst = '\0';
157 return s;
160 static char *putstr(char *dst, char *src)
162 int len = strchr(src, '\0') - src;
163 memcpy(dst, src, len + 1);
164 return dst + len;
167 static void login(char *user, char *pass)
169 char line[BUFFSIZE];
170 int len;
171 char *s = line;
172 s = putstr(s, "USER ");
173 s = putstr(s, user);
174 s = putstr(s, "\n");
175 send_cmd(line);
176 len = reply_line(line, sizeof(line));
177 s = line;
178 s = putstr(s, "PASS ");
179 s = putstr(s, pass);
180 s = putstr(s, "\n");
181 send_cmd(line);
182 len = reply_line(line, sizeof(line));
185 static void mail_stat(void)
187 char line[BUFFSIZE];
188 int len;
189 send_cmd("STAT\n");
190 len = reply_line(line, sizeof(line));
191 print(line, len);
192 send_cmd("LIST\n");
193 len = reply_line(line, sizeof(line));
194 while ((len = reply_line(line, sizeof(line))) != -1) {
195 struct mailinfo *mail;
196 char *s = line;
197 if (is_eoc(line, len))
198 break;
199 mail = &mails[nmails++];
200 s = cutword(mail->name, s);
201 mail->size = atoi(s);
205 static void req_mail(int i)
207 char cmd[100];
208 char *s = cmd;
209 s = putstr(s, "RETR ");
210 s = putstr(s, mails[i].name);
211 s = putstr(s, "\n");
212 send_cmd(cmd);
215 static char *mail_dst(char *line, int len)
217 int i;
218 line[len] = '\0';
219 for (i = 0; i < ARRAY_SIZE(filters); i++) {
220 char *hdr = filters[i].hdr;
221 if (!strncmp(hdr, line, strlen(hdr)) &&
222 strstr(line, filters[i].val))
223 return filters[i].dst;
225 return NULL;
228 int xwrite(int fd, char *buf, int len)
230 int nw = 0;
231 while (nw < len) {
232 int ret = write(fd, buf + nw, len - nw);
233 if (ret == -1 && (errno == EAGAIN || errno == EINTR))
234 continue;
235 if (ret < 0)
236 break;
237 nw += ret;
239 return nw;
242 static int mail_write(char *dst, char *mail, int len)
244 int fd = open(dst, O_WRONLY | O_APPEND | O_CREAT, 0600);
245 if (fd == -1)
246 return -1;
247 if (xwrite(fd, mail, len) != len)
248 return -1;
249 close(fd);
250 return 0;
253 static char *put_from_(char *s)
255 time_t t;
256 time(&t);
257 s = putstr(s, "From ");
258 s = putstr(s, getlogin());
259 s += strftime(s, MAXSIZE, " %a %b %d %H:%M:%S %Y\n", localtime(&t));
260 return s;
263 static int lone_from(char *s)
265 while (*s == '>')
266 s++;
267 return !strncmp("From ", s, 5);
270 static int ret_mail(int i)
272 char mail[MAXSIZE];
273 char line[BUFFSIZE];
274 char *s = mail;
275 int len = reply_line(line, sizeof(line));
276 char *dst = NULL;
277 int hdr = 1;
278 int ret;
279 if (mails[i].size + 100 > sizeof(mail))
280 return -1;
281 print(mails[i].name, strlen(mails[i].name));
282 s = put_from_(s);
283 while ((len = reply_line(line, sizeof(line))) != -1) {
284 if (is_eoc(line, len))
285 break;
286 if (len > 1 && line[len - 2] == '\r')
287 line[len-- - 2] = '\n';
288 if (line[0] == '\n')
289 hdr = 0;
290 if (hdr && !dst)
291 dst = mail_dst(line, len);
292 if (lone_from(line))
293 *s++ = '>';
294 s = putmem(s, line, len);
296 *s++ = '\n';
297 if (!dst)
298 dst = SPOOL;
299 ret = mail_write(dst, mail, s - mail);
300 s = line;
301 s = putstr(s, " -> ");
302 s = putstr(s, dst);
303 s = putstr(s, "\n");
304 print(line, s - line);
305 return ret;
308 static void del_mail(int i)
310 char cmd[100];
311 char *s = cmd;
312 s = putstr(s, "DELE ");
313 s = putstr(s, mails[i].name);
314 s = putstr(s, "\n");
315 send_cmd(cmd);
318 static int fetch(struct account *account, int beg)
320 char line[BUFFSIZE];
321 int len;
322 int i;
323 char *s = line;
324 nmails = 0;
325 fd = pop3_connect(account->server, account->port);
326 if (fd == -1)
327 return 1;
328 s = putstr(s, "fetching ");
329 s = putstr(s, account->user);
330 s = putstr(s, "@");
331 s = putstr(s, account->server);
332 s = putstr(s, "\n");
333 print(line, s - line);
334 #ifdef SSL
335 havege_init(&hs);
336 memset(&ssn, 0, sizeof(ssn));
337 if (ssl_init(&ssl))
338 return 1;
339 ssl_set_endpoint(&ssl, SSL_IS_CLIENT);
340 ssl_set_authmode(&ssl, SSL_VERIFY_NONE);
341 ssl_set_rng(&ssl, havege_rand, &hs);
342 ssl_set_bio(&ssl, ps_recv, &fd, ps_send, &fd);
343 ssl_set_ciphers(&ssl, ssl_default_ciphers);
344 ssl_set_session(&ssl, 1, 600, &ssn);
345 #endif
346 len = reply_line(line, sizeof(line));
347 login(account->user, account->pass);
348 mail_stat();
349 for (i = beg; i < nmails; i++)
350 req_mail(i);
351 for (i = beg; i < nmails; i++)
352 if (ret_mail(i) == -1)
353 return 1;
354 if (account->del) {
355 for (i = beg; i < nmails; i++)
356 del_mail(i);
357 for (i = beg; i < nmails; i++)
358 len = reply_line(line, sizeof(line));
360 send_cmd("QUIT\n");
361 len = reply_line(line, sizeof(line));
362 #ifdef SSL
363 ssl_close_notify(&ssl);
364 #endif
365 close(fd);
366 #ifdef SSL
367 ssl_free(&ssl);
368 #endif
369 return 0;
372 int main(int argc, char *argv[])
374 int i;
375 for (i = 0; i < ARRAY_SIZE(accounts); i++) {
376 int beg = 0;
377 if (argc > i + 1 && isdigit(argv[i + 1][0]))
378 beg = atoi(argv[i + 1]);
379 if (fetch(&accounts[i], beg) == -1)
380 return 1;
382 return 0;