diff --git a/openforti.config b/openforti.config new file mode 100644 index 00000000..47f37a95 --- /dev/null +++ b/openforti.config @@ -0,0 +1,12 @@ +host = vpn.mobica.com +port = 443 +username = miob +password = 2L3xccJV +set-routes = 1 +set-dns = 1 +pppd-use-peerdns = 0 +pppd-log = 1 +# X509 certificate sha256 sum, trust only this one! +trusted-cert = 779ff4a05c011aa92454c45e71ee7857032f7470ad40de62a0aa56809aee3db9 +status-file = /tmp/openforti_status +status-interval = 10 diff --git a/src/config.c b/src/config.c index d30e7a63..bd23a75d 100644 --- a/src/config.c +++ b/src/config.c @@ -86,6 +86,8 @@ const struct vpn_config invalid_cfg = { .user_agent = NULL, .hostcheck = NULL, .check_virtual_desktop = NULL, + .status_file = {'\0'}, + .status_interval = 0 }; /* @@ -458,6 +460,12 @@ int load_config(struct vpn_config *cfg, const char *filename) } else if (strcmp(key, "check-virtual-desktop") == 0) { free(cfg->check_virtual_desktop); cfg->check_virtual_desktop = strdup(val); + } else if (strcmp(key, "status-file") == 0) { + strcpy(cfg->status_file, val); + } else if (strcmp(key, "status-interval") == 0) { + unsigned long status_interval = strtoul(val, NULL, 0); + + cfg->status_interval = status_interval; } else { log_warn("Bad key in config file: \"%s\".\n", key); goto err_free; @@ -614,4 +622,8 @@ void merge_config(struct vpn_config *dst, struct vpn_config *src) dst->hostcheck = src->hostcheck; if (src->check_virtual_desktop != invalid_cfg.check_virtual_desktop) dst->check_virtual_desktop = src->check_virtual_desktop; + if (src->status_file[0]) + strcpy(dst->status_file, src->status_file); + if (src->status_interval != invalid_cfg.status_interval) + dst->status_interval = src->status_interval; } diff --git a/src/config.h b/src/config.h index f7783e1f..54d685b9 100644 --- a/src/config.h +++ b/src/config.h @@ -64,6 +64,7 @@ struct x509_digest { #define OTP_SIZE 64 #define REALM_SIZE 63 #define PEM_PASSPHRASE_SIZE 31 +#define STATUS_FILE_SIZE 253 /* * RFC 6265 does not limit the size of cookies: @@ -96,6 +97,8 @@ struct vpn_config { char *pinentry; char iface_name[IF_NAMESIZE]; char realm[REALM_SIZE + 1]; + char status_file[STATUS_FILE_SIZE + 1]; + unsigned int status_interval; int set_routes; int set_dns; diff --git a/src/io.c b/src/io.c index ae092e13..20a706d9 100644 --- a/src/io.c +++ b/src/io.c @@ -125,6 +125,7 @@ static struct in_addr ip_addr; static struct in_addr ns1_addr; static struct in_addr ns2_addr; static char dns_suffix[MAX_DOMAIN_LENGTH]; +static const char *status_file; int get_sig_received(void) { @@ -594,7 +595,6 @@ static void *if_config(void *arg) struct tunnel *tunnel = (struct tunnel *) arg; int timeout = 60000000; // one minute - current_state = &tunnel->state; log_debug("%s thread\n", __func__); // Wait for the right moment to configure IP interface @@ -625,28 +625,47 @@ static void *if_config(void *arg) return NULL; } -static void print_statistics(void) +static void save_stats(void) { + FILE *fp; + const char *state_str; + if (current_state != NULL) { switch (*current_state) { case STATE_DOWN: { - log_info("State: disconnected\n"); + state_str = "State: disconnected"; break; } case STATE_CONNECTING: { - log_info("State: connecting\n"); + state_str = "State: connecting"; break; } case STATE_UP: { - log_info("State: connected\n"); + state_str = "State: connected"; break; } case STATE_DISCONNECTING: { - log_info("State: disconnecting\n"); + state_str = "State: disconnecting"; break; } } } + if (status_file[0] != '\0') { + fp = fopen(status_file, "w"); + if (fp != NULL) { + fprintf(fp, "State: %s\n", state_str); + fprintf(fp, "Received bytes: %lu\n", total_received_bytes); + fprintf(fp, "Transmitted bytes: %lu\n", total_transmitted_bytes); + fprintf(fp, "IP Address: %s\n", inet_ntoa(ip_addr)); + fprintf(fp, "NSS1 Address: %s\n", inet_ntoa(ns1_addr)); + fprintf(fp, "NSS2 Address: %s\n", inet_ntoa(ns2_addr)); + fprintf(fp, "Domain: %s\n", dns_suffix); + fclose(fp); + } else { + log_error(" Cannot create status file!\n"); + } + } + log_info("State: %s\n", state_str); log_info("Received bytes: %lu\n", total_received_bytes); log_info("Transmitted bytes: %lu\n", total_transmitted_bytes); log_info("IP Address: %s\n", inet_ntoa(ip_addr)); @@ -655,10 +674,33 @@ static void print_statistics(void) log_info("Domain: %s\n", dns_suffix); } +/* + * Thread to print statistics + */ +static void *status_thread(void *arg) +{ + struct tunnel *tunnel = (struct tunnel *) arg; + + log_debug("%s thread\n", __func__); + + // Wait for the right moment to configure IP interface + SEM_WAIT(&sem_if_config); + if (tunnel->config->status_file[0] != '\0') { + while (1) { + current_state = &tunnel->state; + save_stats(); + log_debug("Sleeping statistics thread for %u seconds...", + tunnel->config->status_interval); + sleep(tunnel->config->status_interval); + } + } + return NULL; +} + static void sig_handler(int signo) { if (signo == SIGUSR1) { - print_statistics(); + save_stats(); } else { sig_received = signo; if (signo == SIGINT || signo == SIGTERM) @@ -677,6 +719,7 @@ int io_loop(struct tunnel *tunnel) pthread_t ssl_read_thread; pthread_t ssl_write_thread; pthread_t if_config_thread; + pthread_t stats_thread; SEM_INIT(&sem_pppd_ready, 0, 0); SEM_INIT(&sem_if_config, 0, 0); @@ -688,6 +731,7 @@ int io_loop(struct tunnel *tunnel) init_ssl_locks(); init_hdlc(); + status_file = tunnel->config->status_file; /* * I noticed that using TCP_NODELAY (i.e. disabling Nagle's algorithm) @@ -759,6 +803,14 @@ int io_loop(struct tunnel *tunnel) goto err_thread; } + if (status_file[0] != '\0' && tunnel->config->status_interval != 0) { + ret = pthread_create(&stats_thread, NULL, status_thread, tunnel); + if (ret != 0) { + log_debug("Error creating status_thread: %s\n", strerror(ret)); + goto err_thread; + } + } + #if !HAVE_MACH_MACH_H // Restore the signal for the main thread pthread_sigmask(SIG_SETMASK, &oldset, NULL); @@ -772,6 +824,11 @@ int io_loop(struct tunnel *tunnel) ret = pthread_cancel(if_config_thread); if (ret != 0) log_debug("Error canceling if_config_thread: %s\n", strerror(ret)); + if (status_file[0] != '\0') { + ret = pthread_cancel(stats_thread); + if (ret != 0) + log_debug("Error canceling stats_thread: %s\n", strerror(ret)); + } ret = pthread_cancel(ssl_write_thread); if (ret != 0) @@ -791,6 +848,13 @@ int io_loop(struct tunnel *tunnel) log_info("Cleanup, joining threads...\n"); // failure to clean is a possible zombie thread, consider it fatal + if (status_file[0] != '\0' && tunnel->config->status_interval != 0) { + ret = pthread_join(stats_thread, NULL); + if (ret != 0) { + log_debug("Error joining stats_thread: %s\n", strerror(ret)); + fatal = 1; + } + } ret = pthread_join(if_config_thread, NULL); if (ret != 0) { log_debug("Error joining if_config_thread: %s\n", strerror(ret)); diff --git a/src/main.c b/src/main.c index 1ba130a2..11b69f0f 100644 --- a/src/main.c +++ b/src/main.c @@ -143,7 +143,11 @@ PPPD_USAGE \ " certificate will be matched against this value.\n" \ " is the X509 certificate's sha256 sum.\n" \ " This option can be used multiple times to trust\n" \ -" several certificates.\n" +" several certificates.\n" \ +" --status Create a status file with statistics.\n" \ +" --status-interval Interval between updating status file. \n " \ +" If status file is not set, then print it on log.\n" \ +" Printing is disabled by default.\n" #define help_options_part2 \ " --insecure-ssl Do not disable insecure SSL protocols/ciphers.\n" \ @@ -208,6 +212,8 @@ int main(int argc, char **argv) .use_syslog = 0, .half_internet_routes = 0, .persistent = 0, + .status_file = {'\0'}, + .status_interval = 0, #if HAVE_RESOLVCONF .use_resolvconf = USE_RESOLVCONF, #endif @@ -270,6 +276,8 @@ int main(int argc, char **argv) {"cipher-list", required_argument, NULL, 0}, {"min-tls", required_argument, NULL, 0}, {"seclevel-1", no_argument, &cli_cfg.seclevel_1, 1}, + {"status-file", required_argument, NULL, 0}, + {"status-interval", required_argument, NULL, 0}, #if HAVE_USR_SBIN_PPPD {"pppd-use-peerdns", required_argument, NULL, 0}, {"pppd-no-peerdns", no_argument, &cli_cfg.pppd_use_peerdns, 0}, @@ -307,6 +315,7 @@ int main(int argc, char **argv) /* If this option set a flag, do nothing else now. */ if (long_options[option_index].flag != 0) break; + log_debug("%s\n", long_options[option_index].name); if (strcmp(long_options[option_index].name, "version") == 0) { printf(VERSION "\n"); @@ -509,6 +518,26 @@ int main(int argc, char **argv) cli_cfg.set_dns = set_dns; break; } + if (strcmp(long_options[option_index].name, + "status-file") == 0) { + log_debug(" COpying %s\n", optarg); + strncpy(cli_cfg.status_file, optarg, STATUS_FILE_SIZE); + cli_cfg.status_file[STATUS_FILE_SIZE] = '\0'; + log_debug(" COpying %s\n", cli_cfg.status_file); + break; + } + if (strcmp(long_options[option_index].name, + "status-interval") == 0) { + long status_interval = strtol(optarg, NULL, 0); + + if (status_interval < 0 || status_interval > UINT_MAX) { + log_warn("Bad status_interval option: \"%s\"\n", + optarg); + break; + } + cli_cfg.status_interval = status_interval; + break; + } goto user_error; case 'h': printf("%s%s%s%s%s%s%s", usage, summary, @@ -625,6 +654,9 @@ int main(int argc, char **argv) log_debug_all("Config password = \"%s\"\n", cfg.password); if (cfg.otp[0] != '\0') log_debug("One-time password = \"%s\"\n", cfg.otp); + if (cfg.status_file[0] != '\0') + log_debug("Config status-file = \"%s\"\n", cfg.status_file); + log_debug("Config status-interval = \"%u\"\n", cfg.status_interval); if (geteuid() != 0) { log_error("This process was not spawned with root privileges, which are required.\n");