sieve-extprograms-common.c [plain text]
#include "lib.h"
#include "lib-signals.h"
#include "str.h"
#include "strfuncs.h"
#include "str-sanitize.h"
#include "unichar.h"
#include "array.h"
#include "eacces-error.h"
#include "istream.h"
#include "istream-crlf.h"
#include "istream-header-filter.h"
#include "ostream.h"
#include "mail-user.h"
#include "mail-storage.h"
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include "sieve-common.h"
#include "sieve-settings.h"
#include "sieve-error.h"
#include "sieve-extensions.h"
#include "sieve-ast.h"
#include "sieve-commands.h"
#include "sieve-stringlist.h"
#include "sieve-code.h"
#include "sieve-actions.h"
#include "sieve-validator.h"
#include "sieve-runtime.h"
#include "sieve-interpreter.h"
#include "sieve-ext-copy.h"
#include "sieve-ext-variables.h"
#include "script-client.h"
#include "sieve-extprograms-common.h"
#define SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN 128
#define SIEVE_EXTPROGRAMS_MAX_PROGRAM_ARG_LEN 1024
#define SIEVE_EXTPROGRAMS_DEFAULT_EXEC_TIMEOUT_SECS 10
#define SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS 5
struct sieve_extprograms_config *sieve_extprograms_config_init
(const struct sieve_extension *ext)
{
struct sieve_instance *svinst = ext->svinst;
struct sieve_extprograms_config *ext_config;
const char *extname = sieve_extension_name(ext);
const char *bin_dir, *socket_dir;
sieve_number_t execute_timeout =
SIEVE_EXTPROGRAMS_DEFAULT_EXEC_TIMEOUT_SECS;
extname = strrchr(extname, '.');
i_assert(extname != NULL);
extname++;
bin_dir = sieve_setting_get
(svinst, t_strdup_printf("sieve_%s_bin_dir", extname));
socket_dir = sieve_setting_get
(svinst, t_strdup_printf("sieve_%s_socket_dir", extname));
ext_config = i_new(struct sieve_extprograms_config, 1);
if ( bin_dir == NULL && socket_dir == NULL ) {
if ( svinst->debug ) {
sieve_sys_debug(svinst, "%s extension: "
"no bin or socket directory specified; extension is unconfigured "
"(both sieve_%s_bin_dir and sieve_%s_socket_dir are not set)",
sieve_extension_name(ext), extname, extname);
}
} else {
ext_config->bin_dir = i_strdup(bin_dir);
ext_config->socket_dir = i_strdup(socket_dir);
}
if (sieve_setting_get_duration_value
(svinst, t_strdup_printf("sieve_%s_exec_timeout", extname),
&execute_timeout)) {
ext_config->execute_timeout = execute_timeout;
}
if ( sieve_extension_is(ext, pipe_extension) )
ext_config->copy_ext = sieve_ext_copy_get_extension(ext->svinst);
if ( sieve_extension_is(ext, execute_extension) )
ext_config->var_ext = sieve_ext_variables_get_extension(ext->svinst);
return ext_config;
}
void sieve_extprograms_config_deinit
(struct sieve_extprograms_config **ext_config)
{
if ( *ext_config == NULL )
return;
i_free((*ext_config)->bin_dir);
i_free((*ext_config)->socket_dir);
i_free((*ext_config));
*ext_config = NULL;
}
bool sieve_extprogram_name_is_valid(string_t *name)
{
ARRAY_TYPE(unichars) uni_name;
unsigned int count, i;
const unichar_t *name_chars;
size_t namelen = str_len(name);
if ( namelen == 0 )
return FALSE;
if ( namelen > SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN * 4 )
return FALSE;
t_array_init(&uni_name, namelen * 4);
if ( uni_utf8_to_ucs4_n(str_data(name), namelen, &uni_name) < 0 )
return FALSE;
name_chars = array_get(&uni_name, &count);
if ( count > SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN )
return FALSE;
for ( i = 0; i < count; i++ ) {
if ( name_chars[i] <= 0x001f )
return FALSE;
if ( name_chars[i] == 0x002f )
return FALSE;
if ( name_chars[i] == 0x007f )
return FALSE;
if ( name_chars[i] >= 0x0080 && name_chars[i] <= 0x009f )
return FALSE;
if ( name_chars[i] == 0x00ff )
return FALSE;
if ( name_chars[i] == 0x2028 || name_chars[i] == 0x2029 )
return FALSE;
}
return TRUE;
}
bool sieve_extprogram_arg_is_valid(string_t *arg)
{
const unsigned char *chars;
unsigned int i;
if ( str_len(arg) > SIEVE_EXTPROGRAMS_MAX_PROGRAM_ARG_LEN )
return FALSE;
chars = str_data(arg);
for ( i = 0; i < str_len(arg); i++ ) {
if ( chars[i] == 0x0D )
return FALSE;
if ( chars[i] == 0x0A )
return FALSE;
}
return TRUE;
}
struct _arg_validate_context {
struct sieve_validator *valdtr;
struct sieve_command *cmd;
};
static int _arg_validate
(void *context, struct sieve_ast_argument *item)
{
struct _arg_validate_context *actx = (struct _arg_validate_context *) context;
if ( sieve_argument_is_string_literal(item) ) {
string_t *arg = sieve_ast_argument_str(item);
if ( !sieve_extprogram_arg_is_valid(arg) ) {
sieve_argument_validate_error(actx->valdtr, item,
"%s %s: specified external program argument `%s' is invalid",
sieve_command_identifier(actx->cmd), sieve_command_type_name(actx->cmd),
str_sanitize(str_c(arg), 128));
return FALSE;
}
}
return TRUE;
}
bool sieve_extprogram_command_validate
(struct sieve_validator *valdtr, struct sieve_command *cmd)
{
struct sieve_ast_argument *arg = cmd->first_positional;
struct sieve_ast_argument *stritem;
struct _arg_validate_context actx;
string_t *program_name;
if ( arg == NULL ) {
sieve_command_validate_error(valdtr, cmd,
"the %s %s expects at least one positional argument, but none was found",
sieve_command_identifier(cmd), sieve_command_type_name(cmd));
return FALSE;
}
if ( !sieve_validate_positional_argument
(valdtr, cmd, arg, "program-name", 1, SAAT_STRING) ) {
return FALSE;
}
if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
return FALSE;
if ( !sieve_argument_is_string_literal(arg) ) {
sieve_argument_validate_error(valdtr, arg,
"the %s %s requires a constant string "
"for its program-name argument",
sieve_command_identifier(cmd), sieve_command_type_name(cmd));
return FALSE;
}
program_name = sieve_ast_argument_str(arg);
if ( !sieve_extprogram_name_is_valid(program_name) ) {
sieve_argument_validate_error(valdtr, arg,
"%s %s: invalid program name '%s'",
sieve_command_identifier(cmd), sieve_command_type_name(cmd),
str_sanitize(str_c(program_name), 80));
return FALSE;
}
arg = sieve_ast_argument_next(arg);
if ( arg == NULL )
return TRUE;
if ( !sieve_validate_positional_argument
(valdtr, cmd, arg, "arguments", 2, SAAT_STRING_LIST) ) {
return FALSE;
}
if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
return FALSE;
actx.valdtr = valdtr;
actx.cmd = cmd;
stritem = arg;
if ( sieve_ast_stringlist_map
(&stritem, (void *)&actx, _arg_validate) <= 0 ) {
return FALSE;
}
if ( sieve_ast_argument_next(arg) != NULL ) {
sieve_command_validate_error(valdtr, cmd,
"the %s %s expects at most two positional arguments, but more were found",
sieve_command_identifier(cmd), sieve_command_type_name(cmd));
return FALSE;
}
return TRUE;
}
int sieve_extprogram_command_read_operands
(const struct sieve_runtime_env *renv, sieve_size_t *address,
string_t **pname_r, struct sieve_stringlist **args_list_r)
{
string_t *arg;
int ret;
if ( (ret=sieve_opr_string_read
(renv, address, "program-name", pname_r)) <= 0 )
return ret;
if ( (ret=sieve_opr_stringlist_read_ex
(renv, address, "arguments", TRUE, args_list_r)) <= 0 )
return ret;
arg = NULL;
while ( *args_list_r != NULL &&
(ret=sieve_stringlist_next_item(*args_list_r, &arg)) > 0 ) {
if ( !sieve_extprogram_arg_is_valid(arg) ) {
sieve_runtime_error(renv, NULL,
"specified :args item `%s' is invalid",
str_sanitize(str_c(arg), 128));
return SIEVE_EXEC_FAILURE;
}
}
if ( ret < 0 ) {
sieve_runtime_trace_error(renv, "invalid args-list item");
return SIEVE_EXEC_BIN_CORRUPT;
}
return SIEVE_EXEC_OK;
}
struct sieve_extprogram {
struct sieve_instance *svinst;
struct script_client_settings set;
struct script_client *script_client;
};
void sieve_extprogram_exec_error
(struct sieve_error_handler *ehandler, const char *location,
const char *fmt, ...)
{
char str[256];
struct tm *tm;
const char *timestamp;
tm = localtime(&ioloop_time);
timestamp =
( strftime(str, sizeof(str), " [%Y-%m-%d %H:%M:%S]", tm) > 0 ? str : "" );
va_list args;
va_start(args, fmt);
T_BEGIN {
sieve_error(ehandler, location,
"%s: refer to server log for more information.%s",
t_strdup_vprintf(fmt, args), timestamp);
} T_END;
va_end(args);
}
struct sieve_extprogram *sieve_extprogram_create
(const struct sieve_extension *ext, const struct sieve_script_env *senv,
const struct sieve_message_data *msgdata, const char *action,
const char *program_name, const char * const *args,
enum sieve_error *error_r)
{
struct sieve_instance *svinst = ext->svinst;
struct sieve_extprograms_config *ext_config =
(struct sieve_extprograms_config *) ext->context;
struct sieve_extprogram *sprog;
const char *path = NULL;
struct stat st;
bool fork = FALSE;
int ret;
if ( svinst->debug ) {
sieve_sys_debug(svinst, "action %s: "
"running program: %s", action, program_name);
}
if ( ext_config == NULL ) {
sieve_sys_error(svinst, "action %s: "
"failed to execute program `%s': "
"vnd.dovecot.%s extension is unconfigured", action, program_name, action);
*error_r = SIEVE_ERROR_NOT_FOUND;
return NULL;
}
if ( ext_config->socket_dir != NULL ) {
path = t_strconcat(senv->user->set->base_dir, "/",
ext_config->socket_dir, "/", program_name, NULL);
if ( (ret=stat(path, &st)) < 0 ) {
switch ( errno ) {
case ENOENT:
if ( svinst->debug ) {
sieve_sys_debug(svinst, "action %s: "
"socket path `%s' for program `%s' not found",
action, path, program_name);
}
break;
case EACCES:
sieve_sys_error(svinst, "action %s: "
"failed to stat socket: %s", action, eacces_error_get("stat", path));
*error_r = SIEVE_ERROR_NO_PERMISSION;
return NULL;
default:
sieve_sys_error(svinst, "action %s: "
"failed to stat socket `%s': %m", action, path);
*error_r = SIEVE_ERROR_TEMP_FAILURE;
return NULL;
}
path = NULL;
} else if ( !S_ISSOCK(st.st_mode) ) {
sieve_sys_error(svinst, "action %s: "
"socket path `%s' for program `%s' is not a socket",
action, path, program_name);
*error_r = SIEVE_ERROR_NOT_POSSIBLE;
return NULL;
}
}
if ( path == NULL && ext_config->bin_dir != NULL ) {
fork = TRUE;
path = t_strconcat(ext_config->bin_dir, "/", program_name, NULL);
if ( (ret=stat(path, &st)) < 0 ) {
switch ( errno ) {
case ENOENT:
if ( svinst->debug ) {
sieve_sys_debug(svinst, "action %s: "
"executable path `%s' for program `%s' not found",
action, path, program_name);
}
sieve_sys_error(svinst, "action %s: program `%s' not found",
action, program_name);
*error_r = SIEVE_ERROR_NOT_FOUND;
break;
case EACCES:
sieve_sys_error(svinst, "action %s: "
"failed to stat program: %s", action, eacces_error_get("stat", path));
*error_r = SIEVE_ERROR_NO_PERMISSION;
break;
default:
sieve_sys_error(svinst, "action %s: "
"failed to stat program `%s': %m", action, path);
*error_r = SIEVE_ERROR_TEMP_FAILURE;
break;
}
return NULL;
} else if ( !S_ISREG(st.st_mode) ) {
sieve_sys_error(svinst, "action %s: "
"executable `%s' for program `%s' is not a regular file",
action, path, program_name);
*error_r = SIEVE_ERROR_NOT_POSSIBLE;
return NULL;
} else if ( (st.st_mode & S_IWOTH) != 0 ) {
sieve_sys_error(svinst, "action %s: "
"executable `%s' for program `%s' is world-writable",
action, path, program_name);
*error_r = SIEVE_ERROR_NO_PERMISSION;
return NULL;
}
}
if ( path == NULL ) {
sieve_sys_error(svinst, "action %s: "
"program `%s' not found", action, program_name);
*error_r = SIEVE_ERROR_NOT_FOUND;
return NULL;
}
sprog = i_new(struct sieve_extprogram, 1);
sprog->svinst = ext->svinst;
sprog->set.client_connect_timeout_msecs =
SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS;
sprog->set.input_idle_timeout_secs = ext_config->execute_timeout;
sprog->set.debug = svinst->debug;
if ( fork ) {
sprog->script_client =
script_client_local_create(path, args, &sprog->set);
} else {
sprog->script_client =
script_client_remote_create(path, args, &sprog->set, FALSE);
}
if ( svinst->username != NULL )
script_client_set_env(sprog->script_client, "USER", svinst->username);
if ( svinst->home_dir != NULL )
script_client_set_env(sprog->script_client, "HOME", svinst->home_dir);
if ( svinst->hostname != NULL )
script_client_set_env(sprog->script_client, "HOST", svinst->hostname);
if ( msgdata->return_path != NULL ) {
script_client_set_env
(sprog->script_client, "SENDER", msgdata->return_path);
}
if ( msgdata->final_envelope_to != NULL ) {
script_client_set_env
(sprog->script_client, "RECIPIENT", msgdata->final_envelope_to);
}
if ( msgdata->orig_envelope_to != NULL ) {
script_client_set_env
(sprog->script_client, "ORIG_RECIPIENT", msgdata->orig_envelope_to);
}
return sprog;
}
void sieve_extprogram_destroy(struct sieve_extprogram **_sprog)
{
struct sieve_extprogram *sprog = *_sprog;
script_client_destroy(&sprog->script_client);
i_free(sprog);
*_sprog = NULL;
}
void sieve_extprogram_set_output
(struct sieve_extprogram *sprog, struct ostream *output)
{
script_client_set_output(sprog->script_client, output);
}
void sieve_extprogram_set_input
(struct sieve_extprogram *sprog, struct istream *input)
{
script_client_set_input(sprog->script_client, input);
}
int sieve_extprogram_set_input_mail
(struct sieve_extprogram *sprog, struct mail *mail)
{
struct istream *input;
if (mail_get_stream(mail, NULL, NULL, &input) < 0)
return -1;
input = i_stream_create_crlf(input);
script_client_set_input(sprog->script_client, input);
i_stream_unref(&input);
return 1;
}
int sieve_extprogram_run(struct sieve_extprogram *sprog)
{
return script_client_run(sprog->script_client);
}