Add new command eval.

pull/84/head
Martin Lambers 2022-06-25 12:40:03 +02:00
parent c7d4aec45a
commit 6865777eb0
9 changed files with 245 additions and 69 deletions

5
NEWS
View File

@ -1,3 +1,8 @@
Version 1.8.21:
- Added a new configuration command 'eval' to replace the current configuration
file line with the output of a command (similar to passwordeval, but more
general).
Version 1.8.20:
- Added a new configuration command 'allow_from_override'. When off, the --from
option does not override the envelope-from address anymore.

View File

@ -1,7 +1,7 @@
.\" -*-nroff-*-
.\"
.\" Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014,
.\" 2015, 2016, 2017, 2018, 2019, 2020, 2021
.\" 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022
.\" Martin Lambers
.\" Copyright (C) 2011
.\" Scott Shumate
@ -10,7 +10,7 @@
.\" under the terms of the GNU Free Documentation License, Version 1.2 or
.\" any later version published by the Free Software Foundation; with no
.\" Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
.TH MSMTP 1 2021-03
.TH MSMTP 1 2022-06
.SH NAME
msmtp \- An SMTP client
.SH SYNOPSIS
@ -108,7 +108,7 @@ Enable or disable authentication and optionally choose the method.
See the \fBauth\fP command.
.IP "\-\-user=\fI[username]\fP"
Set or unset the user name for authentication. See the \fBuser\fP command.
.IP "\-\-passwordeval=[\fIeval\fP]"
.IP "\-\-passwordeval=[\fIcmd\fP]"
Evaluate password for authentication. See the \fBpasswordeval\fP command.
.IP "\-\-tls[=(\fIon\fP|\fIoff\fP)]"
Enable or disable TLS/SSL. See the \fBtls\fP command.
@ -287,6 +287,18 @@ are filled in.
If a colon and a list of previously defined accounts is given after the account
name, the new account, with the filled in default values, will inherit all
settings from the accounts in the list.
.IP "eval \fIcmd\fP"
Replace the current configuration file line with the first line of the output
(stdout) of the command \fIcmd\fP. This can be used to decrypt settings or to
create them via scripts. For example, \fIeval echo host localhost\fP replaces
the current line with \fIhost localhost\fP.
.br
The \fIcmd\fP command must not mess with standard input; if in doubt, append
\fI< /dev/null\fP.
.br
Note that for passwords you can also use the \fBpasswordeval\fP command instead
of \fIeval password cmd\fP. This has the advantage that the command is only
evaluated if needed.
.IP "host \fIhostname\fP"
The SMTP server to send the mail to.
The argument may be a host name or a network address.
@ -408,15 +420,14 @@ Set the user name for authentication. An empty argument unsets the user name.
Set the password for authentication. An empty argument unsets the password.
Consider using the \fBpasswordeval\fP command or a key ring instead of this
command, to avoid storing cleartext passwords in the configuration file.
.IP "passwordeval [\fIeval\fP]"
.IP "passwordeval [\fIcmd\fP]"
Set the password for authentication to the output (stdout) of the command
\fIeval\fP.
\fIcmd\fP.
This can be used e.g. to decrypt password files on the fly or to query key
rings, and thus to avoid storing cleartext passwords.
.br
Note that the \fIeval\fP command must not mess with standard input (stdin)
because that is where msmtp reads the mail from. If in doubt, append
\fI</dev/null\fP to \fIeval\fP.
The \fIcmd\fP command must not mess with standard input; if in doubt, append
\fI< /dev/null\fP.
.IP "ntlmdomain [\fIdomain\fP]"
Set a domain for the \fBntlm\fP authentication method. This is obsolete.
.IP "tls [(\fIon\fP|\fIoff\fP)]"

View File

