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 | |
parent | b9d572a2733fa20957a9a3287bd04d66176e3b6a (diff) | |
download | busybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.tar.gz busybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.tar.bz2 busybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.zip |
add mailutils/*
-rw-r--r-- | mailutils/Config.in | 64 | ||||
-rw-r--r-- | mailutils/Kbuild | 11 | ||||
-rw-r--r-- | mailutils/mail.c | 242 | ||||
-rw-r--r-- | mailutils/mail.h | 35 | ||||
-rw-r--r-- | mailutils/mime.c | 354 | ||||
-rw-r--r-- | mailutils/popmaildir.c | 237 | ||||
-rw-r--r-- | mailutils/sendmail.c | 388 |
7 files changed, 1331 insertions, 0 deletions
diff --git a/mailutils/Config.in b/mailutils/Config.in new file mode 100644 index 000000000..b8d697737 --- /dev/null +++ b/mailutils/Config.in | |||
@@ -0,0 +1,64 @@ | |||
1 | menu "Mail Utilities" | ||
2 | |||
3 | config MAKEMIME | ||
4 | bool "makemime" | ||
5 | default n | ||
6 | help | ||
7 | Create MIME-formatted messages. | ||
8 | |||
9 | config FEATURE_MIME_CHARSET | ||
10 | string "Default charset" | ||
11 | default "us-ascii" | ||
12 | depends on MAKEMIME || REFORMIME || SENDMAIL | ||
13 | help | ||
14 | Default charset of the message. | ||
15 | |||
16 | config POPMAILDIR | ||
17 | bool "popmaildir" | ||
18 | default n | ||
19 | help | ||
20 | Simple yet powerful POP3 mail popper. Delivers content of remote mailboxes to local Maildir. | ||
21 | |||
22 | config FEATURE_POPMAILDIR_DELIVERY | ||
23 | bool "Allow message filters and custom delivery program" | ||
24 | default n | ||
25 | depends on POPMAILDIR | ||
26 | help | ||
27 | Allow to use a custom program to filter the content of the message before actual delivery (-F "prog [args...]"). | ||
28 | Allow to use a custom program for message actual delivery (-M "prog [args...]"). | ||
29 | |||
30 | config REFORMIME | ||
31 | bool "reformime" | ||
32 | default n | ||
33 | help | ||
34 | Parse MIME-formatted messages. | ||
35 | |||
36 | config FEATURE_REFORMIME_COMPAT | ||
37 | bool "Accept and ignore options other than -x and -X" | ||
38 | default y | ||
39 | depends on REFORMIME | ||
40 | help | ||
41 | Accept (for compatibility only) and ignore options other than -x and -X. | ||
42 | |||
43 | config SENDMAIL | ||
44 | bool "sendmail" | ||
45 | default n | ||
46 | help | ||
47 | Barebones sendmail. | ||
48 | |||
49 | config FEATURE_SENDMAIL_MAILX | ||
50 | bool "Allow to specify subject, attachments, their charset and connection helper" | ||
51 | default y | ||
52 | depends on SENDMAIL | ||
53 | help | ||
54 | Allow to specify subject, attachments and their charset. | ||
55 | Allow to use custom connection helper. | ||
56 | |||
57 | config FEATURE_SENDMAIL_MAILXX | ||
58 | bool "Allow to specify Cc: addresses and some additional headers" | ||
59 | default n | ||
60 | depends on FEATURE_SENDMAIL_MAILX | ||
61 | help | ||
62 | Allow to specify Cc: addresses and some additional headers: Errors-To:. | ||
63 | |||
64 | endmenu | ||
diff --git a/mailutils/Kbuild b/mailutils/Kbuild new file mode 100644 index 000000000..871e87981 --- /dev/null +++ b/mailutils/Kbuild | |||
@@ -0,0 +1,11 @@ | |||
1 | # Makefile for busybox | ||
2 | # | ||
3 | # Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> | ||
4 | # | ||
5 | # Licensed under the GPL v2, see the file LICENSE in this tarball. | ||
6 | |||
7 | lib-y:= | ||
8 | lib-$(CONFIG_MAKEMIME) += mime.o mail.o | ||
9 | lib-$(CONFIG_POPMAILDIR) += popmaildir.o mail.o | ||
10 | lib-$(CONFIG_REFORMIME) += mime.o mail.o | ||
11 | lib-$(CONFIG_SENDMAIL) += sendmail.o mail.o | ||
diff --git a/mailutils/mail.c b/mailutils/mail.c new file mode 100644 index 000000000..ab1304a7f --- /dev/null +++ b/mailutils/mail.c | |||
@@ -0,0 +1,242 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * helper routines | ||
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 void kill_helper(void) | ||
13 | { | ||
14 | // TODO!!!: is there more elegant way to terminate child on program failure? | ||
15 | if (G.helper_pid > 0) | ||
16 | kill(G.helper_pid, SIGTERM); | ||
17 | } | ||
18 | |||
19 | // generic signal handler | ||
20 | static void signal_handler(int signo) | ||
21 | { | ||
22 | #define err signo | ||
23 | if (SIGALRM == signo) { | ||
24 | kill_helper(); | ||
25 | bb_error_msg_and_die("timed out"); | ||
26 | } | ||
27 | |||
28 | // SIGCHLD. reap zombies | ||
29 | if (safe_waitpid(G.helper_pid, &err, WNOHANG) > 0) | ||
30 | if (WIFEXITED(err)) { | ||
31 | G.helper_pid = 0; | ||
32 | if (WEXITSTATUS(err)) | ||
33 | bb_error_msg_and_die("child exited (%d)", WEXITSTATUS(err)); | ||
34 | } | ||
35 | #undef err | ||
36 | } | ||
37 | |||
38 | void FAST_FUNC launch_helper(const char **argv) | ||
39 | { | ||
40 | // setup vanilla unidirectional pipes interchange | ||
41 | int idx; | ||
42 | int pipes[4]; | ||
43 | |||
44 | xpipe(pipes); | ||
45 | xpipe(pipes+2); | ||
46 | G.helper_pid = vfork(); | ||
47 | if (G.helper_pid < 0) | ||
48 | bb_perror_msg_and_die("vfork"); | ||
49 | idx = (!G.helper_pid) * 2; | ||
50 | xdup2(pipes[idx], STDIN_FILENO); | ||
51 | xdup2(pipes[3-idx], STDOUT_FILENO); | ||
52 | if (ENABLE_FEATURE_CLEAN_UP) | ||
53 | for (int i = 4; --i >= 0; ) | ||
54 | if (pipes[i] > STDOUT_FILENO) | ||
55 | close(pipes[i]); | ||
56 | if (!G.helper_pid) { | ||
57 | // child: try to execute connection helper | ||
58 | BB_EXECVP(*argv, (char **)argv); | ||
59 | _exit(127); | ||
60 | } | ||
61 | // parent: check whether child is alive | ||
62 | bb_signals(0 | ||
63 | + (1 << SIGCHLD) | ||
64 | + (1 << SIGALRM) | ||
65 | , signal_handler); | ||
66 | signal_handler(SIGCHLD); | ||
67 | // child seems OK -> parent goes on | ||
68 | atexit(kill_helper); | ||
69 | } | ||
70 | |||
71 | const FAST_FUNC char *command(const char *fmt, const char *param) | ||
72 | { | ||
73 | const char *msg = fmt; | ||
74 | if (timeout) | ||
75 | alarm(timeout); | ||
76 | if (msg) { | ||
77 | msg = xasprintf(fmt, param); | ||
78 | printf("%s\r\n", msg); | ||
79 | } | ||
80 | fflush(stdout); | ||
81 | return msg; | ||
82 | } | ||
83 | |||
84 | // NB: parse_url can modify url[] (despite const), but only if '@' is there | ||
85 | /* | ||
86 | static char FAST_FUNC *parse_url(char *url, char **user, char **pass) | ||
87 | { | ||
88 | // parse [user[:pass]@]host | ||
89 | // return host | ||
90 | char *s = strchr(url, '@'); | ||
91 | *user = *pass = NULL; | ||
92 | if (s) { | ||
93 | *s++ = '\0'; | ||
94 | *user = url; | ||
95 | url = s; | ||
96 | s = strchr(*user, ':'); | ||
97 | if (s) { | ||
98 | *s++ = '\0'; | ||
99 | *pass = s; | ||
100 | } | ||
101 | } | ||
102 | return url; | ||
103 | } | ||
104 | */ | ||
105 | |||
106 | void FAST_FUNC encode_base64(char *fname, const char *text, const char *eol) | ||
107 | { | ||
108 | enum { | ||
109 | SRC_BUF_SIZE = 45, /* This *MUST* be a multiple of 3 */ | ||
110 | DST_BUF_SIZE = 4 * ((SRC_BUF_SIZE + 2) / 3), | ||
111 | }; | ||
112 | |||
113 | #define src_buf text | ||
114 | FILE *fp = fp; | ||
115 | ssize_t len = len; | ||
116 | char dst_buf[DST_BUF_SIZE + 1]; | ||
117 | |||
118 | if (fname) { | ||
119 | fp = (NOT_LONE_DASH(fname)) ? xfopen_for_read(fname) : (FILE *)text; | ||
120 | src_buf = bb_common_bufsiz1; | ||
121 | // N.B. strlen(NULL) segfaults! | ||
122 | } else if (text) { | ||
123 | // though we do not call uuencode(NULL, NULL) explicitly | ||
124 | // still we do not want to break things suddenly | ||
125 | len = strlen(text); | ||
126 | } else | ||
127 | return; | ||
128 | |||
129 | while (1) { | ||
130 | size_t size; | ||
131 | if (fname) { | ||
132 | size = fread((char *)src_buf, 1, SRC_BUF_SIZE, fp); | ||
133 | if ((ssize_t)size < 0) | ||
134 | bb_perror_msg_and_die(bb_msg_read_error); | ||
135 | } else { | ||
136 | size = len; | ||
137 | if (len > SRC_BUF_SIZE) | ||
138 | size = SRC_BUF_SIZE; | ||
139 | } | ||
140 | if (!size) | ||
141 | break; | ||
142 | // encode the buffer we just read in | ||
143 | bb_uuencode(dst_buf, src_buf, size, bb_uuenc_tbl_base64); | ||
144 | if (fname) { | ||
145 | printf("%s\n", eol); | ||
146 | } else { | ||
147 | src_buf += size; | ||
148 | len -= size; | ||
149 | } | ||
150 | fwrite(dst_buf, 1, 4 * ((size + 2) / 3), stdout); | ||
151 | } | ||
152 | if (fname && NOT_LONE_DASH(fname)) | ||
153 | fclose(fp); | ||
154 | #undef src_buf | ||
155 | } | ||
156 | |||
157 | void FAST_FUNC decode_base64(FILE *src_stream, FILE *dst_stream) | ||
158 | { | ||
159 | int term_count = 1; | ||
160 | |||
161 | while (1) { | ||
162 | char translated[4]; | ||
163 | int count = 0; | ||
164 | |||
165 | while (count < 4) { | ||
166 | char *table_ptr; | ||
167 | int ch; | ||
168 | |||
169 | /* Get next _valid_ character. | ||
170 | * global vector bb_uuenc_tbl_base64[] contains this string: | ||
171 | * "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n" | ||
172 | */ | ||
173 | do { | ||
174 | ch = fgetc(src_stream); | ||
175 | if (ch == EOF) { | ||
176 | bb_error_msg_and_die(bb_msg_read_error); | ||
177 | } | ||
178 | // - means end of MIME section | ||
179 | if ('-' == ch) { | ||
180 | // push it back | ||
181 | ungetc(ch, src_stream); | ||
182 | return; | ||
183 | } | ||
184 | table_ptr = strchr(bb_uuenc_tbl_base64, ch); | ||
185 | } while (table_ptr == NULL); | ||
186 | |||
187 | /* Convert encoded character to decimal */ | ||
188 | ch = table_ptr - bb_uuenc_tbl_base64; | ||
189 | |||
190 | if (*table_ptr == '=') { | ||
191 | if (term_count == 0) { | ||
192 | translated[count] = '\0'; | ||
193 | break; | ||
194 | } | ||
195 | term_count++; | ||
196 | } else if (*table_ptr == '\n') { | ||
197 | /* Check for terminating line */ | ||
198 | if (term_count == 5) { | ||
199 | return; | ||
200 | } | ||
201 | term_count = 1; | ||
202 | continue; | ||
203 | } else { | ||
204 | translated[count] = ch; | ||
205 | count++; | ||
206 | term_count = 0; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | /* Merge 6 bit chars to 8 bit */ | ||
211 | if (count > 1) { | ||
212 | fputc(translated[0] << 2 | translated[1] >> 4, dst_stream); | ||
213 | } | ||
214 | if (count > 2) { | ||
215 | fputc(translated[1] << 4 | translated[2] >> 2, dst_stream); | ||
216 | } | ||
217 | if (count > 3) { | ||
218 | fputc(translated[2] << 6 | translated[3], dst_stream); | ||
219 | } | ||
220 | } | ||
221 | } | ||
222 | |||
223 | |||
224 | /* | ||
225 | * get username and password from a file descriptor | ||
226 | */ | ||
227 | void FAST_FUNC get_cred_or_die(int fd) | ||
228 | { | ||
229 | // either from TTY | ||
230 | if (isatty(fd)) { | ||
231 | G.user = xstrdup(bb_askpass(0, "User: ")); | ||
232 | G.pass = xstrdup(bb_askpass(0, "Password: ")); | ||
233 | // or from STDIN | ||
234 | } else { | ||
235 | FILE *fp = fdopen(fd, "r"); | ||
236 | G.user = xmalloc_fgetline(fp); | ||
237 | G.pass = xmalloc_fgetline(fp); | ||
238 | fclose(fp); | ||
239 | } | ||
240 | if (!G.user || !*G.user || !G.pass || !*G.pass) | ||
241 | bb_error_msg_and_die("no username or password"); | ||
242 | } | ||
diff --git a/mailutils/mail.h b/mailutils/mail.h new file mode 100644 index 000000000..bb747c4c5 --- /dev/null +++ b/mailutils/mail.h | |||
@@ -0,0 +1,35 @@ | |||
1 | |||
2 | struct globals { | ||
3 | pid_t helper_pid; | ||
4 | unsigned timeout; | ||
5 | unsigned opts; | ||
6 | char *user; | ||
7 | char *pass; | ||
8 | FILE *fp0; // initial stdin | ||
9 | char *opt_charset; | ||
10 | char *content_type; | ||
11 | }; | ||
12 | |||
13 | #define G (*ptr_to_globals) | ||
14 | #define timeout (G.timeout ) | ||
15 | #define opts (G.opts ) | ||
16 | //#define user (G.user ) | ||
17 | //#define pass (G.pass ) | ||
18 | //#define fp0 (G.fp0 ) | ||
19 | //#define opt_charset (G.opt_charset) | ||
20 | //#define content_type (G.content_type) | ||
21 | #define INIT_G() do { \ | ||
22 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | ||
23 | G.opt_charset = (char *)CONFIG_FEATURE_MIME_CHARSET; \ | ||
24 | G.content_type = (char *)"text/plain"; \ | ||
25 | } while (0) | ||
26 | |||
27 | //char FAST_FUNC *parse_url(char *url, char **user, char **pass); | ||
28 | |||
29 | void FAST_FUNC launch_helper(const char **argv); | ||
30 | void FAST_FUNC get_cred_or_die(int fd); | ||
31 | |||
32 | const FAST_FUNC char *command(const char *fmt, const char *param); | ||
33 | |||
34 | void FAST_FUNC encode_base64(char *fname, const char *text, const char *eol); | ||
35 | void FAST_FUNC decode_base64(FILE *src_stream, FILE *dst_stream); | ||
diff --git a/mailutils/mime.c b/mailutils/mime.c new file mode 100644 index 000000000..b81cfd54c --- /dev/null +++ b/mailutils/mime.c | |||
@@ -0,0 +1,354 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * makemime: create MIME-encoded message | ||
4 | * reformime: parse MIME-encoded message | ||
5 | * | ||
6 | * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com> | ||
7 | * | ||
8 | * Licensed under GPLv2, see file LICENSE in this tarball for details. | ||
9 | */ | ||
10 | #include "libbb.h" | ||
11 | #include "mail.h" | ||
12 | |||
13 | /* | ||
14 | makemime -c type [-o file] [-e encoding] [-C charset] [-N name] \ | ||
15 | [-a "Header: Contents"] file | ||
16 | -m [ type ] [-o file] [-e encoding] [-a "Header: Contents"] file | ||
17 | -j [-o file] file1 file2 | ||
18 | @file | ||
19 | |||
20 | file: filename - read or write from filename | ||
21 | - - read or write from stdin or stdout | ||
22 | &n - read or write from file descriptor n | ||
23 | \( opts \) - read from child process, that generates [ opts ] | ||
24 | |||
25 | Options: | ||
26 | |||
27 | -c type - create a new MIME section from "file" with this | ||
28 | Content-Type: (default is application/octet-stream). | ||
29 | -C charset - MIME charset of a new text/plain section. | ||
30 | -N name - MIME content name of the new mime section. | ||
31 | -m [ type ] - create a multipart mime section from "file" of this | ||
32 | Content-Type: (default is multipart/mixed). | ||
33 | -e encoding - use the given encoding (7bit, 8bit, quoted-printable, | ||
34 | or base64), instead of guessing. Omit "-e" and use | ||
35 | -c auto to set Content-Type: to text/plain or | ||
36 | application/octet-stream based on picked encoding. | ||
37 | -j file1 file2 - join mime section file2 to multipart section file1. | ||
38 | -o file - write ther result to file, instead of stdout (not | ||
39 | allowed in child processes). | ||
40 | -a header - prepend an additional header to the output. | ||
41 | |||
42 | @file - read all of the above options from file, one option or | ||
43 | value on each line. | ||
44 | */ | ||
45 | |||
46 | int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
47 | int makemime_main(int argc UNUSED_PARAM, char **argv) | ||
48 | { | ||
49 | llist_t *opt_headers = NULL, *l; | ||
50 | const char *opt_output; | ||
51 | #define boundary opt_output | ||
52 | |||
53 | enum { | ||
54 | OPT_c = 1 << 0, // Content-Type: | ||
55 | OPT_e = 1 << 1, // Content-Transfer-Encoding. Ignored. Assumed base64 | ||
56 | OPT_o = 1 << 2, // output to | ||
57 | OPT_C = 1 << 3, // charset | ||
58 | OPT_N = 1 << 4, // COMPAT | ||
59 | OPT_a = 1 << 5, // additional headers | ||
60 | OPT_m = 1 << 6, // COMPAT | ||
61 | OPT_j = 1 << 7, // COMPAT | ||
62 | }; | ||
63 | |||
64 | INIT_G(); | ||
65 | |||
66 | // parse options | ||
67 | opt_complementary = "a::"; | ||
68 | opts = getopt32(argv, | ||
69 | "c:e:o:C:N:a:m:j:", | ||
70 | &G.content_type, NULL, &opt_output, &G.opt_charset, NULL, &opt_headers, NULL, NULL | ||
71 | ); | ||
72 | //argc -= optind; | ||
73 | argv += optind; | ||
74 | |||
75 | // respect -o output | ||
76 | if (opts & OPT_o) | ||
77 | freopen(opt_output, "w", stdout); | ||
78 | |||
79 | // no files given on command line? -> use stdin | ||
80 | if (!*argv) | ||
81 | *--argv = (char *)"-"; | ||
82 | |||
83 | // put additional headers | ||
84 | for (l = opt_headers; l; l = l->link) | ||
85 | puts(l->data); | ||
86 | |||
87 | // make a random string -- it will delimit message parts | ||
88 | srand(monotonic_us()); | ||
89 | boundary = xasprintf("%d-%d-%d", rand(), rand(), rand()); | ||
90 | |||
91 | // put multipart header | ||
92 | printf( | ||
93 | "Mime-Version: 1.0\n" | ||
94 | "Content-Type: multipart/mixed; boundary=\"%s\"\n" | ||
95 | , boundary | ||
96 | ); | ||
97 | |||
98 | // put attachments | ||
99 | while (*argv) { | ||
100 | printf( | ||
101 | "\n--%s\n" | ||
102 | "Content-Type: %s; charset=%s\n" | ||
103 | "Content-Disposition: inline; filename=\"%s\"\n" | ||
104 | "Content-Transfer-Encoding: base64\n" | ||
105 | , boundary | ||
106 | , G.content_type | ||
107 | , G.opt_charset | ||
108 | , bb_get_last_path_component_strip(*argv) | ||
109 | ); | ||
110 | encode_base64(*argv++, (const char *)stdin, ""); | ||
111 | } | ||
112 | |||
113 | // put multipart footer | ||
114 | printf("\n--%s--\n" "\n", boundary); | ||
115 | |||
116 | return EXIT_SUCCESS; | ||
117 | #undef boundary | ||
118 | } | ||
119 | |||
120 | static const char *find_token(const char *const string_array[], const char *key, const char *defvalue) | ||
121 | { | ||
122 | const char *r = NULL; | ||
123 | for (int i = 0; string_array[i] != 0; i++) { | ||
124 | if (strcasecmp(string_array[i], key) == 0) { | ||
125 | r = (char *)string_array[i+1]; | ||
126 | break; | ||
127 | } | ||
128 | } | ||
129 | return (r) ? r : defvalue; | ||
130 | } | ||
131 | |||
132 | static const char *xfind_token(const char *const string_array[], const char *key) | ||
133 | { | ||
134 | const char *r = find_token(string_array, key, NULL); | ||
135 | if (r) | ||
136 | return r; | ||
137 | bb_error_msg_and_die("header: %s", key); | ||
138 | } | ||
139 | |||
140 | enum { | ||
141 | OPT_x = 1 << 0, | ||
142 | OPT_X = 1 << 1, | ||
143 | #if ENABLE_FEATURE_REFORMIME_COMPAT | ||
144 | OPT_d = 1 << 2, | ||
145 | OPT_e = 1 << 3, | ||
146 | OPT_i = 1 << 4, | ||
147 | OPT_s = 1 << 5, | ||
148 | OPT_r = 1 << 6, | ||
149 | OPT_c = 1 << 7, | ||
150 | OPT_m = 1 << 8, | ||
151 | OPT_h = 1 << 9, | ||
152 | OPT_o = 1 << 10, | ||
153 | OPT_O = 1 << 11, | ||
154 | #endif | ||
155 | }; | ||
156 | |||
157 | static int parse(const char *boundary, char **argv) | ||
158 | { | ||
159 | char *line, *s, *p; | ||
160 | const char *type; | ||
161 | int boundary_len = strlen(boundary); | ||
162 | const char *delims = " ;\"\t\r\n"; | ||
163 | const char *uniq; | ||
164 | int ntokens; | ||
165 | const char *tokens[32]; // 32 is enough | ||
166 | |||
167 | // prepare unique string pattern | ||
168 | uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname()); | ||
169 | |||
170 | //bb_info_msg("PARSE[%s]", terminator); | ||
171 | |||
172 | while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) { | ||
173 | |||
174 | // seek to start of MIME section | ||
175 | // N.B. to avoid false positives let us seek to the _last_ occurance | ||
176 | p = NULL; | ||
177 | s = line; | ||
178 | while ((s=strcasestr(s, "Content-Type:")) != NULL) | ||
179 | p = s++; | ||
180 | if (!p) | ||
181 | goto next; | ||
182 | //bb_info_msg("L[%s]", p); | ||
183 | |||
184 | // split to tokens | ||
185 | // TODO: strip of comments which are of form: (comment-text) | ||
186 | ntokens = 0; | ||
187 | tokens[ntokens] = NULL; | ||
188 | for (s = strtok(p, delims); s; s = strtok(NULL, delims)) { | ||
189 | tokens[ntokens] = s; | ||
190 | if (ntokens < ARRAY_SIZE(tokens) - 1) | ||
191 | ntokens++; | ||
192 | //bb_info_msg("L[%d][%s]", ntokens, s); | ||
193 | } | ||
194 | tokens[ntokens] = NULL; | ||
195 | //bb_info_msg("N[%d]", ntokens); | ||
196 | |||
197 | // analyse tokens | ||
198 | type = find_token(tokens, "Content-Type:", "text/plain"); | ||
199 | //bb_info_msg("T[%s]", type); | ||
200 | if (0 == strncasecmp(type, "multipart/", 10)) { | ||
201 | if (0 == strcasecmp(type+10, "mixed")) { | ||
202 | parse(xfind_token(tokens, "boundary="), argv); | ||
203 | } else | ||
204 | bb_error_msg_and_die("no support of content type '%s'", type); | ||
205 | } else { | ||
206 | pid_t pid = pid; | ||
207 | int rc; | ||
208 | FILE *fp; | ||
209 | // fetch charset | ||
210 | const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET); | ||
211 | // fetch encoding | ||
212 | const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit"); | ||
213 | // compose target filename | ||
214 | char *filename = (char *)find_token(tokens, "filename=", NULL); | ||
215 | if (!filename) | ||
216 | filename = xasprintf(uniq, monotonic_us()); | ||
217 | else | ||
218 | filename = bb_get_last_path_component_strip(xstrdup(filename)); | ||
219 | |||
220 | // start external helper, if any | ||
221 | if (opts & OPT_X) { | ||
222 | int fd[2]; | ||
223 | xpipe(fd); | ||
224 | pid = fork(); | ||
225 | if (0 == pid) { | ||
226 | // child reads from fd[0] | ||
227 | xdup2(fd[0], STDIN_FILENO); | ||
228 | close(fd[0]); close(fd[1]); | ||
229 | xsetenv("CONTENT_TYPE", type); | ||
230 | xsetenv("CHARSET", charset); | ||
231 | xsetenv("ENCODING", encoding); | ||
232 | xsetenv("FILENAME", filename); | ||
233 | BB_EXECVP(*argv, argv); | ||
234 | _exit(EXIT_FAILURE); | ||
235 | } | ||
236 | // parent dumps to fd[1] | ||
237 | close(fd[0]); | ||
238 | fp = fdopen(fd[1], "w"); | ||
239 | signal(SIGPIPE, SIG_IGN); // ignore EPIPE | ||
240 | // or create a file for dump | ||
241 | } else { | ||
242 | char *fname = xasprintf("%s%s", *argv, filename); | ||
243 | fp = xfopen_for_write(fname); | ||
244 | free(fname); | ||
245 | } | ||
246 | |||
247 | // housekeeping | ||
248 | free(filename); | ||
249 | |||
250 | // dump to fp | ||
251 | if (0 == strcasecmp(encoding, "base64")) { | ||
252 | decode_base64(stdin, fp); | ||
253 | } else if (0 != strcasecmp(encoding, "7bit") | ||
254 | && 0 != strcasecmp(encoding, "8bit")) { | ||
255 | // quoted-printable, binary, user-defined are unsupported so far | ||
256 | bb_error_msg_and_die("no support of encoding '%s'", encoding); | ||
257 | } else { | ||
258 | // N.B. we have written redundant \n. so truncate the file | ||
259 | // The following weird 2-tacts reading technique is due to | ||
260 | // we have to not write extra \n at the end of the file | ||
261 | // In case of -x option we could truncate the resulting file as | ||
262 | // fseek(fp, -1, SEEK_END); | ||
263 | // if (ftruncate(fileno(fp), ftell(fp))) | ||
264 | // bb_perror_msg("ftruncate"); | ||
265 | // But in case of -X we have to be much more careful. There is | ||
266 | // no means to truncate what we already have sent to the helper. | ||
267 | p = xmalloc_fgets_str(stdin, "\r\n"); | ||
268 | while (p) { | ||
269 | if ((s = xmalloc_fgets_str(stdin, "\r\n")) == NULL) | ||
270 | break; | ||
271 | if ('-' == s[0] && '-' == s[1] | ||
272 | && 0 == strncmp(s+2, boundary, boundary_len)) | ||
273 | break; | ||
274 | fputs(p, fp); | ||
275 | p = s; | ||
276 | } | ||
277 | |||
278 | /* | ||
279 | while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) { | ||
280 | if ('-' == s[0] && '-' == s[1] | ||
281 | && 0 == strncmp(s+2, boundary, boundary_len)) | ||
282 | break; | ||
283 | fprintf(fp, "%s\n", s); | ||
284 | } | ||
285 | // N.B. we have written redundant \n. so truncate the file | ||
286 | fseek(fp, -1, SEEK_END); | ||
287 | if (ftruncate(fileno(fp), ftell(fp))) | ||
288 | bb_perror_msg("ftruncate"); | ||
289 | */ | ||
290 | } | ||
291 | fclose(fp); | ||
292 | |||
293 | // finalize helper | ||
294 | if (opts & OPT_X) { | ||
295 | signal(SIGPIPE, SIG_DFL); | ||
296 | // exit if helper exited >0 | ||
297 | rc = wait4pid(pid); | ||
298 | if (rc) | ||
299 | return rc+20; | ||
300 | } | ||
301 | |||
302 | // check multipart finalized | ||
303 | if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) { | ||
304 | free(line); | ||
305 | break; | ||
306 | } | ||
307 | } | ||
308 | next: | ||
309 | free(line); | ||
310 | } | ||
311 | |||
312 | //bb_info_msg("ENDPARSE[%s]", boundary); | ||
313 | |||
314 | return EXIT_SUCCESS; | ||
315 | } | ||
316 | |||
317 | /* | ||
318 | Usage: reformime [options] | ||
319 | -d - parse a delivery status notification. | ||
320 | -e - extract contents of MIME section. | ||
321 | -x - extract MIME section to a file. | ||
322 | -X - pipe MIME section to a program. | ||
323 | -i - show MIME info. | ||
324 | -s n.n.n.n - specify MIME section. | ||
325 | -r - rewrite message, filling in missing MIME headers. | ||
326 | -r7 - also convert 8bit/raw encoding to quoted-printable, if possible. | ||
327 | -r8 - also convert quoted-printable encoding to 8bit, if possible. | ||
328 | -c charset - default charset for rewriting, -o, and -O. | ||
329 | -m [file] [file]... - create a MIME message digest. | ||
330 | -h "header" - decode RFC 2047-encoded header. | ||
331 | -o "header" - encode unstructured header using RFC 2047. | ||
332 | -O "header" - encode address list header using RFC 2047. | ||
333 | */ | ||
334 | |||
335 | int reformime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
336 | int reformime_main(int argc UNUSED_PARAM, char **argv) | ||
337 | { | ||
338 | const char *opt_prefix = ""; | ||
339 | |||
340 | INIT_G(); | ||
341 | |||
342 | // parse options | ||
343 | // N.B. only -x and -X are supported so far | ||
344 | opt_complementary = "x--X:X--x" USE_FEATURE_REFORMIME_COMPAT(":m::"); | ||
345 | opts = getopt32(argv, | ||
346 | "x:X" USE_FEATURE_REFORMIME_COMPAT("deis:r:c:m:h:o:O:"), | ||
347 | &opt_prefix | ||
348 | USE_FEATURE_REFORMIME_COMPAT(, NULL, NULL, &G.opt_charset, NULL, NULL, NULL, NULL) | ||
349 | ); | ||
350 | //argc -= optind; | ||
351 | argv += optind; | ||
352 | |||
353 | return parse("", (opts & OPT_X) ? argv : (char **)&opt_prefix); | ||
354 | } | ||
diff --git a/mailutils/popmaildir.c b/mailutils/popmaildir.c new file mode 100644 index 000000000..d2cc7c0b9 --- /dev/null +++ b/mailutils/popmaildir.c | |||
@@ -0,0 +1,237 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * popmaildir: a simple yet powerful POP3 client | ||
4 | * Delivers contents of remote mailboxes to local Maildir | ||
5 | * | ||
6 | * Inspired by original utility by Nikola Vladov | ||
7 | * | ||
8 | * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com> | ||
9 | * | ||
10 | * Licensed under GPLv2, see file LICENSE in this tarball for details. | ||
11 | */ | ||
12 | #include "libbb.h" | ||
13 | #include "mail.h" | ||
14 | |||
15 | static void pop3_checkr(const char *fmt, const char *param, char **ret) | ||
16 | { | ||
17 | const char *msg = command(fmt, param); | ||
18 | char *answer = xmalloc_fgetline(stdin); | ||
19 | if (answer && '+' == *answer) { | ||
20 | if (timeout) | ||
21 | alarm(0); | ||
22 | if (ret) | ||
23 | *ret = answer+4; // skip "+OK " | ||
24 | else if (ENABLE_FEATURE_CLEAN_UP) | ||
25 | free(answer); | ||
26 | return; | ||
27 | } | ||
28 | bb_error_msg_and_die("%s failed: %s", msg, answer); | ||
29 | } | ||
30 | |||
31 | static void pop3_check(const char *fmt, const char *param) | ||
32 | { | ||
33 | pop3_checkr(fmt, param, NULL); | ||
34 | } | ||
35 | |||
36 | int popmaildir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
37 | int popmaildir_main(int argc UNUSED_PARAM, char **argv) | ||
38 | { | ||
39 | char *buf; | ||
40 | unsigned nmsg; | ||
41 | char *hostname; | ||
42 | pid_t pid; | ||
43 | const char *retr; | ||
44 | #if ENABLE_FEATURE_POPMAILDIR_DELIVERY | ||
45 | const char *delivery; | ||
46 | #endif | ||
47 | unsigned opt_nlines = 0; | ||
48 | |||
49 | enum { | ||
50 | OPT_b = 1 << 0, // -b binary mode. Ignored | ||
51 | OPT_d = 1 << 1, // -d,-dd,-ddd debug. Ignored | ||
52 | OPT_m = 1 << 2, // -m show used memory. Ignored | ||
53 | OPT_V = 1 << 3, // -V version. Ignored | ||
54 | OPT_c = 1 << 4, // -c use tcpclient. Ignored | ||
55 | OPT_a = 1 << 5, // -a use APOP protocol | ||
56 | OPT_s = 1 << 6, // -s skip authorization | ||
57 | OPT_T = 1 << 7, // -T get messages with TOP instead with RETR | ||
58 | OPT_k = 1 << 8, // -k keep retrieved messages on the server | ||
59 | OPT_t = 1 << 9, // -t90 set timeout to 90 sec | ||
60 | OPT_R = 1 << 10, // -R20000 remove old messages on the server >= 20000 bytes (requires -k). Ignored | ||
61 | OPT_Z = 1 << 11, // -Z11-23 remove messages from 11 to 23 (dangerous). Ignored | ||
62 | OPT_L = 1 << 12, // -L50000 not retrieve new messages >= 50000 bytes. Ignored | ||
63 | OPT_H = 1 << 13, // -H30 type first 30 lines of a message; (-L12000 -H30). Ignored | ||
64 | OPT_M = 1 << 14, // -M\"program arg1 arg2 ...\"; deliver by program. Treated like -F | ||
65 | OPT_F = 1 << 15, // -F\"program arg1 arg2 ...\"; filter by program. Treated like -M | ||
66 | }; | ||
67 | |||
68 | // init global variables | ||
69 | INIT_G(); | ||
70 | |||
71 | // parse options | ||
72 | opt_complementary = "-1:dd:t+:R+:L+:H+"; | ||
73 | opts = getopt32(argv, | ||
74 | "bdmVcasTkt:" "R:Z:L:H:" USE_FEATURE_POPMAILDIR_DELIVERY("M:F:"), | ||
75 | &timeout, NULL, NULL, NULL, &opt_nlines | ||
76 | USE_FEATURE_POPMAILDIR_DELIVERY(, &delivery, &delivery) // we treat -M and -F the same | ||
77 | ); | ||
78 | //argc -= optind; | ||
79 | argv += optind; | ||
80 | |||
81 | // get auth info | ||
82 | if (!(opts & OPT_s)) | ||
83 | get_cred_or_die(STDIN_FILENO); | ||
84 | |||
85 | // goto maildir | ||
86 | xchdir(*argv++); | ||
87 | |||
88 | // launch connect helper, if any | ||
89 | if (*argv) | ||
90 | launch_helper((const char **)argv); | ||
91 | |||
92 | // get server greeting | ||
93 | pop3_checkr(NULL, NULL, &buf); | ||
94 | |||
95 | // authenticate (if no -s given) | ||
96 | if (!(opts & OPT_s)) { | ||
97 | // server supports APOP and we want it? -> use it | ||
98 | if ('<' == *buf && (opts & OPT_a)) { | ||
99 | md5_ctx_t md5; | ||
100 | // yes! compose <stamp><password> | ||
101 | char *s = strchr(buf, '>'); | ||
102 | if (s) | ||
103 | strcpy(s+1, G.pass); | ||
104 | s = buf; | ||
105 | // get md5 sum of <stamp><password> | ||
106 | md5_begin(&md5); | ||
107 | md5_hash(s, strlen(s), &md5); | ||
108 | md5_end(s, &md5); | ||
109 | // NOTE: md5 struct contains enough space | ||
110 | // so we reuse md5 space instead of xzalloc(16*2+1) | ||
111 | #define md5_hex ((uint8_t *)&md5) | ||
112 | // uint8_t *md5_hex = (uint8_t *)&md5; | ||
113 | *bin2hex((char *)md5_hex, s, 16) = '\0'; | ||
114 | // APOP | ||
115 | s = xasprintf("%s %s", G.user, md5_hex); | ||
116 | #undef md5_hex | ||
117 | pop3_check("APOP %s", s); | ||
118 | if (ENABLE_FEATURE_CLEAN_UP) { | ||
119 | free(s); | ||
120 | free(buf-4); // buf is "+OK " away from malloc'ed string | ||
121 | } | ||
122 | // server ignores APOP -> use simple text authentication | ||
123 | } else { | ||
124 | // USER | ||
125 | pop3_check("USER %s", G.user); | ||
126 | // PASS | ||
127 | pop3_check("PASS %s", G.pass); | ||
128 | } | ||
129 | } | ||
130 | |||
131 | // get mailbox statistics | ||
132 | pop3_checkr("STAT", NULL, &buf); | ||
133 | |||
134 | // prepare message filename suffix | ||
135 | hostname = safe_gethostname(); | ||
136 | pid = getpid(); | ||
137 | |||
138 | // get messages counter | ||
139 | // NOTE: we don't use xatou(buf) since buf is "nmsg nbytes" | ||
140 | // we only need nmsg and atoi is just exactly what we need | ||
141 | // if atoi fails to convert buf into number it returns 0 | ||
142 | // in this case the following loop simply will not be executed | ||
143 | nmsg = atoi(buf); | ||
144 | if (ENABLE_FEATURE_CLEAN_UP) | ||
145 | free(buf-4); // buf is "+OK " away from malloc'ed string | ||
146 | |||
147 | // loop through messages | ||
148 | retr = (opts & OPT_T) ? xasprintf("TOP %%u %u", opt_nlines) : "RETR %u"; | ||
149 | for (; nmsg; nmsg--) { | ||
150 | |||
151 | char *filename; | ||
152 | char *target; | ||
153 | char *answer; | ||
154 | FILE *fp; | ||
155 | #if ENABLE_FEATURE_POPMAILDIR_DELIVERY | ||
156 | int rc; | ||
157 | #endif | ||
158 | // generate unique filename | ||
159 | filename = xasprintf("tmp/%llu.%u.%s", | ||
160 | monotonic_us(), (unsigned)pid, hostname); | ||
161 | |||
162 | // retrieve message in ./tmp/ unless filter is specified | ||
163 | pop3_check(retr, (const char *)(ptrdiff_t)nmsg); | ||
164 | |||
165 | #if ENABLE_FEATURE_POPMAILDIR_DELIVERY | ||
166 | // delivery helper ordered? -> setup pipe | ||
167 | if (opts & (OPT_F|OPT_M)) { | ||
168 | // helper will have $FILENAME set to filename | ||
169 | xsetenv("FILENAME", filename); | ||
170 | fp = popen(delivery, "w"); | ||
171 | unsetenv("FILENAME"); | ||
172 | if (!fp) { | ||
173 | bb_perror_msg("delivery helper"); | ||
174 | break; | ||
175 | } | ||
176 | } else | ||
177 | #endif | ||
178 | // create and open file filename | ||
179 | fp = xfopen_for_write(filename); | ||
180 | |||
181 | // copy stdin to fp (either filename or delivery helper) | ||
182 | while ((answer = xmalloc_fgets_str(stdin, "\r\n")) != NULL) { | ||
183 | char *s = answer; | ||
184 | if ('.' == answer[0]) { | ||
185 | if ('.' == answer[1]) | ||
186 | s++; | ||
187 | else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3]) | ||
188 | break; | ||
189 | } | ||
190 | //*strchrnul(s, '\r') = '\n'; | ||
191 | fputs(s, fp); | ||
192 | free(answer); | ||
193 | } | ||
194 | |||
195 | #if ENABLE_FEATURE_POPMAILDIR_DELIVERY | ||
196 | // analyse delivery status | ||
197 | if (opts & (OPT_F|OPT_M)) { | ||
198 | rc = pclose(fp); | ||
199 | if (99 == rc) // 99 means bail out | ||
200 | break; | ||
201 | // if (rc) // !0 means skip to the next message | ||
202 | goto skip; | ||
203 | // // 0 means continue | ||
204 | } else { | ||
205 | // close filename | ||
206 | fclose(fp); | ||
207 | } | ||
208 | #endif | ||
209 | |||
210 | // delete message from server | ||
211 | if (!(opts & OPT_k)) | ||
212 | pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg); | ||
213 | |||
214 | // atomically move message to ./new/ | ||
215 | target = xstrdup(filename); | ||
216 | strncpy(target, "new", 3); | ||
217 | // ... or just stop receiving on failure | ||
218 | if (rename_or_warn(filename, target)) | ||
219 | break; | ||
220 | free(target); | ||
221 | |||
222 | #if ENABLE_FEATURE_POPMAILDIR_DELIVERY | ||
223 | skip: | ||
224 | #endif | ||
225 | free(filename); | ||
226 | } | ||
227 | |||
228 | // Bye | ||
229 | pop3_check("QUIT", NULL); | ||
230 | |||
231 | if (ENABLE_FEATURE_CLEAN_UP) { | ||
232 | free(G.user); | ||
233 | free(G.pass); | ||
234 | } | ||
235 | |||
236 | return EXIT_SUCCESS; | ||
237 | } | ||
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 | } | ||