/imap.c
1 #include <errno.h>
2 #include <limits.h>
3 #include <poll.h>
4 #include <stdarg.h>
5 #include <stddef.h>
6 #include <stdint.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <strings.h>
11 #include <unistd.h>
12
13 #include "errors.h"
14 #include "parser.h"
15 #include "imap.h"
16
17 #define LEN(x) (sizeof(x) / sizeof((x)[0]))
18 #define MIN(a, b) ((a) <= (b) ? (a) : (b))
19
20 static struct capmap {
21 int cap;
22 char *srep;
23 size_t slen;
24 } caps[] = {
25 { CAP_IMAP4R1, "IMAP4rev1", 9 },
26 { CAP_NOLOGIN, "LOGINDISABLED", 13 },
27 { 0 },
28 }, authcaps[] = {
29 { CAP_AUTHPLAIN, "PLAIN", 5 },
30 { 0 },
31 };
32
33 struct respcard {
34 int sel;
35 int (*handler)(struct imapctx *, void *);
36 int req:1;
37 int met:1;
38 void *ctx;
39 };
40
41 struct fetargs {
42 uint32_t lo;
43 uint32_t hi;
44 struct msgd *bag;
45 };
46
47 static int imap_cmd(struct imapctx *, int, const char *, struct respcard *,
48 struct respcard *, ...);
49 static int imap_confirm(struct imapctx *, const char *, struct respcard *,
50 va_list);
51 static int imap_cooloff(struct imapctx *);
52 static int imap_ishot(struct imapctx *);
53 static int imap_matchres(struct imapctx *, int *);
54 static int imap_matchresp(struct imapctx *, struct respcard *, int *);
55 static int imap_parcaps(struct imapctx *, union parnode *);
56 static int imap_readln(struct imapctx *);
57 static int imap_recvgreets(struct imapctx *);
58 static int imap_send(struct imapctx *, const char *, struct respcard *,
59 va_list);
60
61 static int dig_bye(struct imapctx *, void *);
62 static int dig_uxbye(struct imapctx *, void *);
63 static int dig_caps(struct imapctx *, void *);
64 static int dig_exists(struct imapctx *, void *);
65 static int dig_expunge(struct imapctx *, void *);
66 static int dig_fetch(struct imapctx *, void *);
67
68 static int state2res(union parnode *);
69 static int str2cap(struct capmap *, char *, size_t);
70 static int isprod(union parnode *, int);
71 static int islit(union parnode *, int);
72
73 #define UNILAT_COMMON \
74 { IP_EXISTS, dig_exists },\
75 { IP_RESPBYE, dig_uxbye }
76
77 static struct respcard unilat[] = {
78 UNILAT_COMMON,
79 { IP_EXPUNGE, dig_expunge },
80 { -1 },
81 };
82
83 static struct respcard unisexp[] = {
84 UNILAT_COMMON,
85 { -1 },
86 };
87
88 static struct respcard capresp[] = { { IP_CAPDATA, dig_caps, 1 }, { -1 } };
89 static struct respcard selresp[] = { { IP_EXISTS, dig_exists, 1 }, { -1 } };
90 static struct respcard fetresp[] = { { IP_FETCH, dig_fetch }, { -1 } };
91 static struct respcard byeresp[] = { { IP_RESPBYE, dig_bye, 1 }, { -1 } };
92
93 int
94 imap_init(struct imapctx *con, int in, int out)
95 {
96 int e;
97
98 bzero(con, sizeof(*con));
99 con->state = IS_GREET;
100 con->in = in;
101 con->out = out;
102 if ((e = imap_recvgreets(con)))
103 goto exit;
104 if (!(con->caps & CAP_KNOWN))
105 e = imap_discocap(con);
106 exit:
107 return e;
108 emem:
109 e = TE_NOMEM;
110 goto exit;
111 }
112
113 void
114 imap_free(struct imapctx *con)
115 {
116 free(con->resp);
117 }
118
119 int
120 imap_discocap(struct imapctx *con)
121 {
122 return imap_cmd(con, -1, "%u CAPABILITY\r\n", capresp, unilat, con->tag++);
123 }
124
125 int
126 imap_login(struct imapctx *con, const char *user, const char *pass)
127 {
128 return imap_cmd(con, IS_AUTH, "%u LOGIN %LS %LS\r\n", 0, unilat,
129 con->tag++, user, pass);
130 }
131
132 int
133 imap_select(struct imapctx *con, const char *mb)
134 {
135 return imap_cmd(con, IS_SEL, "%u SELECT %LS\r\n", selresp, unilat,
136 con->tag++, mb);
137 }
138
139 int
140 imap_fetch(struct imapctx *con, uint32_t lo, uint32_t hi, struct msgd *bag)
141 {
142 int e;
143 struct msgd *m, *bend;
144 struct fetargs args;
145
146 bend = bag + (hi - lo + 1);
147 for (m = bag; m < bend; m++) {
148 m->off = -1;
149 }
150 args = (struct fetargs){ lo, hi, bag };
151 fetresp->ctx = &args;
152 e = imap_cmd(con, -1, "%u FETCH %u:%u RFC822\r\n", fetresp, unisexp,
153 con->tag++, (unsigned)lo, (unsigned)hi);
154 if (e)
155 goto exit;
156 for (m = bag; m < bend; m++) {
157 if (m->off == -1)
158 goto eproto;
159 }
160 exit:
161 return e;
162 eproto:
163 e = TE_PROTO;
164 goto exit;
165 }
166
167 int
168 imap_delete(struct imapctx *con, uint32_t lo, uint32_t hi)
169 {
170 return imap_cmd(con, -1, "%u STORE %u:%u +FLAGS.SILENT (\\Deleted)\r\n",
171 0, unisexp, con->tag++, (unsigned)lo, (unsigned)hi);
172 }
173
174 int
175 imap_expunge(struct imapctx *con)
176 {
177 return imap_cmd(con, -1, "%u EXPUNGE\r\n", 0, unilat, con->tag++);
178 }
179
180 int
181 imap_nop(struct imapctx *con)
182 {
183 return imap_cmd(con, -1, "%u NOOP\r\n", 0, unilat, con->tag++);
184 }
185
186 int
187 imap_close(struct imapctx *con)
188 {
189 return imap_cmd(con, IS_AUTH, "%u CLOSE\r\n", 0, unilat, con->tag++);
190 }
191
192 int
193 imap_logout(struct imapctx *con)
194 {
195 return imap_cmd(con, IS_LOGOUT, "%u LOGOUT\r\n", byeresp, unilat,
196 con->tag++);
197 }
198
199 int
200 imap_cmd(struct imapctx *con, int nstate, const char *cmd,
201 struct respcard *cmdresp, struct respcard *unilat, ...)
202 {
203 int e;
204 va_list ap;
205 union parnode *s;
206 struct respcard *c;
207
208 va_start(ap, unilat);
209 if (con->rpar > con->rcap / 2) {
210 memmove(con->resp, con->resp + con->rpar,
211 con->rlen - con->rpar);
212 con->rlen -= con->rpar;
213 con->rpar = 0;
214 }
215 if ((e = imap_cooloff(con))) {
216 goto exit;
217 }
218 if ((e = imap_send(con, cmd, unilat, ap))) {
219 goto exit;
220 }
221 for (c = cmdresp; c && c->sel != -1; c++) {
222 c->met = 0;
223 }
224 while (!(e = imap_readln(con))) {
225 if (imap_matchres(con, &e)) {
226 break;
227 }
228 if ((imap_matchresp(con, cmdresp, &e) ||
229 imap_matchresp(con, unilat, &e)) && e) {
230 break;
231 }
232 }
233 if (e) {
234 goto exit;
235 }
236 for (c = cmdresp; c && c->sel != -1; c++) {
237 if (c->req && !c->met)
238 goto eproto;
239 }
240 if (nstate != -1) {
241 con->state = nstate;
242 }
243 e = imap_cooloff(con);
244 exit:
245 va_end(ap);
246 return e;
247 eproto:
248 e = TE_PROTO;
249 goto exit;
250 }
251
252 int
253 imap_confirm(struct imapctx *con, const char *excerpt,
254 struct respcard *unilat, va_list ap)
255 {
256 int e;
257
258 if (vdprintf(con->out, excerpt, ap) < 0)
259 goto eout;
260
261 while (!(e = imap_readln(con))) {
262 if (isprod(con->par, IP_CONTREQ) ||
263 imap_matchres(con, &e)) {
264 break;
265 }
266 if (imap_matchresp(con, unilat, &e) && e) {
267 break;
268 }
269 }
270 exit:
271 return e;
272 eout:
273 e = TE_OUT;
274 goto exit;
275 }
276
277 int
278 imap_cooloff(struct imapctx *con)
279 {
280 int e;
281
282 while (!(e = imap_ishot(con))) {
283 if ((e = imap_readln(con)))
284 goto exit;
285 if (imap_matchresp(con, unisexp, &e) && e)
286 goto exit;
287 }
288 if (e == TE_TIMEOUT) {
289 e = 0;
290 }
291 exit:
292 return e;
293 }
294
295 int
296 imap_ishot(struct imapctx *con)
297 {
298 int e, ready;
299 struct pollfd pd;
300
301 e = 0;
302 if (con->rlen > con->rpar)
303 goto exit;
304
305 pd.fd = con->in;
306 pd.events = POLLIN | POLLHUP;
307 if ((ready = poll(&pd, 1, 0)) == -1) {
308 e = TE_IN;
309 } else if (ready == 0) {
310 e = TE_TIMEOUT;
311 }
312
313 exit:
314 return e;
315 }
316
317 int
318 imap_matchres(struct imapctx *con, int *e)
319 {
320 union parnode *s, *c;
321
322 if ((s = par_sel(con->par, IP_TAGGEDRESP, IP_RESPSTATE, -1))) {
323 if ((c = par_seld(s, IP_RESPTXTCODE, IP_CAPDATA, -1))) {
324 imap_parcaps(con, c);
325 }
326 *e = state2res(s);
327 }
328 return s != 0;
329 }
330
331 int
332 imap_matchresp(struct imapctx *con, struct respcard *set, int *e)
333 {
334 int match;
335 struct respcard *c;
336
337 match = 0;
338 for (c = set; c && c->sel != -1; c++) {
339 if (par_sel(con->par, IP_RESPDATA, c->sel, -1)) {
340 c->met = match = 1;
341 *e = c->handler ? c->handler(con, c->ctx) : 0;
342 break;
343 }
344 }
345 return match;
346 }
347
348 int
349 imap_parcaps(struct imapctx *con, union parnode *n)
350 {
351 union parnode *nend, *atom;
352 char *str;
353 size_t len;
354
355 for (nend = n + n->inter.len + 1, n++; n < nend; n = par_nn(n)) {
356 if (!isprod(n, IP_CAP))
357 continue;
358 atom = par_seld(n, IP_ATOM, -1);
359 str = con->resp + (atom + 1)->str.off;
360 len = (atom + 1)->str.len;
361 if (islit(n + 1, IL_AUTHEQ)) {
362 con->caps |= str2cap(authcaps, str, len);
363 } else {
364 con->caps |= str2cap(caps, str, len);
365 }
366 }
367 con->caps |= CAP_KNOWN;
368 return 0;
369 }
370
371 int
372 imap_readln(struct imapctx *con)
373 {
374 return par_parseln(con->in, &con->resp, &con->rcap, &con->rlen,
375 &con->rpar, con->par, LEN(con->par));
376 }
377
378 int
379 imap_recvgreets(struct imapctx *con)
380 {
381 int e;
382 union parnode *n;
383
384 if ((e = imap_readln(con))) {
385 con->state = IS_LOGOUT;
386 goto exit;
387 }
388
389 if ((n = par_sel(con->par, IP_RESPDATA, IP_RESPAUTH, -1))) {
390 con->state = IS_AUTH;
391 } else if ((n = par_sel(con->par, IP_RESPDATA, IP_RESPSTATE, -1))) {
392 con->state = IS_NAUTH;
393 } else if ((n = par_sel(con->par, IP_RESPDATA, IP_RESPBYE, -1))) {
394 con->state = IS_LOGOUT;
395 goto ebye;
396 } else {
397 goto eproto;
398 }
399
400 if ((n = par_seld(n, IP_RESPTXTCODE, IP_CAPDATA, -1)))
401 e = imap_parcaps(con, n);
402 exit:
403 return e;
404 ebye:
405 e = TE_BYE;
406 goto exit;
407 eproto:
408 e = TE_PROTO;
409 goto exit;
410 }
411
412 int
413 imap_send(struct imapctx *con, const char *cmd, struct respcard *unilat,
414 va_list ap)
415 {
416 int e, n;
417 va_list nap;
418 const char *pend, *ls, *s;
419 const char *lit;
420 char pad[128];
421 size_t xlen;
422
423 e = 0;
424 for (pend = cmd; (ls = strstr(pend, "%LS")); pend = ls + 3) {
425 va_copy(nap, ap);
426 for (s = pend; (s = strchr(s, '%')) && s < ls; s++) {
427 switch (*(s + 1)) {
428 case 'd':
429 case 'i':
430 case 'o':
431 case 'u':
432 case 'x':
433 case 'X':
434 case 'c':
435 va_arg(nap, unsigned);
436 break;
437 case 's':
438 case 'p':
439 va_arg(nap, void *);
440 break;
441 default:
442 goto efmt;
443 }
444 }
445 lit = va_arg(nap, char *);
446 if ((xlen = ls - pend) > INT_MAX)
447 goto exl;
448 n = snprintf(pad, LEN(pad), "%.*s{%u}\r\n",
449 (int)xlen, pend, strlen(lit));
450 if (n < 0 || n >= LEN(pad))
451 goto exl;
452 if ((e = imap_confirm(con, pad, unilat, ap)))
453 goto exit;
454 if (dprintf(con->out, "%s", lit) < 0)
455 goto eout;
456 va_end(ap);
457 ap = nap;
458 }
459 if (*pend && vdprintf(con->out, pend, ap) < 0)
460 goto eout;
461 exit:
462 va_end(ap);
463 if (ls)
464 va_end(nap);
465 return e;
466 efmt:
467 e = TE_CMDFMT;
468 goto exit;
469 eout:
470 e = TE_OUT;
471 goto exit;
472 exl:
473 e = TE_XLCMD;
474 goto exit;
475 }
476
477 int
478 dig_bye(struct imapctx *con, void *ctx)
479 {
480 con->state = IS_LOGOUT;
481 return 0;
482 }
483
484 int
485 dig_uxbye(struct imapctx *con, void *ctx)
486 {
487 con->state = IS_LOGOUT;
488 return TE_BYE;
489 }
490
491 int
492 dig_caps(struct imapctx *con, void *ctx)
493 {
494 union parnode *c;
495
496 c = par_sel(con->par, IP_RESPDATA, IP_CAPDATA, -1);
497 return c ? imap_parcaps(con, c) : 0;
498 }
499
500 int
501 dig_exists(struct imapctx *con, void *ctx)
502 {
503 union parnode *ex;
504
505 ex = par_sel(con->par, IP_RESPDATA, IP_EXISTS, -1);
506 con->mb.exists = (ex + 1)->num.val;
507 return 0;
508 }
509
510 int
511 dig_expunge(struct imapctx *con, void *ctx)
512 {
513 if (con->mb.exists > 0)
514 con->mb.exists--;
515 return 0;
516 }
517
518 int
519 dig_fetch(struct imapctx *con, void *ctx)
520 {
521 int e;
522 struct fetargs *args;
523 union parnode *f, *b, *nstr;
524 uint32_t msn;
525 struct msgd *m;
526 char *bcur;
527 size_t blen;
528 ssize_t n;
529
530 e = 0;
531 args = ctx;
532 f = par_sel(con->par, IP_RESPDATA, IP_FETCH, -1);
533 msn = (f + 1)->num.val;
534 if (msn < args->lo || msn > args->hi)
535 goto exit;
536 if (!(b = par_seld(f, IP_822BODY, -1)))
537 goto exit;
538 nstr = b + 3;
539 if (islit(nstr, IL_NIL))
540 goto enil;
541 m = args->bag + (msn - args->lo);
542 m->off = nstr->str.off;
543 m->len = nstr->str.len;
544 if (nstr->type != PN_LSTR)
545 goto eproto;
546 exit:
547 return e;
548 enil:
549 e = TE_NIL;
550 goto exit;
551 eproto:
552 e = TE_PROTO;
553 goto exit;
554 }
555
556 int
557 state2res(union parnode *respstate)
558 {
559 int e;
560
561 e = TE_OK;
562 switch ((respstate + 1)->lit.val) {
563 case IL_NO:
564 e = TE_NO;
565 break;
566 case IL_BAD:
567 e = TE_BAD;
568 break;
569 }
570 return e;
571 }
572
573 int
574 str2cap(struct capmap *m, char *str, size_t len)
575 {
576 for (; m->srep; m++) {
577 if (m->slen == len && (strncasecmp(m->srep, str, len) == 0))
578 return m->cap;
579 }
580 return 0;
581 }
582
583 int
584 isprod(union parnode *n, int prod)
585 {
586 return n->type == PN_INTER && n->inter.prod == prod;
587 }
588
589 int
590 islit(union parnode *n, int lit)
591 {
592 return n->type == PN_LIT && n->lit.val == lit;
593 }