NeoMutt  2025-09-05-43-g177ed6
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
sendmail.c File Reference

Send email using sendmail. More...

#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <regex.h>
#include <signal.h>
#include <stdbool.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "mutt/lib.h"
#include "address/lib.h"
#include "config/lib.h"
#include "core/lib.h"
#include "gui/lib.h"
#include "sendmail.h"
#include "expando/lib.h"
#include "nntp/lib.h"
#include "pager/lib.h"
#include "globals.h"
+ Include dependency graph for sendmail.c:

Go to the source code of this file.

Macros

#define EX_OK   0
 

Functions

static void alarm_handler (int sig)
 Async notification of an alarm signal.
 
static int send_msg (const char *path, struct StringArray *args, const char *msg, char **tempfile, int wait_time)
 Invoke sendmail in a subshell.
 
static void add_args_one (struct StringArray *args, const struct Address *addr)
 Add an Address to a dynamic array.
 
static void add_args (struct StringArray *args, struct AddressList *al)
 Add a list of Addresses to a dynamic array.
 
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.
 

Variables

char ** environ
 
static volatile sig_atomic_t SigAlrm
 true after SIGALRM is received
 

Detailed Description

Send email using sendmail.

Authors
  • Pietro Cerutti
  • Richard Russon
  • Ihor Antonov
  • Tóth János

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

Definition in file sendmail.c.

Macro Definition Documentation

◆ EX_OK

#define EX_OK   0

Definition at line 57 of file sendmail.c.

Function Documentation

◆ alarm_handler()

static void alarm_handler ( int sig)
static

Async notification of an alarm signal.

Parameters
sigSignal, (SIGALRM)

Definition at line 71 of file sendmail.c.

72{
73 SigAlrm = 1;
74}
static volatile sig_atomic_t SigAlrm
true after SIGALRM is received
Definition sendmail.c:65
+ Here is the caller graph for this function:

◆ send_msg()

static int send_msg ( const char * path,
struct StringArray * args,
const char * msg,
char ** tempfile,
int wait_time )
static

Invoke sendmail in a subshell.

Parameters
[in]pathPath to program to execute
[in]argsArguments to pass to program
[in]msgTemp file containing message to send
[out]tempfileIf sendmail is put in the background, this points to the temporary file containing the stdout of the child process. If it is NULL, stderr and stdout are not redirected.
[in]wait_timeHow long to wait for sendmail, $sendmail_wait
Return values
0Success
>0Failure, return code from sendmail

Definition at line 89 of file sendmail.c.

