#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <syslog.h>
#include <netdb.h>
#include <utmp.h>
#include <paths.h>
#include <sys/queue.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include "vpnd.h"
#include "vpnoptions.h"
#include "vpnplugins.h"
char *no_ppp_msg = "This system lacks PPP kernel support\n";
static char pid_path[MAXPATHLEN] = _PATH_VARRUN DAEMON_NAME "-";
static volatile int rcvd_sig_child = 0;
static volatile int terminate = 0;
static int forwarding = -1;
static FILE *logfile = 0;
static int stdio_is_valid = 1;
static struct vpn_params *params;
static int spawn(struct vpn_params *params);
static void close_file_descriptors(void);
static void redirect_std_file(void);
static void detach(void);
static void write_pid_file(struct vpn_params *params);
static void delete_pid_file(void);
static void sig_chld(int inSignal);
static void sig_term(int inSignal);
static void setup_signal_handlers(void);
static int set_forwarding(int *oldval, int newval);
static void create_log_file(char *inLogPath);
static void close_log_file(void);
void vpnlog(int nSyslogPriority, char *format_str, ...);
int main (int argc, char *argv[])
{
int i;
if (geteuid() != 0) {
fprintf(stderr, "must be root to run %s, since it is not setuid-root\n", argv[0]);
exit(EXIT_NOT_ROOT);
}
params = (struct vpn_params*)malloc(sizeof (struct vpn_params));
if (params == 0)
exit(EXIT_FATAL_ERROR);
bzero(params, sizeof(params));
close_file_descriptors();
if ((i = open("/dev/null", O_RDWR)) >= 0) {
while (0 <= i && i <= 2)
i = dup(i);
if (i >= 0)
close(i);
}
if (process_options(params, argc, argv))
exit(EXIT_OPTION_ERROR);
if (!ppp_available()) {
vpnlog(LOG_ERR, "VPND: The PPP kernel extension could not be loaded\n");
exit(EXIT_NO_KERNEL_SUPPORT);
}
if (params->server_id == 0) {
close_file_descriptors(); redirect_std_file(); spawn(params);
exit(EXIT_OK);
}
if (process_prefs(params)) { vpnlog(LOG_ERR, "VPND: error processing prefs file\n");
exit(EXIT_OPTION_ERROR);
}
if (check_conflicts(params)) exit(EXIT_OPTION_ERROR);
if (kill_orphans(params)) exit(EXIT_FATAL_ERROR);
if (params->log_path)
create_log_file(params->log_path);
if (init_plugin(params)) {
vpnlog(LOG_ERR, "VPND: initialization of vpnd plugin failed\n");
exit(EXIT_FATAL_ERROR);
}
if (get_plugin_args(params)) {
vpnlog(LOG_ERR, "VPND: error getting arguments from plugin\n");
exit(EXIT_OPTION_ERROR);
}
setuid(geteuid());
if (params->daemonize) {
stdio_is_valid = 0;
detach();
setsid();
redirect_std_file();
open_dynamic_store(params); }
setup_signal_handlers();
write_pid_file(params);
vpnlog(LOG_INFO, "VPND: Listening for connections\n");
publish_state(params);
if (set_forwarding(&forwarding, 1))
vpnlog(LOG_ERR, "VPND: cannot activate IP forwarding (error %d)\n", errno);
accept_connections(params);
if (set_forwarding(0, forwarding))
vpnlog(LOG_ERR, "VPND: cannot reset IP forwarding (error %d)\n", errno);
close_log_file();
delete_pid_file();
exit(EXIT_OK) ;
}
static int spawn(struct vpn_params *params)
{
int i, j, count;
char server[OPT_STR_LEN];
pid_t pidChild = 0;
char *args[6];
char *vpnd_prog = VPND_PRGM;
char *debug_opt = "-d";
char *no_detach_opt = "-x";
char *server_id_opt = "-i";
CFArrayRef active_servers;
CFStringRef string;
server[0] = 0;
args[0] = vpnd_prog;
i = 1;
if (params->debug)
args[i++] = debug_opt;
if (params->daemonize == 0)
args[i++] = no_detach_opt;
args[i++] = server_id_opt;
args[i++] = server;
args[i] = 0;
if ((active_servers = get_active_servers(params)) == 0)
return 0; count = CFArrayGetCount(active_servers);
for (j = 0; j < count; j++) {
string = CFArrayGetValueAtIndex(active_servers, j);
if (isString(string))
CFStringGetCString(string, server, OPT_STR_LEN, kCFStringEncodingMacRoman);
else
continue;
switch (pidChild = fork ()) {
case 0: execv(PATH_VPND, args); break;
case -1: vpnlog(LOG_ERR, "VPND: attempt to fork new vpnd failed\n") ;
return -1;
default:
vpnlog(LOG_INFO, "VPND: launched vpnd process id '%d' for server id '%s'\n", pidChild, server);
break;
}
}
CFRelease(active_servers);
return 0;
}
static int set_forwarding(int *oldval, int newval)
{
size_t len = sizeof(int);
if (newval != 0 && newval != 1)
return 0;
return sysctlbyname("net.inet.ip.forwarding", oldval, &len, &newval, sizeof(int));
}
static void close_file_descriptors(void)
{
register int i = OPEN_MAX;
register int nMin = STDERR_FILENO;
struct rlimit lim;
if (!getrlimit (RLIMIT_NOFILE, &lim))
i = lim.rlim_cur;
else
vpnlog(LOG_ERR, "VPND: close_file_descriptors() - getrlimit() failed\n");
while (--i > nMin)
close (i) ;
}
static void redirect_std_file(void)
{
int i;
i = open(_PATH_DEVNULL, O_RDONLY);
if ((i != -1) && (i != STDIN_FILENO)) {
dup2(i, STDIN_FILENO);
close (i);
}
i = open (_PATH_DEVNULL, O_WRONLY);
if (i != STDOUT_FILENO)
dup2(i, STDOUT_FILENO);
if (i != STDERR_FILENO)
dup2(i, STDERR_FILENO);
if ((i != -1) && (i != STDOUT_FILENO) && (i != STDERR_FILENO))
close(i);
}
static void detach(void)
{
errno = 0;
switch (fork()) {
case 0: break;
case -1: vpnlog(LOG_ERR, "VPND: detach failed: errno= %d\n", errno);
default: exit(errno) ;
}
}
static void write_pid_file(struct vpn_params *params)
{
FILE *pidfile;
char subtype[OPT_STR_LEN];
subtype[0] = 0;
CFStringGetCString(params->serverSubTypeRef, subtype, OPT_STR_LEN, kCFStringEncodingUTF8);
if (subtype[0])
strcat(pid_path, subtype);
strcat(pid_path, ".pid");
if ((pidfile = fopen(pid_path, "w")) != NULL) {
fprintf(pidfile, "%d\n", getpid());
fclose(pidfile);
} else
vpnlog(LOG_WARNING, "VPND: Failed to create pid file %s: %m", pid_path);
}
static void delete_pid_file(void)
{
remove(pid_path);
}
static char *log_time_string(time_t inNow, char *inTimeString)
{
struct tm *tmTime;
if (!inTimeString)
return 0;
tmTime = localtime(&inNow);
sprintf(inTimeString, "%04d-%02d-%02d %02d:%02d:%02d %s",
tmTime->tm_year + 1900, tmTime->tm_mon + 1, tmTime->tm_mday,
tmTime->tm_hour, tmTime->tm_min, tmTime->tm_sec,
tmTime->tm_zone);
return inTimeString;
}
static void create_log_file(char *inLogPath)
{
char theTime[40];
if ((logfile = fopen(inLogPath, "a")) == 0)
vpnlog(LOG_ERR, "Could not open log file %s.\n", inLogPath);
else {
fcntl(fileno(logfile), F_SETFD, 1);
fprintf(logfile, "#Start-Date: %s\n"
"#Fields: date time s-comment\n",
log_time_string(time(NULL), theTime));
fflush(logfile);
}
}
static void close_log_file(void)
{
char the_time[40];
if (!logfile)
return;
fprintf (logfile, "#End-Date: %s\n", log_time_string(time(NULL), the_time)) ;
fclose(logfile) ;
}
void vpnlog(int nSyslogPriority, char *format_str, ...)
{
va_list args;
time_t tNow;
struct tm *tmTime;
char theTime[40];
if (!params->debug && LOG_PRI(nSyslogPriority) == LOG_DEBUG)
return;
va_start(args, format_str);
tNow = time(NULL);
tmTime = localtime(&tNow);
if (!(nSyslogPriority & LOG_FACMASK))
nSyslogPriority |= LOG_DAEMON;
sprintf(theTime, "%04d-%02d-%02d %02d:%02d:%02d %s\t",
tmTime->tm_year + 1900, tmTime->tm_mon + 1, tmTime->tm_mday,
tmTime->tm_hour, tmTime->tm_min, tmTime->tm_sec,
tmTime->tm_zone);
if (logfile) {
fputs(theTime, logfile);
vfprintf(logfile, format_str, args);
fflush(logfile);
};
if (!logfile || LOG_PRI(nSyslogPriority) != LOG_DEBUG)
vsyslog(nSyslogPriority, format_str, args);
if (stdio_is_valid && (params->debug || (LOG_PRI(nSyslogPriority) <= LOG_WARNING))) {
fputs(theTime, stderr);
vfprintf(stderr, format_str, args);
fflush(stderr);
}
}
void set_terminate(void)
{
terminate = 1;
}
int got_terminate(void)
{
return terminate;
}
int got_sig_chld(void)
{
if (rcvd_sig_child) {
rcvd_sig_child = 0;
return 1;
}
return 0;
}
static void sig_chld(int inSignal)
{
rcvd_sig_child = 1;
}
static void sig_term(int inSignal)
{
terminate = 1;
}
static void setup_signal_handlers(void)
{
struct sigaction sSigAction, sSigOldAction;
sigemptyset (&sSigAction.sa_mask);
sSigAction.sa_flags = 0;
sSigAction.sa_handler = sig_term;
sigaction(SIGTERM, &sSigAction, &sSigOldAction);
sSigAction.sa_handler = sig_term;
sigaction(SIGINT, &sSigAction, &sSigOldAction);
sSigAction.sa_handler = sig_term;
sigaction(SIGHUP, &sSigAction, &sSigOldAction);
sSigAction.sa_flags |= SA_NOCLDSTOP; sSigAction.sa_handler = sig_chld;
sigaction(SIGCHLD, &sSigAction, &sSigOldAction);
}