tscribo.c - scribo - Email-based phlog generator
 (HTM) git clone git://git.z3bra.org/scribo.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tscribo.c (6629B)
       ---
            1 #include <ctype.h>
            2 #include <dirent.h>
            3 #include <limits.h>
            4 #include <stdio.h>
            5 #include <stdlib.h>
            6 #include <string.h>
            7 #include <time.h>
            8 #include <unistd.h>
            9 
           10 #include <sys/queue.h>
           11 #include <sys/types.h>
           12 
           13 #include "arg.h"
           14 #include "base64.h"
           15 #include "qp.h"
           16 #include "rfc5322.h"
           17 
           18 #include "config.h"
           19 
           20 /* header field */
           21 struct hdr {
           22         char name[BUFSIZ];
           23         char body[BUFSIZ];
           24         SLIST_ENTRY(hdr) entries;
           25 };
           26 
           27 /* header section */
           28 SLIST_HEAD(headers, hdr);
           29 
           30 #ifndef strlcpy
           31 size_t strlcpy(char *dst, const char *src, size_t siz);
           32 #endif
           33 #ifndef strlcat
           34 size_t strlcat(char *dst, const char *src, size_t siz);
           35 #endif
           36 
           37 void usage(char *);
           38 char * sanitize(const char *);
           39 FILE *pipeout(const char *, FILE *);
           40 char * header(struct headers *, char *);
           41 struct hdr * saveheader(struct headers *, char *);
           42 void freeheaders(struct headers *head);
           43 int parseheaders(FILE *, struct headers *);
           44 int verifyheaders(struct headers *);
           45 int write_8bit(FILE *, FILE *);
           46 int write_base64(FILE *, FILE *);
           47 int write_qp(FILE *, FILE *);
           48 int writeentry(FILE *, const char *, char *, struct headers *);
           49 
           50 
           51 void
           52 usage(char *pgm)
           53 {
           54         fprintf(stderr, "usage: %s [-h] [-a address] [-b basedir] [-d fmt] [-x cmd] [file]\n", pgm);
           55 }
           56 
           57 char *
           58 sanitize(const char *s)
           59 {
           60         static char tmp[PATH_MAX];
           61         const char *p;
           62         char *w;
           63 
           64         for (p = s, w = tmp; *p; p++) {
           65                 switch (*p) {
           66                 case '.':
           67                 case '-':
           68                 case '_':
           69                         *(w++) = *p;
           70                         break;
           71                 default:
           72                         if (isblank(*p)) *(w++) = '-';
           73                         if (isalnum(*p)) *(w++) = tolower(*p);
           74                 }
           75         }
           76 
           77         return tmp;
           78 }
           79 
           80 FILE *
           81 pipeout(const char *cmd, FILE *out)
           82 {
           83         int fd[2];
           84         char *sh;
           85 
           86         if (pipe(fd) < 0)
           87                 return NULL;
           88 
           89         if (!(sh = getenv("SHELL")))
           90                 sh = "/bin/sh";
           91 
           92         if (!fork()) {
           93                 close(fd[1]);
           94                 dup2(fd[0], STDIN_FILENO);
           95                 dup2(fileno(out), STDOUT_FILENO);
           96 
           97                 execlp(sh, sh, "-c", cmd, NULL);
           98                 return NULL; /* NOTREACHED */
           99         }
          100 
          101         fclose(out);
          102         close(fd[0]);
          103         return fdopen(fd[1], "w");
          104 }
          105 
          106 char *
          107 header(struct headers *head, char *key)
          108 {
          109         struct hdr *h;
          110         SLIST_FOREACH(h, head, entries) {
          111                 if (!strncmp(h->name, key, 997))
          112                         return h->body;
          113         }
          114 
          115         return NULL;
          116 }
          117 
          118 struct hdr *
          119 saveheader(struct headers *head, char *line)
          120 {
          121         struct hdr *h;
          122 
          123         if (!(h = malloc(sizeof(*h))))
          124                 return NULL;
          125 
          126         strlcpy(h->name, rfc5322_headername(line), sizeof(h->name));
          127         strlcpy(h->body, rfc5322_headerbody(line), sizeof(h->body));
          128         SLIST_INSERT_HEAD(head, h, entries);
          129 
          130         return h;
          131 }
          132 
          133 void
          134 freeheaders(struct headers *head)
          135 {
          136         struct hdr *h;
          137         while ((h = SLIST_FIRST(head))) {
          138                 SLIST_REMOVE_HEAD(head, entries);
          139                 free(h);
          140         }
          141 }
          142 
          143 int
          144 parseheaders(FILE *fp, struct headers *head)
          145 {
          146         char *buf = NULL;
          147         size_t bufsiz = 0;
          148         ssize_t len;
          149         struct hdr *h = NULL;
          150 
          151         SLIST_INIT(head);
          152 
          153         while ((len = getline(&buf, &bufsiz, fp)) > 0) {
          154                 /* a single newline mark the end of header section */
          155                 if (*buf == '\n' || !strncmp(buf, "\r\n", 2))
          156                         break;
          157 
          158                 if (isblank(*buf) && h)
          159                         rfc5322_unfold(h->body, buf, sizeof(h->body));
          160 
          161                 if (!isblank(*buf))
          162                         h = saveheader(head, buf);
          163         }
          164 
          165         if (len < 0) {
          166                 perror("getline");
          167                 free(buf);
          168                 return -1;
          169         }
          170 
          171         free(buf);
          172 
          173         return 0;
          174 }
          175 
          176 int
          177 verifyheaders(struct headers *head)
          178 {
          179         char *addr, *type;
          180 
          181         if (!head)
          182                 return -1;
          183 
          184         if (!header(head, "From")) {
          185                 fprintf(stderr, "Missing header: From\n");
          186                 return -1;
          187         }
          188 
          189         if (!header(head, "Date")) {
          190                 fprintf(stderr, "Missing header: Date\n");
          191                 return -1;
          192         }
          193 
          194         if (!header(head, "Subject")) {
          195                 fprintf(stderr, "Missing header: Subject\n");
          196                 return -1;
          197         }
          198 
          199 
          200         /* only accept plain text emails */
          201         type = header(head, "Content-Type");
          202         if (type && strncmp(type, "text/plain", 10)) {
          203                 fprintf(stderr, "Content-Type: %s is not supported\n", type);
          204                 return -1;
          205         }
          206 
          207         /* verify sender's address */
          208         addr = rfc5322_addr(header(head, "From"));
          209         if (author && strncmp(addr, author, strlen(author))) {
          210                 fprintf(stderr, "<%s> is not authorized to publish content\n", addr);
          211                 return -1;
          212         }
          213 
          214         return 0;
          215 }
          216 
          217 int
          218 write_8bit(FILE *in, FILE *out)
          219 {
          220         ssize_t len;
          221         char buf[BUFSIZ];
          222 
          223         while ((len = fread(buf, 1, sizeof(buf), in)))
          224                 fwrite(buf, 1, len, out);
          225 
          226         return 0;
          227 }
          228 
          229 int
          230 write_base64(FILE *in, FILE *out)
          231 {
          232         size_t n, bufsiz;
          233         ssize_t len;
          234         char *msg, *line, *b64;
          235 
          236         b64 = NULL;
          237         bufsiz = 0;
          238 
          239         line = NULL;
          240         n = 0;
          241 
          242         while ((len = getline(&line, &n, in)) > 0) {
          243                 bufsiz += len;
          244                 b64 = realloc(b64, bufsiz);
          245                 strlcat(b64, line, bufsiz);
          246         }
          247 
          248         len = base64_unfold(b64, bufsiz);
          249         len = base64_decode(&msg, (unsigned char *)b64, len);
          250 
          251         fwrite(msg, 1, len, out);
          252 
          253         free(b64);
          254         free(msg);
          255 
          256         return 0;
          257 }
          258 
          259 int
          260 write_qp(FILE *in, FILE *out)
          261 {
          262         size_t n, bufsiz;
          263         ssize_t len;
          264         char *msg, *line, *qp;
          265 
          266         qp = NULL;
          267         bufsiz = 0;
          268 
          269         line = NULL;
          270         n = 0;
          271 
          272         while ((len = getline(&line, &n, in)) > 0) {
          273                 qp = realloc(qp, bufsiz + len + 1);
          274                 strlcat(qp, line, bufsiz + len + 1);
          275                 bufsiz += len + 1;
          276         }
          277 
          278         len = qp_decode(&msg, (unsigned char *)qp, bufsiz);
          279 
          280         fwrite(msg, 1, len, out);
          281 
          282         free(qp);
          283         free(msg);
          284 
          285         return 0;
          286 }
          287 
          288 int
          289 writeentry(FILE *in, const char *cmd, char *dir, struct headers *head)
          290 {
          291         FILE *out;
          292         struct tm tm = {.tm_isdst = -1};
          293         char stamp[BUFSIZ];
          294         char *subject, *date, *transfer;
          295         char entry[PATH_MAX];
          296 
          297         subject = header(head, "Subject");
          298         date = header(head, "Date");
          299         transfer = header(head, "Content-Transfer-Encoding");
          300 
          301         snprintf(entry, sizeof(entry), "%s/%s%s", dir, sanitize(subject), ext);
          302         out = fopen(entry, "w");
          303         if (!out) {
          304                 perror(entry);
          305                 return -1;
          306         }
          307 
          308         /* convert date to an appropriate format */
          309         strptime(date, "%a, %d %b %Y %T %z", &tm);
          310         strftime(stamp, sizeof(stamp), datefmt, &tm);
          311 
          312         fprintf(out, titlefmt, subject);
          313 
          314         /* pipe email body through the given command, if any */
          315         if (cmd && !(out = pipeout(cmd, out))) {
          316                 perror(cmd);
          317                 return -1;
          318         }
          319 
          320         if (transfer && !strncmp(transfer, "base64", 6))
          321                 write_base64(in, out);
          322         if (transfer && !strncmp(transfer, "quoted-printable", 16))
          323                 write_qp(in, out);
          324         else
          325                 write_8bit(in, out);
          326 
          327         fprintf(out, "\n%s\n", stamp);
          328         fclose(out);
          329 
          330         return 0;
          331 }
          332 
          333 int
          334 main(int argc, char *argv[])
          335 {
          336         FILE *in = stdin;
          337         char *argv0, *cmd;
          338         struct headers headers;
          339 
          340         cmd = NULL;
          341 
          342         ARGBEGIN {
          343         case 'a':
          344                 author = EARGF(usage(argv0));
          345                 break;
          346         case 'b':
          347                 basedir = EARGF(usage(argv0));
          348                 break;
          349         case 'd':
          350                 datefmt = EARGF(usage(argv0));
          351                 break;
          352         case 'x':
          353                 cmd = EARGF(usage(argv0));
          354                 break;
          355         default:
          356                 usage(argv0);
          357                 exit(1);
          358         } ARGEND;
          359 
          360         if (argc && !(in = fopen(*argv, "r"))) {
          361                 perror(*argv);
          362                 return -1;
          363         }
          364 
          365         if (chdir(basedir) < 0) {
          366                 perror(basedir);
          367                 return -1;
          368         }
          369 
          370         if (parseheaders(in, &headers) < 0)
          371                 return -1;
          372 
          373         if (verifyheaders(&headers) < 0)
          374                 return -1;
          375 
          376         if (writeentry(in, cmd, basedir, &headers) < 0)
          377                 return -1;
          378 
          379         fclose(in);
          380 
          381         freeheaders(&headers);
          382 
          383         return 0;
          384 }