/******************************************************************************
 *
 * worker-ping.c - Nagios Core 4 worker to handle ping checke
 *
 * Program: Nagios Core
 * License: GPL
 *
 * First Written:   01-03-2013 (start of development)
 *
 * Description:
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *****************************************************************************/

#include "config.h"
#include "workers.h"

/* Local macro definitions */
#define PING_WORKER_VERSION "0.2"
#define PING_WORKER_MODIFICATION_DATE "02-Feb-13"

#define PW_OK		0
#define PW_ERROR	1

/* Local function declarations */
int			daemon_init( void);
int			drop_privileges( char *, char *);
void		print_license( void);
void		print_usage( char *);
void		print_version( void);
void		parse_worker_command_line( int, char **, char **, int *, char **,
					char **, char **);
static int	worker( const char *);

/* Everything starts here */
int main( int argc, char **argv, char **env) {
	int daemon_mode = FALSE;
	char *worker_socket;
	char *worker_user = ( char *)0;
	char *worker_group = ( char *)0;

	if( FALSE == daemon_mode) {
		printf( "Greetings from the ping worker.\n");
	}

	parse_worker_command_line( argc, argv, env, &daemon_mode, &worker_socket,
			&worker_user, &worker_group);

	if( FALSE == daemon_mode) {
		print_version();
		printf( "Worker socket is %s.\n", worker_socket);
	}

	/* drop privileges */
	if( drop_privileges( worker_user, worker_group) == PW_ERROR) {
		fprintf( stderr, "Failed to drop privileges. Aborting.\n");
		exit( 1);
	}

	if( TRUE == daemon_mode) {
		if( daemon_init() == ERROR) {
			fprintf( stderr,
					"Bailing out due to failure to daemonize. (PID=%d)\n",
					( int)getpid());
			exit( 1);
		}
	}

	/* Enter the worker code */
	if( worker( worker_socket)) {
		exit( 1);
	}
}

void parse_worker_command_line( int argc, char **argv, char **env,
		int *daemon_mode, char **worker_socket, char **worker_user,
		char **worker_group) {
	int c = 0;
	int display_usage = FALSE;
	int display_license = FALSE;

#ifdef HAVE_GETOPT_H
	int option_index = 0;
	static struct option long_options[] = {
		{ "help", no_argument, 0, 'h'},
		{ "version", no_argument, 0, 'V'},
		{ "license", no_argument, 0, 'V'},
		{ "daemon", no_argument, 0, 'd'},
		{ "worker", required_argument, 0, 'W'},
		{ "user", required_argument, 0, 'u'},
		{ "group", required_argument, 0, 'g'},
		{ 0, 0, 0, 0}
	};
#define getopt( argc, argv, o) getopt_long( argc, argv, o, long_options, &option_index)
#endif

	/* get all command line arguments */
	while( 1) {
		c = getopt( argc, argv, "+:hVdW:u:g:");

		if( -1 == c || EOF == c) break;

		switch( c) {

			case '?': /* usage */
			case 'h':
				display_usage = TRUE;
				break;

			case 'V': /* version */
				display_license = TRUE;
				break;

			case 'd': /* daemon mode */
				*daemon_mode = TRUE;
				break;

			case 'W':
				*worker_socket = optarg;
				break;

			case 'u':
				*worker_user = optarg;
				break;

			case 'g':
				*worker_group = optarg;
				break;

			case ':':
				printf( "Missing argument for command line option '%c'.\n\n",
						optopt);
				print_usage( argv[ 0]);
				exit( 1);
				break;

			default:
				printf( "Unknown command line option '%c'.\n\n", c);
				print_usage( argv[ 0]);
				exit( 1);
				break;
		}
	}

	if( TRUE == display_license) {
		print_version();
		print_license();
		exit( 0);
	}

	if( TRUE == display_usage) {
		print_usage( argv[ 0]);
		exit( 0);
	}

}

