NeoMutt  2021-10-29-33-g41675a
Teaching an old dog new tricks
DOXYGEN
sendmail.c
Go to the documentation of this file.
1 
29 #include "config.h"
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <limits.h>
33 #include <signal.h>
34 #include <stdbool.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <sys/wait.h>
38 #include <unistd.h>
39 #include "mutt/lib.h"
40 #include "address/lib.h"
41 #include "config/lib.h"
42 #include "gui/lib.h"
43 #include "lib.h"
44 #include "pager/lib.h"
45 #include "format_flags.h"
46 #include "muttlib.h"
47 #include "options.h"
48 #ifdef USE_NNTP
49 #include "nntp/lib.h"
50 #endif
51 #ifdef HAVE_SYSEXITS_H
52 #include <sysexits.h>
53 #else
54 #define EX_OK 0
55 #endif
56 
57 struct Mailbox;
58 
59 SIG_ATOMIC_VOLATILE_T SigAlrm;
60 
61 ARRAY_HEAD(SendmailArgs, const char *);
62 
67 static void alarm_handler(int sig)
68 {
69  SigAlrm = 1;
70 }
71 
85 static int send_msg(const char *path, struct SendmailArgs *args,
86  const char *msg, char **tempfile, int wait_time)
87 {
88  sigset_t set;
89  int st;
90 
92 
93  sigemptyset(&set);
94  /* we also don't want to be stopped right now */
95  sigaddset(&set, SIGTSTP);
96  sigprocmask(SIG_BLOCK, &set, NULL);
97 
98  if ((wait_time >= 0) && tempfile)
99  {
100  struct Buffer *tmp = mutt_buffer_pool_get();
101  mutt_buffer_mktemp(tmp);
102  *tempfile = mutt_buffer_strdup(tmp);
104  }
105 
106  pid_t pid = fork();
107  if (pid == 0)
108  {
109  struct sigaction act, oldalrm;
110 
111  /* save parent's ID before setsid() */
112  pid_t ppid = getppid();
113 
114  /* we want the delivery to continue even after the main process dies,
115  * so we put ourselves into another session right away */
116  setsid();
117 
118  /* next we close all open files */
119  close(0);
120 #ifdef OPEN_MAX
121  for (int fd = tempfile ? 1 : 3; fd < OPEN_MAX; fd++)
122  close(fd);
123 #elif defined(_POSIX_OPEN_MAX)
124  for (int fd = tempfile ? 1 : 3; fd < _POSIX_OPEN_MAX; fd++)
125  close(fd);
126 #else
127  if (tempfile)
128  {
129  close(1);
130  close(2);
131  }
132 #endif
133 
134  /* now the second fork() */
135  pid = fork();
136  if (pid == 0)
137  {
138  /* "msg" will be opened as stdin */
139  if (open(msg, O_RDONLY, 0) < 0)
140  {
141  unlink(msg);
142  _exit(S_ERR);
143  }
144  unlink(msg);
145 
146  if ((wait_time >= 0) && tempfile && *tempfile)
147  {
148  /* *tempfile will be opened as stdout */
149  if (open(*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
150  _exit(S_ERR);
151  /* redirect stderr to *tempfile too */
152  if (dup(1) < 0)
153  _exit(S_ERR);
154  }
155  else if (tempfile)
156  {
157  if (open("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
158  _exit(S_ERR);
159  if (open("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
160  _exit(S_ERR);
161  }
162 
163  /* execvpe is a glibc extension */
164  /* execvpe (path, args, mutt_envlist_getlist()); */
165  execvp(path, (char **) args->entries);
166  _exit(S_ERR);
167  }
168  else if (pid == -1)
169  {
170  unlink(msg);
171  FREE(tempfile);
172  _exit(S_ERR);
173  }
174 
175  /* wait_time > 0: interrupt waitpid() after wait_time seconds
176  * wait_time = 0: wait forever
177  * wait_time < 0: don't wait */
178  if (wait_time > 0)
179  {
180  SigAlrm = 0;
181  act.sa_handler = alarm_handler;
182 #ifdef SA_INTERRUPT
183  /* need to make sure waitpid() is interrupted on SIGALRM */
184  act.sa_flags = SA_INTERRUPT;
185 #else
186  act.sa_flags = 0;
187 #endif
188  sigemptyset(&act.sa_mask);
189  sigaction(SIGALRM, &act, &oldalrm);
190  alarm(wait_time);
191  }
192  else if (wait_time < 0)
193  _exit(0xff & EX_OK);
194 
195  if (waitpid(pid, &st, 0) > 0)
196  {
197  st = WIFEXITED(st) ? WEXITSTATUS(st) : S_ERR;
198  if (wait_time && (st == (0xff & EX_OK)) && tempfile && *tempfile)
199  {
200  unlink(*tempfile); /* no longer needed */
201  FREE(tempfile);
202  }
203  }
204  else
205  {
206  st = ((wait_time > 0) && (errno == EINTR) && SigAlrm) ? S_BKG : S_ERR;
207  if ((wait_time > 0) && tempfile && *tempfile)
208  {
209  unlink(*tempfile);
210  FREE(tempfile);
211  }
212  }
213 
214  if (wait_time > 0)
215  {
216  /* reset alarm; not really needed, but... */
217  alarm(0);
218  sigaction(SIGALRM, &oldalrm, NULL);
219  }
220 
221  if ((kill(ppid, 0) == -1) && (errno == ESRCH) && tempfile && *tempfile)
222  {
223  /* the parent is already dead */
224  unlink(*tempfile);
225  FREE(tempfile);
226  }
227 
228  _exit(st);
229  }
230 
231  sigprocmask(SIG_UNBLOCK, &set, NULL);
232 
233  if ((pid != -1) && (waitpid(pid, &st, 0) > 0))
234  st = WIFEXITED(st) ? WEXITSTATUS(st) : S_ERR; /* return child status */
235  else
236  st = S_ERR; /* error */
237 
239 
240  return st;
241 }
242 
248 static void add_args_one(struct SendmailArgs *args, const struct Address *addr)
249 {
250  /* weed out group mailboxes, since those are for display only */
251  if (addr->mailbox && !addr->group)
252  {
253  ARRAY_ADD(args, addr->mailbox);
254  }
255 }
256 
262 static void add_args(struct SendmailArgs *args, struct AddressList *al)
263 {
264  if (!al)
265  return;
266 
267  struct Address *a = NULL;
268  TAILQ_FOREACH(a, al, entries)
269  {
270  add_args_one(args, a);
271  }
272 }
273 
287 int mutt_invoke_sendmail(struct Mailbox *m, struct AddressList *from,
288  struct AddressList *to, struct AddressList *cc,
289  struct AddressList *bcc, const char *msg,
290  bool eightbit, struct ConfigSubset *sub)
291 {
292  char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
293  struct SendmailArgs args = ARRAY_HEAD_INITIALIZER;
294  struct SendmailArgs extra_args = ARRAY_HEAD_INITIALIZER;
295  int i;
296 
297 #ifdef USE_NNTP
298  if (OptNewsSend)
299  {
300  char cmd[1024];
301 
302  const char *const c_inews = cs_subset_string(sub, "inews");
303  mutt_expando_format(cmd, sizeof(cmd), 0, sizeof(cmd), NONULL(c_inews),
305  if (*cmd == '\0')
306  {
307  i = nntp_post(m, msg);
308  unlink(msg);
309  return i;
310  }
311 
312  s = mutt_str_dup(cmd);
313  }
314  else
315 #endif
316  {
317  const char *const c_sendmail = cs_subset_string(sub, "sendmail");
318  s = mutt_str_dup(c_sendmail);
319  }
320 
321  /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */
322  if (!s)
323  {
324  mutt_error(_("$sendmail must be set in order to send mail"));
325  return -1;
326  }
327 
328  ps = s;
329  i = 0;
330  while ((ps = strtok(ps, " ")))
331  {
332  if (i)
333  {
334  if (mutt_str_equal(ps, "--"))
335  break;
336  ARRAY_ADD(&args, ps);
337  }
338  else
339  {
340  path = mutt_str_dup(ps);
341  ps = strrchr(ps, '/');
342  if (ps)
343  ps++;
344  else
345  ps = path;
346  ARRAY_ADD(&args, ps);
347  }
348  ps = NULL;
349  i++;
350  }
351 
352 #ifdef USE_NNTP
353  if (!OptNewsSend)
354  {
355 #endif
356  /* If $sendmail contained a "--", we save the recipients to append to
357  * args after other possible options added below. */
358  if (ps)
359  {
360  ps = NULL;
361  while ((ps = strtok(ps, " ")))
362  {
363  ARRAY_ADD(&extra_args, ps);
364  }
365  }
366 
367  const bool c_use_8bit_mime = cs_subset_bool(sub, "use_8bit_mime");
368  if (eightbit && c_use_8bit_mime)
369  ARRAY_ADD(&args, "-B8BITMIME");
370 
371  const bool c_use_envelope_from = cs_subset_bool(sub, "use_envelope_from");
372  if (c_use_envelope_from)
373  {
374  const struct Address *c_envelope_from_address =
375  cs_subset_address(sub, "envelope_from_address");
376  if (c_envelope_from_address)
377  {
378  ARRAY_ADD(&args, "-f");
379  add_args_one(&args, c_envelope_from_address);
380  }
381  else if (!TAILQ_EMPTY(from) && !TAILQ_NEXT(TAILQ_FIRST(from), entries))
382  {
383  ARRAY_ADD(&args, "-f");
384  add_args(&args, from);
385  }
386  }
387 
388  const char *const c_dsn_notify = cs_subset_string(sub, "dsn_notify");
389  if (c_dsn_notify)
390  {
391  ARRAY_ADD(&args, "-N");
392  ARRAY_ADD(&args, c_dsn_notify);
393  }
394 
395  const char *const c_dsn_return = cs_subset_string(sub, "dsn_return");
396  if (c_dsn_return)
397  {
398  ARRAY_ADD(&args, "-R");
399  ARRAY_ADD(&args, c_dsn_return);
400  }
401  ARRAY_ADD(&args, "--");
402  const char **e = NULL;
403  ARRAY_FOREACH(e, &extra_args)
404  {
405  ARRAY_ADD(&args, *e);
406  }
407  add_args(&args, to);
408  add_args(&args, cc);
409  add_args(&args, bcc);
410 #ifdef USE_NNTP
411  }
412 #endif
413 
414  ARRAY_ADD(&args, NULL);
415 
416  /* Some user's $sendmail command uses gpg for password decryption,
417  * and is set up to prompt using ncurses pinentry. If we
418  * mutt_endwin() it leaves other users staring at a blank screen.
419  * So instead, just force a hard redraw on the next refresh. */
420  if (!OptNoCurses)
421  {
423  }
424 
425  const short c_sendmail_wait = cs_subset_number(sub, "sendmail_wait");
426  i = send_msg(path, &args, msg, OptNoCurses ? NULL : &childout, c_sendmail_wait);
427  if (i != (EX_OK & 0xff))
428  {
429  if (i != S_BKG)
430  {
431  const char *e = mutt_str_sysexit(i);
432  mutt_error(_("Error sending message, child exited %d (%s)"), i, NONULL(e));
433  if (childout)
434  {
435  struct stat st = { 0 };
436 
437  if ((stat(childout, &st) == 0) && (st.st_size > 0))
438  {
439  struct PagerData pdata = { 0 };
440  struct PagerView pview = { &pdata };
441 
442  pdata.fname = childout;
443 
444  pview.banner = _("Output of the delivery process");
445  pview.flags = MUTT_PAGER_NO_FLAGS;
446  pview.mode = PAGER_MODE_OTHER;
447 
448  mutt_do_pager(&pview, NULL);
449  }
450  }
451  }
452  }
453  else if (childout)
454  unlink(childout);
455 
456  FREE(&childout);
457  FREE(&path);
458  FREE(&s);
459  ARRAY_FREE(&args);
460  ARRAY_FREE(&extra_args);
461 
462  if (i == (EX_OK & 0xff))
463  i = 0;
464  else if (i == S_BKG)
465  i = 1;
466  else
467  i = -1;
468  return i;
469 }
Email Address Handling.
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:152
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:208
#define ARRAY_FREE(head)
Release all memory.
Definition: array.h:200
#define ARRAY_HEAD_INITIALIZER
Static initializer for arrays.
Definition: array.h:54
char * mutt_buffer_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:432
Convenience wrapper for the config headers.
void mutt_need_hard_redraw(void)
Force a hard refresh.
Definition: curs_lib.c:136
int mutt_do_pager(struct PagerView *pview, struct Email *e)
Display some page-able text to the user (help or attachment)
Definition: do_pager.c:120
Flags to control mutt_expando_format()
#define MUTT_FORMAT_NO_FLAGS
No flags are set.
Definition: format_flags.h:30
const char * nntp_format_str(char *buf, size_t buflen, size_t col, int cols, char op, const char *src, const char *prec, const char *if_str, const char *else_str, intptr_t data, MuttFormatFlags flags)
Expand the newsrc filename - Implements format_t -.
Definition: newsrc.c:927
void mutt_expando_format(char *buf, size_t buflen, size_t col, int cols, const char *src, format_t callback, intptr_t data, MuttFormatFlags flags)
Expand expandos (x) in a string -.
Definition: muttlib.c:780
#define mutt_error(...)
Definition: logging.h:87
Convenience wrapper for the gui headers.
const struct Address * cs_subset_address(const struct ConfigSubset *sub, const char *name)
Get an Address config item by name.
Definition: helpers.c:49
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
#define FREE(x)
Definition: memory.h:40
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:181
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:715
const char * mutt_str_sysexit(int err_num)
Return a string matching an error code.
Definition: string.c:110
Some miscellaneous functions.
#define mutt_buffer_mktemp(buf)
Definition: muttlib.h:74
Usenet network mailbox type; talk to an NNTP server.
int nntp_post(struct Mailbox *m, const char *msg)
Post article.
Definition: nntp.c:1879
Handling of global boolean variables.
bool OptNoCurses
(pseudo) when sending in batch mode
Definition: options.h:53
bool OptNewsSend
(pseudo) used to change behavior when posting
Definition: options.h:51
GUI display a file/email/help in a viewport with paging.
#define MUTT_PAGER_NO_FLAGS
No flags are set.
Definition: lib.h:59
@ PAGER_MODE_OTHER
Pager is invoked via 3rd path. Non-email content is likely to be shown.
Definition: lib.h:140
void mutt_buffer_pool_release(struct Buffer **pbuf)
Free a Buffer from the pool.
Definition: pool.c:112
struct Buffer * mutt_buffer_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:101
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:725
#define TAILQ_FIRST(head)
Definition: queue.h:723
#define TAILQ_NEXT(elm, field)
Definition: queue.h:832
#define TAILQ_EMPTY(head)
Definition: queue.h:721
static void alarm_handler(int sig)
Async notification of an alarm signal.
Definition: sendmail.c:67
static void add_args_one(struct SendmailArgs *args, const struct Address *addr)
Add an Address to a dynamic array.
Definition: sendmail.c:248
int mutt_invoke_sendmail(struct Mailbox *m, struct AddressList *from, struct AddressList *to, struct AddressList *cc, struct AddressList *bcc, const char *msg, bool eightbit, struct ConfigSubset *sub)
Run sendmail.
Definition: sendmail.c:287
ARRAY_HEAD(SendmailArgs, const char *)
static int send_msg(const char *path, struct SendmailArgs *args, const char *msg, char **tempfile, int wait_time)
Invoke sendmail in a subshell.
Definition: sendmail.c:85
static void add_args(struct SendmailArgs *args, struct AddressList *al)
Add a list of Addresses to a dynamic array.
Definition: sendmail.c:262
SIG_ATOMIC_VOLATILE_T SigAlrm
true after SIGALRM is received
Definition: sendmail.c:57
#define EX_OK
Definition: sendmail.c:54
void mutt_sig_block_system(void)
Block signals before calling exec()
Definition: signal.c:183
void mutt_sig_unblock_system(bool restore)
Restore previously blocked signals.
Definition: signal.c:207
Key value store.
#define S_ERR
Definition: string2.h:41
#define NONULL(x)
Definition: string2.h:37
#define S_BKG
Definition: string2.h:42
An email address.
Definition: address.h:36
bool group
Group mailbox?
Definition: address.h:39
char * mailbox
Mailbox and host address.
Definition: address.h:38
String manipulation buffer.
Definition: buffer.h:34
A set of inherited config items.
Definition: subset.h:47
A mailbox.
Definition: mailbox.h:82
Data to be displayed by PagerView.
Definition: lib.h:149
const char * fname
Name of the file to read.
Definition: lib.h:153
Paged view into some data.
Definition: lib.h:160
struct PagerData * pdata
Data that pager displays. NOTNULL.
Definition: lib.h:161
enum PagerMode mode
Pager mode.
Definition: lib.h:162
PagerFlags flags
Additional settings to tweak pager's function.
Definition: lib.h:163
const char * banner
Title to display in status bar.
Definition: lib.h:164