changelog shortlog tags branches changeset files revisions annotate raw help

Mercurial > hg > plan9front / sys/src/cmd/upas/fs/pop3.c

changeset 7251: c67d805b0614
parent: 5f04b19f5ad5
author: Alex Musolino <alex@musolino.id.au>
date: Mon, 20 May 2019 15:00:14 +0930
permissions: -rw-r--r--
description: upas/fs: remove read timeout via alarm(2) in pop3resp

The alarm note is not handled by upas/fs, so if and when it did fire,
the pop3 client process would terminate rendering the entire fs
unresponsive.
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 }