scribo

Email-based phlog generator
git clone git://git.z3bra.org/scribo.git
Log | Files | Refs | README | LICENSE

scribo.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 }