diff --git a/Makefile.am b/Makefile.am index 3b59e0d9..5afafbb2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,7 +5,8 @@ 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/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 + src/xml.h src/userinput.c src/userinput.h \ + src/idlistener.c src/idlistener.h openfortivpn_CPPFLAGS = -DSYSCONFDIR=\"$(sysconfdir)\" \ -DPPP_PATH=\"@PPP_PATH@\" \ -DNETSTAT_PATH=\"@NETSTAT_PATH@\" \ diff --git a/src/config.c b/src/config.c index 9c6d3c5d..0523d7b6 100644 --- a/src/config.c +++ b/src/config.c @@ -87,6 +87,8 @@ const struct vpn_config invalid_cfg = { .user_agent = NULL, .hostcheck = NULL, .check_virtual_desktop = NULL, + .auth_id = NULL, + .listen_port = 0 }; /* @@ -466,6 +468,17 @@ 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, "ext-browser-saml") == 0) { + long port = 8020; + if (val != NULL) { + port = strtol(val, NULL, 0); + if (port < 1 || port > 65535) { + log_error("Bad ext-browser-saml port in configuration file: \"%s\".\n", + val); + goto err_free; + } + } + cfg->listen_port = (uint16_t) port; } else { log_warn("Bad key in configuration file: \"%s\".\n", key); goto err_free; @@ -534,6 +547,13 @@ void merge_config(struct vpn_config *dst, struct vpn_config *src) free(dst->cookie); dst->cookie = src->cookie; } + if (src->auth_id != invalid_cfg.auth_id) { + free(dst->auth_id); + dst->auth_id = src->auth_id; + } + if (src->listen_port != invalid_cfg.listen_port) { + dst->listen_port = src->listen_port; + } if (src->pinentry) { free(dst->pinentry); dst->pinentry = src->pinentry; diff --git a/src/config.h b/src/config.h index eaf7f825..e6573c91 100644 --- a/src/config.h +++ b/src/config.h @@ -90,6 +90,8 @@ struct vpn_config { char password[PASSWORD_SIZE + 1]; int password_set; char otp[OTP_SIZE + 1]; + uint16_t listen_port; + char *auth_id; char *cookie; char *otp_prompt; unsigned int otp_delay; diff --git a/src/http.c b/src/http.c index 82d33d6e..4e8f7cd5 100644 --- a/src/http.c +++ b/src/http.c @@ -47,7 +47,7 @@ * @param[out] dest the buffer to write the URL-encoded string * @param[in] str the input string to be escaped */ -static void url_encode(char *dest, const char *str) +void url_encode(char *dest, const char *str) { while (*str != '\0') { if (isalnum(*str) || *str == '-' || *str == '_' || @@ -667,7 +667,13 @@ int auth_log_in(struct tunnel *tunnel) tunnel->cookie[0] = '\0'; - if (username[0] == '\0' && tunnel->config->password[0] == '\0') { + if (tunnel->config->auth_id != NULL) { + char url[256]; + snprintf(url,sizeof(url), "/remote/saml/auth_id?id=%s", + tunnel->config->auth_id); + ret = http_request(tunnel, "GET", url, "", &res, + &response_size); + }else if (username[0] == '\0' && tunnel->config->password[0] == '\0') { ret = http_request(tunnel, "GET", "/remote/login", data, &res, &response_size); } else { diff --git a/src/http.h b/src/http.h index a4a07e1b..74641df4 100644 --- a/src/http.h +++ b/src/http.h @@ -31,6 +31,19 @@ #define ERR_HTTP_PERMISSION -6 #define ERR_HTTP_NO_COOKIE -7 + +/* + * URL-encodes a string for HTTP requests. + * + * The dest buffer size MUST be at least strlen(str) * 3 + 1. + * + * @param[out] dest the buffer to write the URL-encoded string + * @param[in] str the input string to be escaped + */ +void url_encode(char *dest, const char *str); + + + static inline const char *err_http_str(int code) { if (code > 0) diff --git a/src/idlistener.c b/src/idlistener.c new file mode 100644 index 00000000..4e507939 --- /dev/null +++ b/src/idlistener.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2024 Filippo Rossoni + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "idlistener.h" +#include "log.h" +#include "http.h" +#include +#include +#include +#include +#include +#include + +#define MAX_REQUEST_SIZE 4096 + +static void print_url(struct vpn_config *cfg) { + char url[512]; + snprintf(url, sizeof(url), "https://%s:%d/remote/saml/start?redirect=1", + cfg->gateway_host, cfg->gateway_port); + + if (cfg->realm[0] != '\0') { + strncat(url, "&realm=", sizeof(url) - 1); + char *dt = url + strlen(url); + url_encode(dt, cfg->realm); + } + log_info("Authenticate at %s\n", url); +} + +// Function to parse HTTP request and extract parameter "id" +static char* parse_request(const char *request) { + char *id_param; + char *query_start = strchr(request, '?'); + if (query_start != NULL) { + id_param = strstr(query_start, "id="); + if (id_param != NULL) { + id_param += 3; // Length of "id=" + char *id_end = strchr(id_param, '&'); + if (id_end == NULL) { + id_end = strchr(id_param, ' '); + } + if (id_end == NULL) { + id_end = strchr(id_param, '\r'); + } + if (id_end == NULL) { + id_end = id_param + strlen(id_param); // End of string + } + *id_end = '\0'; // Null-terminate the string + return strdup(id_param); + + } + } + return NULL; +} + +void send_response(int sockfd, const char *message) { + char response[MAX_REQUEST_SIZE]; + sprintf(response, "HTTP/1.1 200 OK\r\n" + "Content-Length: %lu\r\n" + "Content-Type: text/html\r\n\r\n" + "%s", strlen(message), message); + write(sockfd, response, strlen(response)); +} + +char* listen_for_id(struct vpn_config *cfg) { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + log_error("Error opening socket"); + exit(1); + } + + struct sockaddr_in serv_addr; + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + serv_addr.sin_port = htons(cfg->listen_port); + + int opt = 1; + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + log_error("Error setting SO_REUSEADDR"); + } + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(int)) < 0) { + log_error("error set SO_REUSEPORT"); + } + + if (bind(sockfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) { + perror("Error on binding"); + exit(1); + } + + + + listen(sockfd, 1); + log_debug("Server listening on port %d to retrieve the id\n", + cfg->listen_port); + + print_url(cfg); + + // Accept incoming connections + struct sockaddr_in cli_addr; + socklen_t clilen = sizeof(cli_addr); + int newsockfd = accept(sockfd, &cli_addr, &clilen); + if (newsockfd < 0) { + log_error("Error on accept"); + return NULL; + } + close(sockfd); + + // Read HTTP request from client + char buffer[MAX_REQUEST_SIZE]; + bzero(buffer, MAX_REQUEST_SIZE); + read(newsockfd, buffer, MAX_REQUEST_SIZE - 1); + log_debug("Received HTTP request:\n%s\n", buffer); + + char *id = parse_request(buffer); + if (id != NULL) { + log_debug("Extracted id: %s\n", id); + // Send response to client + send_response(newsockfd, + "

ID retrieved. Connecting...!

"); + } else { + log_error("id parameter not found\n"); + send_response(newsockfd, + "

ERROR! id not found

"); + } + close(newsockfd); + + return id; +} + diff --git a/src/idlistener.h b/src/idlistener.h new file mode 100644 index 00000000..48544062 --- /dev/null +++ b/src/idlistener.h @@ -0,0 +1,15 @@ +/* + * cookieRetriever.h + * + * Created on: 28 apr 2024 + * Author: filippor + */ + +#ifndef SRC_IDLISTENER_H_ +#define SRC_IDLISTENER_H_ +#include +#include "config.h" + +char *listen_for_id(struct vpn_config *cfg ); + +#endif /* SRC_IDLISTENER_H_ */ diff --git a/src/main.c b/src/main.c index c1fba334..e07bd061 100644 --- a/src/main.c +++ b/src/main.c @@ -18,8 +18,8 @@ #include "config.h" #include "tunnel.h" #include "userinput.h" +#include "idlistener.h" #include "log.h" - #include #include @@ -78,6 +78,7 @@ #define usage \ "Usage: openfortivpn [[:]] [-u ] [-p ]\n" \ " [--cookie=] [--cookie-on-stdin]\n" \ +" [--ext-browser-saml[=]] [--auth-id=]\n" \ " [--otp=] [--otp-delay=] [--otp-prompt=]\n" \ " [--pinentry=] [--realm=]\n" \ " [--ifname=] [--set-routes=<0|1>]\n" \ @@ -117,6 +118,10 @@ PPPD_USAGE \ " -p , --password= VPN account password.\n" \ " --cookie= A valid session cookie (SVPNCOOKIE).\n" \ " --cookie-on-stdin Read the cookie (SVPNCOOKIE) from standard input.\n" \ +" --ext-browser-saml[=] Print an http address and start listen to recieve \n"\ +" the autentication id to proceed the connection \n"\ +" the default port if omitted is 8020\n"\ +" --auth-id= login with this id on address /remote/saml/auth_id?id=\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" \ @@ -225,6 +230,8 @@ int main(int argc, char *argv[]) .password = {'\0'}, .password_set = 0, .cookie = NULL, + .listen_port =0, + .auth_id = NULL, .otp = {'\0'}, .otp_prompt = NULL, .otp_delay = 0, @@ -286,6 +293,8 @@ int main(int argc, char *argv[]) {"password", required_argument, NULL, 'p'}, {"cookie", required_argument, NULL, 0}, {"cookie-on-stdin", no_argument, NULL, 0}, + {"ext-browser-saml", optional_argument,NULL,0}, + {"auth-id", required_argument,NULL,0}, {"otp", required_argument, NULL, 'o'}, {"otp-prompt", required_argument, NULL, 0}, {"otp-delay", required_argument, NULL, 0}, @@ -604,6 +613,24 @@ int main(int argc, char *argv[]) free(cookie); break; } + if (strcmp(long_options[option_index].name, + "ext-browser-saml") == 0) { + long port = 8020; + if (optarg != NULL) { + port = strtol(optarg, NULL, 0); + if (port < 1 || port > 65535) { + log_error("Specify a valid listen port or omit for parameter ext-browser-saml\n"); + goto user_error; + } + } + cli_cfg.listen_port = (uint16_t)port; + break; + } + if (strcmp(long_options[option_index].name, "auth-id") == 0) { + free(cli_cfg.auth_id); + cli_cfg.auth_id = strdup(optarg); + break; + } goto user_error; case 'h': printf("%s%s%s%s%s%s%s", usage, summary, @@ -706,11 +733,11 @@ int main(int argc, char *argv[]) log_error("Specify a valid host:port couple.\n"); goto user_error; } - // Check username - if (cfg.username[0] == '\0' && !cfg.cookie) + // Check authentication method + if (cfg.username[0] == '\0' && !cfg.cookie && !cfg.auth_id && cfg.listen_port==0) // Need either username or cert if (cfg.user_cert == NULL) { - log_error("Specify a username.\n"); + log_error("Specify a authentication method.\n"); goto user_error; } // If username but no password given, interactively ask user @@ -732,6 +759,15 @@ int main(int argc, char *argv[]) if (cfg.otp[0] != '\0') log_debug("One-time password = \"%s\"\n", cfg.otp); + if (cfg.listen_port != 0){ + if(cfg.auth_id){ + log_warn("auth-id will be ignored conflict with --ext-browser-saml"); + } + log_debug("Will listen on port \"%d\" for authentication id \n", cfg.listen_port); + free(cfg.auth_id); + cfg.auth_id = listen_for_id(&cfg); + } + if (geteuid() != 0) { log_error("This process was not spawned with root privileges, which are required.\n"); ret = EXIT_FAILURE;