Magellan Linux

Annotation of /trunk/mkinitrd-magellan/busybox/mailutils/sendmail.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 984 - (hide annotations) (download)
Sun May 30 11:32:42 2010 UTC (14 years ago) by niro
File MIME type: text/plain
File size: 9100 byte(s)
-updated to busybox-1.16.1 and enabled blkid/uuid support in default config
1 niro 816 /* 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 niro 984 // limit maximum allowed number of headers to prevent overflows.
13     // set to 0 to not limit
14     #define MAX_HEADERS 256
15    
16 niro 816 static int smtp_checkp(const char *fmt, const char *param, int code)
17     {
18     char *answer;
19     const char *msg = command(fmt, param);
20     // read stdin
21     // if the string has a form \d\d\d- -- read next string. E.g. EHLO response
22     // parse first bytes to a number
23     // if code = -1 then just return this number
24     // if code != -1 then checks whether the number equals the code
25     // if not equal -> die saying msg
26     while ((answer = xmalloc_fgetline(stdin)) != NULL)
27     if (strlen(answer) <= 3 || '-' != answer[3])
28     break;
29     if (answer) {
30     int n = atoi(answer);
31     if (timeout)
32     alarm(0);
33     free(answer);
34     if (-1 == code || n == code)
35     return n;
36     }
37     bb_error_msg_and_die("%s failed", msg);
38     }
39    
40     static int smtp_check(const char *fmt, int code)
41     {
42     return smtp_checkp(fmt, NULL, code);
43     }
44    
45     // strip argument of bad chars
46     static char *sane_address(char *str)
47     {
48     char *s = str;
49     char *p = s;
50     while (*s) {
51     if (isalnum(*s) || '_' == *s || '-' == *s || '.' == *s || '@' == *s) {
52     *p++ = *s;
53     }
54     s++;
55     }
56     *p = '\0';
57     return str;
58     }
59    
60     static void rcptto(const char *s)
61     {
62 niro 984 // N.B. we don't die if recipient is rejected, for the other recipients may be accepted
63     if (250 != smtp_checkp("RCPT TO:<%s>", s, -1))
64     bb_error_msg("Bad recipient: <%s>", s);
65 niro 816 }
66    
67     int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
68     int sendmail_main(int argc UNUSED_PARAM, char **argv)
69     {
70     char *opt_connect = opt_connect;
71 niro 984 char *opt_from;
72     char *s;
73     llist_t *list = NULL;
74 niro 816 char *domain = sane_address(safe_getdomainname());
75 niro 984 unsigned nheaders = 0;
76 niro 816 int code;
77    
78     enum {
79 niro 984 //--- standard options
80     OPT_t = 1 << 0, // read message for recipients, append them to those on cmdline
81     OPT_f = 1 << 1, // sender address
82     OPT_o = 1 << 2, // various options. -oi IMPLIED! others are IGNORED!
83     OPT_i = 1 << 3, // IMPLIED!
84     //--- BB specific options
85     OPT_w = 1 << 4, // network timeout
86     OPT_H = 1 << 5, // use external connection helper
87     OPT_S = 1 << 6, // specify connection string
88     OPT_a = 1 << 7, // authentication tokens
89 niro 816 };
90    
91     // init global variables
92     INIT_G();
93    
94     // save initial stdin since body is piped!
95     xdup2(STDIN_FILENO, 3);
96 niro 984 G.fp0 = xfdopen_for_read(3);
97 niro 816
98     // parse options
99 niro 984 // -f is required. -H and -S are mutually exclusive
100     opt_complementary = "f:w+:H--S:S--H:a::";
101     // N.B. since -H and -S are mutually exclusive they do not interfere in opt_connect
102     // -a is for ssmtp (http://downloads.openwrt.org/people/nico/man/man8/ssmtp.8.html) compatibility,
103     // it is still under development.
104     opts = getopt32(argv, "tf:o:iw:H:S:a::", &opt_from, NULL, &timeout, &opt_connect, &opt_connect, &list);
105 niro 816 //argc -= optind;
106     argv += optind;
107    
108 niro 984 // process -a[upm]<token> options
109     if ((opts & OPT_a) && !list)
110     bb_show_usage();
111     while (list) {
112     char *a = (char *) llist_pop(&list);
113     if ('u' == a[0])
114     G.user = xstrdup(a+1);
115     if ('p' == a[0])
116     G.pass = xstrdup(a+1);
117     // N.B. we support only AUTH LOGIN so far
118     //if ('m' == a[0])
119     // G.method = xstrdup(a+1);
120     }
121     // N.B. list == NULL here
122     //bb_info_msg("OPT[%x] AU[%s], AP[%s], AM[%s], ARGV[%s]", opts, au, ap, am, *argv);
123    
124 niro 816 // connect to server
125    
126     // connection helper ordered? ->
127     if (opts & OPT_H) {
128     const char *args[] = { "sh", "-c", opt_connect, NULL };
129     // plug it in
130     launch_helper(args);
131     // vanilla connection
132 niro 984 } else {
133 niro 816 int fd;
134 niro 984 // host[:port] not explicitly specified? -> use $SMTPHOST
135 niro 816 // no $SMTPHOST ? -> use localhost
136     if (!(opts & OPT_S)) {
137     opt_connect = getenv("SMTPHOST");
138     if (!opt_connect)
139     opt_connect = (char *)"127.0.0.1";
140     }
141     // do connect
142     fd = create_and_connect_stream_or_die(opt_connect, 25);
143     // and make ourselves a simple IO filter
144     xmove_fd(fd, STDIN_FILENO);
145     xdup2(STDIN_FILENO, STDOUT_FILENO);
146     }
147     // N.B. from now we know nothing about network :)
148    
149     // wait for initial server OK
150     // N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure
151 niro 984 // so we need to kick the server to see whether we are ok
152 niro 816 code = smtp_check("NOOP", -1);
153     // 220 on plain connection, 250 on openssl-helped TLS session
154     if (220 == code)
155     smtp_check(NULL, 250); // reread the code to stay in sync
156     else if (250 != code)
157     bb_error_msg_and_die("INIT failed");
158    
159     // we should start with modern EHLO
160     if (250 != smtp_checkp("EHLO %s", domain, -1)) {
161     smtp_checkp("HELO %s", domain, 250);
162     }
163 niro 984 if (ENABLE_FEATURE_CLEAN_UP)
164     free(domain);
165 niro 816
166 niro 984 // perform authentication
167     if (opts & OPT_a) {
168     smtp_check("AUTH LOGIN", 334);
169     // we must read credentials unless they are given via -a[up] options
170     if (!G.user || !G.pass)
171     get_cred_or_die(4);
172     encode_base64(NULL, G.user, NULL);
173     smtp_check("", 334);
174     encode_base64(NULL, G.pass, NULL);
175     smtp_check("", 235);
176     }
177    
178 niro 816 // set sender
179     // N.B. we have here a very loosely defined algotythm
180     // since sendmail historically offers no means to specify secrets on cmdline.
181     // 1) server can require no authentication ->
182     // we must just provide a (possibly fake) reply address.
183     // 2) server can require AUTH ->
184     // we must provide valid username and password along with a (possibly fake) reply address.
185     // For the sake of security username and password are to be read either from console or from a secured file.
186     // Since reading from console may defeat usability, the solution is either to read from a predefined
187     // file descriptor (e.g. 4), or again from a secured file.
188    
189     // got no sender address? -> use system username as a resort
190 niro 984 // N.B. we marked -f as required option!
191     //if (!G.user) {
192     // // N.B. IMHO getenv("USER") can be way easily spoofed!
193     // G.user = xuid2uname(getuid());
194     // opt_from = xasprintf("%s@%s", G.user, domain);
195     //}
196     //if (ENABLE_FEATURE_CLEAN_UP)
197     // free(domain);
198     smtp_checkp("MAIL FROM:<%s>", opt_from, 250);
199 niro 816
200 niro 984 // process message
201 niro 816
202 niro 984 // read recipients from message and add them to those given on cmdline.
203     // this means we scan stdin for To:, Cc:, Bcc: lines until an empty line
204     // and then use the rest of stdin as message body
205     code = 0; // set "analyze headers" mode
206     while ((s = xmalloc_fgetline(G.fp0)) != NULL) {
207     dump:
208     // put message lines doubling leading dots
209     if (code) {
210 niro 816 // escape leading dots
211     // N.B. this feature is implied even if no -i (-oi) switch given
212     // N.B. we need to escape the leading dot regardless of
213     // whether it is single or not character on the line
214     if ('.' == s[0] /*&& '\0' == s[1] */)
215     printf(".");
216     // dump read line
217     printf("%s\r\n", s);
218 niro 984 free(s);
219     continue;
220 niro 816 }
221 niro 984
222     // analyze headers
223     // To: or Cc: headers add recipients
224     if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Bcc: " + 1, s, 4)) {
225     rcptto(sane_address(s+4));
226     goto addheader;
227     // Bcc: header adds blind copy (hidden) recipient
228     } else if (0 == strncasecmp("Bcc: ", s, 5)) {
229     rcptto(sane_address(s+5));
230     free(s);
231     // N.B. Bcc: vanishes from headers!
232    
233     // other headers go verbatim
234    
235     // N.B. RFC2822 2.2.3 "Long Header Fields" allows for headers to occupy several lines.
236     // Continuation is denoted by prefixing additional lines with whitespace(s).
237     // Thanks (stefan.seyfried at googlemail.com) for pointing this out.
238     } else if (strchr(s, ':') || (list && skip_whitespace(s) != s)) {
239     addheader:
240     // N.B. we allow MAX_HEADERS generic headers at most to prevent attacks
241     if (MAX_HEADERS && ++nheaders >= MAX_HEADERS)
242     goto bail;
243     llist_add_to_end(&list, s);
244     // a line without ":" (an empty line too, by definition) doesn't look like a valid header
245     // so stop "analyze headers" mode
246     } else {
247     reenter:
248     // put recipients specified on cmdline
249     while (*argv) {
250     char *t = sane_address(*argv);
251     rcptto(t);
252     //if (MAX_HEADERS && ++nheaders >= MAX_HEADERS)
253     // goto bail;
254     llist_add_to_end(&list, xasprintf("To: %s", t));
255     argv++;
256     }
257     // enter "put message" mode
258     // N.B. DATA fails iff no recipients were accepted (or even provided)
259     // in this case just bail out gracefully
260     if (354 != smtp_check("DATA", -1))
261     goto bail;
262     // dump the headers
263     while (list) {
264     printf("%s\r\n", (char *) llist_pop(&list));
265     }
266     // stop analyzing headers
267     code++;
268     // N.B. !s means: we read nothing, and nothing to be read in the future.
269     // just dump empty line and break the loop
270     if (!s) {
271     puts("\r");
272     break;
273     }
274     // go dump message body
275     // N.B. "s" already contains the first non-header line, so pretend we read it from input
276     goto dump;
277     }
278 niro 816 }
279 niro 984 // odd case: we didn't stop "analyze headers" mode -> message body is empty. Reenter the loop
280     // N.B. after reenter code will be > 0
281     if (!code)
282     goto reenter;
283 niro 816
284 niro 984 // finalize the message
285 niro 816 smtp_check(".", 250);
286 niro 984 bail:
287 niro 816 // ... and say goodbye
288     smtp_check("QUIT", 221);
289     // cleanup
290     if (ENABLE_FEATURE_CLEAN_UP)
291     fclose(G.fp0);
292    
293     return EXIT_SUCCESS;
294     }