add mailutils/*
[oweals/busybox.git] / mailutils / popmaildir.c
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 }