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