Skip to content

Commit

Permalink
Listen for SAML login requests
Browse files Browse the repository at this point in the history
These changes were taken from the original author and all
changes unrelated to SAML were removed.
  • Loading branch information
dsl400 authored and Rainer-Keller committed Sep 15, 2024
1 parent c49663d commit 994fabe
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 3 deletions.
3 changes: 2 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

bin_PROGRAMS = openfortivpn
openfortivpn_SOURCES = src/config.c src/config.h src/hdlc.c src/hdlc.h \
src/http.c src/http.h src/io.c src/io.h src/ipv4.c \
src/http.c src/http.h src/io.c src/io.h \
src/http_server.c src/ipv4.c \
src/ipv4.h src/log.c src/log.h src/tunnel.c \
src/tunnel.h src/main.c src/ssl.h src/xml.c \
src/xml.h src/userinput.c src/userinput.h
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ Examples
openfortivpn vpn-gateway:8443 --username= --password= --user-cert=cert.pem --user-key=key.pem
```

* Connect using SAML login:
```shell
openfortivpn vpn-gateway:8443 --saml-login
```

* Don't set IP routes and don't add VPN nameservers to `/etc/resolv.conf`:
```shell
openfortivpn vpn-gateway:8443 -u foo --no-routes --no-dns --pppd-no-peerdns
Expand Down Expand Up @@ -230,6 +235,12 @@ to authenticate and retrieve a session cookie. This cookie can be fed
to openfortivpn using option `--cookie-on-stdin`. Obviously, such a
solution requires a graphic session.

When started using `--saml-login` the program creates a web server that
accepts SAML login requests. To login using SAML you just have to open
`<your-vpn-domain>/remote/saml/start?redirect=1` and follow the login steps.
At the end of the login process the page will be redirected to
`http://127.0.0.1:8020/?id=<session-id>`

Contributing
------------

Expand Down
4 changes: 4 additions & 0 deletions doc/openfortivpn.1.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ openfortivpn \- Client for PPP+TLS VPN tunnel services
[\fB\-p\fR \fI<pass>\fR]
[\fB\-\-cookie=\fI<cookie>\fR]
[\fB\-\-cookie\-on\-stdin\fR]
[\fB\-\-saml\-login[=\fI<port>\fR]]
[\fB\-\-pinentry=\fI<name>\fR]
[\fB\-\-otp=\fI<otp>\fR]
[\fB\-\-otp\-prompt=\fI<prompt>\fR]
Expand Down Expand Up @@ -81,6 +82,9 @@ A valid cookie (SVPNCOOKIE) to use in place of username and password.
\fB\-\-cookie\-on\-stdin\fR
Read the cookie (SVPNCOOKIE) from standard input.
.TP
\fB\-\-saml\-login[=\fI<port>\fR]
Create http server and accept requests for SAML login
.TP
\fB\-\-pinentry=\fI<name>\fR
The pinentry program to use. Allows supplying the password in a secure manner.
For example: pinentry-gnome3 on Linux, or pinentry-mac on macOS.
Expand Down
2 changes: 2 additions & 0 deletions etc/openfortivpn/config.template
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ host = vpn.example.org
port = 443
username = vpnuser
password = VPNpassw0rd