91{
92 sigset_t set;
93 int st;
94
96
97 sigemptyset(&set);
98 /* we also don't want to be stopped right now */
99 sigaddset(&set, SIGTSTP);
100 sigprocmask(SIG_BLOCK, &set, NULL);
101
102 if ((wait_time >= 0) && tempfile)
103 {
104 struct Buffer *tmp = buf_pool_get();
105 buf_mktemp(tmp);
106 *tempfile = buf_strdup(tmp);
107 buf_pool_release(&tmp);
108 }
109
110 pid_t pid = fork();
111 if (pid == 0)
112 {
113 struct sigaction act = { 0 };
114 struct sigaction oldalrm = { 0 };
115
116 /* save parent's ID before setsid() */
117 pid_t ppid = getppid();
118
119 /* we want the delivery to continue even after the main process dies,
120 * so we put ourselves into another session right away */
121 setsid();
122
123 /* next we close all open files */
124 close(0);
125#ifdef OPEN_MAX
126 for (int fd = tempfile ? 1 : 3; fd < OPEN_MAX; fd++)
127 close(fd);
128#elif defined(_POSIX_OPEN_MAX)
129 for (int fd = tempfile ? 1 : 3; fd < _POSIX_OPEN_MAX; fd++)
130 close(fd);
131#else
132 if (tempfile)
133 {
134 close(1);
135 close(2);
136 }
137#endif
138
139 /* now the second fork() */
140 pid = fork();
141 if (pid == 0)
142 {
143 /* "msg" will be opened as stdin */
144 if (open(msg, O_RDONLY, 0) < 0)
145 {
146 unlink(msg);
147 _exit(S_ERR);
148 }
149 unlink(msg);
150
151 if ((wait_time >= 0) && tempfile && *tempfile)
152 {
153 /* *tempfile will be opened as stdout */
154 if (open(*tempfile, O_WRONLY | O_APPEND | O_CREAT | O_EXCL, 0600) < 0)
155 _exit(S_ERR);
156 /* redirect stderr to *tempfile too */
157 if (dup(1) < 0)
158 _exit(S_ERR);
159 }
160 else if (tempfile)
161 {
162 if (open("/dev/null", O_WRONLY | O_APPEND) < 0) /* stdout */
163 _exit(S_ERR);
164 if (open("/dev/null", O_RDWR | O_APPEND) < 0) /* stderr */
165 _exit(S_ERR);
166 }
167
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}
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition buffer.c:571
#define FREE(x)
Definition memory.h:62
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:96
static void alarm_handler(int sig)
Async notification of an alarm signal.
Definition sendmail.c:71
char ** environ
#define EX_OK
Definition sendmail.c:57
void mutt_sig_reset_child_signals(void)
Reset ignored signals back to the default.
Definition signal.c:337
void mutt_sig_block_system(void)
Block signals before calling exec()
Definition signal.c:261
void mutt_sig_unblock_system(bool restore)
Restore previously blocked signals.
Definition signal.c:285
#define S_ERR
Definition string2.h:46
#define S_BKG
Definition string2.h:47
String manipulation buffer.
Definition buffer.h:36
Container for Accounts, Notifications.
Definition neomutt.h:43
char ** env
Private copy of the environment variables.
Definition neomutt.h:55
#define buf_mktemp(buf)
Definition tmp.h:33
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ add_args_one()

static void add_args_one ( struct StringArray * args,
const struct Address * addr )
static

Add an Address to a dynamic array.

Parameters
[in,out]argsArray to add to
[in]addrAddress to add

Definition at line 257 of file sendmail.c.

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}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition array.h:156
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
bool group
Group mailbox?
Definition address.h:39
struct Buffer * mailbox
Mailbox and host address.
Definition address.h:38
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ add_args()

static void add_args ( struct StringArray * args,
struct AddressList * al )
static

Add a list of Addresses to a dynamic array.

Parameters
[in,out]argsArray to add to
[in]alAddresses to add

Definition at line 271 of file sendmail.c.

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}
#define TAILQ_FOREACH(var, head, field)
Definition queue.h:782
static void add_args_one(struct StringArray *args, const struct Address *addr)
Add an Address to a dynamic array.
Definition sendmail.c:257
An email address.
Definition address.h:36
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_invoke_sendmail()

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.

Parameters
mMailbox
fromThe sender
toRecipients
ccRecipients
bccRecipients
msgFile containing message
eightbitMessage contains 8bit chars
subConfig Subset
Return values
0Success
-1Failure
See also
$inews

Definition at line 298 of file sendmail.c.

