1 /* $NetBSD: maillog_client.c,v 1.4 2025/02/25 19:15:45 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* maillog_client 3
6 /* SUMMARY
7 /* choose between syslog client and postlog client
8 /* SYNOPSIS
9 /* #include <maillog_client.h>
10 /*
11 /* int maillog_client_init(
12 /* const char *progname,
13 /* int flags)
14 /* DESCRIPTION
15 /* maillog_client_init() chooses between logging to the syslog
16 /* service or to the internal postlog service.
17 /*
18 /* maillog_client_init() may be called before configuration
19 /* parameters are initialized. During this time, logging is
20 /* controlled by the presence or absence of POSTLOG_SERVICE
21 /* in the process environment (this is ignored if a program
22 /* runs with set-uid or set-gid permissions).
23 /*
24 /* maillog_client_init() may also be called after configuration
25 /* parameters are initialized. During this time, logging is
26 /* controlled by the "maillog_file" parameter value.
27 /*
28 /* Arguments:
29 /* .IP progname
30 /* The program name that will be prepended to logfile records.
31 /* .IP flags
32 /* Specify one of the following:
33 /* .RS
34 /* .IP MAILLOG_CLIENT_FLAG_NONE
35 /* No special processing.
36 /* .IP MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK
37 /* Try to fall back to writing the "maillog_file" directly,
38 /* if logging to the internal postlog service is enabled, but
39 /* the postlog service is unavailable. If the fallback fails,
40 /* die with a fatal error.
41 /* .RE
42 /* ENVIRONMENT
43 /* .ad
44 /* .fi
45 /* When logging to the internal postlog service is enabled,
46 /* each process exports the following information, to help
47 /* initialize the logging in a child process, before the child
48 /* has initialized its configuration parameters.
49 /* .IP POSTLOG_SERVICE
50 /* The pathname of the public postlog service endpoint, usually
51 /* "$queue_directory/public/$postlog_service_name".
52 /* .IP POSTLOG_HOSTNAME
53 /* The hostname to prepend to information that is sent to the
54 /* internal postlog logging service, usually "$myhostname".
55 /* CONFIGURATION PARAMETERS
56 /* .ad
57 /* .fi
58 /* .IP "maillog_file (empty)"
59 /* The name of an optional logfile. If the value is empty, or
60 /* unitialized and the process environment does not specify
61 /* POSTLOG_SERVICE, the program will log to the syslog service
62 /* instead.
63 /* .IP "myhostname (default: see 'postconf -d' output)"
64 /* The internet hostname of this mail system.
65 /* .IP "postlog_service_name (postlog)"
66 /* The name of the internal postlog logging service.
67 /* SEE ALSO
68 /* msg_syslog(3) syslog client
69 /* msg_logger(3) internal logger
70 /* LICENSE
71 /* .ad
72 /* .fi
73 /* The Secure Mailer license must be distributed with this
74 /* software.
75 /* AUTHOR(S)
76 /* Wietse Venema
77 /* Google, Inc.
78 /* 111 8th Avenue
79 /* New York, NY 10011, USA
80 /*
81 /* Wietse Venema
82 /* porcupine.org
83 /*--*/
84
85 /*
86 * System library.
87 */
88 #include <sys_defs.h>
89 #include <stdlib.h>
90 #include <string.h>
91
92 /*
93 * Utility library.
94 */
95 #include <argv.h>
96 #include <logwriter.h>
97 #include <msg_logger.h>
98 #include <msg_syslog.h>
99 #include <safe.h>
100 #include <stringops.h>
101
102 /*
103 * Global library.
104 */
105 #include <mail_params.h>
106 #include <mail_proto.h>
107 #include <maillog_client.h>
108 #include <msg.h>
109
110 /*
111 * Using logging to debug logging is painful.
112 */
113 #define MAILLOG_CLIENT_DEBUG 0
114
115 /*
116 * Application-specific.
117 */
118 static int maillog_client_flags;
119
120 #define POSTLOG_SERVICE_ENV "POSTLOG_SERVICE"
121 #define POSTLOG_HOSTNAME_ENV "POSTLOG_HOSTNAME"
122
123 /* maillog_client_logwriter_fallback - fall back to logfile writer or bust */
124
maillog_client_logwriter_fallback(const char * text)125 static void maillog_client_logwriter_fallback(const char *text)
126 {
127 static int fallback_guard = 0;
128 static VSTREAM *fp;
129
130 /*
131 * Guard against recursive calls.
132 *
133 * If an error happened before the maillog_file parameter was initialized,
134 * or if maillog_file logging is disabled, then we cannot fall back to a
135 * logfile. All we can do is to hope that stderr logging will bring out
136 * the bad news.
137 */
138 if (fallback_guard++ == 0 && var_maillog_file && *var_maillog_file) {
139 if (text == 0 && fp != 0) {
140 (void) vstream_fclose(fp);
141 fp = 0;
142 }
143 if (fp == 0) {
144 fp = logwriter_open_or_die(var_maillog_file);
145 close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC);
146 }
147 if (text && (logwriter_write(fp, text, strlen(text)) != 0 ||
148 vstream_fflush(fp) != 0)) {
149 msg_fatal("logfile '%s' write error: %m", var_maillog_file);
150 }
151 fallback_guard = 0;
152 }
153 }
154
155 /* maillog_client_init - set up syslog or internal log client */
156
maillog_client_init(const char * progname,int flags)157 void maillog_client_init(const char *progname, int flags)
158 {
159 char *import_service_path;
160 char *import_hostname;
161
162 /*
163 * Crucially, only one logger mode can be in effect at any time,
164 * otherwise postlogd(8) may go into a loop.
165 */
166 enum {
167 MAILLOG_CLIENT_MODE_SYSLOG, MAILLOG_CLIENT_MODE_POSTLOG,
168 } logger_mode;
169
170 /*
171 * Security: this code may run before the import_environment setting has
172 * taken effect. It has to guard against privilege escalation attacks on
173 * setgid programs, using malicious environment settings.
174 *
175 * Import the postlog service name and hostname from the environment.
176 *
177 * - These will be used and kept if the process has not yet initialized its
178 * configuration parameters.
179 *
180 * - These will be set or updated if the configuration enables postlog
181 * logging.
182 *
183 * - These will be removed if the configuration does not enable postlog
184 * logging.
185 */
186 if ((import_service_path = safe_getenv(POSTLOG_SERVICE_ENV)) != 0
187 && *import_service_path == 0)
188 import_service_path = 0;
189 if ((import_hostname = safe_getenv(POSTLOG_HOSTNAME_ENV)) != 0
190 && *import_hostname == 0)
191 import_hostname = 0;
192
193 #if MAILLOG_CLIENT_DEBUG
194 #define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
195 msg_syslog_init(progname, LOG_PID, LOG_FACILITY);
196 msg_info("import_service_path=%s", STRING_OR_NULL(import_service_path));
197 msg_info("import_hostname=%s", STRING_OR_NULL(import_hostname));
198 #endif
199
200 /*
201 * Before configuration parameters are initialized, the logging mode is
202 * controlled by the presence or absence of POSTLOG_SERVICE in the
203 * process environment. After configuration parameters are initialized,
204 * the logging mode is controlled by the "maillog_file" parameter value.
205 *
206 * The configured mode may change after a process is started. The
207 * postlogd(8) server will proxy logging to syslogd where needed.
208 */
209 if (var_maillog_file ? *var_maillog_file == 0 : import_service_path == 0) {
210 logger_mode = MAILLOG_CLIENT_MODE_SYSLOG;
211 } else {
212 /* var_maillog_file ? *var_maillog_file : import_service_path != 0 */
213 logger_mode = MAILLOG_CLIENT_MODE_POSTLOG;
214 }
215
216 /*
217 * Postlog logging is enabled. Update the 'progname' as that may have
218 * changed since an earlier call, and update the environment settings if
219 * they differ from configuration settings. This blends two code paths,
220 * one code path where configuration parameters are initialized (the
221 * preferred path), and one code path that uses imports from environment.
222 */
223 if (logger_mode == MAILLOG_CLIENT_MODE_POSTLOG) {
224 char *myhostname;
225 char *service_path;
226
227 if (var_maillog_file && *var_maillog_file) {
228 ARGV *good_prefixes = argv_split(var_maillog_file_pfxs,
229 CHARS_COMMA_SP);
230 char **cpp;
231
232 for (cpp = good_prefixes->argv; /* see below */ ; cpp++) {
233 if (*cpp == 0)
234 msg_fatal("%s value '%s' does not match any prefix in %s",
235 VAR_MAILLOG_FILE, var_maillog_file,
236 VAR_MAILLOG_FILE_PFXS);
237 if (strncmp(var_maillog_file, *cpp, strlen(*cpp)) == 0)
238 break;
239 }
240 argv_free(good_prefixes);
241 }
242 if (var_myhostname && *var_myhostname) {
243 myhostname = var_myhostname;
244 } else if ((myhostname = import_hostname) == 0) {
245 myhostname = "amnesiac";
246 }
247 #if MAILLOG_CLIENT_DEBUG
248 msg_info("myhostname=%s", STRING_OR_NULL(myhostname));
249 #endif
250 if (var_postlog_service) {
251 service_path = concatenate(var_queue_dir, "/", MAIL_CLASS_PUBLIC,
252 "/", var_postlog_service, (char *) 0);
253 } else {
254
255 /*
256 * var_postlog_service == 0, therefore var_maillog_file == 0.
257 * logger_mode == MAILLOG_CLIENT_MODE_POSTLOG && var_maillog_file
258 * == 0, therefore import_service_path != 0.
259 */
260 service_path = import_service_path;
261 }
262 maillog_client_flags = flags;
263 msg_logger_init(progname, myhostname, service_path,
264 (flags & MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK) ?
265 maillog_client_logwriter_fallback :
266 (MSG_LOGGER_FALLBACK_FN) 0);
267
268 /*
269 * Export or update the exported postlog service pathname and the
270 * hostname, so that a child process can bootstrap postlog logging
271 * before it has processed main.cf and command-line options.
272 */
273 if (import_service_path == 0
274 || strcmp(service_path, import_service_path) != 0) {
275 #if MAILLOG_CLIENT_DEBUG
276 msg_info("export %s=%s", POSTLOG_SERVICE_ENV, service_path);
277 #endif
278 if (setenv(POSTLOG_SERVICE_ENV, service_path, 1) < 0)
279 msg_fatal("setenv: %m");
280 }
281 if (import_hostname == 0 || strcmp(myhostname, import_hostname) != 0) {
282 #if MAILLOG_CLIENT_DEBUG
283 msg_info("export %s=%s", POSTLOG_HOSTNAME_ENV, myhostname);
284 #endif
285 if (setenv(POSTLOG_HOSTNAME_ENV, myhostname, 1) < 0)
286 msg_fatal("setenv: %m");
287 }
288 if (service_path != import_service_path)
289 myfree(service_path);
290 msg_logger_control(CA_MSG_LOGGER_CTL_CONNECT_NOW,
291 CA_MSG_LOGGER_CTL_END);
292 }
293
294 /*
295 * Postlog logging is disabled. Silence the msg_logger client, and remove
296 * the environment settings that bootstrap postlog logging in a child
297 * process.
298 */
299 else {
300 msg_logger_control(CA_MSG_LOGGER_CTL_DISABLE, CA_MSG_LOGGER_CTL_END);
301 if ((import_service_path && unsetenv(POSTLOG_SERVICE_ENV))
302 || (import_hostname && unsetenv(POSTLOG_HOSTNAME_ENV)))
303 msg_fatal("unsetenv: %m");
304 }
305
306 /*
307 * Syslog logging is enabled. Update the 'progname' as that may have
308 * changed since an earlier call.
309 */
310 if (logger_mode == MAILLOG_CLIENT_MODE_SYSLOG) {
311 msg_syslog_init(progname, LOG_PID, LOG_FACILITY);
312 }
313
314 /*
315 * Syslog logging is disabled, silence the syslog client.
316 */
317 else {
318 msg_syslog_disable();
319 }
320 }
321