diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2008-11-06 23:42:42 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2008-11-06 23:42:42 +0000 |
commit | 239d06bd4710e8463c6cc7e5411965066a6d134e (patch) | |
tree | 10b9f670ec97b651bca9fcbe42a48003b687cd45 /mailutils/sendmail.c | |
parent | b9d572a2733fa20957a9a3287bd04d66176e3b6a (diff) | |
download | busybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.tar.gz busybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.tar.bz2 busybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.zip |
add mailutils/*
Diffstat (limited to 'mailutils/sendmail.c')
-rw-r--r-- | mailutils/sendmail.c | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/mailutils/sendmail.c b/mailutils/sendmail.c new file mode 100644 index 000000000..55555c326 --- /dev/null +++ b/mailutils/sendmail.c | |||
@@ -0,0 +1,388 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * bare bones sendmail | ||
4 | * | ||
5 | * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com> | ||
6 | * | ||
7 | * Licensed under GPLv2, see file LICENSE in this tarball for details. | ||
8 | */ | ||
9 | #include "libbb.h" | ||
10 | #include "mail.h" | ||
11 | |||
12 | static int smtp_checkp(const char *fmt, const char *param, int code) | ||
13 | { | ||
14 | char *answer; | ||
15 | const char *msg = command(fmt, param); | ||
16 | // read stdin | ||
17 | // if the string has a form \d\d\d- -- read next string. E.g. EHLO response | ||
18 | // parse first bytes to a number | ||
19 | // if code = -1 then just return this number | ||
20 | // if code != -1 then checks whether the number equals the code | ||
21 | // if not equal -> die saying msg | ||
22 | while ((answer = xmalloc_fgetline(stdin)) != NULL) | ||
23 | if (strlen(answer) <= 3 || '-' != answer[3]) | ||
24 | break; | ||
25 | if (answer) { | ||
26 | int n = atoi(answer); | ||
27 | if (timeout) | ||
28 | alarm(0); | ||
29 | free(answer); | ||
30 | if (-1 == code || n == code) | ||
31 | return n; | ||
32 | } | ||
33 | bb_error_msg_and_die("%s failed", msg); | ||
34 | } | ||
35 | |||
36 | static int smtp_check(const char *fmt, int code) | ||
37 | { | ||
38 | return smtp_checkp(fmt, NULL, code); | ||
39 | } | ||
40 | |||
41 | // strip argument of bad chars | ||
42 | static char *sane_address(char *str) | ||
43 | { | ||
44 | char *s = str; | ||
45 | char *p = s; | ||
46 | while (*s) { | ||
47 | if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) { | ||
48 | *p++ = *s; | ||
49 | } | ||
50 | s++; | ||
51 | } | ||
52 | *p = '\0'; | ||
53 | return str; | ||
54 | } | ||
55 | |||
56 | static void rcptto(const char *s) | ||
57 | { | ||
58 | smtp_checkp("RCPT TO:<%s>", s, 250); | ||
59 | } | ||
60 | |||
61 | int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
62 | int sendmail_main(int argc UNUSED_PARAM, char **argv) | ||
63 | { | ||
64 | #if ENABLE_FEATURE_SENDMAIL_MAILX | ||
65 | llist_t *opt_attachments = NULL; | ||
66 | const char *opt_subject; | ||
67 | #if ENABLE_FEATURE_SENDMAIL_MAILXX | ||
68 | llist_t *opt_carboncopies = NULL; | ||
69 | char *opt_errors_to; | ||
70 | #endif | ||
71 | #endif | ||
72 | char *opt_connect = opt_connect; | ||
73 | char *opt_from, *opt_fullname; | ||
74 | char *boundary; | ||
75 | llist_t *l; | ||
76 | llist_t *headers = NULL; | ||
77 | char *domain = sane_address(safe_getdomainname()); | ||
78 | int code; | ||
79 | |||
80 | enum { | ||
81 | OPT_w = 1 << 0, // network timeout | ||
82 | OPT_t = 1 << 1, // read message for recipients | ||
83 | OPT_N = 1 << 2, // request notification | ||
84 | OPT_f = 1 << 3, // sender address | ||
85 | OPT_F = 1 << 4, // sender name, overrides $NAME | ||
86 | OPT_s = 1 << 5, // subject | ||
87 | OPT_j = 1 << 6, // assumed charset | ||
88 | OPT_a = 1 << 7, // attachment(s) | ||
89 | OPT_H = 1 << 8, // use external connection helper | ||
90 | OPT_S = 1 << 9, // specify connection string | ||
91 | OPT_c = 1 << 10, // carbon copy | ||
92 | OPT_e = 1 << 11, // errors-to address | ||
93 | }; | ||
94 | |||
95 | // init global variables | ||
96 | INIT_G(); | ||
97 | |||
98 | // save initial stdin since body is piped! | ||
99 | xdup2(STDIN_FILENO, 3); | ||
100 | G.fp0 = fdopen(3, "r"); | ||
101 | |||
102 | // parse options | ||
103 | opt_complementary = "w+" USE_FEATURE_SENDMAIL_MAILX(":a::H--S:S--H") USE_FEATURE_SENDMAIL_MAILXX(":c::"); | ||
104 | opts = getopt32(argv, | ||
105 | "w:t" "N:f:F:" USE_FEATURE_SENDMAIL_MAILX("s:j:a:H:S:") USE_FEATURE_SENDMAIL_MAILXX("c:e:") | ||
106 | "X:V:vq:R:O:o:nmL:Iih:GC:B:b:A:" // postfix compat only, ignored | ||
107 | // r:Q:p:M:Dd are candidates from another man page. TODO? | ||
108 | "46E", // ssmtp introduces another quirks. TODO?: -a[upm] (user, pass, method) to be supported | ||
109 | &timeout /* -w */, NULL, &opt_from, &opt_fullname, | ||
110 | USE_FEATURE_SENDMAIL_MAILX(&opt_subject, &G.opt_charset, &opt_attachments, &opt_connect, &opt_connect,) | ||
111 | USE_FEATURE_SENDMAIL_MAILXX(&opt_carboncopies, &opt_errors_to,) | ||
112 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL | ||
113 | ); | ||
114 | //argc -= optind; | ||
115 | argv += optind; | ||
116 | |||
117 | // connect to server | ||
118 | |||
119 | #if ENABLE_FEATURE_SENDMAIL_MAILX | ||
120 | // N.B. -H and -S are mutually exclusive so they do not spoil opt_connect | ||
121 | // connection helper ordered? -> | ||
122 | if (opts & OPT_H) { | ||
123 | const char *args[] = { "sh", "-c", opt_connect, NULL }; | ||
124 | // plug it in | ||
125 | launch_helper(args); | ||
126 | // vanilla connection | ||
127 | } else | ||
128 | #endif | ||
129 | { | ||
130 | int fd; | ||
131 | // host[:port] not explicitly specified ? -> use $SMTPHOST | ||
132 | // no $SMTPHOST ? -> use localhost | ||
133 | if (!(opts & OPT_S)) { | ||
134 | opt_connect = getenv("SMTPHOST"); | ||
135 | if (!opt_connect) | ||
136 | opt_connect = (char *)"127.0.0.1"; | ||
137 | } | ||
138 | // do connect | ||
139 | fd = create_and_connect_stream_or_die(opt_connect, 25); | ||
140 | // and make ourselves a simple IO filter | ||
141 | xmove_fd(fd, STDIN_FILENO); | ||
142 | xdup2(STDIN_FILENO, STDOUT_FILENO); | ||
143 | } | ||
144 | // N.B. from now we know nothing about network :) | ||
145 | |||
146 | // wait for initial server OK | ||
147 | // N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure | ||
148 | // so we need to push the server to see whether we are ok | ||
149 | code = smtp_check("NOOP", -1); | ||
150 | // 220 on plain connection, 250 on openssl-helped TLS session | ||
151 | if (220 == code) | ||
152 | smtp_check(NULL, 250); // reread the code to stay in sync | ||
153 | else if (250 != code) | ||
154 | bb_error_msg_and_die("INIT failed"); | ||
155 | |||
156 | // we should start with modern EHLO | ||
157 | if (250 != smtp_checkp("EHLO %s", domain, -1)) { | ||
158 | smtp_checkp("HELO %s", domain, 250); | ||
159 | } | ||
160 | |||
161 | // set sender | ||
162 | // N.B. we have here a very loosely defined algotythm | ||
163 | // since sendmail historically offers no means to specify secrets on cmdline. | ||
164 | // 1) server can require no authentication -> | ||
165 | // we must just provide a (possibly fake) reply address. | ||
166 | // 2) server can require AUTH -> | ||
167 | // we must provide valid username and password along with a (possibly fake) reply address. | ||
168 | // For the sake of security username and password are to be read either from console or from a secured file. | ||
169 | // Since reading from console may defeat usability, the solution is either to read from a predefined | ||
170 | // file descriptor (e.g. 4), or again from a secured file. | ||
171 | |||
172 | // got no sender address? -> use system username as a resort | ||
173 | if (!(opts & OPT_f)) { | ||
174 | // N.B. IMHO getenv("USER") can be way easily spoofed! | ||
175 | G.user = bb_getpwuid(NULL, -1, getuid()); | ||
176 | opt_from = xasprintf("%s@%s", G.user, domain); | ||
177 | } | ||
178 | if (ENABLE_FEATURE_CLEAN_UP) | ||
179 | free(domain); | ||
180 | |||
181 | code = -1; // first try softly without authentication | ||
182 | while (250 != smtp_checkp("MAIL FROM:<%s>", opt_from, code)) { | ||
183 | // MAIL FROM failed -> authentication needed | ||
184 | if (334 == smtp_check("AUTH LOGIN", -1)) { | ||
185 | // we must read credentials | ||
186 | get_cred_or_die(4); | ||
187 | encode_base64(NULL, G.user, NULL); | ||
188 | smtp_check("", 334); | ||
189 | encode_base64(NULL, G.pass, NULL); | ||
190 | smtp_check("", 235); | ||
191 | } | ||
192 | // authenticated OK? -> retry to set sender | ||
193 | // but this time die on failure! | ||
194 | code = 250; | ||
195 | } | ||
196 | |||
197 | // recipients specified as arguments | ||
198 | while (*argv) { | ||
199 | char *s = sane_address(*argv); | ||
200 | // loose test on email address validity | ||
201 | // if (strchr(s, '@')) { | ||
202 | rcptto(s); | ||
203 | llist_add_to_end(&headers, xasprintf("To: %s", s)); | ||
204 | // } | ||
205 | argv++; | ||
206 | } | ||
207 | |||
208 | #if ENABLE_FEATURE_SENDMAIL_MAILXX | ||
209 | // carbon copies recipients specified as -c options | ||
210 | for (l = opt_carboncopies; l; l = l->link) { | ||
211 | char *s = sane_address(l->data); | ||
212 | // loose test on email address validity | ||
213 | // if (strchr(s, '@')) { | ||
214 | rcptto(s); | ||
215 | // TODO: do we ever need to mangle the message? | ||
216 | //llist_add_to_end(&headers, xasprintf("Cc: %s", s)); | ||
217 | // } | ||
218 | } | ||
219 | #endif | ||
220 | |||
221 | // if -t specified or no recipients specified -> read recipients from message | ||
222 | // i.e. scan stdin for To:, Cc:, Bcc: lines ... | ||
223 | // ... and then use the rest of stdin as message body | ||
224 | // N.B. subject read from body can be further overrided with one specified on command line. | ||
225 | // recipients are merged. Bcc: lines are deleted | ||
226 | // N.B. other headers are collected and will be dumped verbatim | ||
227 | if (opts & OPT_t || !headers) { | ||
228 | // fetch recipients and (optionally) subject | ||
229 | char *s; | ||
230 | while ((s = xmalloc_fgetline(G.fp0)) != NULL) { | ||
231 | if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Cc: ", s, 4)) { | ||
232 | rcptto(sane_address(s+4)); | ||
233 | llist_add_to_end(&headers, s); | ||
234 | } else if (0 == strncasecmp("Bcc: ", s, 5)) { | ||
235 | rcptto(sane_address(s+5)); | ||
236 | free(s); | ||
237 | // N.B. Bcc vanishes from headers! | ||
238 | } else if (0 == strncmp("Subject: ", s, 9)) { | ||
239 | // we read subject -> use it verbatim unless it is specified | ||
240 | // on command line | ||
241 | if (!(opts & OPT_s)) | ||
242 | llist_add_to_end(&headers, s); | ||
243 | else | ||
244 | free(s); | ||
245 | } else if (s[0]) { | ||
246 | // misc header | ||
247 | llist_add_to_end(&headers, s); | ||
248 | } else { | ||
249 | free(s); | ||
250 | break; // stop on the first empty line | ||
251 | } | ||
252 | } | ||
253 | } | ||
254 | |||
255 | // enter "put message" mode | ||
256 | smtp_check("DATA", 354); | ||
257 | |||
258 | // put headers we could have preread with -t | ||
259 | for (l = headers; l; l = l->link) { | ||
260 | printf("%s\r\n", l->data); | ||
261 | if (ENABLE_FEATURE_CLEAN_UP) | ||
262 | free(l->data); | ||
263 | } | ||
264 | |||
265 | // put (possibly encoded) subject | ||
266 | #if ENABLE_FEATURE_SENDMAIL_MAILX | ||
267 | if (opts & OPT_s) { | ||
268 | printf("Subject: "); | ||
269 | if (opts & OPT_j) { | ||
270 | printf("=?%s?B?", G.opt_charset); | ||
271 | encode_base64(NULL, opt_subject, NULL); | ||
272 | printf("?="); | ||
273 | } else { | ||
274 | printf("%s", opt_subject); | ||
275 | } | ||
276 | printf("\r\n"); | ||
277 | } | ||
278 | #endif | ||
279 | |||
280 | // put sender name, $NAME is the default | ||
281 | if (!(opts & OPT_F)) | ||
282 | opt_fullname = getenv("NAME"); | ||
283 | if (opt_fullname) | ||
284 | printf("From: \"%s\" <%s>\r\n", opt_fullname, opt_from); | ||
285 | |||
286 | // put notification | ||
287 | if (opts & OPT_N) | ||
288 | printf("Disposition-Notification-To: %s\r\n", opt_from); | ||
289 | |||
290 | #if ENABLE_FEATURE_SENDMAIL_MAILXX | ||
291 | // put errors recipient | ||
292 | if (opts & OPT_e) | ||
293 | printf("Errors-To: %s\r\n", opt_errors_to); | ||
294 | #endif | ||
295 | |||
296 | // make a random string -- it will delimit message parts | ||
297 | srand(monotonic_us()); | ||
298 | boundary = xasprintf("%d=_%d-%d", rand(), rand(), rand()); | ||
299 | |||
300 | // put common headers | ||
301 | // TODO: do we really need this? | ||
302 | // printf("Message-ID: <%s>\r\n", boundary); | ||
303 | |||
304 | #if ENABLE_FEATURE_SENDMAIL_MAILX | ||
305 | // have attachments? -> compose multipart MIME | ||
306 | if (opt_attachments) { | ||
307 | const char *fmt; | ||
308 | const char *p; | ||
309 | char *q; | ||
310 | |||
311 | printf( | ||
312 | "Mime-Version: 1.0\r\n" | ||
313 | "%smultipart/mixed; boundary=\"%s\"\r\n" | ||
314 | , "Content-Type: " | ||
315 | , boundary | ||
316 | ); | ||
317 | |||
318 | // body is pseudo attachment read from stdin in first turn | ||
319 | llist_add_to(&opt_attachments, (char *)"-"); | ||
320 | |||
321 | // put body + attachment(s) | ||
322 | // N.B. all these weird things just to be tiny | ||
323 | // by reusing string patterns! | ||
324 | fmt = | ||
325 | "\r\n--%s\r\n" | ||
326 | "%stext/plain; charset=%s\r\n" | ||
327 | "%s%s\r\n" | ||
328 | "%s" | ||
329 | ; | ||
330 | p = G.opt_charset; | ||
331 | q = (char *)""; | ||
332 | l = opt_attachments; | ||
333 | while (l) { | ||
334 | printf( | ||
335 | fmt | ||
336 | , boundary | ||
337 | , "Content-Type: " | ||
338 | , p | ||
339 | , "Content-Disposition: inline" | ||
340 | , q | ||
341 | , "Content-Transfer-Encoding: base64\r\n" | ||
342 | ); | ||
343 | p = ""; | ||
344 | fmt = | ||
345 | "\r\n--%s\r\n" | ||
346 | "%sapplication/octet-stream%s\r\n" | ||
347 | "%s; filename=\"%s\"\r\n" | ||
348 | "%s" | ||
349 | ; | ||
350 | encode_base64(l->data, (const char *)G.fp0, "\r"); | ||
351 | l = l->link; | ||
352 | if (l) | ||
353 | q = bb_get_last_path_component_strip(l->data); | ||
354 | } | ||
355 | |||
356 | // put message terminator | ||
357 | printf("\r\n--%s--\r\n" "\r\n", boundary); | ||
358 | |||
359 | // no attachments? -> just dump message | ||
360 | } else | ||
361 | #endif | ||
362 | { | ||
363 | char *s; | ||
364 | // terminate headers | ||
365 | printf("\r\n"); | ||
366 | // put plain text respecting leading dots | ||
367 | while ((s = xmalloc_fgetline(G.fp0)) != NULL) { | ||
368 | // escape leading dots | ||
369 | // N.B. this feature is implied even if no -i (-oi) switch given | ||
370 | // N.B. we need to escape the leading dot regardless of | ||
371 | // whether it is single or not character on the line | ||
372 | if ('.' == s[0] /*&& '\0' == s[1] */) | ||
373 | printf("."); | ||
374 | // dump read line | ||
375 | printf("%s\r\n", s); | ||
376 | } | ||
377 | } | ||
378 | |||
379 | // leave "put message" mode | ||
380 | smtp_check(".", 250); | ||
381 | // ... and say goodbye | ||
382 | smtp_check("QUIT", 221); | ||
383 | // cleanup | ||
384 | if (ENABLE_FEATURE_CLEAN_UP) | ||
385 | fclose(G.fp0); | ||
386 | |||
387 | return EXIT_SUCCESS; | ||
388 | } | ||