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