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