/src/dovecot/src/lib-master/master-service-settings.c
Line | Count | Source |
1 | | /* Copyright (c) 2005-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "array.h" |
5 | | #include "event-filter.h" |
6 | | #include "path-util.h" |
7 | | #include "hostpid.h" |
8 | | #include "fdpass.h" |
9 | | #include "write-full.h" |
10 | | #include "str.h" |
11 | | #include "sha2.h" |
12 | | #include "hex-binary.h" |
13 | | #include "restrict-access.h" |
14 | | #include "syslog-util.h" |
15 | | #include "eacces-error.h" |
16 | | #include "env-util.h" |
17 | | #include "execv-const.h" |
18 | | #include "version.h" |
19 | | #include "settings.h" |
20 | | #include "stats-client.h" |
21 | | #include "master-service-private.h" |
22 | | #include "master-service-settings.h" |
23 | | #include "strescape.h" |
24 | | |
25 | | #include <unistd.h> |
26 | | #include <fcntl.h> |
27 | | #include <time.h> |
28 | | #include <sys/stat.h> |
29 | | |
30 | 0 | #define DOVECOT_CONFIG_BIN_PATH BINDIR"/doveconf" |
31 | 0 | #define DOVECOT_CONFIG_BIN_PATH_ENV "DOVECONF_PATH" |
32 | 0 | #define DOVECOT_CONFIG_SOCKET_PATH PKG_RUNDIR"/config" |
33 | | |
34 | 0 | #define CONFIG_READ_TIMEOUT_SECS 10 |
35 | 0 | #define CONFIG_HANDSHAKE "VERSION\tconfig\t3\t0\n" |
36 | | |
37 | | #undef DEF |
38 | | #define DEF(type, name) \ |
39 | | SETTING_DEFINE_STRUCT_##type(#name, name, struct master_service_settings) |
40 | | |
41 | | static bool |
42 | | master_service_settings_check(void *_set, pool_t pool, const char **error_r); |
43 | | |
44 | | static const struct setting_define master_service_setting_defines[] = { |
45 | | DEF(STR_HIDDEN, base_dir), |
46 | | DEF(STR_HIDDEN, state_dir), |
47 | | DEF(STR, instance_name), |
48 | | DEF(STR, log_path), |
49 | | DEF(STR, info_log_path), |
50 | | DEF(STR, debug_log_path), |
51 | | DEF(STR_NOVARS, log_timestamp), |
52 | | DEF(STR, log_debug), |
53 | | DEF(STR, log_core_filter), |
54 | | DEF(STR, process_shutdown_filter), |
55 | | DEF(STR, syslog_facility), |
56 | | DEF(STR, stats_writer_socket_path), |
57 | | DEF(STR, auth_master_socket_path), |
58 | | DEF(STR, dovecot_storage_version), |
59 | | DEF(BOOL, version_ignore), |
60 | | DEF(BOOL, shutdown_clients), |
61 | | DEF(BOOL, verbose_proctitle), |
62 | | |
63 | | DEF(STR, haproxy_trusted_networks), |
64 | | DEF(TIME, haproxy_timeout), |
65 | | |
66 | | { .type = SET_STRLIST, .key = "import_environment", |
67 | | .offset = offsetof(struct master_service_settings, import_environment) }, |
68 | | |
69 | | SETTING_DEFINE_LIST_END |
70 | | }; |
71 | | |
72 | | static const struct master_service_settings master_service_default_settings = { |
73 | | .base_dir = PKG_RUNDIR, |
74 | | .state_dir = PKG_STATEDIR, |
75 | | .instance_name = PACKAGE, |
76 | | .log_path = "syslog", |
77 | | .info_log_path = "", |
78 | | .debug_log_path = "", |
79 | | .log_timestamp = DEFAULT_FAILURE_STAMP_FORMAT, |
80 | | .log_debug = "", |
81 | | .log_core_filter = "", |
82 | | .process_shutdown_filter = "", |
83 | | .syslog_facility = "mail", |
84 | | .import_environment = ARRAY_INIT, |
85 | | .stats_writer_socket_path = "stats-writer", |
86 | | .auth_master_socket_path = "auth-master", |
87 | | .dovecot_storage_version = "", |
88 | | .version_ignore = FALSE, |
89 | | .shutdown_clients = TRUE, |
90 | | .verbose_proctitle = VERBOSE_PROCTITLE_DEFAULT, |
91 | | |
92 | | .haproxy_trusted_networks = "", |
93 | | .haproxy_timeout = 3 |
94 | | }; |
95 | | |
96 | | static const struct setting_keyvalue master_service_default_settings_keyvalue[] = { |
97 | | { "import_environment/TZ", "%{env:TZ}" }, |
98 | | { "import_environment/CORE_OUTOFMEM", "%{env:CORE_OUTOFMEM}" }, |
99 | | { "import_environment/CORE_ERROR", "%{env:CORE_ERROR}" }, |
100 | | { "import_environment/PATH", "%{env:PATH}" }, |
101 | | #ifdef HAVE_LIBSYSTEMD |
102 | | { "import_environment/LISTEN_PID", "%{env:LISTEN_PID}" }, |
103 | | { "import_environment/LISTEN_FDS", "%{env:LISTEN_FDS}" }, |
104 | | { "import_environment/NOTIFY_SOCKET", "%{env:NOTIFY_SOCKET}" }, |
105 | | #endif |
106 | | #ifdef __GLIBC__ |
107 | | { "import_environment/MALLOC_MMAP_THRESHOLD_", "131072" }, |
108 | | #endif |
109 | | #ifdef DEBUG |
110 | | { "import_environment/GDB", "%{env:GDB}" }, |
111 | | { "import_environment/DEBUG_SILENT", "%{env:DEBUG_SILENT}" }, |
112 | | #endif |
113 | | { NULL, NULL }, |
114 | | }; |
115 | | |
116 | | const struct setting_parser_info master_service_setting_parser_info = { |
117 | | .name = "master_service", |
118 | | |
119 | | .defines = master_service_setting_defines, |
120 | | .defaults = &master_service_default_settings, |
121 | | .default_settings = master_service_default_settings_keyvalue, |
122 | | |
123 | | .pool_offset1 = 1 + offsetof(struct master_service_settings, pool), |
124 | | .struct_size = sizeof(struct master_service_settings), |
125 | | .check_func = master_service_settings_check |
126 | | }; |
127 | | |
128 | | /* <settings checks> */ |
129 | | static bool |
130 | | setting_filter_parse(const char *set_name, const char *set_value, |
131 | | void (*handle_filter)(struct event_filter *) ATTR_UNUSED, |
132 | | const char **error_r) |
133 | 0 | { |
134 | 0 | struct event_filter *filter; |
135 | 0 | const char *error; |
136 | |
|
137 | 0 | if (set_value[0] == '\0') |
138 | 0 | return TRUE; |
139 | | |
140 | 0 | filter = event_filter_create(); |
141 | 0 | if (event_filter_parse(set_value, filter, &error) < 0) { |
142 | 0 | *error_r = t_strdup_printf("Invalid %s: %s", set_name, error); |
143 | 0 | event_filter_unref(&filter); |
144 | 0 | return FALSE; |
145 | 0 | } |
146 | 0 | #ifndef CONFIG_BINARY |
147 | 0 | handle_filter(filter); |
148 | 0 | #endif |
149 | 0 | event_filter_unref(&filter); |
150 | 0 | return TRUE; |
151 | 0 | } |
152 | | |
153 | | static void |
154 | | master_service_set_process_shutdown_filter_wrapper(struct event_filter *filter) |
155 | 0 | { |
156 | 0 | master_service_set_process_shutdown_filter(master_service, filter); |
157 | 0 | } |
158 | | |
159 | | static bool storage_version_check(const char *version, const char **error_r) |
160 | 0 | { |
161 | 0 | #define STORAGE_MIN_VERSION "2.3.0" |
162 | |
|
163 | 0 | if (version[0] == '\0') { |
164 | 0 | *error_r = "dovecot_storage_version is empty"; |
165 | 0 | return FALSE; |
166 | 0 | } |
167 | 0 | if (!version_is_valid(version)) { |
168 | 0 | *error_r = "Invalid dovecot_storage_version value (must be in x.y.z format)"; |
169 | 0 | return FALSE; |
170 | 0 | } |
171 | 0 | if (version_cmp(version, STORAGE_MIN_VERSION) < 0) { |
172 | 0 | *error_r = t_strdup_printf( |
173 | 0 | "dovecot_storage_version is too old - " |
174 | 0 | "minimum supported version is %s", |
175 | 0 | STORAGE_MIN_VERSION); |
176 | 0 | return FALSE; |
177 | 0 | } |
178 | 0 | if (version_is_valid(DOVECOT_VERSION) && |
179 | 0 | version_cmp(version, DOVECOT_VERSION) > 0) { |
180 | 0 | *error_r = t_strdup_printf( |
181 | 0 | "dovecot_storage_version is too new - " |
182 | 0 | "current version is %s", DOVECOT_VERSION); |
183 | 0 | return FALSE; |
184 | 0 | } |
185 | 0 | return TRUE; |
186 | 0 | } |
187 | | |
188 | | static bool |
189 | | master_service_settings_check(void *_set, pool_t pool ATTR_UNUSED, |
190 | | const char **error_r) |
191 | 0 | { |
192 | 0 | struct master_service_settings *set = _set; |
193 | 0 | int facility; |
194 | |
|
195 | 0 | if (*set->log_path == '\0') { |
196 | | /* default to syslog logging */ |
197 | 0 | set->log_path = "syslog"; |
198 | 0 | } |
199 | 0 | if (!syslog_facility_find(set->syslog_facility, &facility)) { |
200 | 0 | *error_r = t_strdup_printf("Unknown syslog_facility: %s", |
201 | 0 | set->syslog_facility); |
202 | 0 | return FALSE; |
203 | 0 | } |
204 | | |
205 | 0 | if (!setting_filter_parse("log_debug", set->log_debug, |
206 | 0 | event_set_global_debug_log_filter, error_r)) |
207 | 0 | return FALSE; |
208 | 0 | if (!setting_filter_parse("log_core_filter", set->log_core_filter, |
209 | 0 | event_set_global_core_log_filter, error_r)) |
210 | 0 | return FALSE; |
211 | 0 | if (!setting_filter_parse("process_shutdown_filter", |
212 | 0 | set->process_shutdown_filter, |
213 | 0 | master_service_set_process_shutdown_filter_wrapper, |
214 | 0 | error_r)) |
215 | 0 | return FALSE; |
216 | | /* doveconf / config checks dovecot_storage_version separately. |
217 | | This check shouldn't fail e.g. "doveconf -d" command. */ |
218 | 0 | if (settings_get_config_binary() == SETTINGS_BINARY_OTHER && |
219 | 0 | !storage_version_check(set->dovecot_storage_version, error_r)) |
220 | 0 | return FALSE; |
221 | 0 | return TRUE; |
222 | 0 | } |
223 | | /* </settings checks> */ |
224 | | |
225 | | static void strarr_push(ARRAY_TYPE(const_string) *argv, const char *str) |
226 | 0 | { |
227 | 0 | array_push_back(argv, &str); |
228 | 0 | } |
229 | | |
230 | | static void ATTR_NORETURN |
231 | | master_service_exec_config(struct master_service *service, |
232 | | const struct master_service_settings_input *input) |
233 | 0 | { |
234 | 0 | ARRAY_TYPE(const_string) conf_argv; |
235 | 0 | const char *binary_path = service->argv[0]; |
236 | 0 | const char *error = NULL; |
237 | |
|
238 | 0 | if (!t_binary_abspath(&binary_path, &error)) { |
239 | 0 | i_fatal("t_binary_abspath(%s) failed: %s", binary_path, error); |
240 | 0 | } |
241 | | |
242 | 0 | if (!service->keep_environment && !input->preserve_environment) { |
243 | 0 | if (input->preserve_home) |
244 | 0 | master_service_import_environment("HOME"); |
245 | 0 | if (input->preserve_user) |
246 | 0 | master_service_import_environment("USER"); |
247 | 0 | if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) |
248 | 0 | master_service_import_environment(DOVECOT_LOG_STDERR_TIMESTAMP_ENV); |
249 | | |
250 | | /* doveconf empties the environment before exec()ing us back |
251 | | if DOVECOT_PRESERVE_ENVS is set, so make sure it is. */ |
252 | 0 | if (getenv(DOVECOT_PRESERVE_ENVS_ENV) == NULL) |
253 | 0 | env_put(DOVECOT_PRESERVE_ENVS_ENV, ""); |
254 | 0 | } else { |
255 | | /* make sure doveconf doesn't remove any environment */ |
256 | 0 | env_remove(DOVECOT_PRESERVE_ENVS_ENV); |
257 | 0 | } |
258 | 0 | if (input->use_sysexits) |
259 | 0 | env_put("USE_SYSEXITS", "1"); |
260 | |
|
261 | 0 | if (input->protocol != NULL) |
262 | 0 | env_put("DOVECONF_PROTOCOL", input->protocol); |
263 | |
|
264 | 0 | t_array_init(&conf_argv, 11 + (service->argc + 1) + 1); |
265 | 0 | const char *config_bin_path = getenv(DOVECOT_CONFIG_BIN_PATH_ENV); |
266 | 0 | if (config_bin_path == NULL) |
267 | 0 | config_bin_path = DOVECOT_CONFIG_BIN_PATH; |
268 | 0 | strarr_push(&conf_argv, config_bin_path); |
269 | 0 | if ((service->flags & MASTER_SERVICE_FLAG_CONFIG_DEFAULTS) != 0) |
270 | 0 | strarr_push(&conf_argv, "-d"); |
271 | 0 | else { |
272 | 0 | strarr_push(&conf_argv, "-c"); |
273 | 0 | strarr_push(&conf_argv, service->config_path); |
274 | 0 | } |
275 | |
|
276 | 0 | if (input->check_full_config) |
277 | 0 | strarr_push(&conf_argv, "-C"); |
278 | 0 | if (input->hide_obsolete_warnings) |
279 | 0 | strarr_push(&conf_argv, "-w"); |
280 | 0 | strarr_push(&conf_argv, "-F"); |
281 | 0 | strarr_push(&conf_argv, binary_path); |
282 | 0 | array_append(&conf_argv, (const char *const *)service->argv + 1, |
283 | 0 | service->argc); |
284 | 0 | array_append_zero(&conf_argv); |
285 | |
|
286 | 0 | const char *const *argv = array_front(&conf_argv); |
287 | 0 | execv_const(argv[0], argv); |
288 | 0 | } |
289 | | |
290 | | static void |
291 | | config_error_update_path_source(struct master_service *service, |
292 | | const struct master_service_settings_input *input, |
293 | | const char **error) |
294 | 0 | { |
295 | 0 | if (input->config_path == NULL && service->config_path_from_master) { |
296 | 0 | *error = t_strdup_printf("%s (path is from %s environment)", |
297 | 0 | *error, MASTER_CONFIG_FILE_ENV); |
298 | 0 | } |
299 | 0 | } |
300 | | |
301 | | static void |
302 | | config_exec_fallback(struct master_service *service, |
303 | | const struct master_service_settings_input *input, |
304 | | const char **error) |
305 | 0 | { |
306 | 0 | const char *path, *stat_error; |
307 | 0 | struct stat st; |
308 | 0 | int saved_errno = errno; |
309 | |
|
310 | 0 | if (input->never_exec) { |
311 | 0 | *error = t_strdup_printf( |
312 | 0 | "%s - doveconf execution fallback is disabled", *error); |
313 | 0 | return; |
314 | 0 | } |
315 | | |
316 | 0 | path = input->config_path != NULL ? input->config_path : |
317 | 0 | master_service_get_config_path(service); |
318 | 0 | if (stat(path, &st) < 0) |
319 | 0 | stat_error = t_strdup_printf("stat(%s) failed: %m", path); |
320 | 0 | else if (S_ISSOCK(st.st_mode)) |
321 | 0 | stat_error = t_strdup_printf("%s is a UNIX socket", path); |
322 | 0 | else if (S_ISFIFO(st.st_mode)) |
323 | 0 | stat_error = t_strdup_printf("%s is a FIFO", path); |
324 | 0 | else { |
325 | | /* it's a file, not a socket/pipe */ |
326 | 0 | master_service_exec_config(service, input); |
327 | 0 | } |
328 | 0 | *error = t_strdup_printf( |
329 | 0 | "%s - Also failed to read config by executing doveconf: %s", |
330 | 0 | *error, stat_error); |
331 | 0 | config_error_update_path_source(service, input, error); |
332 | 0 | errno = saved_errno; |
333 | 0 | } |
334 | | |
335 | | static int master_service_binary_config_cache_get(const char *cache_dir, |
336 | | const char *input_path) |
337 | 0 | { |
338 | 0 | if (cache_dir == NULL) |
339 | 0 | return -1; |
340 | | |
341 | 0 | const char *cache_path = |
342 | 0 | master_service_get_binary_config_cache_path(cache_dir, input_path); |
343 | 0 | int fd = open(cache_path, O_RDONLY); |
344 | 0 | if (fd == -1 && errno != ENOENT) |
345 | 0 | i_error("Binary config cache: open(%s) failed: %m", cache_path); |
346 | 0 | else if (fd != -1) |
347 | 0 | fd_close_on_exec(fd, TRUE); |
348 | 0 | return fd; |
349 | 0 | } |
350 | | |
351 | | static int |
352 | | master_service_open_config(struct master_service *service, |
353 | | const struct master_service_settings_input *input, |
354 | | const char *cache_dir, |
355 | | const char **path_r, bool *cached_config_r, |
356 | | const char **error_r) |
357 | 0 | { |
358 | 0 | struct stat st; |
359 | 0 | const char *path; |
360 | 0 | int fd = -1; |
361 | |
|
362 | 0 | if ((service->flags & MASTER_SERVICE_FLAG_CONFIG_DEFAULTS) != 0) |
363 | 0 | path = MASTER_SERVICE_BINARY_CONFIG_DEFAULTS; |
364 | 0 | else if (input->config_path != NULL) |
365 | 0 | path = input->config_path; |
366 | 0 | else |
367 | 0 | path = master_service_get_config_path(service); |
368 | 0 | *path_r = path; |
369 | 0 | *cached_config_r = FALSE; |
370 | |
|
371 | 0 | if ((fd = master_service_binary_config_cache_get(cache_dir, path)) != -1) { |
372 | 0 | *cached_config_r = TRUE; |
373 | 0 | return fd; |
374 | 0 | } |
375 | | |
376 | 0 | if ((service->flags & MASTER_SERVICE_FLAG_CONFIG_DEFAULTS) != 0) |
377 | 0 | master_service_exec_config(service, input); |
378 | | |
379 | 0 | if (!service->config_path_from_master && |
380 | 0 | !service->config_path_changed_with_param && |
381 | 0 | !input->always_exec && |
382 | 0 | input->config_path == NULL) { |
383 | | /* first try to connect to the default config socket. |
384 | | configuration may contain secrets, so in default config |
385 | | this fails because the socket is 0600. it's useful for |
386 | | developers though. :) */ |
387 | 0 | fd = net_connect_unix(DOVECOT_CONFIG_SOCKET_PATH); |
388 | 0 | if (fd >= 0) |
389 | 0 | *path_r = DOVECOT_CONFIG_SOCKET_PATH; |
390 | 0 | else { |
391 | | /* fallback to executing doveconf */ |
392 | 0 | } |
393 | 0 | } |
394 | |
|
395 | 0 | if (fd == -1) { |
396 | 0 | if (stat(path, &st) < 0) { |
397 | 0 | *error_r = errno == EACCES ? |
398 | 0 | eacces_error_get("stat", path) : |
399 | 0 | t_strdup_printf("stat(%s) failed: %m", path); |
400 | 0 | config_error_update_path_source(service, input, error_r); |
401 | 0 | return -1; |
402 | 0 | } |
403 | | |
404 | 0 | if (!S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode)) { |
405 | | /* it's not an UNIX socket, don't even try to connect */ |
406 | 0 | fd = -1; |
407 | 0 | errno = ENOTSOCK; |
408 | 0 | } else { |
409 | 0 | fd = net_connect_unix_with_retries(path, 1000); |
410 | 0 | } |
411 | 0 | if (fd < 0) { |
412 | 0 | *error_r = t_strdup_printf( |
413 | 0 | "net_connect_unix(%s) failed: %m", path); |
414 | 0 | config_exec_fallback(service, input, error_r); |
415 | 0 | return -1; |
416 | 0 | } |
417 | 0 | } |
418 | 0 | net_set_nonblock(fd, FALSE); |
419 | 0 | string_t *str = t_str_new(128); |
420 | 0 | str_append(str, CONFIG_HANDSHAKE"REQ"); |
421 | 0 | if (input->reload_config) |
422 | 0 | str_append(str, "\treload"); |
423 | 0 | str_append_c(str, '\n'); |
424 | 0 | alarm(CONFIG_READ_TIMEOUT_SECS); |
425 | 0 | int ret = write_full(fd, str_data(str), str_len(str)); |
426 | 0 | if (ret < 0) |
427 | 0 | *error_r = t_strdup_printf("write_full(%s) failed: %m", path); |
428 | 0 | else |
429 | 0 | *error_r = NULL; |
430 | |
|
431 | 0 | int config_fd = -1; |
432 | 0 | if (ret == 0) { |
433 | | /* read the config fd as reply */ |
434 | 0 | char buf[1024]; |
435 | 0 | ret = fd_read(fd, buf, sizeof(buf)-1, &config_fd); |
436 | 0 | if (ret < 0) |
437 | 0 | *error_r = t_strdup_printf("fd_read() failed: %m"); |
438 | 0 | else if (ret > 0 && buf[0] == '+' && buf[1] == '\n') { |
439 | | /* success, if fd was received */ |
440 | 0 | if (config_fd == -1) |
441 | 0 | *error_r = "Failed to read config: FD not received"; |
442 | 0 | } else if (ret > 0 && buf[0] == '-') { |
443 | 0 | buf[ret] = '\0'; |
444 | 0 | *error_r = t_strdup_printf("Failed to read config: %s", |
445 | 0 | t_strcut(buf+1, '\n')); |
446 | 0 | i_close_fd(&config_fd); |
447 | 0 | } else { |
448 | 0 | buf[ret] = '\0'; |
449 | 0 | *error_r = t_strdup_printf( |
450 | 0 | "Failed to read config: Unexpected reply '%s'", |
451 | 0 | t_strcut(buf, '\n')); |
452 | 0 | i_close_fd(&config_fd); |
453 | 0 | } |
454 | 0 | } |
455 | 0 | alarm(0); |
456 | 0 | i_close_fd(&fd); |
457 | |
|
458 | 0 | if (config_fd == -1) { |
459 | 0 | i_assert(*error_r != NULL); |
460 | 0 | config_exec_fallback(service, input, error_r); |
461 | 0 | return -1; |
462 | 0 | } |
463 | 0 | return config_fd; |
464 | 0 | } |
465 | | |
466 | | static void |
467 | | master_service_append_config_overrides(struct master_service *service) |
468 | 0 | { |
469 | 0 | const char *const *cli_overrides; |
470 | 0 | unsigned int i, count; |
471 | |
|
472 | 0 | if (!array_is_created(&service->config_overrides)) |
473 | 0 | return; |
474 | | |
475 | 0 | cli_overrides = array_get(&service->config_overrides, &count); |
476 | 0 | for (i = 0; i < count; i++) { |
477 | 0 | const char *key, *value; |
478 | 0 | t_split_key_value_eq(cli_overrides[i], &key, &value); |
479 | |
|
480 | 0 | settings_root_override(service->settings_root, key, value, |
481 | 0 | SETTINGS_OVERRIDE_TYPE_CLI_PARAM); |
482 | 0 | } |
483 | 0 | } |
484 | | |
485 | | static int |
486 | | master_service_settings_read_int(struct master_service *service, |
487 | | const struct master_service_settings_input *input, |
488 | | const char *cache_dir, |
489 | | struct master_service_settings_output *output_r, |
490 | | const char **error_r) |
491 | 0 | { |
492 | 0 | const char *path = NULL, *value, *error; |
493 | 0 | bool cached_config = FALSE; |
494 | 0 | bool import_environment_missing = FALSE; |
495 | 0 | int ret, fd = -1; |
496 | |
|
497 | 0 | i_zero(output_r); |
498 | 0 | output_r->config_fd = -1; |
499 | |
|
500 | 0 | if (input->config_fd > 0) { |
501 | | /* unit test */ |
502 | 0 | fd = input->config_fd; |
503 | 0 | path = t_strdup_printf("<input fd %d>", fd); |
504 | 0 | } else if (settings_has_mmap(service->settings_root) && |
505 | 0 | !input->reload_config) { |
506 | | /* config was already read once */ |
507 | 0 | } else if ((value = getenv(DOVECOT_CONFIG_FD_ENV)) != NULL) { |
508 | | /* doveconf -F parameter already executed us back. |
509 | | The configuration is in DOVECOT_CONFIG_FD. */ |
510 | 0 | if (str_to_int(value, &fd) < 0 || fd < 0) |
511 | 0 | i_fatal("Invalid "DOVECOT_CONFIG_FD_ENV": %s", value); |
512 | 0 | path = t_strdup_printf("<"DOVECOT_CONFIG_FD_ENV" %d>", fd); |
513 | 0 | } else if ((service->flags & MASTER_SERVICE_FLAG_CONFIG_BUILTIN) == 0) { |
514 | | /* Open config via socket if possible. If it doesn't work, |
515 | | execute doveconf -F. */ |
516 | 0 | T_BEGIN { |
517 | 0 | fd = master_service_open_config(service, input, |
518 | 0 | cache_dir, &path, |
519 | 0 | &cached_config, &error); |
520 | 0 | } T_END_PASS_STR_IF(fd == -1, &error); |
521 | 0 | if (fd == -1) { |
522 | 0 | if (errno == EACCES) |
523 | 0 | output_r->permission_denied = TRUE; |
524 | 0 | *error_r = t_strdup_printf( |
525 | 0 | "Failed to read configuration: %s", error); |
526 | 0 | return -1; |
527 | 0 | } |
528 | 0 | if (getenv(MASTER_IS_PARENT_ENV) == NULL) { |
529 | | /* Standalone program read the config via socket or |
530 | | config cache (not via executing doveconf). |
531 | | import_environment setting still needs to be |
532 | | processed. */ |
533 | 0 | import_environment_missing = TRUE; |
534 | 0 | } |
535 | 0 | } else if (!settings_has_mmap(service->settings_root)) { |
536 | | /* Use default settings. Set dovecot_storage_version to the |
537 | | latest version, so it won't cause a failure. Use userdb |
538 | | type, so it can still be overridden with -o parameter. |
539 | | |
540 | | When building from git we don't know the latest version, so |
541 | | just use 9999. The version validity checks are disabled for |
542 | | git builds, so this should work. */ |
543 | 0 | const char *version = version_is_valid(DOVECOT_VERSION) ? |
544 | 0 | DOVECOT_VERSION : "9999"; |
545 | 0 | settings_root_override(service->settings_root, |
546 | 0 | "dovecot_storage_version", version, |
547 | 0 | SETTINGS_OVERRIDE_TYPE_USERDB); |
548 | 0 | } |
549 | 0 | if (!settings_has_mmap(service->settings_root)) { |
550 | | /* first time reading settings */ |
551 | 0 | master_service_append_config_overrides(service); |
552 | 0 | } |
553 | 0 | if (fd != -1) { |
554 | 0 | const char *service_name = input->no_service_filter ? |
555 | 0 | NULL : service->name; |
556 | 0 | const char *protocol_name = input->protocol != NULL ? |
557 | 0 | input->protocol : service->name; |
558 | 0 | enum settings_read_flags read_flags = |
559 | 0 | !input->no_protocol_filter ? 0 : |
560 | 0 | SETTINGS_READ_NO_PROTOCOL_FILTER; |
561 | 0 | if (cached_config) |
562 | 0 | read_flags |= SETTINGS_READ_CHECK_CACHE_TIMESTAMPS; |
563 | 0 | ret = settings_read(service->settings_root, fd, path, |
564 | 0 | service_name, protocol_name, read_flags, |
565 | 0 | &output_r->specific_protocols, |
566 | 0 | &error); |
567 | 0 | if (ret == 0 && cached_config) { |
568 | | /* out-of-date binary config cache */ |
569 | 0 | i_close_fd(&fd); |
570 | 0 | return master_service_settings_read_int(service, input, |
571 | 0 | NULL, output_r, error_r); |
572 | 0 | } |
573 | 0 | if (input->return_config_fd) |
574 | 0 | output_r->config_fd = fd; |
575 | 0 | else |
576 | 0 | i_close_fd(&fd); |
577 | 0 | if (ret < 0) { |
578 | 0 | if (getenv(DOVECOT_CONFIG_FD_ENV) != NULL) { |
579 | 0 | i_fatal("Failed to parse config from fd %d: %s", |
580 | 0 | fd, error); |
581 | 0 | } |
582 | 0 | *error_r = t_strdup_printf( |
583 | 0 | "Failed to parse configuration: %s", error); |
584 | 0 | return -1; |
585 | 0 | } |
586 | 0 | env_remove(DOVECOT_CONFIG_FD_ENV); |
587 | 0 | } |
588 | | |
589 | | /* Create event for matching config filters */ |
590 | 0 | struct event *event = event_create(NULL); |
591 | 0 | event_add_str(event, "protocol", input->protocol != NULL ? |
592 | 0 | input->protocol : service->name); |
593 | |
|
594 | 0 | settings_free(service->set); |
595 | 0 | ret = settings_get(event, &master_service_setting_parser_info, |
596 | 0 | !input->no_key_validation ? 0 : |
597 | 0 | SETTINGS_GET_NO_KEY_VALIDATION, |
598 | 0 | &service->set, error_r); |
599 | 0 | event_unref(&event); |
600 | 0 | if (ret < 0) |
601 | 0 | return -1; |
602 | | |
603 | 0 | if (service->set->version_ignore && |
604 | 0 | (service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) { |
605 | | /* running standalone. we want to ignore plugin versions. */ |
606 | 0 | service->version_string = NULL; |
607 | 0 | } |
608 | 0 | if ((service->flags & MASTER_SERVICE_FLAG_DONT_SEND_STATS) == 0 && |
609 | 0 | (service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) { |
610 | | /* When running standalone (e.g. doveadm) try to connect to the |
611 | | stats socket, but don't log an error if it's not running. |
612 | | It may be intentional. Non-standalone stats-client |
613 | | initialization was already done earlier. */ |
614 | 0 | master_service_init_stats_client(service, TRUE); |
615 | 0 | } |
616 | |
|
617 | 0 | if (service->set->shutdown_clients) |
618 | 0 | master_service_set_die_with_master(master_service, TRUE); |
619 | |
|
620 | 0 | if (import_environment_missing) { |
621 | 0 | const char *import_environment = |
622 | 0 | master_service_get_import_environment_keyvals(service); |
623 | 0 | master_service_import_environment(import_environment); |
624 | | /* DOVECOT_HOST* environments may have changed */ |
625 | 0 | hostpid_init(); |
626 | 0 | } |
627 | 0 | return 0; |
628 | 0 | } |
629 | | |
630 | | int master_service_settings_read(struct master_service *service, |
631 | | const struct master_service_settings_input *input, |
632 | | struct master_service_settings_output *output_r, |
633 | | const char **error_r) |
634 | 0 | { |
635 | 0 | return master_service_settings_read_int(service, input, |
636 | 0 | getenv("DOVECOT_CONFIG_CACHE"), |
637 | 0 | output_r, error_r); |
638 | 0 | } |
639 | | |
640 | | int master_service_settings_read_simple(struct master_service *service, |
641 | | const char **error_r) |
642 | 0 | { |
643 | 0 | struct master_service_settings_input input; |
644 | 0 | struct master_service_settings_output output; |
645 | |
|
646 | 0 | i_zero(&input); |
647 | 0 | return master_service_settings_read(service, &input, &output, error_r); |
648 | 0 | } |
649 | | |
650 | | const struct master_service_settings * |
651 | | master_service_get_service_settings(struct master_service *service) |
652 | 0 | { |
653 | 0 | return service->set; |
654 | 0 | } |
655 | | |
656 | | const char * |
657 | | master_service_get_import_environment_keyvals(struct master_service *service) |
658 | 0 | { |
659 | 0 | ARRAY_TYPE(const_string) arr = service->set->import_environment; |
660 | 0 | unsigned int len = array_count(&arr); |
661 | 0 | string_t *keyvals = t_str_new(64); |
662 | 0 | for (unsigned int i = 0; i < len; i += 2) { |
663 | 0 | const char *const *key = array_idx(&arr, i); |
664 | 0 | const char *const *val = array_idx(&arr, i + 1); |
665 | 0 | str_append_tabescaped(keyvals, *key); |
666 | 0 | str_append_c(keyvals, '='); |
667 | 0 | str_append_tabescaped(keyvals, *val); |
668 | |
|
669 | 0 | if (i + 2 < len) |
670 | 0 | str_append_c(keyvals, '\t'); |
671 | 0 | } |
672 | 0 | return str_c(keyvals); |
673 | 0 | } |
674 | | |
675 | | const char * |
676 | | master_service_get_binary_config_cache_path(const char *cache_dir, |
677 | | const char *main_path) |
678 | 0 | { |
679 | 0 | struct sha256_ctx hash; |
680 | 0 | sha256_init(&hash); |
681 | 0 | sha256_loop(&hash, main_path, strlen(main_path) + 1); |
682 | 0 | uid_t euid = geteuid(); |
683 | 0 | sha256_loop(&hash, &euid, sizeof(euid)); |
684 | 0 | gid_t egid = getegid(); |
685 | 0 | sha256_loop(&hash, &egid, sizeof(egid)); |
686 | 0 | unsigned int groups_count; |
687 | 0 | const gid_t *groups = restrict_get_groups_list(&groups_count); |
688 | 0 | sha256_loop(&hash, groups, sizeof(*groups) * groups_count); |
689 | 0 | unsigned char digest[SHA256_RESULTLEN]; |
690 | 0 | sha256_result(&hash, digest); |
691 | |
|
692 | 0 | string_t *cache_path = t_str_new(128); |
693 | 0 | str_append(cache_path, cache_dir); |
694 | 0 | str_append(cache_path, "/binary.conf."); |
695 | 0 | binary_to_hex_append(cache_path, digest, sizeof(digest)); |
696 | 0 | return str_c(cache_path); |
697 | 0 | } |