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