aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenis Vlasenko <vda.linux@googlemail.com>2008-11-06 23:42:42 +0000
committerDenis Vlasenko <vda.linux@googlemail.com>2008-11-06 23:42:42 +0000
commit239d06bd4710e8463c6cc7e5411965066a6d134e (patch)
tree10b9f670ec97b651bca9fcbe42a48003b687cd45
parentb9d572a2733fa20957a9a3287bd04d66176e3b6a (diff)
downloadbusybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.tar.gz
busybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.tar.bz2
busybox-w32-239d06bd4710e8463c6cc7e5411965066a6d134e.zip
add mailutils/*
-rw-r--r--mailutils/Config.in64
-rw-r--r--mailutils/Kbuild11
-rw-r--r--mailutils/mail.c242
-rw-r--r--mailutils/mail.h35
-rw-r--r--mailutils/mime.c354
-rw-r--r--mailutils/popmaildir.c237
-rw-r--r--mailutils/sendmail.c388
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 @@
1menu "Mail Utilities"
2
3config MAKEMIME
4 bool "makemime"
5 default n
6 help
7 Create MIME-formatted messages.
8
9config 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
16config 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
22config 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
30config REFORMIME
31 bool "reformime"
32 default n
33 help
34 Parse MIME-formatted messages.
35
36config 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
43config SENDMAIL
44 bool "sendmail"
45 default n
46 help
47 Barebones sendmail.
48
49config 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
57config 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
64endmenu
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
7lib-y:=
8lib-$(CONFIG_MAKEMIME) += mime.o mail.o
9lib-$(CONFIG_POPMAILDIR) += popmaildir.o mail.o
10lib-$(CONFIG_REFORMIME) += mime.o mail.o
11lib-$(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
12static 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
20static 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
38void 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
71const 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/*
86static 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
106void 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
157void 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 */
227void 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
2struct 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
29void FAST_FUNC launch_helper(const char **argv);
30void FAST_FUNC get_cred_or_die(int fd);
31
32const FAST_FUNC char *command(const char *fmt, const char *param);
33
34void FAST_FUNC encode_base64(char *fname, const char *text, const char *eol);
35void 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
25Options:
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
46int makemime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
47int 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
120static 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
132static 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
140enum {
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
157static 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/*
318Usage: 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
335int reformime_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
336int 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
15static 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
31static void pop3_check(const char *fmt, const char *param)
32{
33 pop3_checkr(fmt, param, NULL);
34}
35
36int popmaildir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
37int 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
12static 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
36static int smtp_check(const char *fmt, int code)
37{
38 return smtp_checkp(fmt, NULL, code);
39}
40
41// strip argument of bad chars
42static 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
56static void rcptto(const char *s)
57{
58 smtp_checkp("RCPT TO:<%s>", s, 250);
59}
60
61int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
62int 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}