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