tttm

git clone https://orangeshoelaces.net/git/tttm.git

/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 }