summaryrefslogtreecommitdiffstats
path: root/src/global/maillog_client.c
blob: 7f79a1f6de56fdc5e690f14fac0b7afa6c6adc02 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*++
/* NAME
/*	maillog_client 3
/* SUMMARY
/*	choose between syslog client and postlog client
/* SYNOPSIS
/*	#include <maillog_client.h>
/*
/*	int	maillog_client_init(
/*	const char *progname,
/*	int	flags)
/* DESCRIPTION
/*	maillog_client_init() chooses between logging to the syslog
/*	service or to the internal postlog service.
/*
/*	maillog_client_init() may be called before configuration
/*	parameters are initialized. During this time, logging is
/*	controlled by the presence or absence of POSTLOG_SERVICE
/*	in the process environment (this is ignored if a program
/*	runs with set-uid or set-gid permissions).
/*
/*	maillog_client_init() may also be called after configuration
/*	parameters are initialized. During this time, logging is
/*	controlled by the "maillog_file" parameter value.
/*
/*	Arguments:
/* .IP progname
/*	The program name that will be prepended to logfile records.
/* .IP flags
/*	Specify one of the following:
/* .RS
/* .IP MAILLOG_CLIENT_FLAG_NONE
/*	No special processing.
/* .IP MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK
/*	Try to fall back to writing the "maillog_file" directly,
/*	if logging to the internal postlog service is enabled, but
/*	the postlog service is unavailable. If the fallback fails,
/*	die with a fatal error.
/* .RE
/* ENVIRONMENT
/* .ad
/* .fi
/*	When logging to the internal postlog service is enabled,
/*	each process exports the following information, to help
/*	initialize the logging in a child process, before the child
/*	has initialized its configuration parameters.
/* .IP POSTLOG_SERVICE
/*	The pathname of the public postlog service endpoint, usually
/*	"$queue_directory/public/$postlog_service_name".
/* .IP POSTLOG_HOSTNAME
/*	The hostname to prepend to information that is sent to the
/*	internal postlog logging service, usually "$myhostname".
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
/* .IP "maillog_file (empty)"
/*	The name of an optional logfile. If the value is empty, or
/*	unitialized and the process environment does not specify
/*	POSTLOG_SERVICE, the program will log to the syslog service
/*	instead.
/* .IP "myhostname (default: see postconf -d output)"
/*	The internet hostname of this mail system.
/* .IP "postlog_service_name (postlog)"
/*	The name of the internal postlog logging service.
/* SEE ALSO
/*	msg_syslog(3)   syslog client
/*	msg_logger(3)   internal logger
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this
/*	software.
/* AUTHOR(S)
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

 /*
  * System library.
  */
#include <sys_defs.h>
#include <stdlib.h>
#include <string.h>

 /*
  * Utility library.
  */
#include <argv.h>
#include <logwriter.h>
#include <msg_logger.h>
#include <msg_syslog.h>
#include <safe.h>
#include <stringops.h>

 /*
  * Global library.
  */
#include <mail_params.h>
#include <mail_proto.h>
#include <maillog_client.h>
#include <msg.h>

 /*
  * Using logging to debug logging is painful.
  */
#define MAILLOG_CLIENT_DEBUG	0

 /*
  * Application-specific.
  */
static int maillog_client_flags;

#define POSTLOG_SERVICE_ENV	"POSTLOG_SERVICE"
#define POSTLOG_HOSTNAME_ENV	"POSTLOG_HOSTNAME"

/* maillog_client_logwriter_fallback - fall back to logfile writer or bust */

static void maillog_client_logwriter_fallback(const char *text)
{
    static int fallback_guard = 0;

    /*
     * Guard against recursive calls.
     * 
     * If an error happened before the maillog_file parameter was initialized,
     * or if maillog_file logging is disabled, then we cannot fall back to a
     * logfile. All we can do is to hope that stderr logging will bring out
     * the bad news.
     */
    if (fallback_guard == 0 && var_maillog_file && *var_maillog_file
	&& logwriter_one_shot(var_maillog_file, text, strlen(text)) < 0) {
	fallback_guard = 1;
	msg_fatal("logfile '%s' write error: %m", var_maillog_file);
    }
}

/* maillog_client_init - set up syslog or internal log client */

