summaryrefslogtreecommitdiff
path: root/src/daemon.c
blob: a6350538539fd85c3d658733d5c42ad688cbec49 (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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
#include "port/stdio.h"			/* for snprintf */
#include "port/string.h"		/* for strdup */
#include "port/limits.h"		/* for PATH_MAX */
#include "port/noreturn.h"		/* for NORETURN */

#include "daemon.h"
#include "log.h"
#include "signals.h"
#include "pidfile.h"

#include <sys/types.h>			/* for pid_t, ssize_t, off_t */
#include <unistd.h>			/* for getppid, geteuid, fork,
					       chdir, pipe, sysconf */
#include <stdlib.h>			/* for malloc, free */
#include <errno.h>			/* for errno */
#include <sys/stat.h>			/* for umask */
#include <fcntl.h>			/* for O_RDWR */
#include <grp.h>			/* for getgrnam, setgid */
#include <pwd.h>			/* for getpwnam, setuid */

static int exit_code_pipe[2] = { -1, -1 };

int daemon_parent_pipe[2] = { -1, -1 };

#define TERMINATE_EXIT_CODE 99
#define TERMINATE_PARENT 98

struct daemon_t {
	daemon_params_t params;		/**< the parameters for creation */
	error_t error;			/**< error of last start */
	struct pidfile_t pidfile;	/**< pid/lock file of the daemon */
	struct group *groupent;		/**< group entry */
	struct passwd *userent;		/**< password entry */
};

daemon_p daemon_new( daemon_params_t params,
		     error_t *error ) {
	daemon_p d;
	
	d = (struct daemon_t *)malloc( sizeof ( struct daemon_t ) );
	if( d == NULL ) {
		*error = ERR_OUT_OF_MEMORY;
		return NULL;
	}

	d->error = -1;	
	d->params = params;

	if( params.daemon_name == NULL ) {
		*error = ERR_INVALID_VALUE;
		return NULL;
	}
		
	if( params.pid_filename == NULL ) {
		pidfile_set_from_daemon_name( &d->pidfile, params.daemon_name );
	} else {
		pidfile_set_from_filename( &d->pidfile, params.pid_filename );
	}

	*error = OK;
	return d;
}

void daemon_free( daemon_p d ) {
	free( d );
	d = NULL;
}

static error_t close_fd( int fd ) {
	if( close( fd ) < 0 ) {
		switch( errno ) {
			case EBADF:
				/* skip */
				break;
	
			default:
				LOG( LOG_EMERG, "Error while closing file descriptor %d: %s (%d)",
					fd, strerror( errno ), errno );
				return ERR_INTERNAL;
		}
	}

	return OK;
}

static error_t daemon_close_standard_fds( void ) {
	error_t error;

	if( ( error = close_fd( STDIN_FILENO ) ) != OK ) return error;
	if( ( error = close_fd( STDOUT_FILENO ) ) != OK ) return error;
	if( ( error = close_fd( STDERR_FILENO ) ) != OK ) return error;

	return OK;
	
}

static error_t daemon_close_all_fds( void ) {
	long nof_files;
	int i;
	error_t error;
	
	nof_files = sysconf( _SC_OPEN_MAX );
	if( nof_files < 0 ) {
		LOG( LOG_EMERG, "Unable to retrieve maximal number of files: %s (%d)",
			strerror( errno ), errno );
		return ERR_INTERNAL;
	}
	LOG( LOG_DEBUG, "Closing all filedescriptors up to %ld", nof_files );

	for( i = 0; i < nof_files; i++ ) {
		if( ( i == STDIN_FILENO ) ||
		    ( i == STDOUT_FILENO ) ||
		    ( i == STDERR_FILENO ) ||
		    ( i == daemon_signal_pipe[0] ) ||
		    ( i == daemon_signal_pipe[1] ) ||
		    ( i == daemon_parent_pipe[0] ) ||
		    ( i == daemon_parent_pipe[1] ) ||
		    ( i == exit_code_pipe[0] ) ||
		    ( i == exit_code_pipe[1] ) ) {
			continue;
		}
		if( ( error = close_fd( i ) ) != OK ) return error;
	}

	return OK;
}

static error_t open_null_fd( int must_fd, int flags ) {
	int fd;

	fd = open( "/dev/null", flags );
	if( fd < 0 ) {
		LOG( LOG_EMERG, "Unable to open fd %d as /dev/null: %s (%d)",
			must_fd, strerror( errno ), errno );
		return ERR_INTERNAL;
	}
	if( fd != must_fd ) {
		LOG( LOG_EMERG, "Something is wrong with the file descriptors (expecting %d,got %d)!",
			must_fd, fd );
		return ERR_PROGRAMMING;
	}

	return OK;
}

static void atomar_write( int fd, void *data, size_t data_len ) {
	ssize_t res;

atomar_write_again:
	res = write( fd, data, data_len );
	if( res < 0 ) {
		if( errno == EINTR ) goto atomar_write_again;
		LOG( LOG_EMERG, "Error in atomar write to fd %d: %s (%d)",
			fd, strerror( errno ), errno );
	} else if( (size_t)res != data_len ) {
		LOG( LOG_EMERG, "Unexpected number of octets %zd in atomar write to fd %d (expected %zd)",
			res, fd, data_len );
	}
}

static void atomar_read( int fd, void *data, size_t data_len ) {
	ssize_t res;

atomar_read_again:
	res = read( fd, data, data_len );
	if( res < 0 ) {
		if( errno == EINTR ) goto atomar_read_again;
		LOG( LOG_EMERG, "Error in atmoar read from fd %d: %s (%d)",
			fd, strerror( errno ), errno );
	} else if( (size_t)res != data_len ) {
		LOG( LOG_EMERG, "Unexpected number of octets %zd in atomar read from fd %d (expected %zd)",
			res, fd, data_len );
	}
}

error_t daemon_start( daemon_p d ) {
	pid_t pid;
	mode_t mode;
	error_t error;
	int exit_code;
	
	/* Our parent is already the init process (which has pid 1 by
	 * convention), we are already a daemon. This is a programming
	 * error..
	 */
	if( getppid( ) == 1 ) {
		LOG( LOG_EMERG, "Already running as daemon!" );
		return( d->error = ERR_PROGRAMMING );
	}
	
	/* Are we starting as root? If not, bail out, we need root
	 * permissions because we have to listen to ports below 1024
	 * or we must open logfiles/pidfiles with proper permissions.
	 * Also we must change to the proper running user/group and
	 * we must be root (uid 0 by convention) for that.
	 */
	if( geteuid( ) != 0 ) {
		LOG( LOG_EMERG, "Unable to start daemon as not root user!" );
		return( d->error = ERR_INVALID_STATE );
	}

	/* We fork so SIGCHLD signals get to the parent and grandfather.
	 * We don't want that and we are installing empty signal handlers.
	 */
#if defined( SIGCHLD ) && defined( SIGCLD )
#if SIGCLD != SIGCHLD
	signal_install_empty( SIGCLD, 0 );
	signal_install_empty( SIGCHLD, 0 );
#else
#if defined( SIGCHLD )
	signal_install_empty( SIGCHLD, 0 );
#endif
#if defined( SIGCLD )
	signal_install_empty( SIGCLD, 0 );
#endif
#endif
#endif

	/* first pipe communicates from parent of daemon and daemon
	 * itself back to the grand-parent (exit codes).
	 */
	if( pipe( exit_code_pipe ) < 0 ) {
		LOG( LOG_EMERG, "Unable to create exit code pipe: %s (%d)",
			strerror( errno ), errno );
		return ( d->error = ERR_INTERNAL );
	}
	LOG( LOG_DEBUG, "Created exit code pipe (%d,%d)", exit_code_pipe[0], exit_code_pipe[1] );

	/* first fork: make sure we are no longer process group leader.
	 * So we can get our own process group leader by calling setsid
	 */
	switch( ( pid = fork( ) ) ) {
		case -1:
			/* error */
			LOG( LOG_EMERG, "Unable to fork the first time: %s", strerror( errno ) );
			return ( d->error = ERR_INTERNAL );
		
		case 0:
			/* the child becomes the daemon */
			LOG( LOG_DEBUG, "First fork reached" );
			break;
			
		default:
			/* the parent waits till it gets feedback from
			 * the child (error pipe)
			 */
			/* TODO: wait for some time for correct exit
			 * code from the error pipe
			 */
			LOG( LOG_DEBUG, "Parent after first fork: child is %d", pid );
			return ( d->error = TERMINATE_EXIT_CODE );
	}

	/* second pipe communicates from the daemon back to its parent
	 * (for termination and cleanup which can only be done as root) 
	 */
	if( pipe( daemon_parent_pipe ) < 0 ) {
		LOG( LOG_EMERG, "Unable to create parent pipe: %s (%d)",
			strerror( errno ), errno );
		return ( d->error = ERR_INTERNAL );
	}
	LOG( LOG_DEBUG, "Created parent pipe (%d,%d)", daemon_parent_pipe[0], daemon_parent_pipe[1] );

	/* Put the first child in it's own process group and finally detach it
	 * from its controlling terminal. This ensure we don't get funny
	 * signals which could kill the daemon.
	 */
	if( setsid( ) < 0 ) {
		/* should actually never fail */
		LOG( LOG_EMERG, "Starting new process group session for the parent of the daemon failed: %s", strerror( errno ) );
		return ERR_INTERNAL;
	}

	/* We fork so SIGCHLD signals get to the parent and grandfather.
	 * We don't want that and we are installing empty signal handlers.
	 */
#if defined( SIGCHLD ) && defined( SIGCLD )
#if SIGCLD != SIGCHLD
	signal_install_empty( SIGCLD, 0 );
	signal_install_empty( SIGCHLD, 0 );
#else
#if defined( SIGCHLD )
	signal_install_empty( SIGCHLD, 0 );
#endif
#if defined( SIGCLD )
	signal_install_empty( SIGCLD, 0 );
#endif
#endif
#endif
	
	/* Now that the parent is process group leader, fork again. This
	 * way the final child can not inherit a controlling terminal
	  */
	switch( ( pid = fork( ) ) ) {
		case -1:
			/* error */
			LOG( LOG_EMERG, "Unable to fork the second time: %s", strerror( errno ) );
			return ( d->error = ERR_INTERNAL );
		
		case 0:
			/* the child becomes the daemon */
			LOG( LOG_DEBUG, "Second fork reached" );
			break;
			
		default:
			(void)signal_install_handlers_parent( );
			LOG( LOG_DEBUG, "Parent after second fork: child (and daemon) is %d", pid );
			return ( d->error = TERMINATE_PARENT );
	}

	/* Now install signal handlers */
	if( ( error = signal_initialize( ) ) != OK )
		return ( d->error = error );
	if( ( error = signal_install_handlers_daemon( ) ) != OK )
		return ( d->error = error );
		
	/* Put the daemon in it's own process group and finally detach it
	 * from its controlling terminal. This ensure we don't get funny
	 * signals which could kill the daemon.
	 */
	if( setsid( ) < 0 ) {
		/* should actually never fail */
		LOG( LOG_EMERG, "Starting new process group for daemon session failed: %s", strerror( errno ) );
		return ERR_INTERNAL;
	}
		
	/* Change to the root directory for several reasons:
	 * - all but the root directory we may want to unmount without
	 *   stopping the daemon
	 * - we don't have write permissions there so a core dump
	 *   would not result in DoSA
	 * - non-priviledged users can't do anything in the root
	 *   directory (especially they can't write files there)
	 */
	if( chdir( "/" ) < 0 ) {
		LOG( LOG_EMERG, "Changing to root diretory failed: %s", strerror( errno ) );
		return ERR_INTERNAL;
	}
	LOG( LOG_DEBUG, "Changed to root directory /" );
	
	/* Change the umask to 0133 temporarily so we don't have to
	 * chmod the logfiles, pidfiles later.
	 *
	 * Create pid files with permissions 0644 (rw-r--r--)
	 */
	mode = umask( 0133 );
	LOG( LOG_DEBUG, "Switched umask from %04o to %04o", mode, 0133 );
	
	/* check if another daemon is already running, if yes, bail out */
	if( is_daemon_running( &d->pidfile, &pid, &error ) || ( error != OK ) ) {
		if( error == OK ) {
			LOG( LOG_EMERG, "Another daemon is already running with pid '%d', can't start!", pid );
		}
		return ERR_INTERNAL;
	}

	/* we loose logfile and syslog file descriptors anyway, so close
	 * them here */
	closelogtosyslog( );
	closelogtofile( );
	closelogtostderr( );
	
	/* close all filedescriptors */
	if( daemon_close_all_fds( ) != OK )
		return ERR_INTERNAL;

	/* reopen the logs to the logfile and syslog (not stderr) */
	reopenlogtosyslog( );
	reopenlogtofile( );
		
	/* create and lock the pidfile now, write pid of daemon into it */
	if( pidfile_create( &d->pidfile ) != OK ) {
		return ERR_INTERNAL;
	}
	
	/* Install the final permissions for new files. The reason is
	 * simple: We don't want to create files in a defective part
	 * of the daemon, especially third party code. We don't know
	 * what kind of plugins we gonna integrate..
	 *
	 * Make sure that no files can be written except the ones belonging
	 * to us (because now we are no longer root) and that no files can
	 * be created with executable permission (code injection!)
	 *
	 * New files always get the permission 640 (rw-r-----)
	 */
	mode = umask( 0137 );
	LOG( LOG_DEBUG, "Switched umask from %04o to %04o", mode, 0137 );

	/* drop permissions to a non-priviledged user now */
	if( ( d->params.group_name != NULL ) && ( d->params.user_name != NULL ) ) {
		errno = 0;
		d->groupent = getgrnam( d->params.group_name );
		if( d->groupent == NULL ) {
			if( errno == 0 ) {
				LOG( LOG_EMERG, "No group '%s' found", d->params.group_name );
			} else {
				LOG( LOG_EMERG, "Unable to retrieve group information for group '%s': %s (%d)",
					d->params.group_name, strerror( errno ), errno );
			}
			return ERR_INTERNAL;
		}

		errno = 0;
		d->userent = getpwnam( d->params.user_name );
		if( d->userent == NULL ) {
			if( errno == 0 ) {
				LOG( LOG_EMERG, "No user '%s' found", d->params.user_name );
			} else {
				LOG( LOG_EMERG, "Unable to retrieve user information for user '%s': %s (%d)",
					d->params.user_name, strerror( errno ), errno );
			}
			return ERR_INTERNAL;
		}

		if( setgid( d->userent->pw_gid ) < 0 ) {
			LOG( LOG_EMERG, "Setting unprivileged group failed: %s (%d)",
				strerror( errno ), errno );
			return ERR_INTERNAL;
		}

		if( setuid( d->userent->pw_uid ) < 0 ) {
			LOG( LOG_EMERG, "Setting unprivileged user failed: %s (%d)",
				strerror( errno ), errno );
			return ERR_INTERNAL;
		}

		/* TODO: setsid and setting all groups of the user */
		/* TODO: also check the permissions of the home dir
		 *       of the unpriviledged user */

		LOG( LOG_DEBUG, "Switched to user '%s' (%d) and group '%s' (%d)",
			d->params.user_name, d->userent->pw_uid,
			d->params.group_name, d->userent->pw_gid );
	}

	/* assign stdin/stdout/stderr again to something harmless
	 * (/dev/null). Do this as late as possible, so somebody
	 * starting the daemon by hand still gets error messages
	 * on stderr.
	 */
	if( ( error = daemon_close_standard_fds( ) ) != OK ) return error;
	if( ( error = open_null_fd( STDIN_FILENO, O_RDONLY ) ) != OK ) return error;
	if( ( error = open_null_fd( STDOUT_FILENO, O_WRONLY ) ) != OK ) return error;
	if( ( error = open_null_fd( STDERR_FILENO, O_WRONLY ) ) != OK ) return error;

	/* signal the grand-parent process, that he can return 0 to
	 * the calling shell/script
	 */
	exit_code = EXIT_SUCCESS;
	atomar_write( exit_code_pipe[1], &exit_code, sizeof( int ) );

	return ( d->error = OK );
}

NORETURN void daemon_exit( daemon_p d ) {
	int exit_code;
	int pid_file_fd;

	LOG( LOG_DEBUG, "daemon_exit called with error %d", d->error );

	switch( d->error ) {
		case TERMINATE_EXIT_CODE:
			/* wait here for exit code in exit_code_pipe
			 * so we can return the correct exit code to
			 * the calling script or shell
			 */
			LOG( LOG_DEBUG, "Waiting on exit_code pipe for exit code" );

			atomar_read( exit_code_pipe[0], &exit_code, sizeof( int ) );

			LOG( LOG_DEBUG, "Terminating grand-parent of daemon with code %d (PID: %lu)",
				exit_code, getpid( ) );

			signal_terminate( );

			/* Do not close file descriptors of the child!
			 * We are the father or grand-father process
			 */
			_exit( exit_code );

		case TERMINATE_PARENT:
			/* TODO: handle houskeeping here which needs
			 *       root rights (log rotation?)
			 */

			/* wait for termination signal */
			LOG( LOG_DEBUG, "Waiting on parent pipe for termination signal" );

			atomar_read( daemon_parent_pipe[0], &pid_file_fd, sizeof( int ) );
			LOG( LOG_DEBUG, "Parent got termination (pidfile fd: %d).. cleaning up now (PID: %lu)",
				pid_file_fd, getpid( ) );

			/* we need root permissions for that! */
			d->pidfile.fd = pid_file_fd;
			(void)pidfile_remove( &d->pidfile );

			signal_terminate( );

			LOG( LOG_DEBUG, "Terminating parent of daemon pid file fd %d (PID %lu)",
				pid_file_fd, getpid( ) );

			/* exit code is irrelevant here.. */
			exit( EXIT_SUCCESS );

		case OK:
			/* close and unlock the pidfile here */
			(void)pidfile_release( &d->pidfile );

			/* This is the daemon terminating, signal the
			 * parent that we are terminating
			 */
			atomar_write( daemon_parent_pipe[1], &d->pidfile.fd, sizeof( int ) );

			signal_terminate( );

			LOG( LOG_DEBUG, "Terminating daemon (PID: %lu)", getpid( ) );

			/* exit code is irrelevant here */
			_exit( EXIT_SUCCESS );
		
		default:
			/* no exit_code_pipe exists yet, we are before the
			 * first fork, so terminate with proper exit code
			 */
			if( exit_code_pipe[1] == -1 ) {
				exit( EXIT_FAILURE );
			}
			
			/* This is an error case, communicate exit code back
			 * to grand-parent
			 */
			exit_code = EXIT_FAILURE;
			atomar_write( exit_code_pipe[1], &exit_code, sizeof( int ) );

			/* also terminate the parent of the daemon */
			atomar_write( daemon_parent_pipe[1], &d->pidfile.fd, sizeof( int ) );

			signal_terminate( );

			LOG( LOG_DEBUG, "Terminating daemon (PID: %lu)", getpid( ) );

			/* exit code is irrelevant here */
			_exit( EXIT_SUCCESS );
	}
	
	/* silence up some versions of the GCC compiler ("noreturn function returns"),
	 * because they errornously tread _exit as a return, which is clearly wrong
	 * (experienced with gcc 3.3.5 on OpenBSD 4.3) */
	exit( EXIT_SUCCESS );
}