#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 <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 <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <CoreFoundation/CoreFoundation.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include "../../Family/if_ppplink.h"
#include "vpnd.h"
#include "vpnoptions.h"
#include "vpnplugins.h"
#include "cf_utils.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 rcvd_sig_hup = 0;
static volatile int rcvd_sig_usr1 = 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 sig_hup(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, ...);
static void dump_params(struct vpn_params *params);
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);
}
openlog("vpnd", LOG_PID | LOG_NDELAY, LOG_RAS);
if (process_options(params, argc, argv))
exit(EXIT_OPTION_ERROR);
if (!ppp_available()) {
vpnlog(LOG_ERR, "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);
}
vpnlog(LOG_NOTICE, "Server '%s' starting...\n", params->server_id);
open_dynamic_store(params); init_address_lists();
if (process_prefs(params)) { vpnlog(LOG_ERR, "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, "Initialization of vpnd plugin failed\n");
exit(EXIT_FATAL_ERROR);
}
if (get_plugin_args(params, 0)) {
vpnlog(LOG_ERR, "Error getting arguments from plugin\n");
exit(EXIT_OPTION_ERROR);
}
if (params->debug)
dump_params(params);
setuid(geteuid());
if (params->daemonize) {
stdio_is_valid = 0;
close_dynamic_store(params); detach();
setsid();
redirect_std_file();
open_dynamic_store(params); vpnlog(LOG_NOTICE, "Server '%s' moved to background\n", params->server_id);
}
setup_signal_handlers();
write_pid_file(params);
publish_state(params);
if (set_forwarding(&forwarding, 1))
vpnlog(LOG_ERR, "Cannot activate IP forwarding (error %d)\n", errno);
vpnlog(LOG_NOTICE, "Listening for connections...\n");
accept_connections(params);
if (set_forwarding(0, forwarding))
vpnlog(LOG_ERR, "Cannot reset IP forwarding (error %d)\n", errno);
close_log_file();
delete_pid_file();
vpnlog(LOG_NOTICE, "Server '%s' stopped\n", params->server_id);
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: execve(PATH_VPND, args, NULL); break;
case -1: vpnlog(LOG_ERR, "Attempt to fork new vpnd failed\n") ;
return -1;
default:
vpnlog(LOG_INFO, "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, "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, "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;
if (params->serverSubTypeRef)
CFStringGetCString(params->serverSubTypeRef, subtype, OPT_STR_LEN, kCFStringEncodingUTF8);
if (subtype[0])
strlcat(pid_path, subtype, sizeof(pid_path));
strlcat(pid_path, ".pid", sizeof(pid_path));
if ((pidfile = fopen(pid_path, "w")) != NULL) {
fprintf(pidfile, "%d\n", getpid());
fclose(pidfile);
} else
vpnlog(LOG_WARNING, "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, int maxLen)
{
struct tm *tmTime;
if (!inTimeString)
return 0;
tmTime = localtime(&inNow);
snprintf(inTimeString, maxLen, "%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];
mode_t mask;
mask = umask(S_IWGRP|S_IXGRP|S_IWOTH|S_IXOTH);
logfile = fopen(inLogPath, "a");
mask = umask(mask);
if (logfile == 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, sizeof(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, sizeof(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;
tNow = time(NULL);
tmTime = localtime(&tNow);
snprintf(theTime, sizeof(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) {
va_start(args, format_str);
fputs(theTime, logfile);
vfprintf(logfile, format_str, args);
fflush(logfile);
va_end(args);
}
va_start(args, format_str);
vsyslog(nSyslogPriority, format_str, args);
va_end(args);
if (stdio_is_valid && (params->debug || (LOG_PRI(nSyslogPriority) >= LOG_WARNING))) {
va_start(args, format_str);
fputs(theTime, stderr);
vfprintf(stderr, format_str, args);
fflush(stderr);
va_end(args);
}
}
static void dump_params(struct vpn_params *params)
{
int i;
char *subtype = 0, *servertype = "Unknown";
switch (params->server_type) {
case SERVER_TYPE_IPSEC:
servertype = "IPSec";
break;
case SERVER_TYPE_PPP:
servertype = "PPP";
switch (params->server_subtype) {
case PPP_TYPE_SERIAL:
subtype = "Serial";
break;
case PPP_TYPE_PPPoE:
subtype = "PPPoE";
break;
case PPP_TYPE_PPTP:
subtype = "PPTP";
break;
case PPP_TYPE_L2TP:
subtype = "L2TP";
break;
default:
subtype = "Unknown";
break;
}
break;
}
vpnlog(LOG_DEBUG, "params->daemonize = %d\n", params->daemonize);
vpnlog(LOG_DEBUG, "params->max_sessions = %d\n", params->max_sessions);
vpnlog(LOG_DEBUG, "params->server_id = %s\n", params->server_id);
vpnlog(LOG_DEBUG, "params->server_type = %s\n", servertype);
if (subtype)
vpnlog(LOG_DEBUG, "params->server_subtype = %s\n", subtype);
vpnlog(LOG_DEBUG, "params->lb_enable = %d\n", params->lb_enable);
if (params->lb_enable) {
char str[32];
vpnlog(LOG_DEBUG, "params->lb_interface = %s\n", params->lb_interface);
inet_ntop(AF_INET, ¶ms->lb_cluster_address, str, sizeof(str));
vpnlog(LOG_DEBUG, "params->lb_cluster_address = %s\n", str);
inet_ntop(AF_INET, ¶ms->lb_redirect_address, str, sizeof(str));
vpnlog(LOG_DEBUG, "params->lb_redirect_address = %s\n", str);
vpnlog(LOG_DEBUG, "params->lb_port = %d\n", ntohs(params->lb_port));
}
if (params->plugin_path)
vpnlog(LOG_DEBUG, "params->plugin_path = %s\n", params->plugin_path);
vpnlog(LOG_DEBUG, "params->log_path = %s\n", params->log_path);
if (params->next_arg_index)
vpnlog(LOG_DEBUG, "params->next_arg_index = %d\n", params->next_arg_index);
for (i = 0; i < params->next_arg_index; i++)
vpnlog(LOG_DEBUG, "params->exec_args[%d] = %s\n", i, params->exec_args[i]);
}
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;
}
int got_sig_hup(void)
{
if (rcvd_sig_hup) {
rcvd_sig_hup = 0;
return 1;
}
return 0;
}
int got_sig_usr1(void)
{
if (rcvd_sig_usr1) {
rcvd_sig_usr1 = 0;
return 1;
}
return 0;
}
static void sig_chld(int inSignal)
{
rcvd_sig_child = 1;
}
static void sig_term(int inSignal)
{
vpnlog(LOG_INFO, "terminating on signal %d\n", inSignal);
terminate = 1;
}
static void sig_hup(int inSignal)
{
rcvd_sig_hup = 1;
}
static void sig_usr1(int inSignal)
{
rcvd_sig_usr1 = 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_hup;
sigaction(SIGHUP, &sSigAction, &sSigOldAction);
sSigAction.sa_handler = sig_usr1;
sigaction(SIGUSR1, &sSigAction, &sSigOldAction);
sSigAction.sa_flags |= SA_NOCLDSTOP; sSigAction.sa_handler = sig_chld;
sigaction(SIGCHLD, &sSigAction, &sSigOldAction);
}
void toggle_debug(void)
{
if (params->debug) {
vpnlog(LOG_DEBUG, "debugging disabled\n");
params->debug = 0;
} else {
params->debug = 1;
vpnlog(LOG_DEBUG, "debugging enabled - dumping current parameters\n");
dump_params(params);
}
}
int update_prefs(void)
{
int i;
struct vpn_params *old_params;
int debug = params->debug;
old_params = (struct vpn_params*)malloc(sizeof(struct vpn_params));
if (old_params == 0) {
vpnlog(LOG_ERR, "Could not allocate memory for old preferences");
return -1;
}
*old_params = *params;
bzero(params, sizeof(*params));
params->debug = old_params->debug;
params->server_id = old_params->server_id;
params->daemonize = old_params->daemonize;
begin_address_update();
if (process_prefs(params)) {
vpnlog(LOG_ERR, "Update preferences - Error processing prefs file\n");
goto fail;
}
if (params->server_subtype != old_params->server_subtype) {
vpnlog(LOG_ERR, "Update preferences - server subtype does not match\n");
goto fail;
}
if (get_plugin_args(params, 1)) {
vpnlog(LOG_ERR, "Update preferences - plugin arguments invalid or inconsistent\n");
goto fail;
}
CFRelease(old_params->serverSubTypeRef);
CFRelease(old_params->serverRef);
CFRelease(old_params->serverIDRef);
if (old_params->plugin_path)
free(old_params->plugin_path);
for (i = 0; i < old_params->next_arg_index; i++)
free(old_params->exec_args[i]);
free(old_params);
apply_address_update();
vpnlog(LOG_INFO, "Update of preferences succeeded - settings have been changed\n");
if (debug)
dump_params(params);
return 0;
fail:
cancel_address_update();
if (debug)
dump_params(params);
if (params->serverSubTypeRef)
CFRelease(params->serverSubTypeRef);
if (params->serverRef)
CFRelease(params->serverRef);
if (params->serverIDRef)
CFRelease(params->serverIDRef);
if (params->plugin_path)
free(params->plugin_path);
if (params->plugin_path)
free(params->plugin_path);
for (i = 0; i < params->next_arg_index; i++)
free(params->exec_args[i]);
*params = *old_params;
free(old_params);
vpnlog(LOG_ERR, "Update of preferences failed - settings left unchanged\n");
return 0;
}