NeoMutt  2023-03-22
Teaching an old dog new tricks
DOXYGEN
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/wait.h>
#include <unistd.h>
#include "mutt/lib.h"
#include "address/lib.h"
#include "config/lib.h"
#include "gui/lib.h"
#include "lib.h"
#include "pager/lib.h"
#include "format_flags.h"
#include "globals.h"
#include "muttlib.h"
#include "nntp/lib.h"
+ Include dependency graph for sendmail.c:

Go to the source code of this file.

Macros

#define EX_OK   0
 

Functions

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

Variables

char ** environ
 
SIG_ATOMIC_VOLATILE_T SigAlrm
 true after SIGALRM is received More...
 

Detailed Description

Send email using sendmail.

Authors
  • Richard Russon

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 55 of file sendmail.c.

Function Documentation

◆ ARRAY_HEAD()

ARRAY_HEAD ( SendmailArgs  ,
const char *   
)

◆ alarm_handler()

static void alarm_handler ( int  sig)
static

Async notification of an alarm signal.

Parameters
sigSignal, (SIGALRM)

Definition at line 73 of file sendmail.c.

74{
75 SigAlrm = 1;
76}
SIG_ATOMIC_VOLATILE_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 SendmailArgs *  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 91 of file sendmail.c.

93{
94 sigset_t set;
95 int st;
96
98
99 sigemptyset(&set);
100 /* we also don't want to be stopped right now */
101 sigaddset(&set, SIGTSTP);
102 sigprocmask(SIG_BLOCK, &set, NULL);
103
104 if ((wait_time >= 0) && tempfile)
105 {
106 struct Buffer *tmp = mutt_buffer_pool_get();
108 *tempfile = mutt_buffer_strdup(tmp);
110 }
111
112 pid_t pid = fork();
113 if (pid == 0)
114 {
115 struct sigaction act, oldalrm;
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 _exit(0xff & EX_OK);
200
201 if (waitpid(pid, &st, 0) > 0)
202 {
203 st = WIFEXITED(st) ? WEXITSTATUS(st) : S_ERR;
204 if (wait_time && (st == (0xff & EX_OK)) && tempfile && *tempfile)
205 {
206 unlink(*tempfile); /* no longer needed */
207 FREE(tempfile);
208 }
209 }
210 else
211 {
212 st = ((wait_time > 0) && (errno == EINTR) && SigAlrm) ? S_BKG : S_ERR;
213 if ((wait_time > 0) && tempfile && *tempfile)
214 {
215 unlink(*tempfile);
216 FREE(tempfile);
217 }
218 }
219
220 if (wait_time > 0)
221 {
222 /* reset alarm; not really needed, but... */
223 alarm(0);
224 sigaction(SIGALRM, &oldalrm, NULL);
225 }
226
227 if ((kill(ppid, 0) == -1) && (errno == ESRCH) && tempfile && *tempfile)
228 {
229 /* the parent is already dead */
230 unlink(*tempfile);
231 FREE(tempfile);
232 }
233
234 _exit(st);
235 }
236
237 sigprocmask(SIG_UNBLOCK, &set, NULL);
238
239 if ((pid != -1) && (waitpid(pid, &st, 0) > 0))
240 st = WIFEXITED(st) ? WEXITSTATUS(st) : S_ERR; /* return child status */
241 else
242 st = S_ERR; /* error */
243
245
246 return st;
247}
char * mutt_buffer_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:485
char ** mutt_envlist_getlist(void)
Get the private environment.
Definition: envlist.c:169
#define FREE(x)
Definition: memory.h:43
#define mutt_buffer_mktemp(buf)
Definition: muttlib.h:74
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
static void alarm_handler(int sig)
Async notification of an alarm signal.
Definition: sendmail.c:73
char ** environ
#define EX_OK
Definition: sendmail.c:55
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
#define S_ERR
Definition: string2.h:41
#define S_BKG
Definition: string2.h:42
String manipulation buffer.
Definition: buffer.h:34
+ 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 SendmailArgs *  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 254 of file sendmail.c.

255{
256 /* weed out group mailboxes, since those are for display only */
257 if (addr->mailbox && !addr->group)
258 {
259 ARRAY_ADD(args, addr->mailbox);
260 }
261}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition: array.h:155
bool group
Group mailbox?
Definition: address.h:39
char * mailbox
Mailbox and host address.
Definition: address.h:38
+ Here is the caller graph for this function:

◆ add_args()

