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.
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
{
41 } *mails
; /* pop3 message list */
44 static struct uidl
*uidl
;
46 static char buf
[BUFFSIZE
]; /* pop3 input buffer */
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
));
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
)
81 /* read a pop3 response line */
82 static int pop3_res(char *dst
, int len
)
90 /* send a pop3 command */
91 static void pop3_cmd(char *cmd
, ...)
96 vsnprintf(buf
, sizeof(buf
), cmd
, ap
);
98 conn_write(conn
, buf
, strlen(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
)
111 pop3_cmd("USER %s\r\n", user
);
112 if (pop3_res(line
, sizeof(line
)))
114 pop3_cmd("PASS %s\r\n", pass
);
115 if (pop3_res(line
, sizeof(line
)))
120 static int pop3_stat(void)
124 pop3_cmd("STAT\r\n");
125 if (pop3_res(line
, sizeof(line
)))
128 pop3_cmd("LIST\r\n");
129 if (pop3_res(line
, sizeof(line
)))
131 while ((len
= pop3_get(line
, sizeof(line
))) >= 0) {
132 struct mailinfo
*mail
;
133 if (pop3_iseoc(line
, len
))
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]));
142 mail
= &mails
[mails_n
++];
143 sscanf(line
, "%s %d", mail
->name
, &mail
->size
);
148 static int pop3_uidl(void)
154 pop3_cmd("UIDL\r\n");
155 if (pop3_res(line
, sizeof(line
)))
157 while ((len
= pop3_get(line
, sizeof(line
))) > 0 && !pop3_iseoc(line
, len
))
158 sscanf(line
, "%s %s", name
, mails
[i
++].id
);
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
)
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
;
178 static int xwrite(int fd
, char *buf
, int len
)
182 int ret
= write(fd
, buf
+ nw
, len
- nw
);
183 if (ret
== -1 && (errno
== EAGAIN
|| errno
== EINTR
))
192 static int mail_write(char *dst
, char *mail
, int len
)
194 int fd
= open(dst
, O_WRONLY
| O_APPEND
| O_CREAT
, 0600);
197 if (xwrite(fd
, mail
, len
) != len
)
203 static int mail_from_(char *s
)
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
)
216 return !strncmp("From ", s
, 5);
219 static int fetch_one(int i
)
226 if (pop3_res(line
, sizeof(line
)))
228 printf("%s", mails
[i
].name
);
230 pos
= mail_from_(mailbuf
);
232 int len
= pop3_get(line
, sizeof(line
));
233 if (len
<= 0) /* end of stream or error */
235 if (pop3_iseoc(line
, len
))
237 if (len
> 1 && line
[len
- 2] == '\r')
238 line
[len
-- - 2] = '\n';
242 dst
= mail_dst(line
, len
);
243 while (pos
+ len
+ 2 >= mailbuf_sz
) {
244 mailbuf_sz
+= mailbuf_sz
? mailbuf_sz
: 512;
245 mailbuf
= realloc(mailbuf
, mailbuf_sz
);
247 if (pop3_lonefrom_(line
))
248 mailbuf
[pos
++] = '>';
249 memcpy(mailbuf
+ pos
, line
, len
);
252 mailbuf
[pos
++] = '\n';
255 ret
= mail_write(dst
, mailbuf
, pos
);
256 printf(" -> %s%s\n", dst
, ret
? " [failed]" : "");
260 static void pop3_del(int i
)
262 pop3_cmd("DELE %s\r\n", mails
[i
].name
);
265 static int size_ok(int i
)
267 return mailbuf_maxsz
<= 0 || mails
[i
].size
< mailbuf_maxsz
;
270 static int uidl_new(int i
)
272 return !uidl
|| !uidl_find(uidl
, mails
[i
].id
);
275 static int fetch_mails(int beg
, int end
, int del
)
279 for (i
= beg
; i
< end
; i
++)
280 if (size_ok(i
) && uidl_new(i
))
282 for (i
= beg
; i
< end
; i
++) {
283 if (size_ok(i
) && uidl_new(i
)) {
287 uidl_add(uidl
, mails
[i
].id
);
291 for (i
= beg
; i
< end
; i
++)
292 if ((!uidl
&& size_ok(i
)) || (uidl
&& !uidl_new(i
)))
294 for (i
= beg
; i
< end
; i
++)
295 if ((!uidl
&& size_ok(i
)) || (uidl
&& !uidl_new(i
)))
296 pop3_get(line
, sizeof(line
));
301 static int fetch(struct account
*account
)
308 conn
= conn_connect(account
->server
, account
->port
);
312 if (pop3_res(line
, sizeof(line
)))
314 pop3_cmd("STLS\r\n");
315 if (pop3_res(line
, sizeof(line
)))
318 if (conn_tls(conn
, account
->cert
)) {
325 uidl
= uidl_read(account
->uidl
);
326 printf("fetching %s@%s\n", account
->user
, account
->server
);
328 if (pop3_res(line
, sizeof(line
)))
330 if (pop3_login(account
->user
, account
->pass
))
337 batch
= account
->nopipe
? 1 : mails_n
;
338 for (i
= 0; i
< mails_n
; i
+= batch
)
339 if ((failed
= fetch_mails(i
, MIN(mails_n
, i
+ batch
), account
->del
)))
342 pop3_cmd("QUIT\r\n");
343 pop3_get(line
, sizeof(line
));
352 static void sigint(int sig
)
359 int main(int argc
, char *argv
[])
362 signal(SIGINT
, sigint
);
363 mailbuf_sz
= 1 << 21;
364 mailbuf
= malloc(mailbuf_sz
);
365 for (i
= 0; i
< ARRAY_SIZE(accounts
); i
++) {
366 mailbuf_maxsz
= accounts
[i
].maxsize
;