NeoMutt  2023-05-17-56-ga67199
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 <regex.h>
34#include <signal.h>
35#include <stdbool.h>
36#include <string.h>
37#include <sys/stat.h>
38#include <sys/wait.h>
39#include <unistd.h>
40#include "mutt/lib.h"
41#include "address/lib.h"
42#include "config/lib.h"
43#include "core/tmp.h"
44#include "gui/lib.h"
45#include "lib.h"
46#include "pager/lib.h"
47#include "format_flags.h"
48#include "globals.h"
49#include "muttlib.h"
50#ifdef USE_NNTP
51#include "nntp/lib.h"
52#endif
53#ifdef HAVE_SYSEXITS_H
54#include <sysexits.h>
55#else
56#define EX_OK 0
57#endif
58
59struct Mailbox;
60
61/* For execvp environment setting in send_msg() */
62#ifndef __USE_GNU
63extern char **environ;
64#endif
65
66static SIG_ATOMIC_VOLATILE_T SigAlrm;
67
68ARRAY_HEAD(SendmailArgs, const char *);
69
74static void alarm_handler(int sig)
75{
76 SigAlrm = 1;
77}
78
92static int send_msg(const char *path, struct SendmailArgs *args,
93 const char *msg, char **tempfile, int wait_time)
94{
95 sigset_t set;
96 int st;
97
99
100 sigemptyset(&set);
101 /* we also don't want to be stopped right now */
102 sigaddset(&set, SIGTSTP);
103 sigprocmask(SIG_BLOCK, &set, NULL);
104
105 if ((wait_time >= 0) && tempfile)
106 {
107 struct Buffer *tmp = buf_pool_get();
108 buf_mktemp(tmp);
109 *tempfile = buf_strdup(tmp);
110 buf_pool_release(&tmp);
111 }
112
113 pid_t pid = fork();
114 if (pid == 0)
115 {
116 struct sigaction act, oldalrm;
117
118 /* save parent's ID before setsid() */
119 pid_t ppid = getppid();
120
121 /* we want the delivery to continue even after the main process dies,
122 * so we put ourselves into another session right away */
123 setsid();
124
125 /* next we close all open files */
126 close(0);
127#ifdef OPEN_MAX
128 for (int fd = tempfile ? 1 : 3; fd < OPEN_MAX; fd++)
129 close(fd);
130#elif defined(_POSIX_OPEN_MAX)
131 for (int fd = tempfile ? 1 : 3; fd < _POSIX_OPEN_MAX; fd++)
132 close(fd);
133#else
134 if (tempfile)
135 {
136 close(1);
137 close(2);
138 }
139#endif
140
141 /* now the second fork() */
142 pid = fork();
143 if (pid == 0)
144 {
145 /* "msg" will be opened as stdin */
146 if (open(msg, O_RDONLY, 0) < 0)
147 {
148 unlink(msg);
149 _exit(S_ERR);
150 }
151 unlink(msg);
152
153 if ((wait_time >= 0) && tempfile && *tempfile)
154 {
155 /* *tempfile will be opened as stdout */
156 if (open(*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
157 _exit(S_ERR);
158 /* redirect stderr to *tempfile too */
159 if (dup(1) < 0)
160 _exit(S_ERR);
161 }
162 else if (tempfile)
163 {
164 if (open("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
165 _exit(S_ERR);
166 if (open("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
167 _exit(S_ERR);
168 }
169
170 /* execvpe is a glibc extension, so just manually set environ */
172 execvp(path, (char **) args->entries);
173 _exit(S_ERR);
174 }
175 else if (pid == -1)
176 {
177 unlink(msg);
178 FREE(tempfile);
179 _exit(S_ERR);
180 }
181
182 /* wait_time > 0: interrupt waitpid() after wait_time seconds
183 * wait_time = 0: wait forever
184 * wait_time < 0: don't wait */
185 if (wait_time > 0)
186 {
187 SigAlrm = 0;
188 act.sa_handler = alarm_handler;
189#ifdef SA_INTERRUPT
190 /* need to make sure waitpid() is interrupted on SIGALRM */
191 act.sa_flags = SA_INTERRUPT;
192#else
193 act.sa_flags = 0;
194#endif
195 sigemptyset(&act.sa_mask);
196 sigaction(SIGALRM, &act, &oldalrm);
197 alarm(wait_time);
198 }
199 else if (wait_time < 0)
200 {
201 _exit(0xff & EX_OK);
202 }
203
204 if (waitpid(pid, &st, 0) > 0)
205 {
206 st = WIFEXITED(st) ? WEXITSTATUS(st) : S_ERR;
207 if (wait_time && (st == (0xff & EX_OK)) && tempfile && *tempfile)
208 {
209 unlink(*tempfile); /* no longer needed */
210 FREE(tempfile);
211 }
212 }
213 else
214 {
215 st = ((wait_time > 0) && (errno == EINTR) && SigAlrm) ? S_BKG : S_ERR;
216 if ((wait_time > 0) && tempfile && *tempfile)
217 {
218 unlink(*tempfile);
219 FREE(tempfile);
220 }
221 }
222
223 if (wait_time > 0)
224 {
225 /* reset alarm; not really needed, but... */
226 alarm(0);
227 sigaction(SIGALRM, &oldalrm, NULL);
228 }
229
230 if ((kill(ppid, 0) == -1) && (errno == ESRCH) && tempfile && *tempfile)
231 {
232 /* the parent is already dead */
233 unlink(*tempfile);
234 FREE(tempfile);
235 }
236
237 _exit(st);
238 }
239
240 sigprocmask(SIG_UNBLOCK, &set, NULL);
241
242 if ((pid != -1) && (waitpid(pid, &st, 0) > 0))
243 st = WIFEXITED(st) ? WEXITSTATUS(st) : S_ERR; /* return child status */
244 else
245 st = S_ERR; /* error */
246
248
249 return st;
250}
251
257static void add_args_one(struct SendmailArgs *args, const struct Address *addr)
258{
259 /* weed out group mailboxes, since those are for display only */
260 if (addr->mailbox && !addr->group)
261 {
262 ARRAY_ADD(args, buf_string(addr->mailbox));
263 }
264}
265
271static void add_args(struct SendmailArgs *args, struct AddressList *al)
272{
273 if (!al)
274 return;
275
276 struct Address *a = NULL;
277 TAILQ_FOREACH(a, al, entries)
278 {
279 add_args_one(args, a);
280 }
281}
282
298int mutt_invoke_sendmail(struct Mailbox *m, struct AddressList *from,
299 struct AddressList *to, struct AddressList *cc,
300 struct AddressList *bcc, const char *msg,
301 bool eightbit, struct ConfigSubset *sub)
302{
303 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
304 struct SendmailArgs args = ARRAY_HEAD_INITIALIZER;
305 struct SendmailArgs extra_args = ARRAY_HEAD_INITIALIZER;
306 int i;
307
308#ifdef USE_NNTP
309 if (OptNewsSend)
310 {
311 char cmd[1024] = { 0 };
312
313 const char *const c_inews = cs_subset_string(sub, "inews");
314 mutt_expando_format(cmd, sizeof(cmd), 0, sizeof(cmd), NONULL(c_inews),
316 if (*cmd == '\0')
317 {
318 i = nntp_post(m, msg);
319 unlink(msg);
320 return i;
321 }
322
323 s = mutt_str_dup(cmd);
324 }
325 else
326#endif
327 {
328 const char *const c_sendmail = cs_subset_string(sub, "sendmail");
329 s = mutt_str_dup(c_sendmail);
330 }
331
332 /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */
333 if (!s)
334 {
335 mutt_error(_("$sendmail must be set in order to send mail"));
336 return -1;
337 }
338
339 ps = s;
340 i = 0;
341 while ((ps = strtok(ps, " ")))
342 {
343 if (i)
344 {
345 if (mutt_str_equal(ps, "--"))
346 break;
347 ARRAY_ADD(&args, ps);
348 }
349 else
350 {
351 path = mutt_str_dup(ps);
352 ps = strrchr(ps, '/');
353 if (ps)
354 ps++;
355 else
356 ps = path;
357 ARRAY_ADD(&args, ps);
358 }
359 ps = NULL;
360 i++;
361 }
362
363#ifdef USE_NNTP
364 if (!OptNewsSend)
365 {
366#endif
367 /* If $sendmail contained a "--", we save the recipients to append to
368 * args after other possible options added below. */
369 if (ps)
370 {
371 ps = NULL;
372 while ((ps = strtok(ps, " ")))
373 {
374 ARRAY_ADD(&extra_args, ps);
375 ps = NULL;
376 }
377 }
378
379 const bool c_use_8bit_mime = cs_subset_bool(sub, "use_8bit_mime");
380 if (eightbit && c_use_8bit_mime)
381 ARRAY_ADD(&args, "-B8BITMIME");
382
383 const bool c_use_envelope_from = cs_subset_bool(sub, "use_envelope_from");
384 if (c_use_envelope_from)
385 {
386 const struct Address *c_envelope_from_address = cs_subset_address(sub, "envelope_from_address");
387 if (c_envelope_from_address)
388 {
389 ARRAY_ADD(&args, "-f");
390 add_args_one(&args, c_envelope_from_address);
391 }
392 else if (!TAILQ_EMPTY(from) && !TAILQ_NEXT(TAILQ_FIRST(from), entries))
393 {
394 ARRAY_ADD(&args, "-f");
395 add_args(&args, from);
396 }
397 }
398
399 const char *const c_dsn_notify = cs_subset_string(sub, "dsn_notify");
400 if (c_dsn_notify)
401 {
402 ARRAY_ADD(&args, "-N");
403 ARRAY_ADD(&args, c_dsn_notify);
404 }
405
406 const char *const c_dsn_return = cs_subset_string(sub, "dsn_return");
407 if (c_dsn_return)
408 {
409 ARRAY_ADD(&args, "-R");
410 ARRAY_ADD(&args, c_dsn_return);
411 }
412 ARRAY_ADD(&args, "--");
413 const char **e = NULL;
414 ARRAY_FOREACH(e, &extra_args)
415 {
416 ARRAY_ADD(&args, *e);
417 }
418 add_args(&args, to);
419 add_args(&args, cc);
420 add_args(&args, bcc);
421#ifdef USE_NNTP
422 }
423#endif
424
425 ARRAY_ADD(&args, NULL);
426
427 const short c_sendmail_wait = cs_subset_number(sub, "sendmail_wait");
428 i = send_msg(path, &args, msg, OptNoCurses ? NULL : &childout, c_sendmail_wait);
429
430 /* Some user's $sendmail command uses gpg for password decryption,
431 * and is set up to prompt using ncurses pinentry. If we
432 * mutt_endwin() it leaves other users staring at a blank screen.
433 * So instead, just force a hard redraw on the next refresh. */
434 if (!OptNoCurses)
435 {
437 }
438
439 if (i != (EX_OK & 0xff))
440 {
441 if (i != S_BKG)
442 {
443 const char *e = mutt_str_sysexit(i);
444 mutt_error(_("Error sending message, child exited %d (%s)"), i, NONULL(e));
445 if (childout)
446 {
447 struct stat st = { 0 };
448
449 if ((stat(childout, &st) == 0) && (st.st_size > 0))
450 {
451 struct PagerData pdata = { 0 };
452 struct PagerView pview = { &pdata };
453
454 pdata.fname = childout;
455
456 pview.banner = _("Output of the delivery process");
458 pview.mode = PAGER_MODE_OTHER;
459
460 mutt_do_pager(&pview, NULL);
461 }
462 }
463 }
464 }
465 else if (childout)
466 {
467 unlink(childout);
468 }
469
470 FREE(&childout);
471 FREE(&path);
472 FREE(&s);
473 ARRAY_FREE(&args);
474 ARRAY_FREE(&extra_args);
475
476 if (i == (EX_OK & 0xff))
477 i = 0;
478 else if (i == S_BKG)
479 i = 1;
480 else
481 i = -1;
482 return i;
483}
Email Address Handling.
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:155
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition: array.h:211
#define ARRAY_FREE(head)
Release all memory.
Definition: array.h:203
#define ARRAY_HEAD_INITIALIZER
Static initializer for arrays.
Definition: array.h:57
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:536
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:90
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
const struct Address * cs_subset_address(const struct ConfigSubset *sub, const char *name)
Get an Address config item by name.
Definition: helpers.c:49
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
Convenience wrapper for the config headers.
void mutt_need_hard_redraw(void)
Force a hard refresh.
Definition: curs_lib.c:163
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:123
Flags to control mutt_expando_format()
#define MUTT_FORMAT_NO_FLAGS
No flags are set.
Definition: format_flags.h:30
bool OptNoCurses
(pseudo) when sending in batch mode
Definition: globals.c:82
bool OptNewsSend
(pseudo) used to change behavior when posting
Definition: globals.c:80
char ** EnvList
Private copy of the environment variables.
Definition: globals.c:91
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:742
#define mutt_error(...)
Definition: logging2.h:90
Convenience wrapper for the gui headers.
#define FREE(x)
Definition: memory.h:43
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:251
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
const char * mutt_str_sysexit(int err_num)
Return a string matching an error code.
Definition: string.c:167
Some miscellaneous functions.
Usenet network mailbox type; talk to an NNTP server.
int nntp_post(struct Mailbox *m, const char *msg)
Post article.
Definition: nntp.c:1909
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
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
#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:74
static void add_args_one(struct SendmailArgs *args, const struct Address *addr)
Add an Address to a dynamic array.
Definition: sendmail.c:257
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:298
ARRAY_HEAD(SendmailArgs, const char *)
char ** environ
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:92
static void add_args(struct SendmailArgs *args, struct AddressList *al)
Add a list of Addresses to a dynamic array.
Definition: sendmail.c:271
static SIG_ATOMIC_VOLATILE_T SigAlrm
true after SIGALRM is received
Definition: sendmail.c:66
#define EX_OK
Definition: sendmail.c:56
void mutt_sig_block_system(void)
Block signals before calling exec()
Definition: signal.c:197
void mutt_sig_unblock_system(bool restore)
Restore previously blocked signals.
Definition: signal.c:221
Key value store.
#define S_ERR
Definition: string2.h:40
#define NONULL(x)
Definition: string2.h:37
#define S_BKG
Definition: string2.h:41
An email address.
Definition: address.h:36
bool group
Group mailbox?
Definition: address.h:39
struct Buffer * 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:79
Data to be displayed by PagerView.
Definition: lib.h:159
const char * fname
Name of the file to read.
Definition: lib.h:163
Paged view into some data.
Definition: lib.h:170
struct PagerData * pdata
Data that pager displays. NOTNULL.
Definition: lib.h:171
enum PagerMode mode
Pager mode.
Definition: lib.h:172
PagerFlags flags
Additional settings to tweak pager's function.
Definition: lib.h:173
const char * banner
Title to display in status bar.
Definition: lib.h:174
Create Temporary Files.
#define buf_mktemp(buf)
Definition: tmp.h:37