1 /*
2  * Copyright (c) 2007-2012, Vsevolod Stakhov
3  * All rights reserved.
4 
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer. Redistributions in binary form
9  * must reproduce the above copyright notice, this list of conditions and the
10  * following disclaimer in the documentation and/or other materials provided with
11  * the distribution. Neither the name of the author nor the names of its
12  * contributors may be used to endorse or promote products derived from this
13  * software without specific prior written permission.
14 
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 #include <stdbool.h>
29 #include "cfg_file.h"
30 #include "rmilter.h"
31 #include "util.h"
32 #include "mfapi.h"
33 
34 /* config options here... */
35 
36 struct config_file *cfg;
37 bool daemonize;
38 extern struct smfiDesc smfilter;
39 const char *_rmilter_progname;
40 extern int yydebug;
41 
42 pthread_cond_t cfg_cond = PTHREAD_COND_INITIALIZER;
43 pthread_mutex_t cfg_reload_mtx = PTHREAD_MUTEX_INITIALIZER;
44 /* R/W lock for reconfiguring milter */
45 pthread_rwlock_t cfg_mtx = PTHREAD_RWLOCK_INITIALIZER;
46 struct rmilter_rng_state *rng_state = NULL;
47 
48 int
my_strcmp(const void * s1,const void * s2)49 my_strcmp (const void *s1, const void *s2)
50 {
51 	return strcmp (*(const char **)s1, *(const char **)s2);
52 }
53 
54 static void
usage(void)55 usage (void)
56 {
57 	printf ("Rapid Milter Version " MVERSION "\n"
58 			"Usage: rmilter [-h] [-n] [-d] [-c <config_file>]\n"
59 			"-n - do not daemonize on startup\n"
60 			"-d - debug parsing\n"
61 			"-h - this help message\n"
62 			"-c - path to config file\n"
63 			"-v - show version information\n");
64 	exit (0);
65 }
66 
67 static void
version(void)68 version (void)
69 {
70 	printf ("Rapid Milter Version " MVERSION "\n");
71 	exit (0);
72 }
73 
74 static void
sig_usr1_handler(int signo)75 sig_usr1_handler (int signo)
76 {
77 	pthread_cond_signal(&cfg_cond);
78 }
79 
80 static void *
reload_thread(void * unused)81 reload_thread (void *unused)
82 {
83 	extern int yynerrs;
84 	extern FILE *yyin;
85 	FILE *f;
86 	struct config_file *new_cfg = NULL, *tmp;
87 	struct sigaction signals;
88 
89 	/* Initialize signals and start reload thread */
90 	bzero (&signals, sizeof (struct sigaction));
91 	sigemptyset(&signals.sa_mask);
92 	sigaddset(&signals.sa_mask, SIGUSR1);
93 	signals.sa_handler = sig_usr1_handler;
94 	sigaction (SIGUSR1, &signals, NULL);
95 
96 	msg_info ("reload_thread: starting...");
97 
98 	/* lock on mutex until we got SIGUSR1 that unlocks mutex */
99 	while (1) {
100 		pthread_mutex_lock(&cfg_reload_mtx);
101 		pthread_cond_wait(&cfg_cond, &cfg_reload_mtx);
102 		pthread_mutex_unlock(&cfg_reload_mtx);
103 		msg_warn ("reload_thread: reloading, rmilter version %s", MVERSION);
104 		/* lock for writing */
105 		CFG_WLOCK();
106 		f = fopen (cfg->cfg_name, "r");
107 
108 		if (f == NULL) {
109 			CFG_UNLOCK();
110 			msg_warn ("reload_thread: cannot open file %s, %m", cfg->cfg_name);
111 			continue;
112 		}
113 
114 		new_cfg = (struct config_file*) malloc (sizeof (struct config_file));
115 		if (new_cfg == NULL) {
116 			CFG_UNLOCK();
117 			fclose (f);
118 			msg_warn ("reload_thread: malloc, %s", strerror (errno));
119 			continue;
120 		}
121 
122 		bzero (new_cfg, sizeof (struct config_file));
123 		init_defaults (new_cfg);
124 		new_cfg->cfg_name = cfg->cfg_name;
125 		tmp = cfg;
126 		cfg = new_cfg;
127 
128 		yyin = f;
129 		yyrestart (yyin);
130 
131 		if (yyparse() != 0 || yynerrs > 0) {
132 			CFG_UNLOCK();
133 			fclose (f);
134 			msg_warn ("reload_thread: cannot parse config file %s", cfg->cfg_name);
135 			free_config (new_cfg);
136 			free (new_cfg);
137 			cfg = tmp;
138 			continue;
139 		}
140 
141 		fclose (f);
142 		new_cfg->cfg_name = tmp->cfg_name;
143 		new_cfg->serial = tmp->serial + 1;
144 
145 		/* Strictly set temp dir */
146 		if (!cfg->temp_dir) {
147 			msg_warn ("tempdir is not set, trying to use $TMPDIR");
148 			cfg->temp_dir = getenv("TMPDIR");
149 
150 			if (!cfg->temp_dir) {
151 				cfg->temp_dir = strdup("/tmp");
152 			}
153 		}
154 #ifdef HAVE_SRANDOMDEV
155 		srandomdev();
156 #else
157 		srand (time (NULL));
158 #endif
159 		/* Free old config */
160 		free_config (tmp);
161 		free (tmp);
162 
163 		CFG_UNLOCK();
164 	}
165 	return NULL;
166 }
167 
168 static struct rmilter_rng_state*
get_prng_state(void)169 get_prng_state (void)
170 {
171 	static const char *rng_dev = "/dev/urandom";
172 	struct rmilter_rng_state *st;
173 	int fd;
174 	struct timeval tv;
175 
176 	st = malloc (sizeof (*st));
177 
178 	if (st == NULL) {
179 		abort ();
180 	}
181 
182 	fd = open (rng_dev, O_RDONLY);
183 
184 	if (fd == -1) {
185 		msg_warn ("cannot open %s to seed prng, use current time",
186 				rng_dev);
187 		gettimeofday (&tv, NULL);
188 		memcpy (st->s, &tv, sizeof (tv));
189 	}
190 	else {
191 		if (read (fd, st->s, sizeof (st->s)) == -1) {
192 			msg_warn ("cannot read %d bytes from %s to seed prng, use current time",
193 					(int)sizeof (*st), rng_dev);
194 			gettimeofday (&tv, NULL);
195 			memcpy (st->s, &tv, sizeof (tv));
196 		}
197 
198 		close (fd);
199 	}
200 
201 	st->p = 0;
202 	pthread_mutex_init (&st->mtx, NULL);
203 
204 	return st;
205 }
206 
207 int
main(int argc,char * argv[])208 main(int argc, char *argv[])
209 {
210 	int c, r;
211 	extern int yynerrs;
212 	extern FILE *yyin;
213 	const char *args = "c:hndv";
214 	char *cfg_file = NULL;
215 	FILE *f;
216 	pthread_t reload_thr;
217 	rmilter_pidfh_t *pfh = NULL;
218 	pid_t pid;
219 
220 	daemonize = 1;
221 
222 	/* Process command line options */
223 	while ((c = getopt(argc, argv, args)) != -1) {
224 		switch (c) {
225 		case 'c':
226 			if (optarg == NULL || *optarg == '\0') {
227 				fprintf(stderr, "Illegal config_file: %s\n",
228 						optarg);
229 				exit(EX_USAGE);
230 			}
231 			else {
232 				cfg_file = strdup (optarg);
233 			}
234 			break;
235 		case 'n':
236 			daemonize = 0;
237 			break;
238 		case 'd':
239 			yydebug = 1;
240 			break;
241 		case 'v':
242 			version ();
243 			break;
244 		case 'h':
245 		default:
246 			usage ();
247 			break;
248 		}
249 	}
250 
251 	openlog("rmilter", LOG_PID, LOG_MAIL);
252 
253 	cfg = (struct config_file*) malloc (sizeof (struct config_file));
254 	if (cfg == NULL) {
255 		msg_warn ("malloc: %s", strerror (errno));
256 		return -1;
257 	}
258 	bzero (cfg, sizeof (struct config_file));
259 	init_defaults (cfg);
260 
261 	if (cfg_file == NULL) {
262 		cfg_file = strdup ("/usr/local/etc/rmilter.conf");
263 	}
264 
265 	f = fopen (cfg_file, "r");
266 	if (f == NULL) {
267 		msg_warn ("cannot open file: %s", cfg_file);
268 		return EBADF;
269 	}
270 	yyin = f;
271 
272 	yyrestart (yyin);
273 
274 	if (yyparse() != 0 || yynerrs > 0) {
275 		msg_warn ("yyparse: cannot parse config file, %d errors", yynerrs);
276 		return EBADF;
277 	}
278 
279 	if (!cfg->cache_use_redis) {
280 		msg_warn ("rmilter is configured to work with legacy memcached cache,"
281 				" please consider switching to redis by adding "
282 				"'use_redis = true;' into configuration");
283 	}
284 
285 	fclose (f);
286 
287 	if (argv[0] && strrchr (argv[0], '/') != NULL) {
288 		_rmilter_progname = strrchr (argv[0], '/') + 1;
289 	}
290 	else {
291 		_rmilter_progname = argv[0];
292 	}
293 
294 	cfg->cfg_name = strdup (cfg_file);
295 
296 	/* Strictly set temp dir */
297 	if (!cfg->temp_dir) {
298 		msg_warn ("tempdir is not set, trying to use $TMPDIR");
299 		cfg->temp_dir = getenv("TMPDIR");
300 
301 		if (!cfg->temp_dir) {
302 			cfg->temp_dir = strdup("/tmp");
303 		}
304 	}
305 	if (cfg->sizelimit == 0) {
306 		msg_warn ("maxsize is not set, no limits on size of scanned mail");
307 	}
308 
309 #ifdef HAVE_SRANDOMDEV
310 	srandomdev();
311 #else
312 	srand (time (NULL));
313 #endif
314 
315 	umask (0);
316 	rng_state = get_prng_state ();
317 
318 	smfi_setconn(cfg->sock_cred);
319 	if (smfi_register(smfilter) == MI_FAILURE) {
320 		msg_err ("smfi_register failed");
321 		exit(EX_UNAVAILABLE);
322 	}
323 
324 	if (smfi_opensocket(true) == MI_FAILURE) {
325 		msg_err("Unable to open listening socket");
326 		exit(EX_UNAVAILABLE);
327 	}
328 
329 	if (daemonize && daemon (0, 0) == -1) {
330 		msg_err("Unable to daemonize");
331 		exit(EX_UNAVAILABLE);
332 	}
333 
334 	msg_info ("main: starting rmilter version %s, listen on %s", MVERSION,
335 			cfg->sock_cred);
336 
337 	if (pthread_create (&reload_thr, NULL, reload_thread, NULL)) {
338 		msg_warn ("main: cannot start reload thread, ignoring error");
339 	}
340 
341 	if (cfg->pid_file) {
342 		pfh = rmilter_pidfile_open (cfg->pid_file, 0644, &pid);
343 
344 		if (pfh == NULL) {
345 			msg_err("Unable to open pidfile %s", cfg->pid_file);
346 			exit (EX_UNAVAILABLE);
347 		}
348 
349 		rmilter_pidfile_write (pfh);
350 	}
351 
352 	r = smfi_main();
353 
354 	if (cfg_file != NULL) free (cfg_file);
355 
356 	if (pfh) {
357 		rmilter_pidfile_close (pfh);
358 	}
359 
360 	return r;
361 }
362 
363 /*
364  * vi:ts=4
365  */
366