pop3: print message ID before reading its body
[pop3.git] / pop3.c
blob062bc3ed4ad31624b147b4b90946bb04a90d0bd9
1 /*
2 * A NEAT POP3 MAIL CLIENT
4 * Copyright (C) 2010-2017 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[MAXMAILS];
42 static int nmails;
43 static struct uidl *uidl;
45 static char buf[BUFFSIZE];
46 static int buf_len;
47 static int buf_pos;
48 static struct conn *conn;
49 static char *mailbuf;
51 static int pop3_read(void)
53 if (buf_pos == buf_len) {
54 buf_len = conn_read(conn, buf, sizeof(buf));
55 buf_pos = 0;
57 return buf_pos < buf_len ? (unsigned char) buf[buf_pos++] : -1;
60 /* read a line from the server */
61 static int pop3_get(char *dst, int len)
63 int i = 0;
64 int c;
65 while (i < len - 1) {
66 c = pop3_read();
67 if (c < 0)
68 return -1;
69 dst[i++] = c;
70 if (c == '\n')
71 break;
73 dst[i] = '\0';
74 LOG(dst);
75 return i;
78 /* read a pop3 response line */
79 static int pop3_res(char *dst, int len)
81 pop3_get(dst, len);
82 if (dst[0] != '+')
83 printf("%s", dst);
84 return dst[0] != '+';
87 /* send a pop3 command */
88 static void pop3_cmd(char *cmd, ...)
90 static char buf[512];
91 va_list ap;
92 va_start(ap, cmd);
93 vsnprintf(buf, sizeof(buf), cmd, ap);
94 va_end(ap);
95 conn_write(conn, buf, strlen(buf));
96 LOG(buf);
99 static int pop3_iseoc(char *line, int len)
101 return len >= 2 && len <= 3 && line[0] == '.' &&
102 (line[1] == '\r' || line[1] == '\n');
105 static int pop3_login(char *user, char *pass)
107 char line[BUFFSIZE];
108 pop3_cmd("USER %s\r\n", user);
109 if (pop3_res(line, sizeof(line)))
110 return 1;
111 pop3_cmd("PASS %s\r\n", pass);
112 if (pop3_res(line, sizeof(line)))
113 return 1;
114 return 0;
117 static int pop3_stat(void)
119 char line[BUFFSIZE];
120 int len;
121 pop3_cmd("STAT\r\n");
122 if (pop3_res(line, sizeof(line)))
123 return 1;
124 printf("%s", line);
125 pop3_cmd("LIST\r\n");
126 if (pop3_res(line, sizeof(line)))
127 return 1;
128 while ((len = pop3_get(line, sizeof(line))) >= 0) {
129 struct mailinfo *mail;
130 if (pop3_iseoc(line, len))
131 break;
132 mail = &mails[nmails++];
133 sscanf(line, "%s %d", mail->name, &mail->size);
135 return 0;
138 static int pop3_uidl(void)
140 char line[BUFFSIZE];
141 char name[128];
142 int len;
143 int i = 0;
144 pop3_cmd("UIDL\r\n");
145 if (pop3_res(line, sizeof(line)))
146 return 1;
147 while ((len = pop3_get(line, sizeof(line))) > 0 && !pop3_iseoc(line, len))
148 sscanf(line, "%s %s", name, mails[i++].id);
149 return 0;
152 static void pop3_retr(int i)
154 pop3_cmd("RETR %s\r\n", mails[i].name);
157 static char *mail_dst(char *hdr, int len)
159 int i;
160 hdr[len] = '\0';
161 for (i = 0; i < ARRAY_SIZE(filters); i++)
162 if (!strncmp(filters[i].hdr, hdr, strlen(filters[i].hdr)) &&
163 strstr(hdr, filters[i].val))
164 return filters[i].dst;
165 return NULL;
168 static int xwrite(int fd, char *buf, int len)
170 int nw = 0;
171 while (nw < len) {
172 int ret = write(fd, buf + nw, len - nw);
173 if (ret == -1 && (errno == EAGAIN || errno == EINTR))
174 continue;
175 if (ret < 0)
176 break;
177 nw += ret;
179 return nw;
182 static int mail_write(char *dst, char *mail, int len)
184 int fd = open(dst, O_WRONLY | O_APPEND | O_CREAT, 0600);
185 if (fd < 0)
186 return 1;
187 if (xwrite(fd, mail, len) != len)
188 return 1;
189 close(fd);
190 return 0;
193 static int mail_from_(char *s)
195 char date[128];
196 time_t t;
197 time(&t);
198 strftime(date, sizeof(date), "%a %b %d %H:%M:%S %Y", localtime(&t));
199 return sprintf(s, "From %s %s\n", getenv("USER") ? getenv("USER") : "root", date);
202 static int pop3_lonefrom_(char *s)
204 while (*s == '>')
205 s++;
206 return !strncmp("From ", s, 5);
209 static int fetch_one(int i)
211 char line[BUFFSIZE];
212 char *s = mailbuf;
213 char *dst = NULL;
214 int hdr = 1;
215 int len, ret;
216 if (pop3_res(line, sizeof(line)))
217 return 1;
218 printf("%s", mails[i].name);
219 fflush(stdout);
220 s += mail_from_(s);
221 while (1) {
222 len = pop3_get(line, sizeof(line));
223 if (len <= 0) /* end of stream or error */
224 return 1;
225 if (pop3_iseoc(line, len))
226 break;
227 if (len > 1 && line[len - 2] == '\r')
228 line[len-- - 2] = '\n';
229 if (line[0] == '\n')
230 hdr = 0;
231 if (hdr && !dst)
232 dst = mail_dst(line, len);
233 if (pop3_lonefrom_(line))
234 *s++ = '>';
235 memcpy(s, line, len);
236 s += len;
238 *s++ = '\n';
239 if (!dst)
240 dst = SPOOL;
241 ret = mail_write(dst, mailbuf, s - mailbuf);
242 printf(" -> %s%s\n", dst, ret ? " [failed]" : "");
243 return ret;
246 static void pop3_del(int i)
248 pop3_cmd("DELE %s\r\n", mails[i].name);
251 static int size_ok(int i)
253 return mails[i].size + 100 < MAXSIZE;
256 static int uidl_new(int i)
258 return !uidl || !uidl_find(uidl, mails[i].id);
261 static int fetch_mails(int beg, int end, int del)
263 char line[BUFFSIZE];
264 int i;
265 for (i = beg; i < end; i++)
266 if (size_ok(i) && uidl_new(i))
267 pop3_retr(i);
268 for (i = beg; i < end; i++) {
269 if (size_ok(i) && uidl_new(i)) {
270 if (fetch_one(i))
271 return 1;
272 if (uidl)
273 uidl_add(uidl, mails[i].id);
276 if (del) {
277 for (i = beg; i < end; i++)
278 if ((!uidl && size_ok(i)) || (uidl && !uidl_new(i)))
279 pop3_del(i);
280 for (i = beg; i < end; i++)
281 if ((!uidl && size_ok(i)) || (uidl && !uidl_new(i)))
282 pop3_get(line, sizeof(line));
284 return 0;
287 static int fetch(struct account *account)
289 char line[BUFFSIZE];
290 int batch;
291 int failed = 0;
292 int i;
293 nmails = 0;
294 conn = conn_connect(account->server, account->port);
295 if (!conn)
296 return 1;
297 if (account->stls) {
298 if (pop3_res(line, sizeof(line)))
299 return 1;
300 pop3_cmd("STLS\r\n");
301 if (pop3_res(line, sizeof(line)))
302 return 1;
304 if (conn_tls(conn, account->cert)) {
305 conn_close(conn);
306 return 1;
308 buf_pos = 0;
309 buf_len = 0;
310 if (account->uidl)
311 uidl = uidl_read(account->uidl);
312 printf("fetching %s@%s\n", account->user, account->server);
313 if (!account->stls)
314 if (pop3_res(line, sizeof(line)))
315 return 1;
316 if (pop3_login(account->user, account->pass))
317 return 1;
318 if (pop3_stat())
319 return 1;
320 if (account->uidl)
321 if (pop3_uidl())
322 return 1;
323 batch = account->nopipe ? 1 : nmails;
324 for (i = 0; i < nmails; i += batch)
325 if ((failed = fetch_mails(i, MIN(nmails, i + batch), account->del)))
326 break;
327 if (!failed) {
328 pop3_cmd("QUIT\r\n");
329 pop3_get(line, sizeof(line));
331 conn_close(conn);
332 if (uidl)
333 uidl_save(uidl);
334 uidl = NULL;
335 return failed;
338 static void sigint(int sig)
340 if (uidl)
341 uidl_save(uidl);
342 exit(1);
345 int main(int argc, char *argv[])
347 int i;
348 signal(SIGINT, sigint);
349 mailbuf = malloc(MAXSIZE);
350 for (i = 0; i < ARRAY_SIZE(accounts); i++)
351 fetch(&accounts[i]);
352 free(mailbuf);
353 return 0;