Mercurial > email
comparison mail.c @ 0:33bf7ee17644
Initial import from some old code
author | Adam Chlipala <adam@chlipala.net> |
---|---|
date | Sat, 03 Mar 2012 14:59:04 -0500 |
parents | |
children | bf58106560ba |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:33bf7ee17644 |
---|---|
1 #include <stdio.h> | |
2 #include <string.h> | |
3 #include <stdlib.h> | |
4 #include <ctype.h> | |
5 #include <sys/types.h> | |
6 #include <sys/socket.h> | |
7 #include <netinet/in.h> | |
8 #include <arpa/inet.h> | |
9 | |
10 #include <urweb.h> | |
11 | |
12 struct headers { | |
13 uw_Basis_string from, to, cc, bcc, subject; | |
14 }; | |
15 | |
16 typedef struct headers *uw_Mail_headers; | |
17 | |
18 uw_Mail_headers uw_Mail_empty = NULL; | |
19 | |
20 static void header(uw_context ctx, uw_Basis_string s) { | |
21 if (strlen(s) > 100) | |
22 uw_error(ctx, FATAL, "Header value too long"); | |
23 | |
24 for (; *s; ++s) | |
25 if (*s == '\r' || *s == '\n') | |
26 uw_error(ctx, FATAL, "Header value contains newline"); | |
27 } | |
28 | |
29 static void address(uw_context ctx, uw_Basis_string s) { | |
30 header(ctx, s); | |
31 | |
32 if (strchr(s, ',')) | |
33 uw_error(ctx, FATAL, "E-mail address contains comma"); | |
34 } | |
35 | |
36 uw_Mail_headers uw_Mail_from(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { | |
37 // char **allowed = uw_get_global(ctx, "mail_from"); | |
38 // Might add this policy checking (or some expanded version of it) back later. | |
39 uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); | |
40 | |
41 if (h) | |
42 *h2 = *h; | |
43 else | |
44 memset(h2, 0, sizeof(*h2)); | |
45 | |
46 if (h2->from) | |
47 uw_error(ctx, FATAL, "Duplicate From header"); | |
48 | |
49 /* | |
50 if (!allowed) | |
51 uw_error(ctx, FATAL, "No From address whitelist has been set. Perhaps you are not authorized to send e-mail."); | |
52 | |
53 if (!(allowed[0] && !strcmp(allowed[0], "*"))) { | |
54 for (; *allowed; ++allowed) | |
55 if (!strcmp(*allowed, s)) | |
56 goto ok; | |
57 | |
58 uw_error(ctx, FATAL, "From address is not in whitelist"); | |
59 } | |
60 | |
61 ok: | |
62 */ | |
63 address(ctx, s); | |
64 h2->from = s; | |
65 | |
66 return h2; | |
67 } | |
68 | |
69 uw_Mail_headers uw_Mail_to(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { | |
70 uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); | |
71 if (h) | |
72 *h2 = *h; | |
73 else | |
74 memset(h2, 0, sizeof(*h2)); | |
75 | |
76 address(ctx, s); | |
77 if (h2->to) { | |
78 uw_Basis_string all = uw_malloc(ctx, strlen(h2->to) + 1 + strlen(s)); | |
79 sprintf(all, "%s,%s", h2->to, s); | |
80 h2->to = all; | |
81 } else | |
82 h2->to = s; | |
83 | |
84 return h2; | |
85 } | |
86 | |
87 uw_Mail_headers uw_Mail_cc(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { | |
88 uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); | |
89 if (h) | |
90 *h2 = *h; | |
91 else | |
92 memset(h2, 0, sizeof(*h2)); | |
93 | |
94 address(ctx, s); | |
95 if (h2->cc) { | |
96 uw_Basis_string all = uw_malloc(ctx, strlen(h2->cc) + 1 + strlen(s)); | |
97 sprintf(all, "%s,%s", h2->cc, s); | |
98 h2->cc = all; | |
99 } else | |
100 h2->cc = s; | |
101 | |
102 return h2; | |
103 } | |
104 | |
105 uw_Mail_headers uw_Mail_bcc(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { | |
106 uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); | |
107 if (h) | |
108 *h2 = *h; | |
109 else | |
110 memset(h2, 0, sizeof(*h2)); | |
111 | |
112 address(ctx, s); | |
113 if (h2->bcc) { | |
114 uw_Basis_string all = uw_malloc(ctx, strlen(h2->bcc) + 1 + strlen(s)); | |
115 sprintf(all, "%s,%s", h2->bcc, s); | |
116 h2->bcc = all; | |
117 } else | |
118 h2->bcc = s; | |
119 | |
120 return h2; | |
121 } | |
122 | |
123 uw_Mail_headers uw_Mail_subject(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { | |
124 uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); | |
125 | |
126 if (h) | |
127 *h2 = *h; | |
128 else | |
129 memset(h2, 0, sizeof(*h2)); | |
130 | |
131 if (h2->subject) | |
132 uw_error(ctx, FATAL, "Duplicate Subject header"); | |
133 | |
134 header(ctx, s); | |
135 h2->subject = s; | |
136 | |
137 return h2; | |
138 } | |
139 | |
140 typedef struct { | |
141 uw_context ctx; | |
142 uw_Mail_headers h; | |
143 uw_Basis_string body, xbody; | |
144 } job; | |
145 | |
146 #define BUFLEN 1024 | |
147 | |
148 static int smtp_read(uw_context ctx, int sock, char *buf, ssize_t *pos) { | |
149 char *s; | |
150 | |
151 while (1) { | |
152 ssize_t recvd; | |
153 | |
154 buf[*pos] = 0; | |
155 | |
156 if ((s = strchr(buf, '\n'))) { | |
157 int n; | |
158 | |
159 *s = 0; | |
160 | |
161 if (sscanf(buf, "%d ", &n) != 1) { | |
162 close(sock); | |
163 uw_set_error_message(ctx, "Mail server response does not begin with a code."); | |
164 return 0; | |
165 } | |
166 | |
167 *pos -= s - buf + 1; | |
168 memmove(buf, s+1, *pos); | |
169 | |
170 return n; | |
171 } | |
172 | |
173 recvd = recv(sock, buf + *pos, BUFLEN - *pos - 1, 0); | |
174 | |
175 if (recvd == 0) { | |
176 close(sock); | |
177 uw_set_error_message(ctx, "Mail server response ends unexpectedly."); | |
178 return 0; | |
179 } else if (recvd < 0) { | |
180 close(sock); | |
181 uw_set_error_message(ctx, "Error reading mail server response."); | |
182 return 0; | |
183 } | |
184 | |
185 *pos += recvd; | |
186 } | |
187 } | |
188 | |
189 static int really_string(int sock, const char *s) { | |
190 return uw_really_send(sock, s, strlen(s)); | |
191 } | |
192 | |
193 static int sendAddrs(const char *kind, uw_context ctx, int sock, char *s, char *buf, ssize_t *pos) { | |
194 char *p; | |
195 char out[BUFLEN]; | |
196 | |
197 if (!s) | |
198 return 0; | |
199 | |
200 for (p = strchr(s, ','); p; p = strchr(p+1, ',')) { | |
201 *p = 0; | |
202 | |
203 snprintf(out, sizeof(out), "RCPT TO:%s\n", s); | |
204 out[sizeof(out)-1] = 0; | |
205 *p = ','; | |
206 | |
207 if (really_string(sock, out) < 0) { | |
208 close(sock); | |
209 uw_set_error_message(ctx, "Error sending RCPT TO for %s", kind); | |
210 return 1; | |
211 } | |
212 | |
213 if (smtp_read(ctx, sock, buf, pos) != 250) { | |
214 close(sock); | |
215 uw_set_error_message(ctx, "Mail server doesn't respond to %s RCPT TO with code 250.", kind); | |
216 return 1; | |
217 } | |
218 } | |
219 | |
220 if (*s) { | |
221 snprintf(out, sizeof(out), "RCPT TO:%s\n", s); | |
222 out[sizeof(out)-1] = 0; | |
223 | |
224 if (really_string(sock, out) < 0) { | |
225 close(sock); | |
226 uw_set_error_message(ctx, "Error sending RCPT TO for %s", kind); | |
227 return 1; | |
228 } | |
229 | |
230 if (smtp_read(ctx, sock, buf, pos) != 250) { | |
231 close(sock); | |
232 uw_set_error_message(ctx, "Mail server doesn't respond to %s RCPT TO with code 250.", kind); | |
233 return 1; | |
234 } | |
235 } | |
236 | |
237 return 0; | |
238 } | |
239 | |
240 static void commit(void *data) { | |
241 job *j = data; | |
242 int sock; | |
243 struct sockaddr_in my_addr; | |
244 char buf[BUFLEN], out[BUFLEN]; | |
245 ssize_t pos = 0; | |
246 char *s; | |
247 | |
248 if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { | |
249 uw_set_error_message(j->ctx, "Can't create socket for mail server connection"); | |
250 return; | |
251 } | |
252 | |
253 my_addr.sin_family = AF_INET; | |
254 my_addr.sin_port = htons(25); | |
255 my_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); | |
256 memset(my_addr.sin_zero, 0, sizeof my_addr.sin_zero); | |
257 | |
258 if (connect(sock, (struct sockaddr *)&my_addr, sizeof my_addr) < 0) { | |
259 close(sock); | |
260 uw_set_error_message(j->ctx, "Error connecting to mail server"); | |
261 return; | |
262 } | |
263 | |
264 if (smtp_read(j->ctx, sock, buf, &pos) != 220) { | |
265 close(sock); | |
266 uw_set_error_message(j->ctx, "Mail server doesn't greet with code 220."); | |
267 return; | |
268 } | |
269 | |
270 if (really_string(sock, "HELO localhost\n") < 0) { | |
271 close(sock); | |
272 uw_set_error_message(j->ctx, "Error sending HELO"); | |
273 return; | |
274 } | |
275 | |
276 if (smtp_read(j->ctx, sock, buf, &pos) != 250) { | |
277 close(sock); | |
278 uw_set_error_message(j->ctx, "Mail server doesn't respond to HELO with code 250."); | |
279 return; | |
280 } | |
281 | |
282 snprintf(out, sizeof(out), "MAIL FROM:%s\n", j->h->from); | |
283 out[sizeof(out)-1] = 0; | |
284 | |
285 if (really_string(sock, out) < 0) { | |
286 close(sock); | |
287 uw_set_error_message(j->ctx, "Error sending MAIL FROM"); | |
288 return; | |
289 } | |
290 | |
291 if (smtp_read(j->ctx, sock, buf, &pos) != 250) { | |
292 close(sock); | |
293 uw_set_error_message(j->ctx, "Mail server doesn't respond to MAIL FROM with code 250."); | |
294 return; | |
295 } | |
296 | |
297 if (sendAddrs("To", j->ctx, sock, j->h->to, buf, &pos)) return; | |
298 if (sendAddrs("Cc", j->ctx, sock, j->h->cc, buf, &pos)) return; | |
299 if (sendAddrs("Bcc", j->ctx, sock, j->h->bcc, buf, &pos)) return; | |
300 | |
301 if (really_string(sock, "DATA\n") < 0) { | |
302 close(sock); | |
303 uw_set_error_message(j->ctx, "Error sending DATA"); | |
304 return; | |
305 } | |
306 | |
307 if (smtp_read(j->ctx, sock, buf, &pos) != 354) { | |
308 close(sock); | |
309 uw_set_error_message(j->ctx, "Mail server doesn't respond to DATA with code 354."); | |
310 return; | |
311 } | |
312 | |
313 snprintf(out, sizeof(out), "From: %s\r\n", j->h->from); | |
314 out[sizeof(out)-1] = 0; | |
315 | |
316 if (really_string(sock, out) < 0) { | |
317 close(sock); | |
318 uw_set_error_message(j->ctx, "Error sending From"); | |
319 return; | |
320 } | |
321 | |
322 if (j->h->subject) { | |
323 snprintf(out, sizeof(out), "Subject: %s\r\n", j->h->subject); | |
324 out[sizeof(out)-1] = 0; | |
325 | |
326 if (really_string(sock, out) < 0) { | |
327 close(sock); | |
328 uw_set_error_message(j->ctx, "Error sending Subject"); | |
329 return; | |
330 } | |
331 } | |
332 | |
333 if (j->h->to) { | |
334 snprintf(out, sizeof(out), "To: %s\r\n", j->h->to); | |
335 out[sizeof(out)-1] = 0; | |
336 | |
337 if (really_string(sock, out) < 0) { | |
338 close(sock); | |
339 uw_set_error_message(j->ctx, "Error sending To"); | |
340 return; | |
341 } | |
342 } | |
343 | |
344 if (j->h->cc) { | |
345 snprintf(out, sizeof(out), "Cc: %s\r\n", j->h->cc); | |
346 out[sizeof(out)-1] = 0; | |
347 | |
348 if (really_string(sock, out) < 0) { | |
349 close(sock); | |
350 uw_set_error_message(j->ctx, "Error sending Cc"); | |
351 return; | |
352 } | |
353 } | |
354 | |
355 if (j->h->bcc) { | |
356 snprintf(out, sizeof(out), "Bcc: %s\r\n", j->h->bcc); | |
357 out[sizeof(out)-1] = 0; | |
358 | |
359 if (really_string(sock, out) < 0) { | |
360 close(sock); | |
361 uw_set_error_message(j->ctx, "Error sending Bcc"); | |
362 return; | |
363 } | |
364 } | |
365 | |
366 if ((s = uw_get_global(j->ctx, "extra_mail_headers"))) { | |
367 if (really_string(sock, s) < 0) { | |
368 close(sock); | |
369 uw_set_error_message(j->ctx, "Error sending extra headers"); | |
370 return; | |
371 } | |
372 } | |
373 | |
374 if (j->xbody) { | |
375 char separator[11]; | |
376 separator[sizeof(separator)-1] = 0; | |
377 | |
378 do { | |
379 int i; | |
380 | |
381 for (i = 0; i < sizeof(separator)-1; ++i) | |
382 separator[i] = 'A' + (rand() % 26); | |
383 } while (strstr(j->body, separator) || strstr(j->xbody, separator)); | |
384 | |
385 snprintf(out, sizeof(out), "MIME-Version: 1.0\r\n" | |
386 "Content-Type: multipart/alternative; boundary=\"%s\"\r\n" | |
387 "\r\n" | |
388 "--%s\r\n" | |
389 "Content-Type: text/plain\r\n" | |
390 "\r\n", | |
391 separator, separator); | |
392 out[sizeof(out)-1] = 0; | |
393 | |
394 if (really_string(sock, out) < 0) { | |
395 close(sock); | |
396 uw_set_error_message(j->ctx, "Error sending multipart beginning"); | |
397 return; | |
398 } | |
399 | |
400 if (really_string(sock, j->body) < 0) { | |
401 close(sock); | |
402 uw_set_error_message(j->ctx, "Error sending message text body"); | |
403 return; | |
404 } | |
405 | |
406 snprintf(out, sizeof(out), "\r\n" | |
407 "--%s\r\n" | |
408 "Content-Type: text/html\r\n" | |
409 "\r\n", | |
410 separator); | |
411 out[sizeof(out)-1] = 0; | |
412 | |
413 if (really_string(sock, out) < 0) { | |
414 close(sock); | |
415 uw_set_error_message(j->ctx, "Error sending multipart middle"); | |
416 return; | |
417 } | |
418 | |
419 if (really_string(sock, j->xbody) < 0) { | |
420 close(sock); | |
421 uw_set_error_message(j->ctx, "Error sending message HTML body"); | |
422 return; | |
423 } | |
424 | |
425 snprintf(out, sizeof(out), "\r\n" | |
426 "--%s--", | |
427 separator); | |
428 out[sizeof(out)-1] = 0; | |
429 | |
430 if (really_string(sock, out) < 0) { | |
431 close(sock); | |
432 uw_set_error_message(j->ctx, "Error sending multipart end"); | |
433 return; | |
434 } | |
435 } else { | |
436 if (really_string(sock, "Content-Type: text/plain\r\n\r\n") < 0) { | |
437 close(sock); | |
438 uw_set_error_message(j->ctx, "Error sending text Content-Type"); | |
439 return; | |
440 } | |
441 | |
442 if (really_string(sock, j->body) < 0) { | |
443 close(sock); | |
444 uw_set_error_message(j->ctx, "Error sending message body"); | |
445 return; | |
446 } | |
447 } | |
448 | |
449 if (really_string(sock, "\r\n.\r\n") < 0) { | |
450 close(sock); | |
451 uw_set_error_message(j->ctx, "Error sending message terminator"); | |
452 return; | |
453 } | |
454 | |
455 if (smtp_read(j->ctx, sock, buf, &pos) != 250) { | |
456 close(sock); | |
457 uw_set_error_message(j->ctx, "Mail server doesn't respond to end of message with code 250."); | |
458 return; | |
459 } | |
460 | |
461 if (really_string(sock, "QUIT\n") < 0) { | |
462 close(sock); | |
463 uw_set_error_message(j->ctx, "Error sending QUIT"); | |
464 return; | |
465 } | |
466 | |
467 if (smtp_read(j->ctx, sock, buf, &pos) != 221) { | |
468 close(sock); | |
469 uw_set_error_message(j->ctx, "Mail server doesn't respond to QUIT with code 221."); | |
470 return; | |
471 } | |
472 | |
473 close(sock); | |
474 } | |
475 | |
476 uw_unit uw_Mail_send(uw_context ctx, uw_Mail_headers h, uw_Basis_string body, uw_Basis_string xbody) { | |
477 job *j; | |
478 char *s; | |
479 | |
480 if (!h || !h->from) | |
481 uw_error(ctx, FATAL, "No From address set for e-mail message"); | |
482 | |
483 if (!h->to && !h->cc && !h->bcc) | |
484 uw_error(ctx, FATAL, "No recipients specified for e-mail message"); | |
485 | |
486 for (s = strchr(body, '.'); s; s = strchr(s+1, '.')) | |
487 if ((s[1] == '\n' || s[1] == '\r') | |
488 && (s <= body || s[-1] == '\n' || s[-1] == '\r')) | |
489 uw_error(ctx, FATAL, "Message body contains a line with just a period"); | |
490 | |
491 if (xbody) { | |
492 for (s = strchr(xbody, '.'); s; s = strchr(s+1, '.')) | |
493 if ((s[1] == '\n' || s[1] == '\r') | |
494 && (s <= xbody || s[-1] == '\n' || s[-1] == '\r')) | |
495 uw_error(ctx, FATAL, "HTML message body contains a line with just a period"); | |
496 } | |
497 | |
498 j = uw_malloc(ctx, sizeof(job)); | |
499 | |
500 j->ctx = ctx; | |
501 j->h = h; | |
502 j->body = body; | |
503 j->xbody = xbody; | |
504 | |
505 uw_register_transactional(ctx, j, commit, NULL, NULL); | |
506 | |
507 return uw_unit_v; | |
508 } |