From 0437f3808ef02ef20fdbc728f1f57c9c2cf3219b Mon Sep 17 00:00:00 2001 From: Martin Hecht Date: Fri, 8 Mar 2019 13:27:58 +0100 Subject: [PATCH 1/2] make the search string for the otp-prompt configurable and update man page --- doc/openfortivpn.1.in | 145 ++++++++++++++++++++++++------------------ src/config.c | 5 ++ src/config.h | 1 + src/http.c | 2 + src/main.c | 11 +++- 5 files changed, 100 insertions(+), 64 deletions(-) diff --git a/doc/openfortivpn.1.in b/doc/openfortivpn.1.in index 2189c7a6..ca279dff 100644 --- a/doc/openfortivpn.1.in +++ b/doc/openfortivpn.1.in @@ -1,4 +1,4 @@ -.TH OPENFORTIVPN 1 "December 18, 2018" "" +.TH OPENFORTIVPN 1 "March 11, 2019" "" .SH NAME openfortivpn \- Client for PPP+SSL VPN tunnel services @@ -9,25 +9,27 @@ openfortivpn \- Client for PPP+SSL VPN tunnel services [\fB\-u\fR \fI\fR] [\fB\-p\fR \fI\fR] [\fB\-\-otp=\fI\fR] +[\fB\-\-otp\-prompt=\fI\fR] [\fB\-\-realm=\fI\fR] -[\fB\-\-set-routes=\fR] -[\fB\-\-no-routes\fR] -[\fB\-\-set-dns=\fR] -[\fB\-\-no-dns\fR] -[\fB\-\-half-internet-routes=\fR] -[\fB\-\-ca-file=\fI\fR] +[\fB\-\-set\-routes=\fR] +[\fB\-\-no\-routes\fR] +[\fB\-\-set\-dns=\fR] +[\fB\-\-no\-dns\fR] +[\fB\-\-half\-internet\-routes=\fR] +[\fB\-\-ca\-file=\fI\fR] [\fB\-\-user-cert=\fI\fR] -[\fB\-\-user-key=\fI\fR] -[\fB\-\-use-syslog\fR] -[\fB\-\-trusted-cert=\fI\fR] -[\fB\-\-insecure-ssl\fR] -[\fB\-\-cipher-list=\fI\fR] -[\fB\-\-pppd-no-peerdns\fR] -[\fB\-\-pppd-log=\fI\fR] -[\fB\-\-pppd-plugin=\fI\fR] -[\fB\-\-pppd-ipparam=\fI\fR] -[\fB\-\-pppd-ifname=\fI\fR] -[\fB\-\-pppd-call=\fI\fR] +[\fB\-\-user\-key=\fI\fR] +[\fB\-\-use\-syslog\fR] +[\fB\-\-trusted\-cert=\fI\fR] +[\fB\-\-insecure\-ssl\fR] +[\fB\-\-cipher\-list=\fI\fR] +[\fB\-\-pppd\-use\-peerdns=\fR] +[\fB\-\-pppd\-no\-peerdns\fR] +[\fB\-\-pppd\-log=\fI\fR] +[\fB\-\-pppd\-plugin=\fI\fR] +[\fB\-\-pppd\-ipparam=\fI\fR] +[\fB\-\-pppd\-ifname=\fI\fR] +[\fB\-\-pppd\-call=\fI\fR] [\fB\-\-persistent=\fI\fR] [\fB\-c\fR \fI\fR] [\fB\-v|\-q\fR] @@ -63,57 +65,60 @@ VPN account password. \fB\-o \fI\fR, \fB\-\-otp=\fI\fR One-Time-Password. .TP +\fB\-\-otp\-prompt=\fI\fR +Search for the otp password prompt starting with the string \fI\fR. +.TP \fB\-\-realm=\fI\fR Connect to the specified authentication realm. Defaults to empty, which is usually what you want. .TP -\fB\-\-set-routes=\fI\fR, \fB\-\-no-routes\fR +\fB\-\-set\-routes=\fI\fR, \fB\-\-no-routes\fR Set if openfortivpn should try to configure IP routes through the VPN when tunnel is up. If used multiple times, the last one takes priority. -\fB\-\-no-routes\fR is the same as \fB\-\-set-routes=\fI0\fR. +\fB\-\-no\-routes\fR is the same as \fB\-\-set-routes=\fI0\fR. .TP -\fB\-\-half-internet-routes=\fI\fR +\fB\-\-half\-internet\-routes=\fI\fR Set if openfortivpn should add two 0.0.0.0/1 and 128.0.0.0/1 routes with higher priority instead of replacing the default route. .TP -\fB\-\-set-dns=\fI\fR, \fB\-\-no-dns\fR +\fB\-\-set\-dns=\fI\fR, \fB\-\-no\-dns\fR Set if openfortivpn should add VPN nameservers in /etc/resolv.conf when tunnel is up. If used multiple times, the last one takes priority. This option requires that the dns entries are requested from the peer. -So, \fB\-\-pppd-no-peerdns\fR conflicts with \fB\-\-set-dns=\fI1\fR. +So, \fB\-\-pppd\-no\-peerdns\fR conflicts with \fB\-\-set\-dns=\fI1\fR. Note that there may be other mechanisms to update /etc/resolv.conf -which may require that openfortivpn is called with \fB\-\-no-dns\fR. +which may require that openfortivpn is called with \fB\-\-no\-dns\fR. -\fB\-\-no-dns\fR is the same as \fB\-\-set-dns=\fI0\fR. +\fB\-\-no\-dns\fR is the same as \fB\-\-set\-dns=\fI0\fR. .TP -\fB\-\-ca-file=\fI\fR +\fB\-\-ca\-file=\fI\fR Use specified PEM-encoded certificate bundle instead of system-wide store to verify the gateway certificate. .TP -\fB\-\-user-cert=\fI\fR +\fB\-\-user\-cert=\fI\fR Use specified PEM-encoded certificate if the server requires authentication with a certificate. .TP -\fB\-\-user-key=\fI\fR +\fB\-\-user\-key=\fI\fR Use specified PEM-encoded key if the server requires authentication with a certificate. .TP -\fB\-\-use-syslog\fR +\fB\-\-use\-syslog\fR Log to syslog instead of terminal. .TP -\fB\-\-trusted-cert=\fI\fR +\fB\-\-trusted\-cert=\fI\fR Trust a given gateway. If classical SSL certificate validation fails, the gateway certificate will be matched against this value. \fI\fR is the X509 certificate's sha256 sum. This option can be used multiple times to trust several certificates. .TP -\fB\-\-insecure-ssl\fR +\fB\-\-insecure\-ssl\fR Do not disable insecure SSL protocols/ciphers. -If your server requires a specific cipher, consider using \fB\-\-cipher-list\fR +If your server requires a specific cipher, consider using \fB\-\-cipher\-list\fR instead. .TP -\fB\-\-cipher-list=\fI\fR +\fB\-\-cipher\-list=\fI\fR Openssl ciphers to use. If default does not work, you can try alternatives such as HIGH:!MD5:!RC4 or as suggested by the Cipher: line in the output of \fBopenssl\fP(1) (e.g. AES256-GCM-SHA384): @@ -122,37 +127,37 @@ $ openssl s_client -connect \fI\fR (default: HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4) .TP -\fB\-\-pppd-no-peerdns\fR +\fB\-\-pppd\-no\-peerdns\fR Do not ask peer ppp server for DNS server addresses and do not make pppd rewrite /etc/resolv.conf. If the DNS server addresses are not requested, -also \fB\-\-set-dns=\fI1\fR has no effect. On the other hand, with -\fB\-\-set-dns=\fI0\fR, when pppd requests DNS server addresses, there -may be othter mechanisms, such as an pppd-ip-up-script that do the update +also \fB\-\-set\-dns=\fI1\fR has no effect. On the other hand, with +\fB\-\-set\-dns=\fI0\fR, when pppd requests DNS server addresses, there +may be othter mechanisms, such as an pppd\-ip\-up-script that do the update of /etc/resolv.conf. .TP -\fB\-\-pppd-log=\fI\fR +\fB\-\-pppd\-log=\fI\fR Set pppd in debug mode and save its logs into \fI\fR. .TP -\fB\-\-pppd-plugin=\fI\fR +\fB\-\-pppd\-plugin=\fI\fR Use specified pppd plugin instead of configuring the resolver and routes directly. .TP -\fB\-\-pppd-ipparam=\fI\fR -Provides an extra parameter to the ip-up, ip-pre-up and ip-down scripts. See man +\fB\-\-pppd\-ipparam=\fI\fR +Provides an extra parameter to the ip\-up, ip\-pre\-up and ip\-down scripts. See man .BR pppd(8) for further details .TP -\fB\-\-pppd-ifname=\fI\fR +\fB\-\-pppd\-ifname=\fI\fR Set the ppp interface name. Only if supported by pppd. Patched versions of pppd implement this option but may not be available on your platform. .TP -\fB\-\-pppd-call=\fI\fR +\fB\-\-pppd\-call=\fI\fR Drop usual arguments from pppd command line and add `call ' instead. This can be useful on Debian and Ubuntu, where unprivileged users in group `dip' can invoke `pppd call ' to make pppd read and apply options from /etc/ppp/peers/ (including privileged ones). .TP -\fB\-\-ppp-system=\fI\fR +\fB\-\-ppp\-system=\fI\fR Only available if compiled for ppp user space client (e.g. on FreeBSD). Connect to the specified system as defined in /etc/ppp/ppp.conf .TP @@ -208,7 +213,7 @@ VPN_ROUTE_MASK_... the network mask for this route VPN_ROUTE_GATEWAY_... the gateway for the current route entry If not compiled for pppd the pppd options and features that rely on them are not -available. On FreeBSD \fB\-\-ppp-system\fR is available instead. +available. On FreeBSD \fB\-\-ppp\-system\fR is available instead. .SH CONFIG FILE Options can be taken from a configuration file. Options passed in the command @@ -221,56 +226,70 @@ An empty template for the config file is installed to A config file looks like: # this is a comment .br -host = vpn-gateway +host = vpn\-gateway .br -port = 8443 +port = 443 .br username = foo .br password = bar .br -user-cert = @SYSCONFDIR@/openfortivpn/user-cert.pem +# realm = some-realm +.br +# useful for a gui that passes a config file to openfortivpn +.br +# otp = 123456 +.br +# otp\-prompt = Please .br -user-key = @SYSCONFDIR@/openfortivpn/user-key.pem +user\-cert = @SYSCONFDIR@/openfortivpn/user\-cert.pem +.br +user\-key = @SYSCONFDIR@/openfortivpn/user\-key.pem .br # the sha256 digest of the trusted host certs obtained by .br -# openssl dgst -sha256 server-cert.pem: +# openssl dgst -sha256 server\-cert.pem: .br -trusted-cert = certificatedigest4daa8c5fe6c... +trusted\-cert = certificatedigest4daa8c5fe6c... .br -trusted-cert = othercertificatedigest6631bf... +trusted\-cert = othercertificatedigest6631bf... .br # This would specify a ca bundle instead of system-wide store .br -# ca-file = @SYSCONFDIR@/openfortivpn/ca-bundle.pem +# ca\-file = @SYSCONFDIR@/openfortivpn/ca\-bundle.pem .br -set-dns = 0 +set\-dns = 0 .br -set-routes = 1 +set\-routes = 1 .br -half-internet-routes = 0 +half\-internet\-routes = 0 .br -pppd-use-peerdns = 1 +pppd\-use\-peerdns = 1 .br # alternatively, use a specific pppd plugin instead .br -# pppd-plugin = /usr/lib/pppd/default/some-plugin.so +# pppd\-plugin = /usr/lib/pppd/default/some\-plugin.so .br # for debugging pppd write logs here .br -# pppd-log = /var/log/pppd.log +# pppd\-log = /var/log/pppd.log .br # pass ppp interface name to pppd (if supported by a patched pppd) .br -# pppd-ifname = ppp1 +# pppd\-ifname = ppp1 .br # pass an ipparam string to pppd, e.g. the device name (a similar use case) .br -# pppd-ipparam = 'device=$DEVICE' +# pppd\-ipparam = 'device=$DEVICE' +.br +# instruct pppd to call a script instead of passing arguments (if pppd supports it) +.br +# pppd\-call = script +.br +# use\-syslog = 0 .br -insecure-ssl = 0 +insecure\-ssl = 0 .br -cipher-list = HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4 +cipher\-list = HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4 .br persistent = 0 diff --git a/src/config.c b/src/config.c index 4cc3b60a..db386204 100644 --- a/src/config.c +++ b/src/config.c @@ -32,6 +32,7 @@ const struct vpn_config invalid_cfg = { .username = {'\0'}, .password = NULL, .otp = {'\0'}, + .otp_prompt = NULL, .realm = {'\0'}, .set_routes = -1, .set_dns = -1, @@ -205,6 +206,9 @@ int load_config(struct vpn_config *cfg, const char *filename) } else if (strcmp(key, "otp") == 0) { strncpy(cfg->otp, val, FIELD_SIZE - 1); cfg->otp[FIELD_SIZE] = '\0'; + } else if (strcmp(key, "otp-prompt") == 0) { + free(cfg->otp_prompt); + cfg->otp_prompt = strdup(val); } else if (strcmp(key, "realm") == 0) { strncpy(cfg->realm, val, FIELD_SIZE - 1); cfg->realm[FIELD_SIZE] = '\0'; @@ -330,6 +334,7 @@ int load_config(struct vpn_config *cfg, const char *filename) void destroy_vpn_config(struct vpn_config *cfg) { free(cfg->password); + free(cfg->otp_prompt); #if HAVE_USR_SBIN_PPPD free(cfg->pppd_log); free(cfg->pppd_plugin); diff --git a/src/config.h b/src/config.h index 35c091f4..e6612c27 100644 --- a/src/config.h +++ b/src/config.h @@ -66,6 +66,7 @@ struct vpn_config { char username[FIELD_SIZE + 1]; char *password; char otp[FIELD_SIZE + 1]; + char *otp_prompt; char realm[FIELD_SIZE + 1]; int set_routes; diff --git a/src/http.c b/src/http.c index 35e2dc23..87863043 100644 --- a/src/http.c +++ b/src/http.c @@ -410,6 +410,8 @@ int try_otp_auth( * Fall back to default prompt if not found/parseable */ p = strstr(s, "Please"); + if (tunnel->config->otp_prompt != NULL) + p = strstr(s, tunnel->config->otp_prompt); if (p) { e = strchr(p, '<'); if (e != NULL) { diff --git a/src/main.c b/src/main.c index 27c5756a..c3769a1f 100644 --- a/src/main.c +++ b/src/main.c @@ -60,7 +60,8 @@ #define usage \ "Usage: openfortivpn [[:]] [-u ] [-p ]\n" \ -" [--realm=] [--otp=] [--set-routes=<0|1>]\n" \ +" [--realm=] [--otp=]\n" \ +" [--otp-prompt=] [--set-routes=<0|1>]\n" \ " [--half-internet-routes=<0|1>] [--set-dns=<0|1>]\n" \ PPPD_USAGE \ " [--ca-file=]\n" \ @@ -87,6 +88,7 @@ PPPD_USAGE \ " -u , --username= VPN account username.\n" \ " -p , --password= VPN account password.\n" \ " -o , --otp= One-Time-Password.\n" \ +" --otp-prompt= Search for the otp prompt starting with this string\n" \ " --realm= Use specified authentication realm on VPN gateway\n" \ " when tunnel is up.\n" \ " --set-routes=[01] Set if openfortivpn should configure output routes through\n" \ @@ -156,6 +158,7 @@ int main(int argc, char **argv) .username = {'\0'}, .password = NULL, .otp = {'\0'}, + .otp_prompt = NULL, .realm = {'\0'}, .set_routes = 1, .set_dns = 1, @@ -190,6 +193,7 @@ int main(int argc, char **argv) {"username", required_argument, 0, 'u'}, {"password", required_argument, 0, 'p'}, {"otp", required_argument, 0, 'o'}, + {"otp-prompt", required_argument, 0, 0}, {"set-routes", required_argument, 0, 0}, {"no-routes", no_argument, &cli_cfg.set_routes, 0}, {"half-internet-routes", required_argument, 0, 0}, @@ -315,6 +319,11 @@ int main(int argc, char **argv) cli_cfg.cipher_list = strdup(optarg); break; } + if (strcmp(long_options[option_index].name, + "otp-prompt") == 0) { + cli_cfg.otp_prompt = strdup(optarg); + break; + } if (strcmp(long_options[option_index].name, "set-routes") == 0) { int set_routes = strtob(optarg); From 8b300ba975d3418f3d2f7a790cafd762126ac94d Mon Sep 17 00:00:00 2001 From: Shogun Date: Mon, 7 Jan 2019 10:18:36 +0100 Subject: [PATCH 2/2] Add --otp-delay option. --- doc/openfortivpn.1.in | 8 ++++++++ src/config.c | 11 +++++++++++ src/config.h | 1 + src/http.c | 12 ++++++++++++ src/main.c | 16 +++++++++++++++- 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/doc/openfortivpn.1.in b/doc/openfortivpn.1.in index ca279dff..24dcb41e 100644 --- a/doc/openfortivpn.1.in +++ b/doc/openfortivpn.1.in @@ -10,6 +10,7 @@ openfortivpn \- Client for PPP+SSL VPN tunnel services [\fB\-p\fR \fI\fR] [\fB\-\-otp=\fI\fR] [\fB\-\-otp\-prompt=\fI\fR] +[\fB\-\-otp\-delay=\fI\fR] [\fB\-\-realm=\fI\fR] [\fB\-\-set\-routes=\fR] [\fB\-\-no\-routes\fR] @@ -68,6 +69,11 @@ One-Time-Password. \fB\-\-otp\-prompt=\fI\fR Search for the otp password prompt starting with the string \fI\fR. .TP +\fB\-\-otp\-delay\=\fI\fR +Set the amount of time to wait before sending the One-Time-Password. +The delay time must be specified in seconds, where 0 means +no wait (this is the default). +.TP \fB\-\-realm=\fI\fR Connect to the specified authentication realm. Defaults to empty, which is usually what you want. @@ -240,6 +246,8 @@ password = bar .br # otp = 123456 .br +# otp\-delay = 0 +.br # otp\-prompt = Please .br user\-cert = @SYSCONFDIR@/openfortivpn/user\-cert.pem diff --git a/src/config.c b/src/config.c index db386204..e969dbe4 100644 --- a/src/config.c +++ b/src/config.c @@ -33,6 +33,7 @@ const struct vpn_config invalid_cfg = { .password = NULL, .otp = {'\0'}, .otp_prompt = NULL, + .otp_delay = -1, .realm = {'\0'}, .set_routes = -1, .set_dns = -1, @@ -209,6 +210,14 @@ int load_config(struct vpn_config *cfg, const char *filename) } else if (strcmp(key, "otp-prompt") == 0) { free(cfg->otp_prompt); cfg->otp_prompt = strdup(val); + } else if (strcmp(key, "otp-delay") == 0) { + long int otp_delay = strtol(val, NULL, 0); + if (otp_delay < 0 || otp_delay > UINT_MAX) { + log_warn("Bad value for otp-delay in config file: \"%s\".\n", + val); + continue; + } + cfg->otp_delay = otp_delay; } else if (strcmp(key, "realm") == 0) { strncpy(cfg->realm, val, FIELD_SIZE - 1); cfg->realm[FIELD_SIZE] = '\0'; @@ -368,6 +377,8 @@ void merge_config(struct vpn_config *dst, struct vpn_config *src) dst->password = strdup(src->password); if (src->otp[0]) strcpy(dst->otp, src->otp); + if (src->otp_delay != invalid_cfg.otp_delay) + dst->otp_delay = src->otp_delay; if (src->realm[0]) strcpy(dst->realm, src->realm); if (src->set_routes != invalid_cfg.set_routes) diff --git a/src/config.h b/src/config.h index e6612c27..2f04e157 100644 --- a/src/config.h +++ b/src/config.h @@ -67,6 +67,7 @@ struct vpn_config { char *password; char otp[FIELD_SIZE + 1]; char *otp_prompt; + unsigned int otp_delay; char realm[FIELD_SIZE + 1]; int set_routes; diff --git a/src/http.c b/src/http.c index 87863043..9a28c41c 100644 --- a/src/http.c +++ b/src/http.c @@ -27,6 +27,7 @@ #include #include #include +#include #define BUFSZ 0x8000 @@ -373,6 +374,14 @@ static int get_auth_cookie( return ret; } +static void delay_otp(struct tunnel *tunnel) +{ + if (tunnel->config->otp_delay > 0) { + log_info("Delaying OTP by %d seconds...\n", tunnel->config->otp_delay); + sleep(tunnel->config->otp_delay); + } +} + static int try_otp_auth( struct tunnel *tunnel, @@ -560,6 +569,8 @@ int auth_log_in(struct tunnel *tunnel) /* Probably one-time password required */ if (strncmp(res, "HTTP/1.1 401 Authorization Required\r\n", 37) == 0) { + delay_otp(tunnel); + ret = try_otp_auth(tunnel, res, &res, &response_size); if (ret != 1) goto end; @@ -614,6 +625,7 @@ int auth_log_in(struct tunnel *tunnel) "&redir=%%2Fremote%%2Findex&just_logged_in=1", username, realm, reqid, polid, group, tokenresponse); + delay_otp(tunnel); ret = http_request( tunnel, "POST", "/remote/logincheck", data, &res, &response_size); diff --git a/src/main.c b/src/main.c index c3769a1f..81fce0ec 100644 --- a/src/main.c +++ b/src/main.c @@ -60,7 +60,7 @@ #define usage \ "Usage: openfortivpn [[:]] [-u ] [-p ]\n" \ -" [--realm=] [--otp=]\n" \ +" [--realm=] [--otp=] [--otp-delay=]\n" \ " [--otp-prompt=] [--set-routes=<0|1>]\n" \ " [--half-internet-routes=<0|1>] [--set-dns=<0|1>]\n" \ PPPD_USAGE \ @@ -89,6 +89,7 @@ PPPD_USAGE \ " -p , --password= VPN account password.\n" \ " -o , --otp= One-Time-Password.\n" \ " --otp-prompt= Search for the otp prompt starting with this string\n" \ +" --otp-delay= Wait seconds before sending the OTP.\n" \ " --realm= Use specified authentication realm on VPN gateway\n" \ " when tunnel is up.\n" \ " --set-routes=[01] Set if openfortivpn should configure output routes through\n" \ @@ -159,6 +160,7 @@ int main(int argc, char **argv) .password = NULL, .otp = {'\0'}, .otp_prompt = NULL, + .otp_delay = 0, .realm = {'\0'}, .set_routes = 1, .set_dns = 1, @@ -194,6 +196,7 @@ int main(int argc, char **argv) {"password", required_argument, 0, 'p'}, {"otp", required_argument, 0, 'o'}, {"otp-prompt", required_argument, 0, 0}, + {"otp-delay", required_argument, 0, 0}, {"set-routes", required_argument, 0, 0}, {"no-routes", no_argument, &cli_cfg.set_routes, 0}, {"half-internet-routes", required_argument, 0, 0}, @@ -346,6 +349,17 @@ int main(int argc, char **argv) cli_cfg.half_internet_routes = half_internet_routes; break; } + if (strcmp(long_options[option_index].name, + "otp-delay") == 0) { + long int otp_delay = strtol(optarg, NULL, 0); + if (otp_delay < 0 || otp_delay > UINT_MAX) { + log_warn("Bad otp-delay option: \"%s\"\n", + optarg); + break; + } + cli_cfg.otp_delay = otp_delay; + break; + } if (strcmp(long_options[option_index].name, "persistent") == 0) { long int persistent = strtol(optarg, NULL, 0);