Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 983 by niro, Fri Apr 24 18:33:46 2009 UTC revision 984 by niro, Sun May 30 11:32:42 2010 UTC
# Line 9  Line 9 
9  #include "libbb.h"  #include "libbb.h"
10  #include "mail.h"  #include "mail.h"
11    
12    // limit maximum allowed number of headers to prevent overflows.
13    // set to 0 to not limit
14    #define MAX_HEADERS 256
15    
16  static int smtp_checkp(const char *fmt, const char *param, int code)  static int smtp_checkp(const char *fmt, const char *param, int code)
17  {  {
18   char *answer;   char *answer;
# Line 55  static char *sane_address(char *str) Line 59  static char *sane_address(char *str)
59    
60  static void rcptto(const char *s)  static void rcptto(const char *s)
61  {  {
62   smtp_checkp("RCPT TO:<%s>", s, 250);   // N.B. we don't die if recipient is rejected, for the other recipients may be accepted
63     if (250 != smtp_checkp("RCPT TO:<%s>", s, -1))
64     bb_error_msg("Bad recipient: <%s>", s);
65  }  }
66    
67  int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;  int sendmail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
68  int sendmail_main(int argc UNUSED_PARAM, char **argv)  int sendmail_main(int argc UNUSED_PARAM, char **argv)
69  {  {
 #if ENABLE_FEATURE_SENDMAIL_MAILX  
  llist_t *opt_attachments = NULL;  
  const char *opt_subject;  
 #if ENABLE_FEATURE_SENDMAIL_MAILXX  
  llist_t *opt_carboncopies = NULL;  
  char *opt_errors_to;  
 #endif  
 #endif  
70   char *opt_connect = opt_connect;   char *opt_connect = opt_connect;
71   char *opt_from, *opt_fullname;   char *opt_from;
72   char *boundary;   char *s;
73   llist_t *l;   llist_t *list = NULL;
  llist_t *headers = NULL;  
74   char *domain = sane_address(safe_getdomainname());   char *domain = sane_address(safe_getdomainname());
75     unsigned nheaders = 0;
76   int code;   int code;
77    
78   enum {   enum {
79   OPT_w = 1 << 0,         // network timeout   //--- standard options
80   OPT_t = 1 << 1,         // read message for recipients   OPT_t = 1 << 0,         // read message for recipients, append them to those on cmdline
81   OPT_N = 1 << 2,         // request notification   OPT_f = 1 << 1,         // sender address
82   OPT_f = 1 << 3,         // sender address   OPT_o = 1 << 2,         // various options. -oi IMPLIED! others are IGNORED!
83   OPT_F = 1 << 4,         // sender name, overrides $NAME   OPT_i = 1 << 3,         // IMPLIED!
84   OPT_s = 1 << 5,         // subject   //--- BB specific options
85   OPT_j = 1 << 6,         // assumed charset   OPT_w = 1 << 4,         // network timeout
86   OPT_a = 1 << 7,         // attachment(s)   OPT_H = 1 << 5,         // use external connection helper
87   OPT_H = 1 << 8,         // use external connection helper   OPT_S = 1 << 6,         // specify connection string
88   OPT_S = 1 << 9,         // specify connection string   OPT_a = 1 << 7,         // authentication tokens
  OPT_c = 1 << 10,        // carbon copy  
  OPT_e = 1 << 11,        // errors-to address  
89   };   };
90    
91   // init global variables   // init global variables
# Line 97  int sendmail_main(int argc UNUSED_PARAM, Line 93  int sendmail_main(int argc UNUSED_PARAM,
93    
94   // save initial stdin since body is piped!   // save initial stdin since body is piped!
95   xdup2(STDIN_FILENO, 3);   xdup2(STDIN_FILENO, 3);
96   G.fp0 = fdopen(3, "r");   G.fp0 = xfdopen_for_read(3);
97    
98   // parse options   // parse options
99   opt_complementary = "w+" USE_FEATURE_SENDMAIL_MAILX(":a::H--S:S--H") USE_FEATURE_SENDMAIL_MAILXX(":c::");   // -f is required. -H and -S are mutually exclusive
100   opts = getopt32(argv,   opt_complementary = "f:w+:H--S:S--H:a::";
101   "w:t" "N:f:F:" USE_FEATURE_SENDMAIL_MAILX("s:j:a:H:S:") USE_FEATURE_SENDMAIL_MAILXX("c:e:")   // N.B. since -H and -S are mutually exclusive they do not interfere in opt_connect
102   "X:V:vq:R:O:o:nmL:Iih:GC:B:b:A:" // postfix compat only, ignored   // -a is for ssmtp (http://downloads.openwrt.org/people/nico/man/man8/ssmtp.8.html) compatibility,
103   // r:Q:p:M:Dd are candidates from another man page. TODO?   // it is still under development.
104   "46E", // ssmtp introduces another quirks. TODO?: -a[upm] (user, pass, method) to be supported   opts = getopt32(argv, "tf:o:iw:H:S:a::", &opt_from, NULL, &timeout, &opt_connect, &opt_connect, &list);
  &timeout /* -w */, NULL, &opt_from, &opt_fullname,  
  USE_FEATURE_SENDMAIL_MAILX(&opt_subject, &G.opt_charset, &opt_attachments, &opt_connect, &opt_connect,)  
  USE_FEATURE_SENDMAIL_MAILXX(&opt_carboncopies, &opt_errors_to,)  
  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL  
  );  
105   //argc -= optind;   //argc -= optind;
106   argv += optind;   argv += optind;
107    
108     // process -a[upm]<token> options
109     if ((opts & OPT_a) && !list)
110     bb_show_usage();
111     while (list) {
112     char *a = (char *) llist_pop(&list);
113     if ('u' == a[0])
114     G.user = xstrdup(a+1);
115     if ('p' == a[0])
116     G.pass = xstrdup(a+1);
117     // N.B. we support only AUTH LOGIN so far
118     //if ('m' == a[0])
119     // G.method = xstrdup(a+1);
120     }
121     // N.B. list == NULL here
122     //bb_info_msg("OPT[%x] AU[%s], AP[%s], AM[%s], ARGV[%s]", opts, au, ap, am, *argv);
123    
124   // connect to server   // connect to server
125    
 #if ENABLE_FEATURE_SENDMAIL_MAILX  
  // N.B. -H and -S are mutually exclusive so they do not spoil opt_connect  
126   // connection helper ordered? ->   // connection helper ordered? ->
127   if (opts & OPT_H) {   if (opts & OPT_H) {
128   const char *args[] = { "sh", "-c", opt_connect, NULL };   const char *args[] = { "sh", "-c", opt_connect, NULL };
129   // plug it in   // plug it in
130   launch_helper(args);   launch_helper(args);
131   // vanilla connection   // vanilla connection
132   } else   } else {
 #endif  
  {  
133   int fd;   int fd;
134   // host[:port] not explicitly specified ? -> use $SMTPHOST   // host[:port] not explicitly specified? -> use $SMTPHOST
135   // no $SMTPHOST ? -> use localhost   // no $SMTPHOST ? -> use localhost
136   if (!(opts & OPT_S)) {   if (!(opts & OPT_S)) {
137   opt_connect = getenv("SMTPHOST");   opt_connect = getenv("SMTPHOST");
# Line 145  int sendmail_main(int argc UNUSED_PARAM, Line 148  int sendmail_main(int argc UNUSED_PARAM,
148    
149   // wait for initial server OK   // wait for initial server OK
150   // N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure   // N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure
151   // so we need to push the server to see whether we are ok   // so we need to kick the server to see whether we are ok
152   code = smtp_check("NOOP", -1);   code = smtp_check("NOOP", -1);
153   // 220 on plain connection, 250 on openssl-helped TLS session   // 220 on plain connection, 250 on openssl-helped TLS session
154   if (220 == code)   if (220 == code)
# Line 157  int sendmail_main(int argc UNUSED_PARAM, Line 160  int sendmail_main(int argc UNUSED_PARAM,
160   if (250 != smtp_checkp("EHLO %s", domain, -1)) {   if (250 != smtp_checkp("EHLO %s", domain, -1)) {
161   smtp_checkp("HELO %s", domain, 250);   smtp_checkp("HELO %s", domain, 250);
162   }   }
163     if (ENABLE_FEATURE_CLEAN_UP)
164     free(domain);
165    
166     // perform authentication
167     if (opts & OPT_a) {
168     smtp_check("AUTH LOGIN", 334);
169     // we must read credentials unless they are given via -a[up] options
170     if (!G.user || !G.pass)
171     get_cred_or_die(4);
172     encode_base64(NULL, G.user, NULL);
173     smtp_check("", 334);
174     encode_base64(NULL, G.pass, NULL);
175     smtp_check("", 235);
176     }
177    
178   // set sender   // set sender
179   // N.B. we have here a very loosely defined algotythm   // N.B. we have here a very loosely defined algotythm
# Line 170  int sendmail_main(int argc UNUSED_PARAM, Line 187  int sendmail_main(int argc UNUSED_PARAM,
187   // file descriptor (e.g. 4), or again from a secured file.   // file descriptor (e.g. 4), or again from a secured file.
188    
189   // got no sender address? -> use system username as a resort   // got no sender address? -> use system username as a resort
190   if (!(opts & OPT_f)) {   // N.B. we marked -f as required option!
191   // N.B. IMHO getenv("USER") can be way easily spoofed!   //if (!G.user) {
192   G.user = bb_getpwuid(NULL, -1, getuid());   // // N.B. IMHO getenv("USER") can be way easily spoofed!
193   opt_from = xasprintf("%s@%s", G.user, domain);   // G.user = xuid2uname(getuid());
194   }   // opt_from = xasprintf("%s@%s", G.user, domain);
195   if (ENABLE_FEATURE_CLEAN_UP)   //}
196   free(domain);   //if (ENABLE_FEATURE_CLEAN_UP)
197     // free(domain);
198   code = -1; // first try softly without authentication   smtp_checkp("MAIL FROM:<%s>", opt_from, 250);
199   while (250 != smtp_checkp("MAIL FROM:<%s>", opt_from, code)) {  
200   // MAIL FROM failed -> authentication needed   // process message
201   if (334 == smtp_check("AUTH LOGIN", -1)) {  
202   // we must read credentials   // read recipients from message and add them to those given on cmdline.
203   get_cred_or_die(4);   // this means we scan stdin for To:, Cc:, Bcc: lines until an empty line
204   encode_base64(NULL, G.user, NULL);   // and then use the rest of stdin as message body
205   smtp_check("", 334);   code = 0; // set "analyze headers" mode
206   encode_base64(NULL, G.pass, NULL);   while ((s = xmalloc_fgetline(G.fp0)) != NULL) {
207   smtp_check("", 235);   dump:
208   }   // put message lines doubling leading dots
209   // authenticated OK? -> retry to set sender   if (code) {
  // but this time die on failure!  
  code = 250;  
  }  
   
  // recipients specified as arguments  
  while (*argv) {  
  char *s = sane_address(*argv);  
  // loose test on email address validity  
 // if (strchr(s, '@')) {  
  rcptto(s);  
  llist_add_to_end(&headers, xasprintf("To: %s", s));  
 // }  
  argv++;  
  }  
   
 #if ENABLE_FEATURE_SENDMAIL_MAILXX  
  // carbon copies recipients specified as -c options  
  for (l = opt_carboncopies; l; l = l->link) {  
  char *s = sane_address(l->data);  
  // loose test on email address validity  
 // if (strchr(s, '@')) {  
  rcptto(s);  
  // TODO: do we ever need to mangle the message?  
  //llist_add_to_end(&headers, xasprintf("Cc: %s", s));  
 // }  
  }  
 #endif  
   
  // if -t specified or no recipients specified -> read recipients from message  
  // i.e. scan stdin for To:, Cc:, Bcc: lines ...  
  // ... and then use the rest of stdin as message body  
  // N.B. subject read from body can be further overrided with one specified on command line.  
  // recipients are merged. Bcc: lines are deleted  
  // N.B. other headers are collected and will be dumped verbatim  
  if (opts & OPT_t || !headers) {  
  // fetch recipients and (optionally) subject  
  char *s;  
  while ((s = xmalloc_fgetline(G.fp0)) != NULL) {  
  if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Cc: ", s, 4)) {  
  rcptto(sane_address(s+4));  
  llist_add_to_end(&headers, s);  
  } else if (0 == strncasecmp("Bcc: ", s, 5)) {  
  rcptto(sane_address(s+5));  
  free(s);  
  // N.B. Bcc vanishes from headers!  
  } else if (0 == strncmp("Subject: ", s, 9)) {  
  // we read subject -> use it verbatim unless it is specified  
  // on command line  
  if (!(opts & OPT_s))  
  llist_add_to_end(&headers, s);  
  else  
  free(s);  
  } else if (s[0]) {  
  // misc header  
  llist_add_to_end(&headers, s);  
  } else {  
  free(s);  
  break; // stop on the first empty line  
  }  
  }  
  }  
   
  // enter "put message" mode  
  smtp_check("DATA", 354);  
   
  // put headers we could have preread with -t  
  for (l = headers; l; l = l->link) {  
  printf("%s\r\n", l->data);  
  if (ENABLE_FEATURE_CLEAN_UP)  
  free(l->data);  
  }  
   
  // put (possibly encoded) subject  
 #if ENABLE_FEATURE_SENDMAIL_MAILX  
  if (opts & OPT_s) {  
  printf("Subject: ");  
  if (opts & OPT_j) {  
  printf("=?%s?B?", G.opt_charset);  
  encode_base64(NULL, opt_subject, NULL);  
  printf("?=");  
  } else {  
  printf("%s", opt_subject);  
  }  
  printf("\r\n");  
  }  
 #endif  
   
  // put sender name, $NAME is the default  
  if (!(opts & OPT_F))  
  opt_fullname = getenv("NAME");  
  if (opt_fullname)  
  printf("From: \"%s\" <%s>\r\n", opt_fullname, opt_from);  
   
  // put notification  
  if (opts & OPT_N)  
  printf("Disposition-Notification-To: %s\r\n", opt_from);  
   
 #if ENABLE_FEATURE_SENDMAIL_MAILXX  
  // put errors recipient  
  if (opts & OPT_e)  
  printf("Errors-To: %s\r\n", opt_errors_to);  
 #endif  
   
  // make a random string -- it will delimit message parts  
  srand(monotonic_us());  
  boundary = xasprintf("%d=_%d-%d", rand(), rand(), rand());  
   
  // put common headers  
  // TODO: do we really need this?  
 // printf("Message-ID: <%s>\r\n", boundary);  
   
 #if ENABLE_FEATURE_SENDMAIL_MAILX  
  // have attachments? -> compose multipart MIME  
  if (opt_attachments) {  
  const char *fmt;  
  const char *p;  
  char *q;  
   
  printf(  
  "Mime-Version: 1.0\r\n"  
  "%smultipart/mixed; boundary=\"%s\"\r\n"  
  , "Content-Type: "  
  , boundary  
  );  
   
  // body is pseudo attachment read from stdin in first turn  
  llist_add_to(&opt_attachments, (char *)"-");  
   
  // put body + attachment(s)  
  // N.B. all these weird things just to be tiny  
  // by reusing string patterns!  
  fmt =  
  "\r\n--%s\r\n"  
  "%stext/plain; charset=%s\r\n"  
  "%s%s\r\n"  
  "%s"  
  ;  
  p = G.opt_charset;  
  q = (char *)"";  
  l = opt_attachments;  
  while (l) {  
  printf(  
  fmt  
  , boundary  
  , "Content-Type: "  
  , p  
  , "Content-Disposition: inline"  
  , q  
  , "Content-Transfer-Encoding: base64\r\n"  
  );  
  p = "";  
  fmt =  
  "\r\n--%s\r\n"  
  "%sapplication/octet-stream%s\r\n"  
  "%s; filename=\"%s\"\r\n"  
  "%s"  
  ;  
  encode_base64(l->data, (const char *)G.fp0, "\r");  
  l = l->link;  
  if (l)  
  q = bb_get_last_path_component_strip(l->data);  
  }  
   
  // put message terminator  
  printf("\r\n--%s--\r\n" "\r\n", boundary);  
   
  // no attachments? -> just dump message  
  } else  
 #endif  
  {  
  char *s;  
  // terminate headers  
  printf("\r\n");  
  // put plain text respecting leading dots  
  while ((s = xmalloc_fgetline(G.fp0)) != NULL) {  
210   // escape leading dots   // escape leading dots
211   // N.B. this feature is implied even if no -i (-oi) switch given   // N.B. this feature is implied even if no -i (-oi) switch given
212   // N.B. we need to escape the leading dot regardless of   // N.B. we need to escape the leading dot regardless of
# Line 373  int sendmail_main(int argc UNUSED_PARAM, Line 215  int sendmail_main(int argc UNUSED_PARAM,
215   printf(".");   printf(".");
216   // dump read line   // dump read line
217   printf("%s\r\n", s);   printf("%s\r\n", s);
218     free(s);
219     continue;
220     }
221    
222     // analyze headers
223     // To: or Cc: headers add recipients
224     if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Bcc: " + 1, s, 4)) {
225     rcptto(sane_address(s+4));
226     goto addheader;
227     // Bcc: header adds blind copy (hidden) recipient
228     } else if (0 == strncasecmp("Bcc: ", s, 5)) {
229     rcptto(sane_address(s+5));
230     free(s);
231     // N.B. Bcc: vanishes from headers!
232    
233     // other headers go verbatim
234    
235     // N.B. RFC2822 2.2.3 "Long Header Fields" allows for headers to occupy several lines.
236     // Continuation is denoted by prefixing additional lines with whitespace(s).
237     // Thanks (stefan.seyfried at googlemail.com) for pointing this out.
238     } else if (strchr(s, ':') || (list && skip_whitespace(s) != s)) {
239     addheader:
240     // N.B. we allow MAX_HEADERS generic headers at most to prevent attacks
241     if (MAX_HEADERS && ++nheaders >= MAX_HEADERS)
242     goto bail;
243     llist_add_to_end(&list, s);
244     // a line without ":" (an empty line too, by definition) doesn't look like a valid header
245     // so stop "analyze headers" mode
246     } else {
247     reenter:
248     // put recipients specified on cmdline
249     while (*argv) {
250     char *t = sane_address(*argv);
251     rcptto(t);
252     //if (MAX_HEADERS && ++nheaders >= MAX_HEADERS)
253     // goto bail;
254     llist_add_to_end(&list, xasprintf("To: %s", t));
255     argv++;
256     }
257     // enter "put message" mode
258     // N.B. DATA fails iff no recipients were accepted (or even provided)
259     // in this case just bail out gracefully
260     if (354 != smtp_check("DATA", -1))
261     goto bail;
262     // dump the headers
263     while (list) {
264     printf("%s\r\n", (char *) llist_pop(&list));
265     }
266     // stop analyzing headers
267     code++;
268     // N.B. !s means: we read nothing, and nothing to be read in the future.
269     // just dump empty line and break the loop
270     if (!s) {
271     puts("\r");
272     break;
273     }
274     // go dump message body
275     // N.B. "s" already contains the first non-header line, so pretend we read it from input
276     goto dump;
277   }   }
278   }   }
279     // odd case: we didn't stop "analyze headers" mode -> message body is empty. Reenter the loop
280     // N.B. after reenter code will be > 0
281     if (!code)
282     goto reenter;
283    
284   // leave "put message" mode   // finalize the message
285   smtp_check(".", 250);   smtp_check(".", 250);
286     bail:
287   // ... and say goodbye   // ... and say goodbye
288   smtp_check("QUIT", 221);   smtp_check("QUIT", 221);
289   // cleanup   // cleanup

Legend:
Removed from v.983  
changed lines
  Added in v.984