xref: /netbsd-src/external/ibm-public/postfix/dist/src/postlog/postlog.c (revision c48c605c14fd8622b523d1d6a3f0c0bad133ea89)
1 /*	$NetBSD: postlog.c,v 1.5 2023/12/23 20:30:44 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	postlog 1
6 /* SUMMARY
7 /*	Postfix-compatible logging utility
8 /* SYNOPSIS
9 /* .fi
10 /* .ad
11 /*	\fBpostlog\fR [\fB-iv\fR] [\fB-c \fIconfig_dir\fR]
12 /*	[\fB-p \fIpriority\fR] [\fB-t \fItag\fR] [\fItext...\fR]
13 /* DESCRIPTION
14 /*	The \fBpostlog\fR(1) command implements a Postfix-compatible logging
15 /*	interface for use in, for example, shell scripts.
16 /*
17 /*	By default, \fBpostlog\fR(1) logs the \fItext\fR given on the command
18 /*	line as one record. If no \fItext\fR is specified on the command
19 /*	line, \fBpostlog\fR(1) reads from standard input and logs each input
20 /*	line as one record.
21 /*
22 /*	By default, logging is sent to \fBsyslogd\fR(8) or
23 /*	\fBpostlogd\fR(8); when the
24 /*	standard error stream is connected to a terminal, logging
25 /*	is sent there as well.
26 /*
27 /*	The following options are implemented:
28 /* .IP "\fB-c \fIconfig_dir\fR"
29 /*	Read the \fBmain.cf\fR configuration file in the named directory
30 /*	instead of the default configuration directory.
31 /* .IP "\fB-i\fR (obsolete)"
32 /*	Include the process ID in the logging tag. This flag is ignored as
33 /*	of Postfix 3.4, where the PID is always included.
34 /* .IP "\fB-p \fIpriority\fR (default: \fBinfo\fR)"
35 /*	Specifies the logging severity: \fBinfo\fR, \fBwarn\fR,
36 /*	\fBerror\fR, \fBfatal\fR, or \fBpanic\fR. With Postfix 3.1
37 /*	and later, the program will pause for 1 second after reporting
38 /*	a \fBfatal\fR or \fBpanic\fR condition, just like other
39 /*	Postfix programs.
40 /* .IP "\fB-t \fItag\fR"
41 /*	Specifies the logging tag, that is, the identifying name that
42 /*	appears at the beginning of each logging record. A default tag
43 /*	is used when none is specified.
44 /* .IP \fB-v\fR
45 /*	Enable verbose logging for debugging purposes. Multiple \fB-v\fR
46 /*	options make the software increasingly verbose.
47 /* SECURITY
48 /* .ad
49 /* .fi
50 /*	The \fBpostlog\fR(1) command is designed to run with
51 /*	set-groupid privileges, so that it can connect to the
52 /*	\fBpostlogd\fR(8) daemon process (Postfix 3.7 and later;
53 /*	earlier implementations of this command must not have
54 /*	set-groupid or set-userid permissions).
55 /* ENVIRONMENT
56 /* .ad
57 /* .fi
58 /* .IP MAIL_CONFIG
59 /*	Directory with the \fBmain.cf\fR file.
60 /* CONFIGURATION PARAMETERS
61 /* .ad
62 /* .fi
63 /*	The following \fBmain.cf\fR parameters are especially relevant to
64 /*	this program.
65 /*
66 /*	The text below provides only a parameter summary. See
67 /*	\fBpostconf\fR(5) for more details including examples.
68 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
69 /*	The default location of the Postfix main.cf and master.cf
70 /*	configuration files.
71 /* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
72 /*	The list of environment variables that a privileged Postfix
73 /*	process will import from a non-Postfix parent process, or name=value
74 /*	environment overrides.
75 /* .IP "\fBsyslog_facility (mail)\fR"
76 /*	The syslog facility of Postfix logging.
77 /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
78 /*	A prefix that is prepended to the process name in syslog
79 /*	records, so that, for example, "smtpd" becomes "prefix/smtpd".
80 /* .PP
81 /*	Available in Postfix 3.4 and later:
82 /* .IP "\fBmaillog_file (empty)\fR"
83 /*	The name of an optional logfile that is written by the Postfix
84 /*	\fBpostlogd\fR(8) service.
85 /* .IP "\fBpostlog_service_name (postlog)\fR"
86 /*	The name of the \fBpostlogd\fR(8) service entry in master.cf.
87 /* SEE ALSO
88 /*	postconf(5), configuration parameters
89 /*	postlogd(8), Postfix logging
90 /*	syslogd(8), system logging
91 /* LICENSE
92 /* .ad
93 /* .fi
94 /*	The Secure Mailer license must be distributed with this software.
95 /* HISTORY
96 /*	The \fBpostlog\fR(1) command was introduced with Postfix
97 /*	version 3.4.
98 /* AUTHOR(S)
99 /*	Wietse Venema
100 /*	IBM T.J. Watson Research
101 /*	P.O. Box 704
102 /*	Yorktown Heights, NY 10598, USA
103 /*
104 /*	Wietse Venema
105 /*	Google, Inc.
106 /*	111 8th Avenue
107 /*	New York, NY 10011, USA
108 /*--*/
109 
110 /* System library. */
111 
112 #include <sys_defs.h>
113 #include <sys/stat.h>
114 #include <string.h>
115 #include <fcntl.h>
116 #include <stdlib.h>
117 #include <unistd.h>
118 
119 #ifdef STRCASECMP_IN_STRINGS_H
120 #include <strings.h>
121 #endif
122 
123 /* Utility library. */
124 
125 #include <msg.h>
126 #include <vstring.h>
127 #include <vstream.h>
128 #include <vstring_vstream.h>
129 #include <msg_output.h>
130 #include <msg_vstream.h>
131 #include <warn_stat.h>
132 #include <clean_env.h>
133 #include <stringops.h>
134 
135 /* Global library. */
136 
137 #include <mail_params.h>		/* XXX right place for LOG_FACILITY? */
138 #include <mail_version.h>
139 #include <mail_conf.h>
140 #include <mail_task.h>
141 #include <mail_parm_split.h>
142 #include <maillog_client.h>
143 
144 /* Application-specific. */
145 
146  /*
147   * WARNING WARNING WARNING
148   *
149   * This software is designed to run set-gid. In order to avoid exploitation of
150   * privilege, this software should not run any external commands, nor should
151   * it take any information from the user, unless that information can be
152   * properly sanitized. To get an idea of how much information a process can
153   * inherit from a potentially hostile user, examine all the members of the
154   * process structure (typically, in /usr/include/sys/proc.h): the current
155   * directory, open files, timers, signals, environment, command line, umask,
156   * and so on.
157   */
158 
159  /*
160   * Access lists.
161   */
162 #if 0
163 char   *var_postlog_acl;
164 
165 X static const CONFIG_STR_TABLE str_table[] = {
166     X VAR_POSTLOG_ACL, DEF_POSTLOG_ACL, &var_postlog_acl, 0, 0,
167     X 0,
168 X};
169 
170 #endif
171 
172  /*
173   * Support for the severity level mapping.
174   */
175 struct level_table {
176     char   *name;
177     int     level;
178 };
179 
180 static struct level_table level_table[] = {
181     "info", MSG_INFO,
182     "warn", MSG_WARN,
183     "warning", MSG_WARN,
184     "error", MSG_ERROR,
185     "err", MSG_ERROR,
186     "fatal", MSG_FATAL,
187     "crit", MSG_FATAL,
188     "panic", MSG_PANIC,
189     0,
190 };
191 
192 #define POSTLOG_CMD	"postlog"
193 
194 /* level_map - lookup facility or severity value */
195 
level_map(char * name)196 static int level_map(char *name)
197 {
198     struct level_table *t;
199 
200     for (t = level_table; t->name; t++)
201 	if (strcasecmp(t->name, name) == 0)
202 	    return (t->level);
203     msg_fatal("bad severity: \"%s\"", name);
204 }
205 
206 /* log_argv - log the command line */
207 
log_argv(int level,char ** argv)208 static void log_argv(int level, char **argv)
209 {
210     VSTRING *buf = vstring_alloc(100);
211 
212     while (*argv) {
213 	vstring_strcat(buf, *argv++);
214 	if (*argv)
215 	    vstring_strcat(buf, " ");
216     }
217     msg_printf(level, "%s", vstring_str(buf));
218     vstring_free(buf);
219 }
220 
221 /* log_stream - log lines from a stream */
222 
log_stream(int level,VSTREAM * fp)223 static void log_stream(int level, VSTREAM *fp)
224 {
225     VSTRING *buf = vstring_alloc(100);
226 
227     while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
228 	msg_printf(level, "%s", vstring_str(buf));
229     vstring_free(buf);
230 }
231 
232 MAIL_VERSION_STAMP_DECLARE;
233 
234 /* main - logger */
235 
main(int argc,char ** argv)236 int     main(int argc, char **argv)
237 {
238     struct stat st;
239     int     fd;
240     int     ch;
241     const char *tag;
242     char   *unsanitized_tag;
243     int     level = MSG_INFO;
244     ARGV   *import_env;
245 
246     /*
247      * Fingerprint executables and core dumps.
248      */
249     MAIL_VERSION_STAMP_ALLOCATE;
250 
251     /*
252      * Be consistent with file permissions.
253      */
254     umask(022);
255 
256     /*
257      * To minimize confusion, make sure that the standard file descriptors
258      * are open before opening anything else. XXX Work around for 44BSD where
259      * fstat can return EBADF on an open file descriptor.
260      */
261     for (fd = 0; fd < 3; fd++)
262 	if (fstat(fd, &st) == -1
263 	    && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
264 	    msg_fatal("open /dev/null: %m");
265 
266     /*
267      * Set up diagnostics. Censor the process name: it is provided by the
268      * user.
269      */
270     argv[0] = POSTLOG_CMD;
271     tag = mail_task(argv[0]);
272     msg_vstream_init(tag, VSTREAM_ERR);
273     maillog_client_init(tag, MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
274 
275     /*
276      * Check the Postfix library version as soon as we enable logging.
277      */
278     MAIL_VERSION_CHECK;
279 
280     /*
281      * Parse switches. This program is set-gid and must sanitize all
282      * command-line parameters. The configuration directory argument is
283      * validated by the mail configuration read routine. Don't do complex
284      * things until we have completed initializations.
285      */
286     unsanitized_tag = 0;
287     while ((ch = GETOPT(argc, argv, "c:ip:t:v")) > 0) {
288 	switch (ch) {
289 	default:
290 	    msg_fatal("usage: %s [-c config_dir] [-i] [-p priority] [-t tag] [-v] [text]", argv[0]);
291 	    break;
292 	case 'c':
293 	    if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
294 		msg_fatal("out of memory");
295 	    break;
296 	case 'i':
297 	    break;
298 	case 'p':
299 	    level = level_map(optarg);
300 	    break;
301 	case 't':
302 	    unsanitized_tag = optarg;
303 	    break;
304 	case 'v':
305 	    msg_verbose++;
306 	    break;
307 	}
308     }
309 
310     /*
311      * Process the main.cf file. This may change the syslog_name setting and
312      * may require that mail_task() be re-evaluated.
313      */
314     mail_conf_read();
315     /* Re-evaluate mail_task() after reading main.cf. */
316     maillog_client_init(mail_task(POSTLOG_CMD), MAILLOG_CLIENT_FLAG_NONE);
317 #if 0
318     mail_dict_init();				/* proxy, sql, ldap */
319     get_mail_conf_str_table(str_table);
320 #endif
321 
322     /*
323      * This program is designed to be set-gid, which makes it a potential
324      * target for attack. Strip and optionally override the process
325      * environment so that we don't have to trust the C library.
326      */
327     import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
328     clean_env(import_env->argv);
329     argv_free(import_env);
330 
331     /*
332      * Sanitize the user-specified tag. The result depends on the value of
333      * var_smtputf8_enable, therefore this code is after the mail_conf_read()
334      * call.
335      */
336     if (unsanitized_tag != 0)
337 	tag = printable(unsanitized_tag, '?');
338 
339     /*
340      * Re-initialize the logging, this time with the tag specified in main.cf
341      * or on the command line.
342      */
343     if (isatty(STDERR_FILENO))
344 	msg_vstream_init(tag, VSTREAM_ERR);
345     maillog_client_init(tag, MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
346 
347 #if 0
348     uid_t   uid = getuid();
349 
350     if (uid != 0 && uid != var_owner_uid
351 	&& (errstr = check_user_acl_byuid(VAR_SHOWQ_ACL, var_showq_acl,
352 					  uid)) != 0)
353 	msg_fatal_status(EX_NOPERM,
354 			 "User %s(%ld) is not allowed to invoke 'postlog'",
355 			 errstr, (long) uid);
356 #endif
357 
358     /*
359      * Log the command line or log lines from standard input.
360      */
361     if (argc > optind) {
362 	log_argv(level, argv + optind);
363     } else {
364 	log_stream(level, VSTREAM_IN);
365     }
366 
367     /*
368      * Consistency with msg(3) functions.
369      */
370     if (level >= MSG_FATAL)
371 	sleep(1);
372     exit(0);
373 }
374