]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/upas/fs/pop3.c
upas/fs: remove read timeout via alarm(2) in pop3resp
[plan9front.git] / sys / src / cmd / upas / fs / pop3.c
1 #include "common.h"
2 #include <libsec.h>
3 #include <auth.h>
4 #include "dat.h"
5
6 #pragma varargck type "M" uchar*
7 #pragma varargck argpos pop3cmd 2
8 #define pdprint(p, ...) if((p)->debug) fprint(2, __VA_ARGS__); else{}
9
10 typedef struct Popm Popm;
11 struct Popm{
12         int     mesgno;
13 };
14
15 typedef struct Pop Pop;
16 struct Pop {
17         char    *freep;         /* free this to free the strings below */
18         char    *host;
19         char    *user;
20         char    *port;
21
22         int     ppop;
23         int     refreshtime;
24         int     debug;
25         int     pipeline;
26         int     encrypted;
27         int     needtls;
28         int     notls;
29         int     needssl;
30
31         Biobuf  bin;            /* open network connection */
32         Biobuf  bout;
33         int     fd;
34         char    *lastline;              /* from Brdstr */
35 };
36
37 static int
38 mesgno(Message *m)
39 {
40         Popm *a;
41
42         a = m->aux;
43         return a->mesgno;
44 }
45
46 static char*
47 geterrstr(void)
48 {
49         static char err[64];
50
51         err[0] = '\0';
52         errstr(err, sizeof(err));
53         return err;
54 }
55
56 /*
57  *  get pop3 response line , without worrying
58  *  about multiline responses; the clients
59  *  will deal with that.
60  */
61 static int
62 isokay(char *s)
63 {
64         return s!=nil && strncmp(s, "+OK", 3)==0;
65 }
66
67 static void
68 pop3cmd(Pop *pop, char *fmt, ...)
69 {
70         char buf[128], *p;
71         va_list va;
72
73         va_start(va, fmt);
74         vseprint(buf, buf + sizeof buf, fmt, va);
75         va_end(va);
76
77         p = buf + strlen(buf);
78         if(p > buf + sizeof buf - 3)
79                 sysfatal("pop3 command too long");
80         pdprint(pop, "<- %s\n", buf);
81         strcpy(p, "\r\n");
82         Bwrite(&pop->bout, buf, strlen(buf));
83         Bflush(&pop->bout);
84 }
85
86 static char*
87 pop3resp(Pop *pop)
88 {
89         char *s;
90         char *p;
91
92         if((s = Brdstr(&pop->bin, '\n', 0)) == nil){
93                 close(pop->fd);
94                 pop->fd = -1;
95                 return "unexpected eof";
96         }
97
98         p = s + strlen(s) - 1;
99         while(p >= s && (*p == '\r' || *p == '\n'))
100                 *p-- = '\0';
101
102         pdprint(pop, "-> %s\n", s);
103         free(pop->lastline);
104         pop->lastline = s;
105         return s;
106 }
107
108 /*
109  *  get capability list, possibly start tls
110  */
111 static char*
112 pop3capa(Pop *pop)
113 {
114         char *s;
115         int hastls;
116
117         pop3cmd(pop, "CAPA");
118         if(!isokay(pop3resp(pop)))
119                 return nil;
120
121         hastls = 0;
122         for(;;){
123                 s = pop3resp(pop);
124                 if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0)
125                         break;
126                 if(strcmp(s, "STLS") == 0)
127                         hastls = 1;
128                 if(strcmp(s, "PIPELINING") == 0)
129                         pop->pipeline = 1;
130                 if(strcmp(s, "EXPIRE 0") == 0)
131                         return "server does not allow mail to be left on server";
132         }
133
134         if(hastls && !pop->notls){
135                 pop3cmd(pop, "STLS");
136                 if(!isokay(s = pop3resp(pop)))
137                         return s;
138                 Bterm(&pop->bin);
139                 Bterm(&pop->bout);
140                 if((pop->fd = wraptls(pop->fd, pop->host)) < 0)
141                         return geterrstr();
142                 pop->encrypted = 1;
143                 Binit(&pop->bin, pop->fd, OREAD);
144                 Binit(&pop->bout, pop->fd, OWRITE);
145         }
146         return nil;
147 }
148
149 /*
150  *  log in using APOP if possible, password if allowed by user
151  */
152 static char*
153 pop3login(Pop *pop)
154 {
155         int n;
156         char *s, *p, *q;
157         char ubuf[128], user[128];
158         char buf[500];
159         UserPasswd *up;
160
161         s = pop3resp(pop);
162         if(!isokay(s))
163                 return "error in initial handshake";
164
165         if(pop->user)
166                 snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
167         else
168                 ubuf[0] = '\0';
169
170         /* look for apop banner */
171         if(pop->ppop == 0 && (p = strchr(s, '<')) && (q = strchr(p + 1, '>'))) {
172                 *++q = '\0';
173                 if((n=auth_respond(p, q - p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
174                         pop->host, ubuf)) < 0)
175                         return "factotum failed";
176                 if(user[0]=='\0')
177                         return "factotum did not return a user name";
178
179                 if(s = pop3capa(pop))
180                         return s;
181
182                 pop3cmd(pop, "APOP %s %.*s", user, utfnlen(buf, n), buf);
183                 if(!isokay(s = pop3resp(pop)))
184                         return s;
185
186                 return nil;
187         } else {
188                 if(pop->ppop == 0)
189                         return "no APOP hdr from server";
190
191                 if(s = pop3capa(pop))
192                         return s;
193
194                 if(pop->needtls && !pop->encrypted)
195                         return "could not negotiate TLS";
196
197                 up = auth_getuserpasswd(auth_getkey, "proto=pass service=pop dom=%q%s",
198                         pop->host, ubuf);
199                 if(up == nil)
200                         return "no usable keys found";
201
202                 pop3cmd(pop, "USER %s", up->user);
203                 if(!isokay(s = pop3resp(pop))){
204                         free(up);
205                         return s;
206                 }
207                 pop3cmd(pop, "PASS %s", up->passwd);
208                 free(up);
209                 if(!isokay(s = pop3resp(pop)))
210                         return s;
211
212                 return nil;
213         }
214 }
215
216 /*
217  *  dial and handshake with pop server
218  */
219 static char*
220 pop3dial(Pop *pop)
221 {
222         char *err;
223
224         if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
225                 return geterrstr();
226         if(pop->needssl && (pop->fd = wraptls(pop->fd, pop->host)) < 0)
227                 return geterrstr();
228         pop->encrypted = pop->needssl;
229         Binit(&pop->bin, pop->fd, OREAD);
230         Binit(&pop->bout, pop->fd, OWRITE);
231         if(err = pop3login(pop)) {
232                 close(pop->fd);
233                 return err;
234         }
235
236         return nil;
237 }
238
239 /*
240  *  close connection
241  */
242 static void
243 pop3hangup(Pop *pop)
244 {
245         pop3cmd(pop, "QUIT");
246         pop3resp(pop);
247         close(pop->fd);
248 }
249
250 /*
251  *  download a single message
252  */
253 static char*
254 pop3download(Mailbox *mb, Pop *pop, Message *m)
255 {
256         char *s, *f[3], *wp, *ep;
257         int l, sz, pos, n;
258         Popm *a;
259
260         a = m->aux;
261         if(!pop->pipeline)
262                 pop3cmd(pop, "LIST %d", a->mesgno);
263         if(!isokay(s = pop3resp(pop)))
264                 return s;
265
266         if(tokenize(s, f, 3) != 3)
267                 return "syntax error in LIST response";
268
269         if(atoi(f[1]) != a->mesgno)
270                 return "out of sync with pop3 server";
271
272         sz = atoi(f[2]) + 200;  /* 200 because the plan9 pop3 server lies */
273         if(sz == 0)
274                 return "invalid size in LIST response";
275
276         m->start = wp = emalloc(sz + 1);
277         ep = wp + sz;
278
279         if(!pop->pipeline)
280                 pop3cmd(pop, "RETR %d", a->mesgno);
281         if(!isokay(s = pop3resp(pop))) {
282                 m->start = nil;
283                 free(wp);
284                 return s;
285         }
286
287         s = nil;
288         while(wp <= ep) {
289                 s = pop3resp(pop);
290                 if(strcmp(s, "unexpected eof") == 0) {
291                         free(m->start);
292                         m->start = nil;
293                         return "unexpected end of conversation";
294                 }
295                 if(strcmp(s, ".") == 0)
296                         break;
297
298                 l = strlen(s) + 1;
299                 if(s[0] == '.') {
300                         s++;
301                         l--;
302                 }
303                 /*
304                  * grow by 10%/200bytes - some servers
305                  *  lie about message sizes
306                  */
307                 if(wp + l > ep) {
308                         pos = wp - m->start;
309                         n = sz/10;
310                         if(n < 200)
311                                 n = 200;
312                         sz += n;
313                         m->start = erealloc(m->start, sz + 1);
314                         wp = m->start + pos;
315                         ep = m->start + sz;
316                 }
317                 memmove(wp, s, l - 1);
318                 wp[l-1] = '\n';
319                 wp += l;
320         }
321
322         if(s == nil || strcmp(s, ".") != 0)
323                 return "out of sync with pop3 server";
324
325         m->end = wp;
326
327         /*
328          *  make sure there's a trailing null
329          *  (helps in body searches)
330          */
331         *m->end = 0;
332         m->bend = m->rbend = m->end;
333         m->header = m->start;
334         m->size = m->end - m->start;
335         if(m->digest == nil)
336                 digestmessage(mb, m);
337
338         return nil;
339 }
340
341 /*
342  *  check for new messages on pop server
343  *  UIDL is not required by RFC 1939, but
344  *  netscape requires it, so almost every server supports it.
345  *  we'll use it to make our lives easier.
346  */
347 static char*
348 pop3read(Pop *pop, Mailbox *mb)
349 {
350         char *s, *p, *uidl, *f[2];
351         int mno, ignore;
352         Message *m, *next, **l;
353         Popm *a;
354
355         /* Some POP servers disallow UIDL if the maildrop is empty. */
356         pop3cmd(pop, "STAT");
357         if(!isokay(s = pop3resp(pop)))
358                 return s;
359
360         /* fetch message listing; note messages to grab */
361         l = &mb->root->part;
362         if(strncmp(s, "+OK 0 ", 6) != 0) {
363                 pop3cmd(pop, "UIDL");
364                 if(!isokay(s = pop3resp(pop)))
365                         return s;
366
367                 for(;;){
368                         p = pop3resp(pop);
369                         if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
370                                 break;
371
372                         if(tokenize(p, f, 2) != 2)
373                                 continue;
374
375                         mno = atoi(f[0]);
376                         uidl = f[1];
377                         if(strlen(uidl) > 75)   /* RFC 1939 says 70 characters max */
378                                 continue;
379
380                         ignore = 0;
381                         while(*l != nil) {
382                                 a = (*l)->aux;
383                                 if(strcmp((*l)->idxaux, uidl) == 0){
384                                         if(a == 0){
385                                                 m = *l;
386                                                 m->mallocd = 1;
387                                                 m->inmbox = 1;
388                                                 m->aux = a = emalloc(sizeof *a);
389                                         }
390                                         /* matches mail we already have, note mesgno for deletion */
391                                         a->mesgno = mno;
392                                         ignore = 1;
393                                         l = &(*l)->next;
394                                         break;
395                                 }else{
396                                         /* old mail no longer in box mark deleted */
397                                         (*l)->inmbox = 0;
398                                         (*l)->deleted = Deleted;
399                                         l = &(*l)->next;
400                                 }
401                         }
402                         if(ignore)
403                                 continue;
404
405                         m = newmessage(mb->root);
406                         m->mallocd = 1;
407                         m->inmbox = 1;
408                         m->idxaux = strdup(uidl);
409                         m->aux = a = emalloc(sizeof *a);
410                         a->mesgno = mno;
411
412                         /* chain in; will fill in message later */
413                         *l = m;
414                         l = &m->next;
415                 }
416         }
417
418         /* whatever is left has been removed from the mbox, mark as deleted */
419         while(*l != nil) {
420                 (*l)->inmbox = 0;
421                 (*l)->deleted = Disappear;
422                 l = &(*l)->next;
423         }
424
425         /* download new messages */
426         if(pop->pipeline){
427                 switch(rfork(RFPROC|RFMEM)){
428                 case -1:
429                         eprint("pop3: rfork: %r\n");
430                         pop->pipeline = 0;
431
432                 default:
433                         break;
434
435                 case 0:
436                         for(m = mb->root->part; m != nil; m = m->next){
437                                 if(m->start != nil || m->deleted)
438                                         continue;
439                                 Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", mesgno(m), mesgno(m));
440                         }
441                         Bflush(&pop->bout);
442                         _exits("");
443                 }
444         }
445
446         for(m = mb->root->part; m != nil; m = next) {
447                 next = m->next;
448
449                 if(m->start != nil || m->deleted)
450                         continue;
451                 if(s = pop3download(mb, pop, m)) {
452                         eprint("pop3: download %d: %s\n", mesgno(m), s);
453                         unnewmessage(mb, mb->root, m);
454                         continue;
455                 }
456                 parse(mb, m, 1, 0);
457         }
458         if(pop->pipeline)
459                 waitpid();
460         return nil;     
461 }
462
463 /*
464  *  delete marked messages
465  */
466 static void
467 pop3purge(Pop *pop, Mailbox *mb)
468 {
469         Message *m;
470
471         if(pop->pipeline){
472                 switch(rfork(RFPROC|RFMEM)){
473                 case -1:
474                         eprint("pop3: rfork: %r\n");
475                         pop->pipeline = 0;
476
477                 default:
478                         break;
479
480                 case 0:
481                         for(m = mb->root->part; m != nil; m = m->next){
482                                 if(m->deleted && m->inmbox)
483                                         Bprint(&pop->bout, "DELE %d\r\n", mesgno(m));
484                         }
485                         Bflush(&pop->bout);
486                         _exits("");
487                 }
488         }
489         for(m = mb->root->part; m != nil; m = m->next) {
490                 if(m->deleted && m->inmbox) {
491                         if(!pop->pipeline)
492                                 pop3cmd(pop, "DELE %d", mesgno(m));
493                         if(!isokay(pop3resp(pop)))
494                                 continue;
495                         m->inmbox = 0;
496                 }
497         }
498 }
499
500
501 /* connect to pop3 server, sync mailbox */
502 static char*
503 pop3sync(Mailbox *mb)
504 {
505         char *err;
506         Pop *pop;
507
508         pop = mb->aux;
509         if(err = pop3dial(pop))
510                 goto out;
511         if((err = pop3read(pop, mb)) == nil)
512                 pop3purge(pop, mb);
513         pop3hangup(pop);
514 out:
515         mb->waketime = (ulong)time(0) + pop->refreshtime;
516         return err;
517 }
518
519 static char Epop3ctl[] = "bad pop3 control message";
520
521 static char*
522 pop3ctl(Mailbox *mb, int argc, char **argv)
523 {
524         int n;
525         Pop *pop;
526
527         pop = mb->aux;
528         if(argc < 1)
529                 return Epop3ctl;
530
531         if(argc==1 && strcmp(argv[0], "debug")==0){
532                 pop->debug = 1;
533                 return nil;
534         }
535
536         if(argc==1 && strcmp(argv[0], "nodebug")==0){
537                 pop->debug = 0;
538                 return nil;
539         }
540
541         if(strcmp(argv[0], "refresh")==0){
542                 if(argc==1){
543                         pop->refreshtime = 60;
544                         return nil;
545                 }
546                 if(argc==2){
547                         n = atoi(argv[1]);
548                         if(n < 15)
549                                 return Epop3ctl;
550                         pop->refreshtime = n;
551                         return nil;
552                 }
553         }
554
555         return Epop3ctl;
556 }
557
558 /* free extra memory associated with mb */
559 static void
560 pop3close(Mailbox *mb)
561 {
562         Pop *pop;
563
564         pop = mb->aux;
565         free(pop->freep);
566         free(pop);
567 }
568
569 static char*
570 mkmbox(Pop *pop, char *p, char *e)
571 {
572         p = seprint(p, e, "%s/box/%s/pop.%s", MAILROOT, getlog(), pop->host);
573         if(pop->user && strcmp(pop->user, getlog()))
574                 p = seprint(p, e, ".%s", pop->user);
575         return p;
576 }
577
578 /*
579  *  open mailboxes of the form /pop/host/user or /apop/host/user
580  */
581 char*
582 pop3mbox(Mailbox *mb, char *path)
583 {
584         char *f[10];
585         int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
586         Pop *pop;
587
588         popssl = strncmp(path, "/pops/", 6) == 0;
589         apopssl = strncmp(path, "/apops/", 7) == 0;
590         poptls = strncmp(path, "/poptls/", 8) == 0;
591         popnotls = strncmp(path, "/popnotls/", 10) == 0;
592         ppop = popssl || poptls || popnotls || strncmp(path, "/pop/", 5) == 0;
593         apoptls = strncmp(path, "/apoptls/", 9) == 0;
594         apopnotls = strncmp(path, "/apopnotls/", 11) == 0;
595         apop = apopssl || apoptls || apopnotls || strncmp(path, "/apop/", 6) == 0;
596
597         if(!ppop && !apop)
598                 return Enotme;
599
600         path = strdup(path);
601         if(path == nil)
602                 return "out of memory";
603
604         nf = getfields(path, f, nelem(f), 0, "/");
605         if(nf != 3 && nf != 4) {
606                 free(path);
607                 return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]";
608         }
609
610         pop = emalloc(sizeof *pop);
611         pop->freep = path;
612         pop->host = f[2];
613         if(nf < 4)
614                 pop->user = nil;
615         else
616                 pop->user = f[3];
617         pop->ppop = ppop;
618         pop->needssl = popssl || apopssl;
619         pop->needtls = poptls || apoptls;
620         pop->refreshtime = 60;
621         pop->notls = popnotls || apopnotls;
622         mkmbox(pop, mb->path, mb->path + sizeof mb->path);
623         mb->aux = pop;
624         mb->sync = pop3sync;
625         mb->close = pop3close;
626         mb->ctl = pop3ctl;
627         mb->addfrom = 1;
628         return nil;
629 }