diff options
author | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2011-08-13 14:10:55 +0200 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2011-08-13 14:15:05 +0200 |
commit | 2385c7f999c12802b11859a34b89ff7662b1f4af (patch) | |
tree | a6dc75e9438f5ea2413a627ce8dde5d28e2a195b /src/crywrap | |
parent | 7f373a9e760c1aaad40aff7878dba1399831c9c3 (diff) | |
download | gnutls-2385c7f999c12802b11859a34b89ff7662b1f4af.tar.gz |
Added crywrap to the distributed programs.
Diffstat (limited to 'src/crywrap')
-rw-r--r-- | src/crywrap/Makefile.am | 29 | ||||
-rw-r--r-- | src/crywrap/README | 2 | ||||
-rw-r--r-- | src/crywrap/crywrap.c | 1119 | ||||
-rw-r--r-- | src/crywrap/crywrap.h | 105 | ||||
-rw-r--r-- | src/crywrap/primes.h | 42 |
5 files changed, 1297 insertions, 0 deletions
diff --git a/src/crywrap/Makefile.am b/src/crywrap/Makefile.am new file mode 100644 index 0000000000..e5bd90d8e5 --- /dev/null +++ b/src/crywrap/Makefile.am @@ -0,0 +1,29 @@ +## Process this file with automake to produce Makefile.in +# Copyright (C) 2011 Free Software Foundation, Inc. +# +# This file 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 file 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 file; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +AM_CFLAGS = $(WARN_CFLAGS) +AM_CPPFLAGS = \ + -I$(srcdir)/../../gl \ + -I$(builddir)/../../lib/includes \ + -I$(srcdir)/../../lib/includes \ + -DSYSCONFDIR=\"${sysconfdir}\" \ + -DCRYWRAP_PATCHLEVEL=\"${CRYWRAP_PATCHLEVEL}\" + +bin_PROGRAMS = crywrap + +crywrap_SOURCES = crywrap.c primes.h #compat.h compat.c +crywrap_LDADD = ../../lib/libgnutls.la ../../gl/libgnu.la -lidn diff --git a/src/crywrap/README b/src/crywrap/README new file mode 100644 index 0000000000..9408304833 --- /dev/null +++ b/src/crywrap/README @@ -0,0 +1,2 @@ +The crywrap program by Gergely Nagy is not part of the GnuTLS library, but is +distributed with GnuTLS. diff --git a/src/crywrap/crywrap.c b/src/crywrap/crywrap.c new file mode 100644 index 0000000000..3c1b6e87e4 --- /dev/null +++ b/src/crywrap/crywrap.c @@ -0,0 +1,1119 @@ +/* -*- mode: c; c-file-style: "gnu" -*- + * crywrap.c -- CryWrap + * Copyright (C) 2003, 2004 Gergely Nagy <algernon@bonehunter.rulez.org> + * Copyright (C) 2011 Nikos Mavrogiannopoulos + * + * This file is part of CryWrap. + * + * CryWrap 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. + * + * CryWrap 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 <http://www.gnu.org/licenses/>. + */ + +/** @file crywrap.c + * CryWrap itself. + */ + +#include <config.h> + +#ifdef HAVE_ARGP_H +#include <argp.h> +#endif +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <grp.h> +#include <idna.h> +#include <netdb.h> +#include <netinet/in.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stringprep.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <syslog.h> +#include <stdarg.h> +#include <unistd.h> + +/* Gnulib portability files. */ +#include "progname.h" +#include "argp.h" + +#include "crywrap.h" +#include "primes.h" + +static int system_log(const char* fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; + +static int system_log_error(const char* fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; + +static int debug_log(const char* fmt, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; + +typedef int (*cry_log_func)(const char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; + +static cry_log_func cry_log = system_log; +static cry_log_func cry_error = system_log_error; + +/** @defgroup globals Global variables. + * @{ + */ +/** Status flag to toggle on SIGCHLD. + */ +static sig_atomic_t sigchld = 0; +/** An array of pids. + * This array holds the PIDs of all of our children, indexed by the + * socket the associated client connected to us. + */ +static pid_t crywrap_children[_CRYWRAP_MAXCONN + 2]; +static pid_t main_pid = -1; /**< Pid of the main process */ +static const char *pidfile = _CRYWRAP_PIDFILE; /**< File to log our PID + into. */ +/** GNUTLS server credentials. + */ +static gnutls_certificate_server_credentials cred; +static gnutls_dh_params dh_params; /**< GNUTLS DH parameters. */ +static const int dh_bits = 1024; /**< GNUTLS DH bits. */ + +/** Bugreport address. + * Used by the argp suite. + */ +const char *argp_program_bug_address = "<bugs-gnutls@gnu.org>"; +/** Porgram version. + * Used by the argp suite. + */ +const char *argp_program_version = __CRYWRAP__ " " _CRYWRAP_VERSION; + +/** The options CryWrap takes. + * Used by the argp suite. + */ +static const struct argp_option _crywrap_options[] = { + {NULL, 0, NULL, 0, "Mandatory options:", 1}, + {"destination", 'd', "IP/PORT", 0, "IP and port to connect to", 1}, + {NULL, 0, NULL, 0, "TLS certificates:", 2}, + {"pem", 'p', "TYPE=PATH", 0, "Server key and certificate", 2}, + {"anon", 'a', NULL, 0, "Enable Anon-DH (don't use a certificate)", 2}, + {"verify", 'v', "LEVEL", OPTION_ARG_OPTIONAL, + "Verify clients certificate", 2}, + {NULL, 0, NULL, 0, "Other options:", 3}, + {"user", 'u', "UID", 0, "User ID to run as", 3}, + {"pidfile", 'P', "PATH", 0, "File to log the PID into", 3}, + {"inetd", 'i', NULL, 0, "Enable inetd mode", 3}, + {"listen", 'l', "IP/PORT", 0, "IP and port to listen on", 3}, + {"debug", 'D', NULL, 0, "Do not fork", 2}, + {0, 0, 0, 0, NULL, 0} +}; + +static error_t _crywrap_config_parse_opt (int key, char *arg, + struct argp_state *state); +/** The main argp structure for Crywrap. + */ +static const struct argp _crywrap_argp = + {_crywrap_options, _crywrap_config_parse_opt, 0, + __CRYWRAP__ " -- Security for the masses\v" + "The --destination option is mandatory, as is --listen if --inetd " + "was not used.", + NULL, NULL, NULL}; + +#ifndef __DOXYGEN__ +enum +{ + CRYWRAP_P_SUBOPT_CERT, + CRYWRAP_P_SUBOPT_KEY, + CRYWRAP_P_SUBOPT_END +}; +#endif + +/** Helper structure for parsing --pem subopts. + */ +static char * const _crywrap_p_subopts[] = { + [CRYWRAP_P_SUBOPT_CERT] = (char*)"cert", + [CRYWRAP_P_SUBOPT_KEY] = (char*)"key", + [CRYWRAP_P_SUBOPT_END] = NULL +}; + +/** Helper variable to set if a certificate was explictly set. + */ +static int cert_seen = 0; + +/** @} */ + +/* Forward declaration */ +static int _crywrap_dh_params_generate (void); + +/** @defgroup signal Signal handlers & co. + * @{ + */ + +/** SIGCHLD handler + */ +static void +_crywrap_sigchld_handler (int sig) +{ + sigchld = 1; + signal (sig, _crywrap_sigchld_handler); +} + +/** SIGHUP handler. + * Regenerates DH and RSA paramaters. Takes a bit long... + */ +static void +_crywrap_sighup_handler (int sig) +{ + _crywrap_dh_params_generate (); + + gnutls_certificate_set_dh_params (cred, dh_params); + + signal (sig, _crywrap_sighup_handler); +} + +/** Generic signal handler. + * This one removes the #pidfile, if necessary. + */ +static void +_crywrap_sighandler (int sig) +{ + if (getpid () == main_pid) + { + cry_log ("Exiting on signal %d", sig); + if (pidfile && *pidfile) + unlink (pidfile); + closelog (); + exit (0); + } +} +/** @} */ + +/** @defgroup parsing Option parsing + * @{ + */ + +/** Service resolver. + * Resolves a service - be it a name or a number. + * + * @param serv is the port to resolve. + * + * @returns The purt number, or -1 on error. + */ +static int +_crywrap_port_get (const char *serv) +{ + int port; + struct servent *se; + + if (!serv) + return -1; + + se = getservbyname (serv, "tcp"); + if (!se) + port = atoi (serv); + else + port = ntohs (se->s_port); + + return port; +} + +/** Address resolver. + * Resolves an address - be it numeric or a hostname, IPv4 or IPv6. + * + * @param hostname is the host to resolve. + * @param addr is the structure to put the result into. + * + * @returns Zero on success, -1 on error. + */ +static int +_crywrap_addr_get (const char *hostname, struct sockaddr_storage **addr) +{ + struct addrinfo *res; + struct addrinfo hints; + ssize_t len; + char *lz = NULL; + + if (idna_to_ascii_lz (hostname, &lz, 0) != IDNA_SUCCESS) + return -1; + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_IP; + *addr = calloc (1, sizeof (struct sockaddr_storage)); + + if (getaddrinfo (lz, NULL, &hints, &res) != 0) + { + free (lz); + return -1; + } + + free (lz); + + switch (res->ai_addr->sa_family) + { + case AF_INET: + len = sizeof (struct sockaddr_in); + break; + case AF_INET6: + len = sizeof (struct sockaddr_in6); + break; + default: + freeaddrinfo (res); + return -1; + } + + if (len < (ssize_t)res->ai_addrlen) + { + freeaddrinfo (res); + return -1; + } + + memcpy (*addr, res->ai_addr, res->ai_addrlen); + freeaddrinfo (res); + + return 0; +} + +/** Parse a HOST/IP pair. + * Splits up a given HOST/IP pair, and converts them into structures + * directly usable by libc routines. + * + * @param ip is the HOST/IP pair to parse. + * @param port is a pointer to an integer where the port number should + * go. + * @param addr is the destination of the resolved and parsed IP. + * + * @returns Zero on success, -1 on error. + */ +static int +_crywrap_parse_ip (const char *ip, in_port_t *port, + struct sockaddr_storage **addr, char **host) +{ + char *s_ip; + char *tmp; + + tmp = strchr (ip, '/'); + + if (!tmp) + return -1; + + if (tmp == ip) + { + s_ip = strdup ("0.0.0.0"); + *port = (in_port_t)_crywrap_port_get (&ip[1]); + } + else + { + *port = (in_port_t)_crywrap_port_get (&tmp[1]); + s_ip = strndup (ip, tmp - ip); + } + + if (!*port) + return -1; + + if (host) + *host = strdup (s_ip); + + return _crywrap_addr_get (s_ip, addr); +} + +/** Argument parsing routine. + * Used by the argp suite. + */ +static error_t +_crywrap_config_parse_opt (int key, char *arg, struct argp_state *state) +{ + crywrap_config_t *cfg = (crywrap_config_t *)state->input; + char *pem_cert, *pem_key, *subopts, *value; + + switch (key) + { + case 'D': + cfg->debug = 1; + cry_log = debug_log; + cry_error = debug_log; + break; + case 'd': + if (_crywrap_parse_ip (arg, &cfg->dest.port, &cfg->dest.addr, + &cfg->dest.host) < 0) + argp_error (state, "Could not resolve address: `%s'", arg); + break; + case 'l': + if (_crywrap_parse_ip (arg, &cfg->listen.port, + &cfg->listen.addr, NULL) < 0) + argp_error (state, "Could not resolve address: `%s'", arg); + break; + case 'u': + cfg->uid = atoi (arg); + break; + case 'P': + if (arg && *arg) + cfg->pidfile = strdup (arg); + else + cfg->pidfile = NULL; + break; + case 'p': + subopts = optarg; + pem_cert = NULL; + pem_key = NULL; + while (*subopts != '\0') + switch (getsubopt (&subopts, _crywrap_p_subopts, &value)) + { + case CRYWRAP_P_SUBOPT_CERT: + pem_cert = strdup (value); + break; + case CRYWRAP_P_SUBOPT_KEY: + pem_key = strdup (value); + break; + default: + pem_cert = strdup (value); + break; + } + if (!pem_key) + pem_key = strdup (pem_cert); + if (!pem_cert) + pem_cert = strdup (pem_key); + if (gnutls_certificate_set_x509_key_file (cred, pem_cert, pem_key, + GNUTLS_X509_FMT_PEM) < 0) + argp_error (state, "error reading X.509 key or certificate file."); + cert_seen = 1; + break; + case 'i': + cfg->inetd = 1; + break; + case 'a': + cfg->anon = 1; + cfg->verify = 0; + break; + case 'v': + cfg->verify = (arg) ? atoi (arg) : 1; + break; + case ARGP_KEY_END: + if (!cfg->inetd) + { + if (!cfg->listen.addr || !cfg->dest.addr) + argp_error + (state, + "a listening and a destination address must be set!"); + } + else + if (!cfg->dest.addr) + argp_error (state, "a destination address must be set!"); + if (cert_seen) + break; + if (cfg->anon) + break; + if (gnutls_certificate_set_x509_key_file (cred, _CRYWRAP_PEMFILE, + _CRYWRAP_PEMFILE, + GNUTLS_X509_FMT_PEM) < 0) + argp_error (state, "error reading X.509 key or certificate file."); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/** Configuration parsing. + * Sets up the default values, and parses the command-line options + * using argp. + * + * @note Does not return if an error occurred. + */ +static crywrap_config_t * +_crywrap_config_parse (int argc, char **argv) +{ + crywrap_config_t *config = + (crywrap_config_t *)malloc (sizeof (crywrap_config_t)); + + config->listen.port = 0; + config->listen.addr = NULL; + config->dest.port = 0; + config->dest.addr = NULL; + + config->uid = _CRYWRAP_UID; + config->pidfile = _CRYWRAP_PIDFILE; + config->inetd = 0; + config->anon = 0; + config->verify = 1; + + argp_parse (&_crywrap_argp, argc, argv, 0, 0, config); + + return config; +} +/** @} */ + +/** @defgroup tls Lower-level TLS routines. + * @{ + */ + +/** Create a GNUTLS session. + * Initialises the cyphers and the session database for a new TLS + * session. + * + * @returns The newly created TLS session. + */ +static gnutls_session +_crywrap_tls_session_create (const crywrap_config_t *config) +{ + gnutls_session session; + + gnutls_init (&session, GNUTLS_SERVER); + + + if (config->anon) { + gnutls_priority_set_direct(session, "NORMAL:+ANON-ECDH:+ANON-DH", NULL); + gnutls_credentials_set (session, GNUTLS_CRD_ANON, cred); + } else { + gnutls_priority_set_direct(session, "NORMAL", NULL); + gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, cred); + } + + gnutls_dh_set_prime_bits (session, dh_bits); + + gnutls_handshake_set_private_extensions (session, 1); + + if (config->verify) + gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUEST); + + return session; +} + +/** (Re)Initialise Diffie Hellman parameters. + * @returns Zero. + */ +static int +_crywrap_dh_params_generate (void) +{ + if (gnutls_dh_params_init (&dh_params) < 0) + { + cry_error ("%s", "Error in dh parameter initialisation."); + exit (3); + } + + if (gnutls_dh_params_generate2 (dh_params, dh_bits) < 0) + { + cry_error ("%s", "Error in prime generation."); + exit (3); + } + + gnutls_certificate_set_dh_params (cred, dh_params); + + return 0; +} + +/** Generate initial DH and RSA params. + * Loads the pre-generated DH primes. + */ +static void +_crywrap_tls_init (void) +{ + gnutls_datum dh = { _crywrap_prime_dh_1024, sizeof(_crywrap_prime_dh_1024) }; + + gnutls_dh_params_init (&dh_params); + gnutls_dh_params_import_pkcs3 (dh_params, &dh, GNUTLS_X509_FMT_PEM); + + gnutls_certificate_set_dh_params (cred, dh_params); +} +/** @} */ + +/** @defgroup networking Networking + * @{ + */ + +/** Bind to an address. + * This one binds to an address, handles errors and anything that may + * arise. + * + * @param ai is the address information. + * @param listen_port is the port to bind to, and listen on. + * + * @returns The bound filedescriptor, or -1 on error. + */ +static int +_crywrap_bind (const struct addrinfo *ai, int listen_port) +{ + int ret; + const int one = 1; + int listenfd; + char sock_name[NI_MAXHOST]; + + listenfd = socket (ai->ai_family, SOCK_STREAM, IPPROTO_IP); + if (listenfd == -1) + { + cry_error ("socket: %s", strerror (errno)); + return -1; + } + + memset (sock_name, 0, sizeof (sock_name)); + getnameinfo ((struct sockaddr *)ai->ai_addr, ai->ai_addrlen, sock_name, + sizeof (sock_name), NULL, 0, NI_NUMERICHOST); + + switch (ai->ai_family) + { + case AF_INET6: + ((struct sockaddr_in6 *)(ai->ai_addr))->sin6_port = listen_port; + break; + case AF_INET: + ((struct sockaddr_in *)(ai->ai_addr))->sin_port = listen_port; + break; + } + + ret = setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, + &one, sizeof (one)); + if (ret != 0) + { + cry_error ("setsockopt: %s (%s)", strerror (errno), sock_name); + return -1; + } + + ret = bind (listenfd, ai->ai_addr, ai->ai_addrlen); + if (ret != 0) + { + cry_error ("bind to %s failed: %s", sock_name, strerror (errno)); + return -1; + } + + if (listen (listenfd, _CRYWRAP_MAXCONN) != 0) + { + cry_error ("listen on %s failed: %s", sock_name, strerror (errno)); + return -1; + } + + cry_log ("Socket bound to port %d on %s.", ntohs (listen_port), sock_name); + + return listenfd; +} + +/** Set up a listening socket. + * Sets up a listening socket on all the required addresses. + * + * @param config holds the CryWrap configuration, from where the + * listen address and port will be extracted. + * + * @returns The listening FD on success, -1 on error. + */ +static int +_crywrap_listen (const crywrap_config_t *config) +{ + struct addrinfo *cur; + int ret; + + cur = calloc (1, sizeof (struct addrinfo)); + cur->ai_family = config->listen.addr->ss_family; + + switch (cur->ai_family) + { + case AF_INET6: + cur->ai_addrlen = sizeof (struct sockaddr_in6); + break; + case AF_INET: + cur->ai_addrlen = sizeof (struct sockaddr_in); + break; + } + + cur->ai_addr = malloc (cur->ai_addrlen); + memcpy (cur->ai_addr, config->listen.addr, cur->ai_addrlen); + + ret = _crywrap_bind (cur, htons (config->listen.port)); + free (cur->ai_addr); + free (cur); + + return ret; +} + +/** Connect to a remote server. + * Estabilishes a connection to a remote server, and handles all + * errors and anything that may arise during this process. + * + * @param addr is the address of the remote server. + * @param port is the port to connect to. + * + * @returns the connected socket on success, otherwise it exits. + */ +static int +_crywrap_remote_connect (const struct sockaddr_storage *addr, int port) +{ + struct addrinfo *cur; + int sock; + + cur = calloc (1, sizeof (struct addrinfo)); + cur->ai_family = addr->ss_family; + + switch (cur->ai_family) + { + case AF_INET6: + cur->ai_addrlen = sizeof (struct sockaddr_in6); + break; + case AF_INET: + cur->ai_addrlen = sizeof (struct sockaddr_in); + break; + } + + cur->ai_addr = malloc (cur->ai_addrlen); + memcpy (cur->ai_addr, addr, cur->ai_addrlen); + + switch (cur->ai_family) + { + case AF_INET6: + ((struct sockaddr_in6 *)(cur->ai_addr))->sin6_port = port; + break; + case AF_INET: + ((struct sockaddr_in *)(cur->ai_addr))->sin_port = port; + break; + } + + sock = socket (cur->ai_family, SOCK_STREAM, IPPROTO_IP); + if (sock < 0) + { + cry_error ("socket(): %s", strerror (errno)); + exit (1); + } + + if (connect (sock, cur->ai_addr, cur->ai_addrlen) < 0) + { + cry_error ("connect(): %s", strerror (errno)); + exit (1); + } + + free (cur->ai_addr); + free (cur); + + return sock; +} + +/** @} */ + +/** @defgroup crywrap Main CryWrap code. + * @{ + */ + +/** Drop privileges. + * Drop privileges, if running as root. + * Upon failure, it will make CryWrap exit. + */ +static void +_crywrap_privs_drop (const crywrap_config_t *config) +{ + struct passwd *pwd; + + if (getuid () != 0) + { + cry_log ("%s", "Not running as root, not dropping privileges."); + return; + } + + if ((pwd = getpwuid (config->uid)) == NULL) + { + cry_error ("getpwuid(): %s", strerror (errno)); + exit (1); + } + + if (initgroups (pwd->pw_name, pwd->pw_gid) == -1) + { + cry_error ("initgroups(): %s", strerror (errno)); + exit (1); + } + + if (setgid (pwd->pw_gid) == -1) + { + cry_error ("setgid(): %s", strerror (errno)); + exit (1); + } + + if (setuid (config->uid)) + { + cry_error ("setuid(): %s", strerror (errno)); + exit (1); + } +} + +/** Set up the PID file. + * Checks if a #pidfile already exists, and create one - containing the + * current PID - if one does not. + * + * @note Exits upon error. + */ +static void +_crywrap_setup_pidfile (const crywrap_config_t *config) +{ + char mypid[128]; + int pidfilefd; + + if (!config->pidfile || !*(config->pidfile)) + return; + + if (!access (config->pidfile, F_OK)) + { + cry_error ("Pidfile (%s) already exists. Exiting.", config->pidfile); + exit (1); + } + if ((pidfilefd = open (config->pidfile, + O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) + { + cry_error ("Cannot create pidfile (%s): %s.\n", config->pidfile, + strerror (errno)); + exit (1); + } + fchown (pidfilefd, config->uid, (gid_t)-1); + + main_pid = getpid (); + snprintf (mypid, sizeof (mypid), "%d\n", main_pid); + write (pidfilefd, mypid, strlen (mypid)); + close (pidfilefd); + pidfile = config->pidfile; +} + +/** Child cleanup routine. + * Called after a SIGCHLD is received. Walks through #crywrap_children + * and closes the socket of the one that exited. + */ +static void +_crywrap_reap_children (void) +{ + pid_t child; + int status, i; + + while ((child = waitpid (-1, &status, WNOHANG)) > (pid_t) 0) + { + for (i = 0; i < _CRYWRAP_MAXCONN; i++) + { + if (!crywrap_children[i]) + continue; + if (child == crywrap_children[i]) + { + shutdown (i, SHUT_RDWR); + close (i); + crywrap_children[i] = 0; + } + } + } + sigchld = 0; +} + +/** Handles one client. + * This one connects to the remote server, and proxies every traffic + * between our client and the server. + * + * @param config is the main CryWrap configuration structure. + * @param insock is the socket through which the client sends input. + * @param outsock is the socket through which we send output. + * + * @note Exits on error. + */ +static int +_crywrap_do_one (const crywrap_config_t *config, int insock, int outsock) +{ + int sock, ret, tls_pending; + gnutls_session session; + char buffer[_CRYWRAP_MAXBUF + 2]; + fd_set fdset; + unsigned int status = 0; + struct sockaddr_storage faddr; + socklen_t socklen = sizeof (struct sockaddr_storage); + char peer_name[NI_MAXHOST]; + + /* Log the connection */ + if (getpeername (insock, (struct sockaddr *)&faddr, &socklen) != 0) + cry_error ("getpeername(): %s", strerror (errno)); + else + { + getnameinfo ((struct sockaddr *)&faddr, + sizeof (struct sockaddr_storage), peer_name, + sizeof (peer_name), NULL, 0, NI_NUMERICHOST); + cry_log ("Accepted connection from %s on %d to %s/%d", + peer_name, insock, config->dest.host, + config->dest.port); + } + + /* Do the handshake with our peer */ + session = _crywrap_tls_session_create (config); + gnutls_transport_set_ptr2 (session, + (gnutls_transport_ptr_t)insock, + (gnutls_transport_ptr_t)outsock); + + do + { + ret = gnutls_handshake(session); + } + while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (ret < 0) + { + cry_error ("Handshake failed: %s", gnutls_strerror (ret)); + gnutls_alert_send_appropriate(session, ret); + goto error; + } + + /* Verify the client's certificate, if any. */ + if (config->verify) + { + ret = gnutls_certificate_verify_peers2 (session, &status); + if (ret < 0) + cry_log ("Error getting certificate from client: %s", + gnutls_strerror (ret)); + + if (ret == 0 && status != 0) + switch (ret) + { + case GNUTLS_CERT_INVALID: + cry_log ("%s", "Client certificate not trusted or invalid"); + break; + default: + cry_log ("%s", "Unknown error while getting the certificate"); + break; + } + + if (config->verify > 1 && (ret < 0 || status != 0)) + { + ret = -1; + goto error; + } + } + + /* Connect to the remote host */ + sock = _crywrap_remote_connect (config->dest.addr, + htons (config->dest.port)); + + for (;;) + { + FD_ZERO (&fdset); + FD_SET (insock, &fdset); + FD_SET (sock, &fdset); + + memset (buffer, 0, _CRYWRAP_MAXBUF + 1); + + tls_pending = 0; + + if (gnutls_record_check_pending(session) > 0) + tls_pending = 1; + else + { + select (sock + 1, &fdset, NULL, NULL, NULL); + if (FD_ISSET (insock, &fdset)) + tls_pending = 1; + } + /* TLS client */ + if (tls_pending != 0) + { + ret = gnutls_record_recv (session, buffer, _CRYWRAP_MAXBUF); + if (ret == 0) + { + cry_log ("%s", "Peer has closed the GNUTLS connection"); + break; + } + else if (ret < 0) + { + cry_log ("Received corrupted data: %s.", + gnutls_strerror (ret)); + break; + } + else + send (sock, buffer, ret, 0); + } + + /* Remote server */ + if (FD_ISSET (sock, &fdset)) + { + ret = recv (sock, buffer, _CRYWRAP_MAXBUF, 0); + if (ret == 0) + { + cry_log ("%s", "Server has closed the connection"); + break; + } + else if (ret < 0) + { + cry_log ("Received corrupted data: %s.", strerror (errno)); + break; + } + else + { + int r, o = 0; + + do + { + r = gnutls_record_send (session, &buffer[o], ret - o); + o += r; + } while (r > 0 && ret > o); + + if (r < 0) + cry_log ("Received corrupted data: %s", gnutls_strerror (r)); + } + } + } + +error: + gnutls_bye (session, GNUTLS_SHUT_WR); + gnutls_deinit (session); + close (insock); + close (outsock); + + return (ret == 0) ? 0 : 1; +} + +/** CryWrap entry point. + * This is the main entry point - controls the whole program and so + * on... + */ +int +main (int argc, char **argv, char **envp) +{ + crywrap_config_t *config; + int server_socket; + + openlog (__CRYWRAP__, LOG_PID, LOG_DAEMON); + + if (gnutls_global_init () < 0) + { + cry_error ("%s", "Global TLS state initialisation failed."); + exit (1); + } + if (gnutls_certificate_allocate_credentials (&cred) < 0) + { + cry_error ("%s", "Couldn't allocate credentials."); + exit (1); + } + + stringprep_locale_charset (); + + config = _crywrap_config_parse (argc, argv); + set_program_name(__CRYWRAP__); + + _crywrap_tls_init (); + + if (config->inetd) + { + _crywrap_privs_drop (config); + exit (_crywrap_do_one (config, 0, 1)); + } + +#if CRYWRAP_OPTION_FORK + if (!config->debug) + if (daemon (0, 0)) + { + cry_error ("daemon: %s", strerror (errno)); + exit (1); + } +#endif + + cry_log ("%s", "Crywrap starting..."); + + server_socket = _crywrap_listen (config); + if (server_socket < 0) + exit (1); + + if (!config->debug) _crywrap_setup_pidfile (config); + _crywrap_privs_drop (config); + + signal (SIGTERM, _crywrap_sighandler); + signal (SIGQUIT, _crywrap_sighandler); + signal (SIGSEGV, _crywrap_sighandler); + signal (SIGPIPE, SIG_IGN); + signal (SIGHUP, _crywrap_sighup_handler); + + cry_log ("%s", "Accepting connections"); + + memset (crywrap_children, 0, sizeof (crywrap_children)); + signal (SIGCHLD, _crywrap_sigchld_handler); + + for (;;) + { + int csock; +#if !BHC_OPTION_DEBUG + int child; +#endif + + if (sigchld) + _crywrap_reap_children (); + + csock = accept (server_socket, NULL, NULL); + if (csock < 0) + continue; + +#if !BHC_OPTION_DEBUG + child = fork (); + switch (child) + { + case 0: + exit (_crywrap_do_one (config, csock, csock)); + break; + case -1: + cry_error ("%s", "Forking error."); + exit (1); + break; + default: + crywrap_children[csock] = child; + break; + } +#else + _crywrap_do_one (config, csock, csock); +#endif + close(csock); + } + + return 0; +} + +static int system_log(const char* fmt, ...) +{ + va_list args; + + va_start (args, fmt); + vsyslog(LOG_NOTICE, fmt, args); + va_end (args); + + return 0; +} + +static int system_log_error(const char* fmt, ...) +{ + va_list args; + + va_start (args, fmt); + vsyslog(LOG_ERR, fmt, args); + va_end (args); + + return 0; +} + +static int debug_log(const char* fmt, ...) +{ + va_list args; + + va_start (args, fmt); + vprintf(fmt, args); + puts(""); + va_end (args); + + return 0; +} + +/** @} */ + diff --git a/src/crywrap/crywrap.h b/src/crywrap/crywrap.h new file mode 100644 index 0000000000..5cadd5dda9 --- /dev/null +++ b/src/crywrap/crywrap.h @@ -0,0 +1,105 @@ +/* -*- mode: c; c-file-style: "gnu" -*- + * crywrap.h -- Global definitions for CryWrap + * Copyright (C) 2003, 2004 Gergely Nagy <algernon@bonehunter.rulez.org> + * + * This file is part of CryWrap. + * + * CryWrap 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. + * + * CryWrap 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 <http://www.gnu.org/licenses/>. + */ + +/** @file crywrap.h + * Global variables and declarations for CryWrap. + * + * All of the global types, structures and whatnot are declared in + * this file. Not variables, though. Those are in crywrap.c. + */ + +#ifndef _CRYWRAP_H +#define _CRYWRAP_H 1 /**< crywrap.h multi-inclusion guard. */ + +/** @defgroup defaults Built-in defaults. + * @{ + */ +#define __CRYWRAP__ "crywrap" /**< Software name. */ +/** Software version. + */ +#define _CRYWRAP_VERSION "0.2." CRYWRAP_PATCHLEVEL +/** Configuration directory. + */ +#define _CRYWRAP_CONFDIR SYSCONFDIR "/crywrap" +#define _CRYWRAP_UID 65534 /**< Default UID to run as. */ +/** Default PID file. + */ +#define _CRYWRAP_PIDFILE "/var/run/crywrap.pid" +/** Maximum number of clients supported. + */ +#define _CRYWRAP_MAXCONN 1024 +/** Maximum I/O buffer size. + */ +#define _CRYWRAP_MAXBUF 64 * 1024 +/** Default server certificate and key. + */ +#define _CRYWRAP_PEMFILE _CRYWRAP_CONFDIR "/server.pem" +/** @} */ + +/** Configuration structure. + * Most of the CryWrap configuration - those options that are settable + * via the command-line are stored in a variable of this type. + */ +typedef struct +{ + /** Properties of the listening socket. + */ + struct + { + in_port_t port; + struct sockaddr_storage *addr; + } listen; + + /** Properties of the destination socket. + */ + struct + { + in_port_t port; + char *host; + struct sockaddr_storage *addr; + } dest; + + const char *pidfile; /**< File to store our PID in. */ + uid_t uid; /**< User ID to run as. */ + int inetd; /**< InetD-mode toggle. */ + int anon; /**< Anon-DH toggle. */ + int verify; /**< Client certificate verify level. */ + int debug; +} crywrap_config_t; + +/** @defgroup options Options. + * These are the compile-time options. + * @{ + */ +/** If this option is set, CryWrap will fork into the background. + */ +#ifndef CRYWRAP_OPTION_FORK +#define CRYWRAP_OPTION_FORK 1 +#endif + +#if CRYWRAP_OPTION_NOFORK +#undef CRYWRAP_OPTION_FORK +#endif + +/** @} *//* End of the Options group */ + +#endif /* !_CRYWRAP_H */ + +/* arch-tag: ebfe1550-0fec-4c0d-8833-23e48292e75d */ diff --git a/src/crywrap/primes.h b/src/crywrap/primes.h new file mode 100644 index 0000000000..50c331d39d --- /dev/null +++ b/src/crywrap/primes.h @@ -0,0 +1,42 @@ +/* primes.h -- initial DH primes for CryWrap + * + * Copyright (C) 2003 Gergely Nagy <algernon@bonehunter.rulez.org> + * Copyright (C) 2011 Nikos Mavrogiannopoulos + * + * This file is part of CryWrap. + * + * CryWrap 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. + * + * CryWrap 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 <http://www.gnu.org/licenses/>. + */ + +/** @file primes.h + * Initial DH primes for CryWrap. + * + * In order to speed up the startup time, CryWrap does not generate a + * new DH prime upon every startup, but only when it receives a + * SIGHUP. + */ + +#ifndef _CRYWRAP_PRIMES_H +#define _CRYWRAP_PRIMES_H + +/** Initial DH primes, 1024 bits. + */ +static char _crywrap_prime_dh_1024[] = "-----BEGIN DH PARAMETERS-----\n" +"MIGHAoGBAO6vCrmts43WnDP4CvqPxehgcmGHdf88C56iMUycJWV21nTfdJbqgdM4\n" +"O0gT1pLG4ODV2OJQuYvkjklcHWCJ2tFdx9e0YVTWts6O9K1psV1JglWbKXvPGIXF\n" +"KfVmZg5X7GjtvDwFcmzAL9TL9Jduqpr9UTj+g3ZDW5/GHS/A6wbjAgEC\n" +"-----END DH PARAMETERS-----\n"; + +#endif + |