# saml-login = 8020
7 changes: 7 additions & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ int load_config(struct vpn_config *cfg, const char *filename)
cfg->user_cert = strdup(val);
if (strncmp(cfg->user_cert, "pkcs11:", 7) == 0)
cfg->use_engine = 1;
} else if (strcmp(key, "saml-login") == 0) {
free(cfg->saml_port);
cfg->saml_port = atol(val);
continue;
} else if (strcmp(key, "user-key") == 0) {
free(cfg->user_key);
cfg->user_key = strdup(val);
Expand Down Expand Up @@ -534,6 +538,9 @@ void merge_config(struct vpn_config *dst, struct vpn_config *src)
free(dst->cookie);
dst->cookie = src->cookie;
}
if(src->saml_port != 0){
dst->saml_port = src->saml_port;
}
if (src->pinentry) {
free(dst->pinentry);
dst->pinentry = src->pinentry;
Expand Down
2 changes: 2 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ struct vpn_config {
int password_set;
char otp[OTP_SIZE + 1];
char *cookie;
int saml_port;
char saml_session_id[1024];
char *otp_prompt;
unsigned int otp_delay;
int no_ftm_push;
Expand Down
36 changes: 36 additions & 0 deletions src/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,42 @@ static int try_otp_auth(struct tunnel *tunnel, const char *buffer,
}


/*
* Authenticates to gateway by sending username and password.
*
* @return 1 in case of success
* < 0 in case of error
*/
int saml_login(struct tunnel *tunnel)
{
log_debug("SAML login\n");

int ret;
ssl_connect(tunnel);

char uri[1024];
snprintf(uri, sizeof(uri), "/remote/saml/auth_id?id=%s", tunnel->config->saml_session_id);
char *response;
uint32_t response_size = 0;
ret = http_request(tunnel, "GET", uri, "", &response, &response_size);
if(ret != 1 || response_size <= 15) return ret;
if (memcmp(response, "HTTP/1.1 200 OK", 15) != 0){
log_error("SAML login failed: %s\n", response);
return ret;
}
auth_get_cookie(tunnel, response, response_size);
if (ret == ERR_HTTP_NO_COOKIE){
log_error("SAML login failed: no cookie\n");
return ret;
}

// free(response);



return ret;
}

/*
* Authenticates to gateway by sending username and password.
*
Expand Down
2 changes: 2 additions & 0 deletions src/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#define OPENFORTIVPN_HTTP_H

#include "tunnel.h"
#include "config.h"

#include <stdint.h>

Expand Down Expand Up @@ -55,6 +56,7 @@ static inline const char *err_http_str(int code)
int http_send(struct tunnel *tunnel, const char *request, ...);
int http_receive(struct tunnel *tunnel, char **response, uint32_t *response_size);

int saml_login(struct tunnel *tunnel);
int auth_log_in(struct tunnel *tunnel);
int auth_log_out(struct tunnel *tunnel);
int auth_request_vpn_allocation(struct tunnel *tunnel);
Expand Down
155 changes: 155 additions & 0 deletions src/http_server.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include "config.h"
#include "log.h"
#include <pthread.h>
#include "tunnel.h"
#include <ctype.h>
#include <signal.h>
#include <netinet/tcp.h>



int process_request(int new_socket, char *id) {
log_info("Processing request\n");

int flag = 1;
setsockopt(new_socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));

const int buffer_size = 2048;
char *response[buffer_size];
memset(response,' ',buffer_size);
response[buffer_size - 1] = '\0';
char *reply = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n"
"SAML Login...\r\n\0";
memcpy(response,reply,strlen(reply));

// Read the request
ssize_t write_result = write(new_socket, response, buffer_size);
(void)write_result;

// dup2(new_socket, STDOUT_FILENO);
// dup2(new_socket, STDERR_FILENO);



char request[1024];
ssize_t read_result = read(new_socket, request, sizeof(request));

// Check for '=id' in the response
if (read_result < 0 || strlen(request) < 10 || strncmp(request, "GET /?id=", 9) != 0) {
log_error("Bad request\n");
return -1;
}

// Extract the id
const int id_start = 9;
char *id_end = memchr(&request[id_start], ' ', 1000);

if (id_end == NULL) {
log_error("Bad request format\n");
return -1;
}

int id_length = id_end - &request[id_start];

if(id_length == 0 || id_length > 1000) {
log_error("Bad request id\n");
return -1;
}

strncpy(id, &request[id_start], id_length);

for (int i = 0; i < id_length; i++) {
if (isalnum(id[i]) || id[i] == '-') continue;
log_error("Invalid id format\n");
return -1;
}
// close(new_socket);
log_info("Extracted id: %s\n", id);
return 0;
}

/**
* run a http server to listen for saml login requests
*/
void* start_http_server(void *void_config) {
struct vpn_config *config = (struct vpn_config *)void_config;

int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
long saml_port = config->saml_port;

// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
log_error("Failed to create socket\n");
return NULL;
}

// Forcefully attaching socket to the port
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
log_error("Failed to set socket options\n");
return NULL;
}

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(saml_port);

// Forcefully attaching socket to the port
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
log_error("Failed to bind socket to port %d \n",saml_port);
return NULL;
}

if (listen(server_fd, 3) < 0) {
log_error("Failed to listen on socket\n");
return NULL;
}
log_info("Listening for saml login on port: %d\n", saml_port);
int running = 1;

pthread_t vpn_thread = 0;


while(running) {
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
log_error("Failed to accept connection\n");
continue;
}