@ -14,7 +14,7 @@
This manual was last updated @value{UPDATED} for version
@value{VERSION} of msmtp.
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022
Martin Lambers@*
Copyright (C) 2011
Scott Shumate
@ -158,6 +158,18 @@ are filled in (see @ref{defaults}).@*
If a colon and a list of previously defined accounts is given after the account
name, the new account, with the filled in default values, will inherit all
settings from the accounts in the list.
@anchor{eval}
@item eval [@var{cmd}]
@cmindex eval
Replace the current configuration file line with the first line of the output
(stdout) of the command @var{cmd}. This can be used to decrypt settings or to
create them via scripts. For example, @code{eval echo host localhost} replaces
the current line with @code{host localhost}.@*
The @var{cmd} command must not mess with standard input; if in doubt, append
@code{< /dev/null}.@*
Note that for passwords you can also use the @ref{passwordeval} command instead
of @code{eval password cmd}. This has the advantage that the command is only
evaluated if needed.
@anchor{host}
@item host @var{hostname}
@cmindex host
@ -251,15 +263,14 @@ Consider using the @samp{passwordeval} command or a key ring instead of this
command, to avoid storing cleartext passwords in the configuration file.
@xref{Authentication}.
@anchor{passwordeval}
@item passwordeval [@var{eval}]
@item passwordeval [@var{cmd}]
@cmindex passwordeval
Set the password for authentication to the output (stdout) of the command
@var{eval}.
@var{cmd}.
This can be used e.g. to decrypt password files on the fly or to query key
rings, and thus to avoid storing cleartext passwords.@*
Note that the @var{eval} command must not mess with standard input (stdin)
because that is where msmtp reads the mail from. If in doubt, append
@var{< /dev/null} to @var{eval}.@*
The @var{cmd} command must not mess with standard input; if in doubt, append
@code{< /dev/null}.@*
@xref{Authentication}.
@anchor{ntlmdomain}
@item ntlmdomain [@var{ntlmdomain}]

View File

@ -2,6 +2,7 @@ bin_PROGRAMS = msmtp
msmtp_SOURCES = \
conf.c conf.h \
eval.c eval.h \
list.c list.h \
msmtp.c \
net.c net.h \
@ -42,6 +43,6 @@ DEFS += -DSYSCONFDIR=\"$(sysconfdir)\" -DLOCALEDIR=\"$(localedir)\" -DBINDIR=\"$
if BUILD_MSMTPD
bin_PROGRAMS += msmtpd
msmtpd_SOURCES = msmtpd.c base64.c base64.h password.c password.h tools.c tools.h xalloc.c xalloc.h netrc.c netrc.h
msmtpd_SOURCES = msmtpd.c base64.c base64.h eval.c eval.h password.c password.h tools.c tools.h xalloc.c xalloc.h netrc.c netrc.h
msmtpd_LDADD = $(libsecret_LIBS) $(LIBINTL)
endif

View File