static void add_args ( struct SendmailArgs *  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 268 of file sendmail.c.

269{
270 if (!al)
271 return;
272
273 struct Address *a = NULL;
274 TAILQ_FOREACH(a, al, entries)
275 {
276 add_args_one(args, a);
277 }
278}
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:725
static void add_args_one(struct SendmailArgs *args, const struct Address *addr)
Add an Address to a dynamic array.
Definition: sendmail.c:254
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, nntp_format_str()

Definition at line 295 of file sendmail.c.

299{
300 char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL;
301 struct SendmailArgs args = ARRAY_HEAD_INITIALIZER;
302 struct SendmailArgs extra_args = ARRAY_HEAD_INITIALIZER;
303 int i;
304
305#ifdef USE_NNTP
306 if (OptNewsSend)
307 {
308 char cmd[1024] = { 0 };
309
310 const char *const c_inews = cs_subset_string(sub, "inews");
311 mutt_expando_format(cmd, sizeof(cmd), 0, sizeof(cmd), NONULL(c_inews),
313 if (*cmd == '\0')
314 {
315 i = nntp_post(m, msg);
316 unlink(msg);
317 return i;
318 }
319
320 s = mutt_str_dup(cmd);
321 }
322 else
323#endif
324 {
325 const char *const c_sendmail = cs_subset_string(sub, "sendmail");
326 s = mutt_str_dup(c_sendmail);
327 }
328
329 /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */
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#ifdef USE_NNTP
361 if (!OptNewsSend)
362 {
363#endif
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#ifdef USE_NNTP
419 }
420#endif
421
422 ARRAY_ADD(&args, NULL);
423
424 const short c_sendmail_wait = cs_subset_number(sub, "sendmail_wait");
425 i = send_msg(path, &args, msg, OptNoCurses ? NULL : &childout, c_sendmail_wait);
426
427 /* Some user's $sendmail command uses gpg for password decryption,
428 * and is set up to prompt using ncurses pinentry. If we
429 * mutt_endwin() it leaves other users staring at a blank screen.
430 * So instead, just force a hard redraw on the next refresh. */
431 if (!OptNoCurses)
432 {
434 }
435
436 if (i != (EX_OK & 0xff))
437 {
438 if (i != S_BKG)
439 {
440 const char *e = mutt_str_sysexit(i);
441 mutt_error(_("Error sending message, child exited %d (%s)"), i, NONULL(e));
442 if (childout)
443 {
444 struct stat st = { 0 };
445
446 if ((stat(childout, &st) == 0) && (st.st_size > 0))
447 {
448 struct PagerData pdata = { 0 };
449 struct PagerView pview = { &pdata };
450
451 pdata.fname = childout;
452
453 pview.banner = _("Output of the delivery process");
455 pview.mode = PAGER_MODE_OTHER;
456
457 mutt_do_pager(&pview, NULL);
458 }
459 }
460 }
461 }
462 else if (childout)
463 unlink(childout);
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}
#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
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
void mutt_need_hard_redraw(void)
Force a hard refresh.
Definition: curs_lib.c:161
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
#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:81
bool OptNewsSend
(pseudo) used to change behavior when posting
Definition: globals.c:79
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:923
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:778
#define mutt_error(...)
Definition: logging.h:87
#define _(a)
Definition: message.h:28
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:807
const char * mutt_str_sysexit(int err_num)
Return a string matching an error code.
Definition: string.c:166
int nntp_post(struct Mailbox *m, const char *msg)
Post article.
Definition: nntp.c:1874
#define MUTT_PAGER_NO_FLAGS
No flags are set.
Definition: lib.h:58
@ PAGER_MODE_OTHER
Pager is invoked via 3rd path. Non-email content is likely to be shown.
Definition: lib.h:139
#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 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:91
static void add_args(struct SendmailArgs *args, struct AddressList *al)
Add a list of Addresses to a dynamic array.
Definition: sendmail.c:268
#define NONULL(x)
Definition: string2.h:37
Data to be displayed by PagerView.
Definition: lib.h:158
const char * fname
Name of the file to read.
Definition: lib.h:162
Paged view into some data.
Definition: lib.h:169
struct PagerData * pdata
Data that pager displays. NOTNULL.
Definition: lib.h:170
enum PagerMode mode
Pager mode.
Definition: lib.h:171
PagerFlags flags
Additional settings to tweak pager's function.
Definition: lib.h:172
const char * banner
Title to display in status bar.
Definition: lib.h:173
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ environ

char** environ
extern

◆ SigAlrm

SIG_ATOMIC_VOLATILE_T SigAlrm

true after SIGALRM is received

Definition at line 65 of file sendmail.c.