void print_license( void) {
	printf( "\nThis program is free software; you can redistribute it and/or modify\n");
	printf( "it under the terms of the GNU General Public License version 2 as\n");
	printf( "published by the Free Software Foundation.\n\n");
	printf( "This program is distributed in the hope that it will be useful,\n");
	printf( "but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
	printf( "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n");
	printf( "GNU General Public License for more details.\n\n");
	printf( "You should have received a copy of the GNU General Public License\n");
	printf( "along with this program; if not, write to the Free Software\n");
	printf( "Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\n");
}

void print_usage( char *program_name) {
	printf( "\nUsage: %s [-?|-h|--help] [-V|--version] [-d]\n", program_name);
	printf( "        -W /path/to/socket -u user -g group\n");
	printf( "\n");
	printf( "Options:\n");
	printf( "\n");
	printf( "  -d, --daemon                 Starts the ping worker in daemon mode,\n");
	printf( "                               instead of as a foreground process\n");
	printf( "  -W, --worker /path/to/socket Act as a worker for an already running daemon\n");
	printf( "  -u, --user user              Drop privileges to the specified user\n");
	printf( "  -g, --group group            Drop privileges to the specified user\n");
	printf( "\n");
	printf( "Visit the Nagios website at https://www.nagios.org/ for bug fixes, new\n");
	printf( "releases, online documentation, FAQs, information on subscribing to\n");
	printf( "the mailing lists, and commercial support options for Nagios.\n");
	printf( "\n");
}

void print_version( void) {
	printf( "\nNagios Core 4 Ping Worker %s\n", PING_WORKER_VERSION);
	printf( "Copyright (c) 2013-present Nagios Core Development Team\n");
	printf( "        and Community Contributors\n");
	printf( "Last Modified: %s\n", PING_WORKER_MODIFICATION_DATE);
	printf( "License: GPL\n");
	printf( "Website: https://www.nagios.org\n");
}

static int worker( const char *path)
{
	int sd, ret;
	char response[128];

	/*set_loadctl_defaults();*/

	sd = nsock_unix( path, NSOCK_TCP | NSOCK_CONNECT);
	if( sd < 0) {
		printf( "Failed to connect to query socket '%s': %s: %s\n",
				path, nsock_strerror( sd), strerror( errno));
		return 1;
	}

	ret = nsock_printf_nul( sd, "@wproc register name=Core Ping Worker %d;pid=%d;plugin=check_ping", getpid(), getpid());
	if( ret < 0) {
		printf( "Failed to register as worker.\n");
		return 1;
	}

	ret = read( sd, response, 3);
	if( ret != 3) {
		printf( "Failed to read response from wproc manager\n");
		return 1;
	}
	if( memcmp( response, "OK", 3)) {
		read( sd, response + 3, sizeof(response) - 4);
		response[ sizeof( response) - 2] = 0;
		printf( "Failed to register with wproc manager: %s\n", response);
		return 1;
	}

	enter_worker( sd, start_cmd);
	return 0;
}

int drop_privileges( char *user, char *group) {
	uid_t uid = -1;
	gid_t gid = -1;
	struct group *grp = NULL;
	struct passwd *pw = NULL;
	int result = PW_OK;

	/* only drop privileges if we're running as root, so we don't
		interfere with being debugged while running as some random user */
	if( getuid() != 0) {
		return PW_OK;
	}

	/* set effective group ID */
	if( NULL != group) {

		/* see if this is a group name */
		if( strspn( group, "0123456789") < strlen( group)) {
			grp = ( struct group *)getgrnam( group);
			if( NULL != grp) {
				gid = ( gid_t)(grp->gr_gid);
			}
			else {
				fprintf( stderr,
					"Warning: Could not get group entry for '%s'\n", group);
			}
		}

		/* else we were passed the GID */
		else {
			gid = ( gid_t)atoi( group);
		}

		/* set effective group ID if other than current EGID */
		if( gid != getegid()) {
			if( setgid( gid) == -1) {
				fprintf( stderr, "Warning: Could not set effective GID=%d\n",
						( int)gid);
				result = PW_ERROR;
			}
		}
	}

	/* set effective user ID */
	if( NULL != user) {

		/* see if this is a user name */
		if( strspn( user, "0123456789") < strlen( user)) {
			pw = ( struct passwd *)getpwnam( user);
			if( NULL != pw) {
				uid = ( uid_t)(pw->pw_uid);
			}
			else {
				fprintf( stderr,
						"Warning: Could not get passwd entry for '%s'\n", user);
			}
		}

		/* else we were passed the UID */
		else {
			uid = ( uid_t)atoi( user);
		}

#ifdef HAVE_INITGROUPS

		if( uid != geteuid()) {

			/* initialize supplementary groups */
			if( initgroups( user, gid) == -1) {
				if( EPERM == errno) {
					fprintf( stderr, "Warning: Unable to change supplementary "
							"groups using initgroups() -- I hope you know what "
							"you're doing\n");
				}
				else {
					fprintf( stderr, "Warning: Possibly root user failed "
							"dropping privileges with initgroups()\n");
					return PW_ERROR;
				}
			}
		}
#endif
		if( setuid( uid) == -1) {
			fprintf( stderr, "Warning: Could not set effective UID=%d\n",
					( int)uid);
			result = PW_ERROR;
		}
	}

	return result;
}

int daemon_init( void) {
	pid_t pid = -1;
	int pidno = 0;
	int lockfile = 0;
	int val = 0;
	char buf[256];
	struct flock lock;
	char *homedir = NULL;

#ifdef RLIMIT_CORE
	struct rlimit limit;
#endif

	/* change working directory. scuttle home if we're dumping core */
	homedir = getenv( "HOME");
#ifdef DAEMON_DUMPS_CORE
	if( NULL != homedir) {
		chdir( homedir);
	}
	else {
#endif
		chdir( "/");
#ifdef DAEMON_DUMPS_CORE
	}
#endif

	umask( S_IWGRP | S_IWOTH);

	/* close existing stdin, stdout, stderr */
	close( 0);
	close( 1);
	close( 2);

	/* THIS HAS TO BE DONE TO AVOID PROBLEMS WITH STDERR BEING REDIRECTED TO SERVICE MESSAGE PIPE! */
	/* re-open stdin, stdout, stderr with known values */
	open( "/dev/null", O_RDONLY);
	open( "/dev/null", O_WRONLY);
	open( "/dev/null", O_WRONLY);

#ifdef USE_LOCKFILE
	lockfile = open( lock_file,
			O_RDWR | O_CREAT, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);

	if( lockfile < 0) {
		logit( NSLOG_RUNTIME_ERROR, TRUE, "Failed to obtain lock on file %s: %s\n", lock_file, strerror(errno));
		logit( NSLOG_PROCESS_INFO | NSLOG_RUNTIME_ERROR, TRUE, "Bailing out due to errors encountered while attempting to daemonize... (PID=%d)", (int)getpid());

		exit( 1);
	}

	/* see if we can read the contents of the lockfile */
	if(( val = read( lockfile, buf, ( size_t)10)) < 0) {
		logit(NSLOG_RUNTIME_ERROR, TRUE, "Lockfile exists but cannot be read");
		exit( 1);
	}

	/* we read something - check the PID */
	if( val > 0) {
		if(( val = sscanf( buf, "%d", &pidno)) < 1) {
			logit( NSLOG_RUNTIME_ERROR, TRUE, "Lockfile '%s' does not contain a valid PID (%s)", lock_file, buf);
			exit( 1);
		}
	}

	/* check for SIGHUP */
	if( val == 1 && ( pid = ( pid_t)pidno) == getpid()) {
		close( lockfile);
		return OK;
	}
#endif

	/* exit on errors... */
	if(( pid = fork()) < 0) {
		return( PW_ERROR);
	}

	/* parent process goes away.. */
	else if((int)pid != 0) {
		exit( 0);
	}

	/* child continues... */

	/* child becomes session leader... */
	setsid();

#ifdef USE_LOCKFILE
	/* place a file lock on the lock file */
	lock.l_type = F_WRLCK;
	lock.l_start = 0;
	lock.l_whence = SEEK_SET;
	lock.l_len = 0;
	if( fcntl( lockfile, F_SETLK, &lock) < 0) {
		if( EACCES == errno || EAGAIN == errno) {
			fcntl( lockfile, F_GETLK, &lock);
			logit( NSLOG_RUNTIME_ERROR, TRUE, "Lockfile '%s' looks like its already held by another instance of Nagios (PID %d).  Bailing out...", lock_file, (int)lock.l_pid);
			}
		else {
			logit(NSLOG_RUNTIME_ERROR, TRUE, "Cannot lock lockfile '%s': %s. Bailing out...", lock_file, strerror(errno));

		}
		exit( 1);
	}
#endif

	/* prevent daemon from dumping a core file... */
#if defined( RLIMIT_CORE) && defined( DAEMON_DUMPS_CORE)
	getrlimit( RLIMIT_CORE, &limit);
	limit.rlim_cur = 0;
	setrlimit( RLIMIT_CORE, &limit);
#endif

#ifdef USE_LOCKFILE
	/* write PID to lockfile... */
	lseek( lockfile, 0, SEEK_SET);
	ftruncate( lockfile, 0);
	sprintf( buf, "%d\n", ( int)getpid());
	write( lockfile, buf, strlen( buf));

	/* make sure lock file stays open while program is executing... */
	val = fcntl( lockfile, F_GETFD, 0);
	val |= FD_CLOEXEC;
	fcntl( lockfile, F_SETFD, val);
#endif

	return PW_OK;
}