302{
303 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
304 struct StringArray args = ARRAY_HEAD_INITIALIZER;
305 struct StringArray extra_args = ARRAY_HEAD_INITIALIZER;
306 int i;
307
308 if (OptNewsSend)
309 {
310 struct Buffer *cmd = buf_pool_get();
311
312 const struct Expando *c_inews = cs_subset_expando(sub, "inews");
314 cmd->dsize, NeoMutt->env, cmd);
315 if (buf_is_empty(cmd))
316 {
317 i = nntp_post(m, msg);
318 unlink(msg);
319 buf_pool_release(&cmd);
320 return i;
321 }
322
323 s = buf_strdup(cmd);
324 buf_pool_release(&cmd);
325 }
326 else
327 {
328 const char *const c_sendmail = cs_subset_string(sub, "sendmail");
329 s = mutt_str_dup(c_sendmail);
330 }
331
332 if (!s)
333 {
334 mutt_error(_("$sendmail must be set in order to send mail"));
335 return -1;
336 }
337
338 ps = s;
339 i = 0;
340 while ((ps = strtok(ps, " ")))
341 {
342 if (i)
343 {
344 if (mutt_str_equal(ps, "--"))
345 break;
346 ARRAY_ADD(&args, ps);
347 }
348 else
349 {
350 path = mutt_str_dup(ps);
351 ps = strrchr(ps, '/');
352 if (ps)
353 ps++;
354 else
355 ps = path;
356 ARRAY_ADD(&args, ps);
357 }
358 ps = NULL;
359 i++;
360 }
361
362 if (!OptNewsSend)
363 {
364 /* If $sendmail contained a "--", we save the recipients to append to
365 * args after other possible options added below. */
366 if (ps)
367 {
368 ps = NULL;
369 while ((ps = strtok(ps, " ")))
370 {
371 ARRAY_ADD(&extra_args, ps);
372 ps = NULL;
373 }
374 }
375
376 const bool c_use_8bit_mime = cs_subset_bool(sub, "use_8bit_mime");
377 if (eightbit && c_use_8bit_mime)
378 ARRAY_ADD(&args, "-B8BITMIME");
379
380 const bool c_use_envelope_from = cs_subset_bool(sub, "use_envelope_from");
381 if (c_use_envelope_from)
382 {
383 const struct Address *c_envelope_from_address = cs_subset_address(sub, "envelope_from_address");
384 if (c_envelope_from_address)
385 {
386 ARRAY_ADD(&args, "-f");
387 add_args_one(&args, c_envelope_from_address);
388 }
389 else if (!TAILQ_EMPTY(from) && !TAILQ_NEXT(TAILQ_FIRST(from), entries))
390 {
391 ARRAY_ADD(&args, "-f");
392 add_args(&args, from);
393 }
394 }
395
396 const char *const c_dsn_notify = cs_subset_string(sub, "dsn_notify");
397 if (c_dsn_notify)
398 {
399 ARRAY_ADD(&args, "-N");
400 ARRAY_ADD(&args, c_dsn_notify);
401 }
402
403 const char *const c_dsn_return = cs_subset_string(sub, "dsn_return");
404 if (c_dsn_return)
405 {
406 ARRAY_ADD(&args, "-R");
407 ARRAY_ADD(&args, c_dsn_return);
408 }
409 ARRAY_ADD(&args, "--");
410 const char **e = NULL;
411 ARRAY_FOREACH(e, &extra_args)
412 {
413 ARRAY_ADD(&args, *e);
414 }
415 add_args(&args, to);
416 add_args(&args, cc);
417 add_args(&args, bcc);
418 }
419
420 ARRAY_ADD(&args, NULL);
421
422 const short c_sendmail_wait = cs_subset_number(sub, "sendmail_wait");
423 i = send_msg(path, &args, msg, OptGui ? &childout : NULL, c_sendmail_wait);
424
425 /* Some user's $sendmail command uses gpg for password decryption,
426 * and is set up to prompt using ncurses pinentry. If we
427 * mutt_endwin() it leaves other users staring at a blank screen.
428 * So instead, just force a hard redraw on the next refresh. */
429 if (OptGui)
430 {
432 }
433
434 if (i != (EX_OK & 0xff))
435 {
436 if (i != S_BKG)
437 {
438 const char *e = mutt_str_sysexit(i);
439 mutt_error(_("Error sending message, child exited %d (%s)"), i, NONULL(e));
440 if (childout)
441 {
442 struct stat st = { 0 };
443
444 if ((stat(childout, &st) == 0) && (st.st_size > 0))
445 {
446 struct PagerData pdata = { 0 };
447 struct PagerView pview = { &pdata };
448
449 pdata.fname = childout;
450
451 pview.banner = _("Output of the delivery process");
453 pview.mode = PAGER_MODE_OTHER;
454
455 mutt_do_pager(&pview, NULL);
456 }
457 }
458 }
459 }
460 else if (childout)
461 {
462 unlink(childout);
463 }
464
465 FREE(&childout);
466 FREE(&path);
467 FREE(&s);
468 ARRAY_FREE(&args);
469 ARRAY_FREE(&extra_args);
470
471 if (i == (EX_OK & 0xff))
472 i = 0;
473 else if (i == S_BKG)
474 i = 1;
475 else
476 i = -1;
477 return i;
478}
const struct Address * cs_subset_address(const struct ConfigSubset *sub, const char *name)
Get an Address config item by name.
#define ARRAY_FOREACH(elem, head)
Iterate over all elements of the array.
Definition array.h:214
#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:291
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.
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
const struct ExpandoRenderCallback NntpRenderCallbacks[]
Callbacks for Newsrc Expandos.
bool OptGui
(pseudo) when the gui (and curses) are started
Definition globals.c:59
bool OptNewsSend
(pseudo) used to change behavior when posting
Definition globals.c:65
#define mutt_error(...)
Definition logging2.h:93
#define _(a)
Definition message.h:28
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:255
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:660
const char * mutt_str_sysexit(int err_num)
Return a string matching an error code.
Definition string.c:171
int nntp_post(struct Mailbox *m, const char *msg)
Post article.
Definition nntp.c:1945
#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:140
#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
#define MUTT_FORMAT_NO_FLAGS
No flags are set.
Definition render.h:33
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:271
#define NONULL(x)
Definition string2.h:43
size_t dsize
Length of data.
Definition buffer.h:39
Parsed Expando trees.
Definition expando.h:41
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
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ environ

char** environ
extern

◆ SigAlrm

volatile sig_atomic_t SigAlrm
static

true after SIGALRM is received

Definition at line 65 of file sendmail.c.