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