1 /* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2 */
3
4 #include "lib.h"
5 #include "lib-signals.h"
6 #include "str.h"
7 #include "strfuncs.h"
8 #include "str-sanitize.h"
9 #include "unichar.h"
10 #include "array.h"
11 #include "eacces-error.h"
12 #include "smtp-params.h"
13 #include "istream.h"
14 #include "istream-crlf.h"
15 #include "istream-header-filter.h"
16 #include "ostream.h"
17 #include "mail-user.h"
18 #include "mail-storage.h"
19
20 #include "program-client.h"
21
22 #include "sieve-common.h"
23 #include "sieve-settings.h"
24 #include "sieve-error.h"
25 #include "sieve-extensions.h"
26 #include "sieve-ast.h"
27 #include "sieve-commands.h"
28 #include "sieve-stringlist.h"
29 #include "sieve-code.h"
30 #include "sieve-actions.h"
31 #include "sieve-validator.h"
32 #include "sieve-runtime.h"
33 #include "sieve-interpreter.h"
34
35 #include "sieve-ext-copy.h"
36 #include "sieve-ext-variables.h"
37
38 #include "sieve-extprograms-common.h"
39
40 #include <unistd.h>
41 #include <sys/types.h>
42 #include <sys/wait.h>
43 #include <sys/socket.h>
44
45 /*
46 * Limits
47 */
48
49 #define SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN 128
50 #define SIEVE_EXTPROGRAMS_MAX_PROGRAM_ARG_LEN 1024
51
52 #define SIEVE_EXTPROGRAMS_DEFAULT_EXEC_TIMEOUT_SECS 10
53 #define SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS 5
54
55 /*
56 * Pipe Extension Context
57 */
58
sieve_extprograms_config_init(const struct sieve_extension * ext)59 struct sieve_extprograms_config *sieve_extprograms_config_init
60 (const struct sieve_extension *ext)
61 {
62 struct sieve_instance *svinst = ext->svinst;
63 struct sieve_extprograms_config *ext_config;
64 const char *extname = sieve_extension_name(ext);
65 const char *bin_dir, *socket_dir, *input_eol;
66 sieve_number_t execute_timeout;
67
68 extname = strrchr(extname, '.');
69 i_assert(extname != NULL);
70 extname++;
71
72 bin_dir = sieve_setting_get
73 (svinst, t_strdup_printf("sieve_%s_bin_dir", extname));
74 socket_dir = sieve_setting_get
75 (svinst, t_strdup_printf("sieve_%s_socket_dir", extname));
76 input_eol = sieve_setting_get
77 (svinst, t_strdup_printf("sieve_%s_input_eol", extname));
78
79 ext_config = i_new(struct sieve_extprograms_config, 1);
80 ext_config->execute_timeout =
81 SIEVE_EXTPROGRAMS_DEFAULT_EXEC_TIMEOUT_SECS;
82
83 if ( bin_dir == NULL && socket_dir == NULL ) {
84 e_debug(svinst->event, "%s extension: "
85 "no bin or socket directory specified; extension is unconfigured "
86 "(both sieve_%s_bin_dir and sieve_%s_socket_dir are not set)",
87 sieve_extension_name(ext), extname, extname);
88 } else {
89 ext_config->bin_dir = i_strdup(bin_dir);
90 ext_config->socket_dir = i_strdup(socket_dir);
91
92 if (sieve_setting_get_duration_value
93 (svinst, t_strdup_printf("sieve_%s_exec_timeout", extname),
94 &execute_timeout)) {
95 ext_config->execute_timeout = execute_timeout;
96 }
97
98 ext_config->default_input_eol = SIEVE_EXTPROGRAMS_EOL_CRLF;
99 if (input_eol != NULL && strcasecmp(input_eol, "lf") == 0)
100 ext_config->default_input_eol = SIEVE_EXTPROGRAMS_EOL_LF;
101 }
102
103 if ( sieve_extension_is(ext, sieve_ext_vnd_pipe) )
104 ext_config->copy_ext = sieve_ext_copy_get_extension(ext->svinst);
105 if ( sieve_extension_is(ext, sieve_ext_vnd_execute) )
106 ext_config->var_ext = sieve_ext_variables_get_extension(ext->svinst);
107 return ext_config;
108 }
109
sieve_extprograms_config_deinit(struct sieve_extprograms_config ** ext_config)110 void sieve_extprograms_config_deinit
111 (struct sieve_extprograms_config **ext_config)
112 {
113 if ( *ext_config == NULL )
114 return;
115
116 i_free((*ext_config)->bin_dir);
117 i_free((*ext_config)->socket_dir);
118 i_free((*ext_config));
119
120 *ext_config = NULL;
121 }
122
123 /*
124 * Program name and arguments
125 */
126
sieve_extprogram_name_is_valid(string_t * name)127 bool sieve_extprogram_name_is_valid(string_t *name)
128 {
129 ARRAY_TYPE(unichars) uni_name;
130 unsigned int count, i;
131 const unichar_t *name_chars;
132 size_t namelen = str_len(name);
133
134 /* Check minimum length */
135 if ( namelen == 0 )
136 return FALSE;
137
138 /* Check worst-case maximum length */
139 if ( namelen > SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN * 4 )
140 return FALSE;
141
142 /* Intialize array for unicode characters */
143 t_array_init(&uni_name, namelen * 4);
144
145 /* Convert UTF-8 to UCS4/UTF-32 */
146 if ( uni_utf8_to_ucs4_n(str_data(name), namelen, &uni_name) < 0 )
147 return FALSE;
148 name_chars = array_get(&uni_name, &count);
149
150 /* Check true maximum length */
151 if ( count > SIEVE_EXTPROGRAMS_MAX_PROGRAM_NAME_LEN )
152 return FALSE;
153
154 /* Scan name for invalid characters
155 * FIXME: compliance with Net-Unicode Definition (Section 2 of
156 * RFC 5198) is not checked fully and no normalization
157 * is performed.
158 */
159 for ( i = 0; i < count; i++ ) {
160
161 /* 0000-001F; [CONTROL CHARACTERS] */
162 if ( name_chars[i] <= 0x001f )
163 return FALSE;
164
165 /* 002F; SLASH */
166 if ( name_chars[i] == 0x002f )
167 return FALSE;
168
169 /* 007F; DELETE */
170 if ( name_chars[i] == 0x007f )
171 return FALSE;
172
173 /* 0080-009F; [CONTROL CHARACTERS] */
174 if ( name_chars[i] >= 0x0080 && name_chars[i] <= 0x009f )
175 return FALSE;
176
177 /* 00FF */
178 if ( name_chars[i] == 0x00ff )
179 return FALSE;
180
181 /* 2028; LINE SEPARATOR */
182 /* 2029; PARAGRAPH SEPARATOR */
183 if ( name_chars[i] == 0x2028 || name_chars[i] == 0x2029 )
184 return FALSE;
185 }
186
187 return TRUE;
188 }
189
sieve_extprogram_arg_is_valid(string_t * arg)190 bool sieve_extprogram_arg_is_valid(string_t *arg)
191 {
192 const unsigned char *chars;
193 unsigned int i;
194
195 /* Check maximum length */
196 if ( str_len(arg) > SIEVE_EXTPROGRAMS_MAX_PROGRAM_ARG_LEN )
197 return FALSE;
198
199 /* Check invalid characters */
200 chars = str_data(arg);
201 for ( i = 0; i < str_len(arg); i++ ) {
202 /* 0010; CR */
203 if ( chars[i] == 0x0D )
204 return FALSE;
205
206 /* 0010; LF */
207 if ( chars[i] == 0x0A )
208 return FALSE;
209 }
210
211 return TRUE;
212 }
213
214 /*
215 * Command validation
216 */
217
218 struct _arg_validate_context {
219 struct sieve_validator *valdtr;
220 struct sieve_command *cmd;
221 };
222
_arg_validate(void * context,struct sieve_ast_argument * item)223 static int _arg_validate
224 (void *context, struct sieve_ast_argument *item)
225 {
226 struct _arg_validate_context *actx = (struct _arg_validate_context *) context;
227
228 if ( sieve_argument_is_string_literal(item) ) {
229 string_t *arg = sieve_ast_argument_str(item);
230
231 if ( !sieve_extprogram_arg_is_valid(arg) ) {
232 sieve_argument_validate_error(actx->valdtr, item,
233 "%s %s: specified external program argument `%s' is invalid",
234 sieve_command_identifier(actx->cmd), sieve_command_type_name(actx->cmd),
235 str_sanitize(str_c(arg), 128));
236
237 return -1;
238 }
239 }
240
241 return 1;
242 }
243
sieve_extprogram_command_validate(struct sieve_validator * valdtr,struct sieve_command * cmd)244 bool sieve_extprogram_command_validate
245 (struct sieve_validator *valdtr, struct sieve_command *cmd)
246 {
247 struct sieve_ast_argument *arg = cmd->first_positional;
248 struct sieve_ast_argument *stritem;
249 struct _arg_validate_context actx;
250 string_t *program_name;
251
252 if ( arg == NULL ) {
253 sieve_command_validate_error(valdtr, cmd,
254 "the %s %s expects at least one positional argument, but none was found",
255 sieve_command_identifier(cmd), sieve_command_type_name(cmd));
256 return FALSE;
257 }
258
259 /* <program-name: string> argument */
260
261 if ( !sieve_validate_positional_argument
262 (valdtr, cmd, arg, "program-name", 1, SAAT_STRING) ) {
263 return FALSE;
264 }
265
266 if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
267 return FALSE;
268
269 /* Variables are not allowed */
270 if ( !sieve_argument_is_string_literal(arg) ) {
271 sieve_argument_validate_error(valdtr, arg,
272 "the %s %s requires a constant string "
273 "for its program-name argument",
274 sieve_command_identifier(cmd), sieve_command_type_name(cmd));
275 return FALSE;
276 }
277
278 /* Check program name */
279 program_name = sieve_ast_argument_str(arg);
280 if ( !sieve_extprogram_name_is_valid(program_name) ) {
281 sieve_argument_validate_error(valdtr, arg,
282 "%s %s: invalid program name '%s'",
283 sieve_command_identifier(cmd), sieve_command_type_name(cmd),
284 str_sanitize(str_c(program_name), 80));
285 return FALSE;
286 }
287
288 /* Optional <arguments: string-list> argument */
289
290 arg = sieve_ast_argument_next(arg);
291 if ( arg == NULL )
292 return TRUE;
293
294 if ( !sieve_validate_positional_argument
295 (valdtr, cmd, arg, "arguments", 2, SAAT_STRING_LIST) ) {
296 return FALSE;
297 }
298
299 if ( !sieve_validator_argument_activate(valdtr, cmd, arg, FALSE) )
300 return FALSE;
301
302 /* Check arguments */
303 actx.valdtr = valdtr;
304 actx.cmd = cmd;
305 stritem = arg;
306 if ( sieve_ast_stringlist_map
307 (&stritem, (void *)&actx, _arg_validate) <= 0 ) {
308 return FALSE;
309 }
310
311 if ( sieve_ast_argument_next(arg) != NULL ) {
312 sieve_command_validate_error(valdtr, cmd,
313 "the %s %s expects at most two positional arguments, but more were found",
314 sieve_command_identifier(cmd), sieve_command_type_name(cmd));
315 return FALSE;
316 }
317
318 return TRUE;
319 }
320
321 /*
322 * Common command operands
323 */
324
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)325 int sieve_extprogram_command_read_operands
326 (const struct sieve_runtime_env *renv, sieve_size_t *address,
327 string_t **pname_r, struct sieve_stringlist **args_list_r)
328 {
329 string_t *arg;
330 int ret;
331
332 /*
333 * Read fixed operands
334 */
335
336 if ( (ret=sieve_opr_string_read
337 (renv, address, "program-name", pname_r)) <= 0 )
338 return ret;
339
340 if ( (ret=sieve_opr_stringlist_read_ex
341 (renv, address, "arguments", TRUE, args_list_r)) <= 0 )
342 return ret;
343
344 /*
345 * Check operands
346 */
347
348 arg = NULL;
349 while ( *args_list_r != NULL &&
350 (ret=sieve_stringlist_next_item(*args_list_r, &arg)) > 0 ) {
351 if ( !sieve_extprogram_arg_is_valid(arg) ) {
352 sieve_runtime_error(renv, NULL,
353 "specified :args item `%s' is invalid",
354 str_sanitize(str_c(arg), 128));
355 return SIEVE_EXEC_FAILURE;
356 }
357 }
358
359 if ( ret < 0 ) {
360 sieve_runtime_trace_error(renv, "invalid args-list item");
361 return SIEVE_EXEC_BIN_CORRUPT;
362 }
363
364 return SIEVE_EXEC_OK;
365 }
366
367 /*
368 * Running external programs
369 */
370
371 struct sieve_extprogram {
372 struct sieve_instance *svinst;
373 const struct sieve_extprograms_config *ext_config;
374
375 const struct sieve_script_env *scriptenv;
376 struct program_client_settings set;
377 struct program_client *program_client;
378 };
379
sieve_extprogram_exec_error(struct sieve_error_handler * ehandler,const char * location,const char * fmt,...)380 void sieve_extprogram_exec_error
381 (struct sieve_error_handler *ehandler, const char *location,
382 const char *fmt, ...)
383 {
384 char str[256];
385 struct tm *tm;
386 const char *timestamp;
387
388 tm = localtime(&ioloop_time);
389
390 timestamp =
391 ( strftime(str, sizeof(str), " [%Y-%m-%d %H:%M:%S]", tm) > 0 ? str : "" );
392
393 va_list args;
394 va_start(args, fmt);
395
396 T_BEGIN {
397 sieve_error(ehandler, location,
398 "%s: refer to server log for more information.%s",
399 t_strdup_vprintf(fmt, args), timestamp);
400 } T_END;
401
402 va_end(args);
403 }
404
405 /* API */
406
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)407 struct sieve_extprogram *sieve_extprogram_create
408 (const struct sieve_extension *ext, const struct sieve_script_env *senv,
409 const struct sieve_message_data *msgdata, const char *action,
410 const char *program_name, const char * const *args,
411 enum sieve_error *error_r)
412 {
413 struct sieve_instance *svinst = ext->svinst;
414 struct sieve_extprograms_config *ext_config =
415 (struct sieve_extprograms_config *) ext->context;
416 const struct smtp_address *sender, *recipient, *orig_recipient;
417 struct sieve_extprogram *sprog;
418 const char *path = NULL;
419 struct stat st;
420 bool fork = FALSE;
421
422 e_debug(svinst->event, "action %s: "
423 "running program: %s", action, program_name);
424
425 if ( ext_config == NULL ||
426 (ext_config->bin_dir == NULL && ext_config->socket_dir == NULL) ) {
427 e_error(svinst->event, "action %s: "
428 "failed to execute program `%s': "
429 "vnd.dovecot.%s extension is unconfigured",
430 action, program_name, action);
431 *error_r = SIEVE_ERROR_NOT_FOUND;
432 return NULL;
433 }
434
435 /* Try socket first */
436 if ( ext_config->socket_dir != NULL ) {
437 path = t_strconcat(senv->user->set->base_dir, "/",
438 ext_config->socket_dir, "/", program_name, NULL);
439 if ( stat(path, &st) < 0 ) {
440 switch ( errno ) {
441 case ENOENT:
442 e_debug(svinst->event, "action %s: "
443 "socket path `%s' for program `%s' not found",
444 action, path, program_name);
445 break;
446 case EACCES:
447 e_error(svinst->event, "action %s: "
448 "failed to stat socket: %s",
449 action, eacces_error_get("stat", path));
450 *error_r = SIEVE_ERROR_NO_PERMISSION;
451 return NULL;
452 default:
453 e_error(svinst->event, "action %s: "
454 "failed to stat socket `%s': %m",
455 action, path);
456 *error_r = SIEVE_ERROR_TEMP_FAILURE;
457 return NULL;
458 }
459 path = NULL;
460 } else if ( !S_ISSOCK(st.st_mode) ) {
461 e_error(svinst->event, "action %s: "
462 "socket path `%s' for program `%s' is not a socket",
463 action, path, program_name);
464 *error_r = SIEVE_ERROR_NOT_POSSIBLE;
465 return NULL;
466 }
467 }
468
469 /* Try executable next */
470 if ( path == NULL && ext_config->bin_dir != NULL ) {
471 fork = TRUE;
472 path = t_strconcat(ext_config->bin_dir, "/", program_name, NULL);
473 if ( stat(path, &st) < 0 ) {
474 switch ( errno ) {
475 case ENOENT:
476 e_debug(svinst->event, "action %s: "
477 "executable path `%s' for program `%s' not found",
478 action, path, program_name);
479 *error_r = SIEVE_ERROR_NOT_FOUND;
480 break;
481 case EACCES:
482 e_error(svinst->event, "action %s: "
483 "failed to stat program: %s",
484 action, eacces_error_get("stat", path));
485 *error_r = SIEVE_ERROR_NO_PERMISSION;
486 break;
487 default:
488 e_error(svinst->event, "action %s: "
489 "failed to stat program `%s': %m",
490 action, path);
491 *error_r = SIEVE_ERROR_TEMP_FAILURE;
492 break;
493 }
494
495 return NULL;
496 } else if ( !S_ISREG(st.st_mode) ) {
497 e_error(svinst->event, "action %s: "
498 "executable `%s' for program `%s' is not a regular file",
499 action, path, program_name);
500 *error_r = SIEVE_ERROR_NOT_POSSIBLE;
501 return NULL;
502 } else if ( (st.st_mode & S_IWOTH) != 0 ) {
503 e_error(svinst->event, "action %s: "
504 "executable `%s' for program `%s' is world-writable",
505 action, path, program_name);
506 *error_r = SIEVE_ERROR_NO_PERMISSION;
507 return NULL;
508 }
509 }
510
511 /* None found ? */
512 if ( path == NULL ) {
513 e_error(svinst->event, "action %s: "
514 "program `%s' not found", action, program_name);
515 *error_r = SIEVE_ERROR_NOT_FOUND;
516 return NULL;
517 }
518
519 sprog = i_new(struct sieve_extprogram, 1);
520 sprog->svinst = ext->svinst;
521 sprog->ext_config = ext_config;
522 sprog->scriptenv = senv;
523
524 sprog->set.client_connect_timeout_msecs =
525 SIEVE_EXTPROGRAMS_CONNECT_TIMEOUT_MSECS;
526 sprog->set.input_idle_timeout_msecs =
527 ext_config->execute_timeout * 1000;
528 restrict_access_init(&sprog->set.restrict_set);
529 if (senv->user->uid != 0)
530 sprog->set.restrict_set.uid = senv->user->uid;
531 if (senv->user->gid != 0)
532 sprog->set.restrict_set.gid = senv->user->gid;
533 sprog->set.debug = svinst->debug;
534
535 if ( fork ) {
536 sprog->program_client =
537 program_client_local_create(path, args, &sprog->set);
538 } else {
539 sprog->program_client =
540 program_client_unix_create(path, args, &sprog->set, FALSE);
541 }
542
543 if ( svinst->username != NULL )
544 program_client_set_env(sprog->program_client, "USER", svinst->username);
545 if ( svinst->home_dir != NULL )
546 program_client_set_env(sprog->program_client, "HOME", svinst->home_dir);
547 if ( svinst->hostname != NULL )
548 program_client_set_env(sprog->program_client, "HOST", svinst->hostname);
549
550 sender = msgdata->envelope.mail_from;
551 recipient = msgdata->envelope.rcpt_to;
552 orig_recipient = NULL;
553 if ( msgdata->envelope.rcpt_params != NULL )
554 orig_recipient = msgdata->envelope.rcpt_params->orcpt.addr;
555
556 if ( !smtp_address_isnull(sender) ) {
557 program_client_set_env(sprog->program_client, "SENDER",
558 smtp_address_encode(sender));
559 }
560 if ( !smtp_address_isnull(recipient) ) {
561 program_client_set_env(sprog->program_client, "RECIPIENT",
562 smtp_address_encode(recipient));
563 }
564 if ( !smtp_address_isnull(orig_recipient) ) {
565 program_client_set_env(sprog->program_client, "ORIG_RECIPIENT",
566 smtp_address_encode(orig_recipient));
567 }
568
569 return sprog;
570 }
571
sieve_extprogram_destroy(struct sieve_extprogram ** _sprog)572 void sieve_extprogram_destroy(struct sieve_extprogram **_sprog)
573 {
574 struct sieve_extprogram *sprog = *_sprog;
575
576 program_client_destroy(&sprog->program_client);
577 i_free(sprog);
578 *_sprog = NULL;
579 }
580
581 /* I/0 */
582
sieve_extprogram_set_output(struct sieve_extprogram * sprog,struct ostream * output)583 void sieve_extprogram_set_output
584 (struct sieve_extprogram *sprog, struct ostream *output)
585 {
586 program_client_set_output(sprog->program_client, output);
587 }
588
sieve_extprogram_set_input(struct sieve_extprogram * sprog,struct istream * input)589 void sieve_extprogram_set_input
590 (struct sieve_extprogram *sprog, struct istream *input)
591 {
592 switch (sprog->ext_config->default_input_eol) {
593 case SIEVE_EXTPROGRAMS_EOL_LF:
594 input = i_stream_create_lf(input);
595 break;
596 case SIEVE_EXTPROGRAMS_EOL_CRLF:
597 input = i_stream_create_crlf(input);
598 break;
599 default:
600 i_unreached();
601 }
602
603 program_client_set_input(sprog->program_client, input);
604
605 i_stream_unref(&input);
606 }
607
sieve_extprogram_set_output_seekable(struct sieve_extprogram * sprog)608 void sieve_extprogram_set_output_seekable
609 (struct sieve_extprogram *sprog)
610 {
611 string_t *prefix;
612 prefix = t_str_new(128);
613 mail_user_set_get_temp_prefix(prefix, sprog->scriptenv->user->set);
614
615 program_client_set_output_seekable(sprog->program_client, str_c(prefix));
616 }
617
sieve_extprogram_get_output_seekable(struct sieve_extprogram * sprog)618 struct istream *sieve_extprogram_get_output_seekable
619 (struct sieve_extprogram *sprog)
620 {
621 return program_client_get_output_seekable(sprog->program_client);
622 }
623
sieve_extprogram_set_input_mail(struct sieve_extprogram * sprog,struct mail * mail)624 int sieve_extprogram_set_input_mail
625 (struct sieve_extprogram *sprog, struct mail *mail)
626 {
627 struct istream *input;
628
629 if (mail_get_stream(mail, NULL, NULL, &input) < 0)
630 return -1;
631
632 sieve_extprogram_set_input(sprog, input);
633 return 1;
634 }
635
sieve_extprogram_run(struct sieve_extprogram * sprog)636 int sieve_extprogram_run(struct sieve_extprogram *sprog)
637 {
638 switch (program_client_run(sprog->program_client)) {
639 case PROGRAM_CLIENT_EXIT_STATUS_INTERNAL_FAILURE:
640 return -1;
641 case PROGRAM_CLIENT_EXIT_STATUS_FAILURE:
642 return 0;
643 case PROGRAM_CLIENT_EXIT_STATUS_SUCCESS:
644 return 1;
645 }
646 i_unreached();
647 }
648
649