1 /* $NetBSD: maillog_client.c,v 1.3 2022/10/08 16:12: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
82 /*
83 * System library.
84 */
85 #include <sys_defs.h>
86 #include <stdlib.h>
87 #include <string.h>
88
89 /*
90 * Utility library.
91 */
92 #include <argv.h>
93 #include <logwriter.h>
94 #include <msg_logger.h>
95 #include <msg_syslog.h>
96 #include <safe.h>
97 #include <stringops.h>
98
99 /*
100 * Global library.
101 */
102 #include <mail_params.h>
103 #include <mail_proto.h>
104 #include <maillog_client.h>
105 #include <msg.h>
106
107 /*
108 * Using logging to debug logging is painful.
109 */
110 #define MAILLOG_CLIENT_DEBUG 0
111
112 /*
113 * Application-specific.
114 */
115 static int maillog_client_flags;
116
117 #define POSTLOG_SERVICE_ENV "POSTLOG_SERVICE"
118 #define POSTLOG_HOSTNAME_ENV "POSTLOG_HOSTNAME"
119
120 /* maillog_client_logwriter_fallback - fall back to logfile writer or bust */
121
maillog_client_logwriter_fallback(const char * text)122 static void maillog_client_logwriter_fallback(const char *text)
123 {
124 static int fallback_guard = 0;
125
126 /*
127 * Guard against recursive calls.
128 *
129 * If an error happened before the maillog_file parameter was initialized,
130 * or if maillog_file logging is disabled, then we cannot fall back to a
131 * logfile. All we can do is to hope that stderr logging will bring out
132 * the bad news.
133 */
134 if (fallback_guard == 0 && var_maillog_file && *var_maillog_file
135 && logwriter_one_shot(var_maillog_file, text, strlen(text)) < 0) {
136 fallback_guard = 1;
137 msg_fatal("logfile '%s' write error: %m", var_maillog_file);
138 }
139 }
140
141 /* maillog_client_init - set up syslog or internal log client */
142
maillog_client_init(const char * progname,int flags)143 void maillog_client_init(const char *progname, int flags)
144 {
145 char *import_service_path;
146 char *import_hostname;
147
148 /*
149 * Crucially, only one logger mode can be in effect at any time,
150 * otherwise postlogd(8) may go into a loop.
151 */
152 enum {
153 MAILLOG_CLIENT_MODE_SYSLOG, MAILLOG_CLIENT_MODE_POSTLOG,
154 } logger_mode;
155
156 /*
157 * Security: this code may run before the import_environment setting has
158 * taken effect. It has to guard against privilege escalation attacks on
159 * setgid programs, using malicious environment settings.
160 *
161 * Import the postlog service name and hostname from the environment.
162 *
163 * - These will be used and kept if the process has not yet initialized its
164 * configuration parameters.
165 *
166 * - These will be set or updated if the configuration enables postlog
167 * logging.
168 *
169 * - These will be removed if the configuration does not enable postlog
170 * logging.
171 */
172 if ((import_service_path = safe_getenv(POSTLOG_SERVICE_ENV)) != 0
173 && *import_service_path == 0)
174 import_service_path = 0;
175 if ((import_hostname = safe_getenv(POSTLOG_HOSTNAME_ENV)) != 0
176 && *import_hostname == 0)
177 import_hostname = 0;
178
179 #if MAILLOG_CLIENT_DEBUG
180 #define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
181 msg_syslog_init(progname, LOG_PID, LOG_FACILITY);
182 msg_info("import_service_path=%s", STRING_OR_NULL(import_service_path));
183 msg_info("import_hostname=%s", STRING_OR_NULL(import_hostname));
184 #endif
185
186 /*
187 * Before configuration parameters are initialized, the logging mode is
188 * controlled by the presence or absence of POSTLOG_SERVICE in the
189 * process environment. After configuration parameters are initialized,
190 * the logging mode is controlled by the "maillog_file" parameter value.
191 *
192 * The configured mode may change after a process is started. The
193 * postlogd(8) server will proxy logging to syslogd where needed.
194 */
195 if (var_maillog_file ? *var_maillog_file == 0 : import_service_path == 0) {
196 logger_mode = MAILLOG_CLIENT_MODE_SYSLOG;
197 } else {
198 /* var_maillog_file ? *var_maillog_file : import_service_path != 0 */
199 logger_mode = MAILLOG_CLIENT_MODE_POSTLOG;
200 }
201
202 /*
203 * Postlog logging is enabled. Update the 'progname' as that may have
204 * changed since an earlier call, and update the environment settings if
205 * they differ from configuration settings. This blends two code paths,
206 * one code path where configuration parameters are initialized (the
207 * preferred path), and one code path that uses imports from environment.
208 */
209 if (logger_mode == MAILLOG_CLIENT_MODE_POSTLOG) {
210 char *myhostname;
211 char *service_path;
212
213 if (var_maillog_file && *var_maillog_file) {
214 ARGV *good_prefixes = argv_split(var_maillog_file_pfxs,
215 CHARS_COMMA_SP);
216 char **cpp;
217
218 for (cpp = good_prefixes->argv; /* see below */ ; cpp++) {
219 if (*cpp == 0)
220 msg_fatal("%s value '%s' does not match any prefix in %s",
221 VAR_MAILLOG_FILE, var_maillog_file,
222 VAR_MAILLOG_FILE_PFXS);
223 if (strncmp(var_maillog_file, *cpp, strlen(*cpp)) == 0)
224 break;
225 }
226 argv_free(good_prefixes);
227 }
228 if (var_myhostname && *var_myhostname) {
229 myhostname = var_myhostname;
230 } else if ((myhostname = import_hostname) == 0) {
231 myhostname = "amnesiac";
232 }
233 #if MAILLOG_CLIENT_DEBUG
234 msg_info("myhostname=%s", STRING_OR_NULL(myhostname));
235 #endif
236 if (var_postlog_service) {
237 service_path = concatenate(var_queue_dir, "/", MAIL_CLASS_PUBLIC,
238 "/", var_postlog_service, (char *) 0);
239 } else {
240
241 /*
242 * var_postlog_service == 0, therefore var_maillog_file == 0.
243 * logger_mode == MAILLOG_CLIENT_MODE_POSTLOG && var_maillog_file ==
244 * 0, therefore import_service_path != 0.
245 */
246 service_path = import_service_path;
247 }
248 maillog_client_flags = flags;
249 msg_logger_init(progname, myhostname, service_path,
250 (flags & MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK) ?
251 maillog_client_logwriter_fallback :
252 (MSG_LOGGER_FALLBACK_FN) 0);
253
254 /*
255 * Export or update the exported postlog service pathname and the
256 * hostname, so that a child process can bootstrap postlog logging
257 * before it has processed main.cf and command-line options.
258 */
259 if (import_service_path == 0
260 || strcmp(service_path, import_service_path) != 0) {
261 #if MAILLOG_CLIENT_DEBUG
262 msg_info("export %s=%s", POSTLOG_SERVICE_ENV, service_path);
263 #endif
264 if (setenv(POSTLOG_SERVICE_ENV, service_path, 1) < 0)
265 msg_fatal("setenv: %m");
266 }
267 if (import_hostname == 0 || strcmp(myhostname, import_hostname) != 0) {
268 #if MAILLOG_CLIENT_DEBUG
269 msg_info("export %s=%s", POSTLOG_HOSTNAME_ENV, myhostname);
270 #endif
271 if (setenv(POSTLOG_HOSTNAME_ENV, myhostname, 1) < 0)
272 msg_fatal("setenv: %m");
273 }
274 if (service_path != import_service_path)
275 myfree(service_path);
276 msg_logger_control(CA_MSG_LOGGER_CTL_CONNECT_NOW,
277 CA_MSG_LOGGER_CTL_END);
278 }
279
280 /*
281 * Postlog logging is disabled. Silence the msg_logger client, and remove
282 * the environment settings that bootstrap postlog logging in a child
283 * process.
284 */
285 else {
286 msg_logger_control(CA_MSG_LOGGER_CTL_DISABLE, CA_MSG_LOGGER_CTL_END);
287 if ((import_service_path && unsetenv(POSTLOG_SERVICE_ENV))
288 || (import_hostname && unsetenv(POSTLOG_HOSTNAME_ENV)))
289 msg_fatal("unsetenv: %m");
290 }
291
292 /*
293 * Syslog logging is enabled. Update the 'progname' as that may have
294 * changed since an earlier call.
295 */
296 if (logger_mode == MAILLOG_CLIENT_MODE_SYSLOG) {
297 msg_syslog_init(progname, LOG_PID, LOG_FACILITY);
298 }
299
300 /*
301 * Syslog logging is disabled, silence the syslog client.
302 */
303 else {
304 msg_syslog_disable();
305 }
306 }
307