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 }