Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const struct vpn_config invalid_cfg = {
.otp = {'\0'},
.otp_prompt = NULL,
.otp_delay = -1,
.token_script = NULL,
.no_ftm_push = -1,
.pinentry = NULL,
.realm = {'\0'},
Expand Down Expand Up @@ -266,6 +267,9 @@ 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, "2fa-token-script") == 0) {
free(cfg->token_script);
cfg->token_script = strdup(val);
} else if (strcmp(key, "otp-delay") == 0) {
long otp_delay = strtol(val, NULL, 0);

Expand Down Expand Up @@ -495,6 +499,7 @@ int load_config(struct vpn_config *cfg, const char *filename)
void destroy_vpn_config(struct vpn_config *cfg)
{
free(cfg->otp_prompt);
free(cfg->token_script);
free(cfg->pinentry);
free(cfg->cookie);
#if HAVE_USR_SBIN_PPPD
Expand Down Expand Up @@ -538,6 +543,8 @@ void merge_config(struct vpn_config *dst, struct vpn_config *src)
strcpy(dst->otp, src->otp);
if (src->otp_delay != invalid_cfg.otp_delay)
dst->otp_delay = src->otp_delay;
if (src->token_script != invalid_cfg.token_script)
dst->token_script = src->token_script;
if (src->no_ftm_push != invalid_cfg.no_ftm_push)
dst->no_ftm_push = src->no_ftm_push;
if (src->cookie != invalid_cfg.cookie) {
Expand Down
1 change: 1 addition & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ struct vpn_config {
uint16_t saml_port;
char saml_session_id[MAX_SAML_SESSION_ID_LENGTH + 1];
char *otp_prompt;
char *token_script;
unsigned int otp_delay;
int no_ftm_push;
char *pinentry;
Expand Down
71 changes: 59 additions & 12 deletions src/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -795,22 +795,69 @@ int auth_log_in(struct tunnel *tunnel)
snprintf(tokenparams, sizeof(tokenparams), "ftmpush=1");
} else {
if (cfg->otp[0] == '\0') {
// Interactively ask user for 2FA token
char hint[USERNAME_SIZE + 1 + REALM_SIZE + 1 + GATEWAY_HOST_SIZE + 5];

sprintf(hint, "%s_%s_%s_2fa",
cfg->username, cfg->realm, cfg->gateway_host);
read_password(cfg->pinentry, hint,
"Two-factor authentication token: ",
cfg->otp, OTP_SIZE);

if (cfg->otp[0] == '\0') {
log_error("No token specified\n");
return 0;
if (cfg->token_script != NULL && cfg->token_script[0] != '\0') {
// Use the script to retrieve the 2fa token
FILE *fp_script;

if (access(cfg->token_script, F_OK) != 0) {
log_error("2FA token script '%s' not found.\n", cfg->token_script);
return 0;
}

if (access(cfg->token_script, X_OK) != 0) {
log_error("2FA token script '%s' does not have execution permission.\n", cfg->token_script);
return 0;
}

int status;

log_info("Executing 2FA token script: %s\n", cfg->token_script);
fp_script = popen(cfg->token_script, "r");
if (fp_script == NULL) {
log_error("Unable to execute the script.\n");
return 0;
}

char *fgets_ret = fgets(cfg->otp, OTP_SIZE, fp_script);

status = pclose(fp_script);

if (status == -1) {
log_error("Error while closing script process.\n");
return 0;
}

if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
log_error("2FA token script failed with exit code %d.\n", WEXITSTATUS(status));
return 0;
}

if (fgets_ret != NULL) {
cfg->otp[strcspn(cfg->otp, "\r\n ")] = 0;
log_info("2FA token successfully retrieved from script.\n");
} else {
log_error("The script terminated successfully but didn't output any token.\n");
return 0;
}
} else {
// Interactively ask user for 2FA token
char hint[USERNAME_SIZE + 1 + REALM_SIZE + 1 + GATEWAY_HOST_SIZE + 5];

sprintf(hint, "%s_%s_%s_2fa",
cfg->username, cfg->realm, cfg->gateway_host);
read_password(cfg->pinentry, hint,
"Two-factor authentication token: ",
cfg->otp, OTP_SIZE);

if (cfg->otp[0] == '\0') {
log_error("No token specified\n");
return 0;
}
}
}

url_encode(tokenresponse, cfg->otp);
memset(cfg->otp, '\0', OTP_SIZE + 1); // clear OTP for next run
snprintf(tokenparams, sizeof(tokenparams),
"code=%s&code2=&magic=%s",
tokenresponse, magic);
Expand Down
12 changes: 12 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"Usage: openfortivpn [<host>[:<port>]] [-u <user>] [-p <pass>]\n" \
" [--cookie=<cookie>] [--cookie-on-stdin] [--saml-login]\n" \
" [--otp=<otp>] [--otp-delay=<delay>] [--otp-prompt=<prompt>]\n" \
" [--2fa-token-script=<path>]\n" \
" [--pinentry=<program>] [--realm=<realm>]\n" \
" [--ifname=<ifname>] [--set-routes=<0|1>]\n" \
" [--half-internet-routes=<0|1>] [--set-dns=<0|1>]\n" \
Expand Down Expand Up @@ -122,6 +123,10 @@ PPPD_USAGE \
" -o <otp>, --otp=<otp> One-Time-Password.\n" \
" --otp-prompt=<prompt> Search for the OTP prompt starting with this string.\n" \
" --otp-delay=<delay> Wait <delay> seconds before sending the OTP.\n" \
" --2fa-token-script=<path> Path to an external script to retrieve the 2FA token.\n" \
" The script must return 0 exit code and print the token to stdout.\n" \
" Any output other than the token (e.g. logs) must be printed to stderr.\n" \
" In case of error it must return a non 0 exit code.\n" \
" --no-ftm-push Do not use FTM push if the server provides the option.\n" \
" --pinentry=<program> Use the program to supply a secret instead of asking for it.\n" \
" --realm=<realm> Use specified authentication realm.\n" \
Expand Down Expand Up @@ -294,6 +299,7 @@ int main(int argc, char *argv[])
{"otp", required_argument, NULL, 'o'},
{"otp-prompt", required_argument, NULL, 0},
{"otp-delay", required_argument, NULL, 0},
{"2fa-token-script", required_argument, NULL, 0},
{"no-ftm-push", no_argument, &cli_cfg.no_ftm_push, 1},
{"ifname", required_argument, NULL, 0},
{"set-routes", required_argument, NULL, 0},
Expand Down Expand Up @@ -508,6 +514,12 @@ int main(int argc, char *argv[])
cli_cfg.otp_prompt = strdup(optarg);
break;
}
if (strcmp(long_options[option_index].name,
"2fa-token-script") == 0) {
free(cli_cfg.token_script);
cli_cfg.token_script = strdup(optarg);
break;
}
if (strcmp(long_options[option_index].name,
"ifname") == 0) {
strncpy(cli_cfg.iface_name, optarg, IF_NAMESIZE - 1);
Expand Down