tttm

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

/tttm.c

   1 #include <errno.h>
   2 #include <fcntl.h>
   3 #include <libgen.h>
   4 #include <signal.h>
   5 #include <stdint.h>
   6 #include <stdlib.h>
   7 #include <unistd.h>
   8 #include <sys/mman.h>
   9 #include <sys/wait.h>
  10 #include <sys/stat.h>
  11 
  12 #include "errors.h"
  13 #include "parser.h"
  14 #include "imap.h"
  15 
  16 #define MIN(a, b) ((a) <= (b) ? (a) : (b))
  17 
  18 /* Run fetch, pipe and delete cycle on this many messages a pop.  */
  19 #define BATCHSZ 10
  20 
  21 /* Retry fetching mail at most this many times after getting a nil fetch.  */
  22 #define MAXREFC 2
  23 
  24 static int      pipemsg(struct msgd *, char *, char **);
  25 static int      sink_init(char **, pid_t *, int *);
  26 static int      sink_free(pid_t, int);
  27 static void     fin(struct imapctx *, int, const char *);
  28 static void     terr(int, const char *);
  29 static void     twarn(int, const char *);
  30 static void     usage(void);
  31 
  32 static char     *userkey = "IMAP_USER";
  33 static char     *passkey = "IMAP_PASS";
  34 
  35 char *prog;
  36 
  37 int
  38 main(int argc, char **argv)
  39 {
  40         int e;
  41         struct imapctx con;
  42         char *user, *pass;
  43         int refetchc, batchsz, mpiped;
  44         struct msgd bag[BATCHSZ], *m, *bend;
  45         long sysvar;
  46 
  47         prog = basename(argv[0]);
  48 
  49         #ifdef __OpenBSD__
  50         if(pledge("stdio proc exec", 0) == -1) {
  51                 err(1, "pledge");
  52         }
  53         #endif
  54 
  55         if (argc < 2) {
  56                 usage();
  57         }
  58 
  59         if ((e = imap_init(&con, STDIN_FILENO, STDOUT_FILENO))) {
  60                 terr(e, "failed to initialize session");
  61         }
  62         if (con.state == IS_AUTH) {
  63                 goto select;
  64         }
  65 
  66         if (!(user = getenv(userkey))) {
  67                 warnx("cannot find user name within environment");
  68                 goto logout;
  69         }
  70         if (!(pass = getenv(passkey))) {
  71                 warnx("cannot find password within environment");
  72                 goto logout;
  73         }
  74 
  75         e = imap_login(&con, user, pass);
  76         if (e == TE_NO) {
  77                 warnx("login credentials rejected");
  78                 goto logout;
  79         } else if (e == TE_BAD) {
  80                 warnx("failed to log in: %s", errmsgs[e]);
  81                 goto logout;
  82         } else if (e) {
  83                 terr(e, "failed to log in");
  84         }
  85 
  86  select:
  87         if (unsetenv(userkey) == -1 || unsetenv(passkey) == -1) {
  88                 warn("failed to wipe credentials off of environment");
  89                 goto logout;
  90         }
  91 
  92         if ((e = imap_select(&con, "inbox"))) {
  93                 fin(&con, e, "failed to select mailbox");
  94         }
  95         refetchc = 0;
  96         while (con.mb.exists > 0) {
  97                 batchsz = MIN(BATCHSZ, con.mb.exists);
  98                 bend = bag + batchsz;
  99                 e = imap_fetch(&con, 1, batchsz, bag);
 100                 if (e == TE_NIL && refetchc < MAXREFC) {
 101                         if ((e = imap_nop(&con))) {
 102                                 fin(&con, e, "failed to receive updates");
 103                         }
 104                         refetchc++;
 105                         continue;
 106                 } else if (e) {
 107                         fin(&con, e, "failed to fetch mail");
 108                 }
 109                 refetchc = 0;
 110                 for (m = bag; m < bend; m++) {
 111                         if (pipemsg(m, con.resp, argv + 1) == -1)
 112                                 break;
 113                 }
 114                 mpiped = m - bag;
 115                 if (mpiped == 0) {
 116                         break;
 117                 }
 118                 if ((e = imap_delete(&con, 1, mpiped))) {
 119                         fin(&con, e, "failed to delete mail");
 120                 }
 121                 if ((e = imap_expunge(&con))) {
 122                         fin(&con, e, "failed to expunge mail");
 123                 }
 124                 if (mpiped < batchsz) {
 125                         break;
 126                 }
 127         }
 128         if ((e = imap_close(&con))) {
 129                 fin(&con, e, "failed to close mailbox");
 130         }
 131 
 132  logout:
 133         if ((e = imap_logout(&con))) {
 134                 fin(&con, e, "failed to log out");
 135         }
 136         imap_free(&con);
 137         return 0;
 138 }
 139 
 140 int
 141 pipemsg(struct msgd *m, char *stor, char **argv)
 142 {
 143         int e, sin;
 144         void (*origpipe)(int);
 145         char *cur;
 146         size_t len;
 147         pid_t sid;
 148         ssize_t n;
 149 
 150         e = -1;
 151         cur = stor + m->off;
 152         len = m->len;
 153         n = -2;
 154 
 155         if ((origpipe = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) {
 156                 warn("failed to install SIGPIPE handler");
 157                 goto exit;
 158         }
 159         if (sink_init(argv, &sid, &sin) == -1)
 160                 goto csig;
 161 
 162         while (len > 0) {
 163                 if ((n = write(sin, cur, len)) == 0) {
 164                         break;
 165                 } else if (n == -1) {
 166                         if (errno == EINTR || errno == EAGAIN)
 167                                 continue;
 168                         else break;
 169                 }
 170                 cur += n;
 171                 len -= n;
 172         }
 173         if (n == 0) {
 174                 warn("sink shut down unexpectedly");
 175                 goto csink;
 176         } else if (n == -1) {
 177                 warn("failed to write to sink");
 178                 goto csink;
 179         }
 180         e = 0;
 181  csink:
 182         if (sink_free(sid, sin) == -1)
 183                 e = -1;
 184  csig:
 185         if (signal(SIGPIPE, origpipe) == SIG_ERR) {
 186                 e = -1;
 187                 warn("failed to restore SIGPIPE handler");
 188         }
 189  exit:
 190         return e;
 191 }
 192 
 193 int
 194 sink_init(char **argv, pid_t *sid, int *sin)
 195 {
 196         int e;
 197         int pd[2], r, w;
 198 
 199         e = -1;
 200         if (pipe(pd) == -1) {
 201                 warn("failed to create pipe");
 202                 goto exit;
 203         }
 204         r = pd[0];
 205         *sin = w = pd[1];
 206         *sid = fork();
 207         if (*sid == -1) {
 208                 warn("failed to fork");
 209                 close(r);
 210                 close(w);
 211                 goto exit;
 212         } else if (*sid == 0) {
 213                 if (dup2(2, 1) == -1 || dup2(r, 0) == -1 ||
 214                     close(r) == -1 || close(w) == -1) {
 215                         warn("failed to setup sink");
 216                         _exit(1);
 217                 }
 218                 execvp(argv[0], argv);
 219                 warn("failed to launch sink");
 220                 _exit(1);
 221         }
 222         if (close(r) == -1) {
 223                 warn("failed to let read end of pipe loose");
 224                 close(w);
 225                 goto exit;
 226         }
 227         e = 0;
 228  exit:
 229         return e;
 230 }
 231 
 232 int
 233 sink_free(pid_t sid, int in)
 234 {
 235         int e, s;
 236 
 237         e = -1;
 238         if (close(in) == -1) {
 239                 warn("failed to close write end of pipe");
 240                 if (kill(sid, SIGINT) == -1) {
 241                         warn("failed to shut down sink");
 242                         goto exit;
 243                 }
 244         }
 245         if (waitpid(sid, &s, 0) == -1) {
 246                 warn("failed to randezvous with sink");
 247                 goto exit;
 248         }
 249         if (!WIFEXITED(s)) {
 250                 warnx("sink process terminated");
 251                 goto exit;
 252         }
 253         if (WEXITSTATUS(s)) {
 254                 warnx("sink process failed with exit satus %d",
 255                     (int)WEXITSTATUS(s));
 256                 goto exit;
 257         }
 258         e = 0;
 259  exit:
 260         return e;
 261 }
 262 
 263 void
 264 fin(struct imapctx *con, int e, const char *msg)
 265 {
 266         int loerr;
 267 
 268         if (e == TE_NO || e == TE_BAD || e == TE_NIL) {
 269                 if ((loerr = imap_logout(con)))
 270                         twarn(loerr, "failed to log out");
 271         }
 272         imap_free(con);
 273         terr(e, msg);
 274 }
 275 
 276 void
 277 terr(int e, const char *msg)
 278 {
 279         if (e >= TE_ERRNO) {
 280                 err(1, "%s: %s", msg, errmsgs[e]);
 281         } else {
 282                 errx(1, "%s: %s", msg, errmsgs[e]);
 283         }
 284 }
 285 
 286 void
 287 twarn(int e, const char *msg)
 288 {
 289         if (e >= TE_ERRNO) {
 290                 warn("%s: %s", msg, errmsgs[e]);
 291         } else {
 292                 warnx("%s: %s", msg, errmsgs[e]);
 293         }
 294 }
 295 
 296 void
 297 usage(void)
 298 {
 299         errx(1, "usage: tttm sink [argument ...]");
 300 }