xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/maillog_client.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
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