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