int result = process_request(new_socket, config->saml_session_id);
if(result != 0) {
log_error("Failed to process request\n");
continue;
}


// Kill previous thread if it exists
if (vpn_thread != 0) {
log_error("Stop existing tunnel\n");
pthread_kill(vpn_thread,SIGTERM);
pthread_join(vpn_thread, NULL);
}

int thread_create_result = pthread_create(&vpn_thread, NULL, run_tunnel_wrapper, (void *)config);
if (thread_create_result != 0) {
log_error("Failed to create VPN thread\n");
continue;
}


// pthread_detach(vpn_thread);

result = 0;
}

return NULL;
}

3 changes: 3 additions & 0 deletions src/http_server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "config.h"

void* start_http_server(void *void_config);
35 changes: 33 additions & 2 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "tunnel.h"
#include "userinput.h"
#include "log.h"
#include "http_server.h"

#include <openssl/ssl.h>

Expand Down Expand Up @@ -77,7 +78,7 @@

#define usage \
"Usage: openfortivpn [<host>[:<port>]] [-u <user>] [-p <pass>]\n" \
" [--cookie=<cookie>] [--cookie-on-stdin]\n" \
" [--cookie=<cookie>] [--cookie-on-stdin] [--saml-login]\n" \
" [--otp=<otp>] [--otp-delay=<delay>] [--otp-prompt=<prompt>]\n" \
" [--pinentry=<program>] [--realm=<realm>]\n" \
" [--ifname=<ifname>] [--set-routes=<0|1>]\n" \
Expand Down Expand Up @@ -117,6 +118,7 @@ PPPD_USAGE \
" -p <pass>, --password=<pass> VPN account password.\n" \
" --cookie=<cookie> A valid session cookie (SVPNCOOKIE).\n" \
" --cookie-on-stdin Read the cookie (SVPNCOOKIE) from standard input.\n" \
" --saml-login[=port] Run a http server to handle SAML login requests\n" \
" -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" \
Expand Down Expand Up @@ -225,6 +227,8 @@ int main(int argc, char *argv[])
.password = {'\0'},
.password_set = 0,
.cookie = NULL,
.saml_port = 0,
.saml_session_id = NULL,
.otp = {'\0'},
.otp_prompt = NULL,
.otp_delay = 0,
Expand Down Expand Up @@ -286,6 +290,7 @@ int main(int argc, char *argv[])
{"password", required_argument, NULL, 'p'},
{"cookie", required_argument, NULL, 0},
{"cookie-on-stdin", no_argument, NULL, 0},
{"saml-login", optional_argument, NULL, 0},
{"otp", required_argument, NULL, 'o'},
{"otp-prompt", required_argument, NULL, 0},
{"otp-delay", required_argument, NULL, 0},
Expand Down Expand Up @@ -604,6 +609,19 @@ int main(int argc, char *argv[])
free(cookie);
break;
}
if (strcmp(long_options[option_index].name,
"saml-login") == 0) {
long port = 8020;
if(optarg != NULL){
port = strtol(optarg, NULL, 0);
}
if (port < 0 || port > UINT_MAX) {
log_warn("Invalid saml listen port: %s! Default port is 8020 \n", optarg);
break;
}
cli_cfg.saml_port = port;
break;
}
goto user_error;
case 'h':
printf("%s%s%s%s%s%s%s", usage, summary,
Expand Down Expand Up @@ -707,7 +725,7 @@ int main(int argc, char *argv[])
goto user_error;
}
// Check username
if (cfg.username[0] == '\0' && !cfg.cookie)
if (cfg.username[0] == '\0' && !cfg.cookie && !cfg.saml_port)
// Need either username or cert
if (cfg.user_cert == NULL) {
log_error("Specify a username.\n");
Expand Down Expand Up @@ -738,6 +756,19 @@ int main(int argc, char *argv[])
goto exit;
}

if(cfg.saml_port != 0) {
pthread_t server_thread;

if(pthread_create(&server_thread, NULL, start_http_server, &cfg) != 0){
log_error("Failed to create saml login server thread\n");
// ret = EXIT_FAILURE;
goto exit;
}
// log_debug("Running http server on port %d\n", cfg.saml_port);
while(get_sig_received() == 0);
goto exit;
}

do {
if (run_tunnel(&cfg) != 0)
ret = EXIT_FAILURE;
Expand Down
Loading

0 comments on commit 994fabe

Please sign in to comment.