void    maillog_client_init(const char *progname, int flags)
{
    char   *import_service_path;
    char   *import_hostname;

    /*
     * Crucially, only one logger mode can be in effect at any time,
     * otherwise postlogd(8) may go into a loop.
     */
    enum {
	MAILLOG_CLIENT_MODE_SYSLOG, MAILLOG_CLIENT_MODE_POSTLOG,
    }       logger_mode;

    /*
     * Security: this code may run before the import_environment setting has
     * taken effect. It has to guard against privilege escalation attacks on
     * setgid programs, using malicious environment settings.
     * 
     * Import the postlog service name and hostname from the environment.
     * 
     * - These will be used and kept if the process has not yet initialized its
     * configuration parameters.
     * 
     * - These will be set or updated if the configuration enables postlog
     * logging.
     * 
     * - These will be removed if the configuration does not enable postlog
     * logging.
     */
    if ((import_service_path = safe_getenv(POSTLOG_SERVICE_ENV)) != 0
	&& *import_service_path == 0)
	import_service_path = 0;
    if ((import_hostname = safe_getenv(POSTLOG_HOSTNAME_ENV)) != 0
	&& *import_hostname == 0)
	import_hostname = 0;

#if MAILLOG_CLIENT_DEBUG
#define STRING_OR_NULL(s) ((s) ? (s) : "(null)")
    msg_syslog_init(progname, LOG_PID, LOG_FACILITY);
    msg_info("import_service_path=%s", STRING_OR_NULL(import_service_path));
    msg_info("import_hostname=%s", STRING_OR_NULL(import_hostname));
#endif

    /*
     * Before configuration parameters are initialized, the logging mode is
     * controlled by the presence or absence of POSTLOG_SERVICE in the
     * process environment. After configuration parameters are initialized,
     * the logging mode is controlled by the "maillog_file" parameter value.
     * 
     * The configured mode may change after a process is started. The
     * postlogd(8) server will proxy logging to syslogd where needed.
     */
    if (var_maillog_file ? *var_maillog_file == 0 : import_service_path == 0) {
	logger_mode = MAILLOG_CLIENT_MODE_SYSLOG;
    } else {
	/* var_maillog_file ? *var_maillog_file : import_service_path != 0 */
	logger_mode = MAILLOG_CLIENT_MODE_POSTLOG;
    }

    /*
     * Postlog logging is enabled. Update the 'progname' as that may have
     * changed since an earlier call, and update the environment settings if
     * they differ from configuration settings. This blends two code paths,
     * one code path where configuration parameters are initialized (the
     * preferred path), and one code path that uses imports from environment.
     */
    if (logger_mode == MAILLOG_CLIENT_MODE_POSTLOG) {
	char   *myhostname;
	char   *service_path;

	if (var_maillog_file && *var_maillog_file) {
	    ARGV   *good_prefixes = argv_split(var_maillog_file_pfxs,
					       CHARS_COMMA_SP);
	    char  **cpp;

	    for (cpp = good_prefixes->argv; /* see below */ ; cpp++) {
		if (*cpp == 0)
		    msg_fatal("%s value '%s' does not match any prefix in %s",
			      VAR_MAILLOG_FILE, var_maillog_file,
			      VAR_MAILLOG_FILE_PFXS);
		if (strncmp(var_maillog_file, *cpp, strlen(*cpp)) == 0)
		    break;
	    }
	    argv_free(good_prefixes);
	}
	if (var_myhostname && *var_myhostname) {
	    myhostname = var_myhostname;
	} else if ((myhostname = import_hostname) == 0) {
	    myhostname = "amnesiac";
	}
#if MAILLOG_CLIENT_DEBUG
	msg_info("myhostname=%s", STRING_OR_NULL(myhostname));
#endif
	if (var_postlog_service) {
	    service_path = concatenate(var_queue_dir, "/", MAIL_CLASS_PUBLIC,
				       "/", var_postlog_service, (char *) 0);
	} else {

	    /*
	     * var_postlog_service == 0, therefore var_maillog_file == 0.
	     * logger_mode == MAILLOG_CLIENT_MODE_POSTLOG && var_maillog_file ==
	     * 0, therefore import_service_path != 0.
	     */
	    service_path = import_service_path;
	}
	maillog_client_flags = flags;
	msg_logger_init(progname, myhostname, service_path,
			(flags & MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK) ?
			maillog_client_logwriter_fallback :
			(MSG_LOGGER_FALLBACK_FN) 0);

	/*
	 * Export or update the exported postlog service pathname and the
	 * hostname, so that a child process can bootstrap postlog logging
	 * before it has processed main.cf and command-line options.
	 */
	if (import_service_path == 0
	    || strcmp(service_path, import_service_path) != 0) {
#if MAILLOG_CLIENT_DEBUG
	    msg_info("export %s=%s", POSTLOG_SERVICE_ENV, service_path);
#endif
	    if (setenv(POSTLOG_SERVICE_ENV, service_path, 1) < 0)
		msg_fatal("setenv: %m");
	}
	if (import_hostname == 0 || strcmp(myhostname, import_hostname) != 0) {
#if MAILLOG_CLIENT_DEBUG
	    msg_info("export %s=%s", POSTLOG_HOSTNAME_ENV, myhostname);
#endif
	    if (setenv(POSTLOG_HOSTNAME_ENV, myhostname, 1) < 0)
		msg_fatal("setenv: %m");
	}
	if (service_path != import_service_path)
	    myfree(service_path);
	msg_logger_control(CA_MSG_LOGGER_CTL_CONNECT_NOW,
			   CA_MSG_LOGGER_CTL_END);
    }

    /*
     * Postlog logging is disabled. Silence the msg_logger client, and remove
     * the environment settings that bootstrap postlog logging in a child
     * process.
     */
    else {
	msg_logger_control(CA_MSG_LOGGER_CTL_DISABLE, CA_MSG_LOGGER_CTL_END);
	if ((import_service_path && unsetenv(POSTLOG_SERVICE_ENV))
	    || (import_hostname && unsetenv(POSTLOG_HOSTNAME_ENV)))
	    msg_fatal("unsetenv: %m");
    }

    /*
     * Syslog logging is enabled. Update the 'progname' as that may have
     * changed since an earlier call.
     */
    if (logger_mode == MAILLOG_CLIENT_MODE_SYSLOG) {
	msg_syslog_init(progname, LOG_PID, LOG_FACILITY);
    }

    /*
     * Syslog logging is disabled, silence the syslog client.
     */
    else {
	msg_syslog_disable();
    }
}