@ -4,7 +4,7 @@
* This file is part of msmtp, an SMTP client.
*
* Copyright (C) 2000, 2003, 2004, 2005, 2006, 2007, 2008, 2010, 2011, 2012,
* 2014, 2015, 2016, 2018, 2019, 2020, 2021
* 2014, 2015, 2016, 2018, 2019, 2020, 2021, 2022
* Martin Lambers <marlam@marlam.de>
* Martin Stenberg <martin@gnutiken.se> (passwordeval support)
* Scott Shumate <sshumate@austin.rr.com> (aliases support)
@ -46,6 +46,7 @@
#include "tools.h"
#include "net.h"
#include "xalloc.h"
#include "eval.h"
#include "conf.h"
/* buffer size for configuration file lines */
@ -1041,44 +1042,20 @@ char *trim_string(const char *s)
/*
* get_next_cmd()
* get_cmd()
*
* Read a line from 'f'. Split it in a command part (first word after
* Split the given line into a command part (first word after
* whitespace) and an argument part (the word after the command).
* Whitespace is ignored.
* Sets the flag 'empty_line' if the line is empty.
* Sets the flag 'eof' if EOF occurred.
* On errors, 'empty_line' and 'eof', 'cmd' and 'arg' NULL.
* On success, 'cmd' and 'arg' are allocated strings.
* Used error codes: CONF_EIO, CONF_EPARSE
* If the line is empty or a comment, 'cmd' and 'arg' are unchanged.
*/
int get_next_cmd(FILE *f, char **cmd, char **arg, int *empty_line, int *eof,
char **errstr)
void get_cmd(const char* line, char **cmd, char **arg)
{
char line[LINEBUFSIZE];
char *p;
int i;
int l;
*eof = 0;
*empty_line = 0;
*cmd = NULL;
*arg = NULL;
if (!fgets(line, (int)sizeof(line), f))
{
if (ferror(f))
{
*errstr = xasprintf(_("input error"));
return CONF_EIO;
}
else /* EOF */
{
*eof = 1;
return CONF_EOK;
}
}
/* Kill '\n'. Beware: sometimes the last line of a file has no '\n' */
if ((p = strchr(line, '\n')))
{
@ -1089,19 +1066,12 @@ int get_next_cmd(FILE *f, char **cmd, char **arg, int *empty_line, int *eof,
*(p - 1) = '\0';
}
}
else if (strlen(line) == LINEBUFSIZE - 1)
{
*errstr = xasprintf(_("line longer than %d characters"),
LINEBUFSIZE - 1);
return CONF_EPARSE;
}
i = skip_blanks(line, 0);
if (line[i] == '#' || line[i] == '\0')
{
*empty_line = 1;
return CONF_EOK;
return;
}
l = get_cmd_length(line + i);
@ -1110,8 +1080,6 @@ int get_next_cmd(FILE *f, char **cmd, char **arg, int *empty_line, int *eof,
(*cmd)[l] = '\0';
*arg = trim_string(line + i + l);
return CONF_EOK;
}
@ -1191,37 +1159,52 @@ int read_conffile(const char *conffile, FILE *f, list_t **acc_list,
int line;
char *cmd;
char *arg;
int empty_line;
int eof;
/* for the account command: */
char *acc_id;
char *t;
list_t *copy_from;
list_t *lp;
*conffile_contains_secrets = 0;
defaults = account_new(NULL, NULL);
*acc_list = list_new();
p = *acc_list;
acc = NULL;
e = CONF_EOK;
cmd = NULL;
arg = NULL;
for (line = 1; ; line++)
{
if ((e = get_next_cmd(f, &cmd, &arg, &empty_line, &eof,
errstr)) != CONF_EOK)
int line_comes_from_eval = 0;
char linebuf[LINEBUFSIZE];
size_t linelen;
if (!fgets(linebuf, sizeof(linebuf), f))
{
break;
if (ferror(f))
{
*errstr = xasprintf(_("input error"));
e = CONF_EIO;
break;
}
else /* EOF */
{
break;
}
}
if (empty_line)
linelen = strlen(linebuf);
if (linelen == LINEBUFSIZE - 1 && linebuf[linelen - 1] != '\n')
{
*errstr = xasprintf(_("line longer than %d characters"),
LINEBUFSIZE - 1);
return CONF_EPARSE;
}
get_cmd(linebuf, &cmd, &arg);
if (!cmd)
{
continue;
}
if (eof)
{
break;
}
/* compatibility with 1.2.x: if no account command is given, the first
* account will be named "default". */
@ -1234,6 +1217,35 @@ int read_conffile(const char *conffile, FILE *f, list_t **acc_list,
list_insert(p, acc);
p = p->next;
}
if (strcmp(cmd, "eval") == 0)
{
if (*arg == '\0')
{
*errstr = xasprintf(_("line %d: command %s needs an argument"),
line, cmd);
e = CONF_ESYNTAX;
break;
}
/* replace the current cmd/arg with the output of the given command */
char* evalbuf;
if (eval(arg, &evalbuf, errstr) != 0)
{
e = CONF_EIO;
break;
}
free(cmd);
cmd = NULL;
free(arg);
arg = NULL;
get_cmd(evalbuf, &cmd, &arg);
if (!cmd)
{
continue;
}
line_comes_from_eval = 1;
}
if (strcmp(cmd, "defaults") == 0)
{
if (*arg != '\0')
@ -1477,7 +1489,8 @@ int read_conffile(const char *conffile, FILE *f, list_t **acc_list,
}
else if (strcmp(cmd, "password") == 0)
{
*conffile_contains_secrets = 1;
if (!line_comes_from_eval)
*conffile_contains_secrets = 1;
acc->mask |= ACC_PASSWORD;
free(acc->password);
acc->password = (*arg == '\0') ? NULL : xstrdup(arg);
@ -2095,7 +2108,9 @@ int read_conffile(const char *conffile, FILE *f, list_t **acc_list,
break;
}
free(cmd);
cmd = NULL;
free(arg);
arg = NULL;
}
free(cmd);
free(arg);

98
src/eval.c Normal file
View File

@ -0,0 +1,98 @@
/*
* eval.c
*
* This file is part of msmtp, an SMTP client, and of mpop, a POP3 client.
*
* Copyright (C) 2019, 2020, 2021, 2022 Martin Lambers <marlam@marlam.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gettext.h"
#define _(string) gettext(string)
#include "xalloc.h"
#include "eval.h"
/*
* eval()
*
* see eval.h
*/
#define LINEBUFSIZE 501
int eval(const char *arg, char **buf, char **errstr)
{
FILE *f;
size_t bufsize;
size_t len;
*buf = NULL;
*errstr = NULL;
errno = 0;
bufsize = 1; /* Account for the null character. */
if (!(f = popen(arg, "r")))
{
if (errno == 0)
{
errno = ENOMEM;
}
*errstr = xasprintf(_("cannot evaluate '%s': %s"), arg, strerror(errno));
return 1;
}
do
{
bufsize += LINEBUFSIZE;
*buf = xrealloc(*buf, bufsize);
if (!fgets(&(*buf)[bufsize - LINEBUFSIZE - 1], LINEBUFSIZE + 1, f))
{
*errstr = xasprintf(_("cannot read output of '%s'"), arg);
pclose(f);
free(*buf);
*buf = NULL;
return 1;
}
len = strlen(*buf);
if (len > 0 && (*buf)[len - 1] == '\n')
{
/* Read only the first line. */
break;
}
}
while (!feof(f));
pclose(f);
if (len > 0 && (*buf)[len - 1] == '\n')
{
(*buf)[len - 1] = '\0';
if (len - 1 > 0 && (*buf)[len - 2] == '\r')
{
(*buf)[len - 2] = '\0';
}
}
return 0;
}

34
src/eval.h Normal file
View File

@ -0,0 +1,34 @@
/*
* eval.h
*
* This file is part of msmtp, an SMTP client, and of mpop, a POP3 client.
*
* Copyright (C) 2019, 2020, 2021, 2022 Martin Lambers <marlam@marlam.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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/>.
*/
#ifndef EVAL_H
#define EVAL_H
/*
* eval()
*
* Evaluates command in 'arg' and stores result in 'buf' (which is allocated).
* Returns non-zero if command execution failed, otherwise zero. On error,
* *errstr will contain an error string.
*/
int eval(const char *arg, char **buf, char **errstr);
#endif

View File

@ -60,6 +60,7 @@ extern int optind;
#include "tools.h"
#include "aliases.h"
#include "password.h"
#include "eval.h"
#ifdef HAVE_TLS
# include "mtls.h"
#endif /* HAVE_TLS */
@ -3908,8 +3909,7 @@ int main(int argc, char *argv[])
* them. */
if (account->auth_mech && !account->password && account->passwordeval)
{
if (password_eval(account->passwordeval,
&account->password, &errstr) != 0)
if (eval(account->passwordeval, &account->password, &errstr) != 0)
{
print_error("%s", sanitize_string(errstr));
error_code = EX_CONFIG;

View File

@ -44,6 +44,7 @@ extern int optind;
#include "base64.h"
#include "password.h"
#include "eval.h"
#include "xalloc.h"
@ -629,7 +630,7 @@ int parse_command_line(int argc, char* argv[],
char* tmp_user = xstrndup(optarg, comma - optarg);
char* tmp_password = NULL;
char* errstr = NULL;
if (password_eval(comma + 1, &tmp_password, &errstr) != 0) {
if (eval(comma + 1, &tmp_password, &errstr) != 0) {
fprintf(stderr, "%s: cannot get password: %s\n", argv[0], errstr);
free(tmp_user);
free(errstr);