The entire wtmp support for OpenVPN. -- wtmp.c | 397 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ wtmp.h | 20 ++++ 2 files changed, 417 insertions(+), 0 deletions(-) diff --git a/wtmp.c b/wtmp.c new file mode 100644 index 0000000..1c1a0af --- /dev/null +++ b/wtmp.c @@ -0,0 +1,397 @@ +/* + * OpenVPN wtmp support + * + * (C) 2007 Maximilian Wilhelm + * (C) 2007 Jan-Benedict Glaw + * + */ + +#include "config.h" +#include "syshead.h" + +#ifdef ENABLE_WTMP + +#define _GNU_SOURCE + +#include /* assert() */ +#include /* opendir() */ +#include /* dirname() */ +#include /* struct passwd */ +#include /* fopen() */ +#include /* *alloc(), free() */ +#include /* strncpy(), strlen() */ +#include /* stat() */ +#include /* struct timeval, gettimeofday() */ +#include /* opendir() chown() */ +#include /* gettimeofday() */ +#include /* chown() */ +#include /* utmp stuff */ + +#include "openvpn.h" /* struct context* */ +#include "forward.h" +#include "forward-inline.h" +#include "multi.h" /* struct multi_instance */ +#include "buffer.h" /* struct buffer */ + +#include "wtmp.h" + + +int wtmp_initialized = 0; + +// #define OVPN_WTMP_DEBUG 1 + +/* static function declarations */ +static void set_utmp_hostname (const struct multi_instance *mi, struct utmp *entry); +static void set_utmp_id (const struct multi_instance *mi, struct utmp *entry); +static void set_utmp_line (const struct multi_instance *mi, struct utmp *entry); +static void set_utmp_username (const struct multi_instance *mi, struct utmp *entry); +static void set_utmp_time (struct utmp *ut); + + +/* + * Initialize wtmp subsystem + */ +int +wtmp_init (const struct options *vpn_options) +{ + struct stat wtmp_stat; + struct passwd *pw; + struct group *grp; + int do_chmod = 1; + uid_t uid = -1; + gid_t gid = -1; + char *wtmp_file_path; + char *wtmp_file_dirname; + char *wtmp_file_path_copy; + int fd; + + assert (vpn_options); + +#ifdef OVPN_WTMP_DEBUG + fprintf (stderr, "%s() called.\n", __FUNCTION__); +#endif + + /* Only initialize once */ + if (wtmp_initialized) + return 1; + + wtmp_file_path = vpn_options->wtmp_file; + +#ifdef OVPN_WTMP_DEBUG + fprintf (stderr, "wtmp_file_path = %s\n", wtmp_file_path); +#endif + + wtmp_file_path_copy = strdup (wtmp_file_path); + if (! wtmp_file_path_copy) { + fprintf (stderr, "%s: strdup() failed.\n", __FUNCTION__); + wtmp_initialized = 0; + goto out; + + } + + wtmp_file_dirname = dirname (wtmp_file_path_copy); + if (! wtmp_file_dirname) { + fprintf (stderr, "%s: Could not get dirname of wtmp_file_path %s.\n", + __FUNCTION__); + wtmp_initialized = 0; + goto out; + } + + /* Get UID and GID of the configured user and group */ + if (vpn_options->username) { + pw = getpwnam (vpn_options->username); + if (pw) + uid = pw->pw_uid; + else + do_chmod = 0; + } + + if (vpn_options->groupname) { + grp = getgrnam (vpn_options->groupname); + if (grp) + gid = grp->gr_gid; + else + do_chmod = 0; + + } + + /* If the wtmp directory does not exist, create it. Also create the + * wtmp file if neccessary, but pay attention that a regular user + * cannot symlink-attack an already-present root-owned system file. */ + (void) mkdir (wtmp_file_dirname, S_IRWXU); + fd = open (wtmp_file_path, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP); + if (fd >= 0) + close (fd); + + if (do_chmod && uid != -1 && gid != -1) { + if (lchown (wtmp_file_path, uid, gid)) { + fprintf (stderr, "%s: Could not set owner/group of wtmp file %s.\n", + __FUNCTION__, wtmp_file_path); + wtmp_initialized = 0; + goto out; + } + } + + /* OK wtmp_file is (now) there, use it */ + utmpname (wtmp_file_path); + + /* Everything is fine */ + wtmp_initialized = 1; + +out: + free (wtmp_file_path_copy); + + return wtmp_initialized; +} + + +void +wtmp_start (const struct multi_instance *mi) +{ +#ifdef OVPN_WTMP_DEBUG + fprintf (stderr, "%s() called.\n", __FUNCTION__); +#endif + + struct utmp entry; + struct sockaddr_in *remote_addr; + + char *temp; + + assert (mi); + + if (! wtmp_initialized) + return; + + /* Get IP address of this connection + * FIXME: IPv6?! + */ + remote_addr = get_remote_sockaddr_from_multi_instance (mi); + if (! remote_addr) { + fprintf (stderr, "%s: get_remote_sockaddr_from_multi_instance (mi) failed.\n", + __FUNCTION__); + } + + /* Clear entry */ + memset(&entry, '\0', sizeof (entry)); + + /* This entry is a user process */ + entry.ut_type = USER_PROCESS; + + /* The connection started now, so set epoch time */ + set_utmp_time (&entry); + + /* Set the user name */ + set_utmp_username (mi, &entry); + + /* Set the line */ + set_utmp_line (mi, &entry); + + /* Store the IP we gave this client as the hostname */ + set_utmp_hostname (mi, &entry); + + /* Use the first 4 chars of the session ID as id */ + set_utmp_id (mi, &entry); + + /* Store the remote client IP(v4), if available */ + entry.ut_addr = remote_addr->sin_addr.s_addr; + +#ifdef OVPN_WTMP_DEBUG +fprintf (stderr, "%s: entry.ut_line = \"%s\"\n", __FUNCTION__, entry.ut_line); +fprintf (stderr, "%s: entry.ut_id = \"%s\"\n", __FUNCTION__, entry.ut_id); +fprintf (stderr, "%s: entry.ut_user = \"%s\"\n", __FUNCTION__, entry.ut_user); +fprintf (stderr, "%s: entry.ut_host = \"%s\"\n", __FUNCTION__, entry.ut_host); + +unsigned int ip = ntohl (entry.ut_addr); +fprintf (stderr, "%s: entry.ut_addr = %d.%d.%d.%d\n", __FUNCTION__, ip >> 24, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip >> 0) & 0xff); +#endif + + updwtmp (mi->context.options.wtmp_file, &entry); +} + + +void +wtmp_stop (const struct multi_instance *mi) +{ +#ifdef OVPN_WTMP_DEBUG + fprintf (stderr, "%s() called.\n", __FUNCTION__); +#endif + + struct utmp entry; + + assert (mi); + + if (! wtmp_initialized) + return; + + /* Clear entry */ + memset (&entry, '\0', sizeof (entry)); + + /* The connection stopped now, so set epoch time */ + set_utmp_time (&entry); + + /* He's dead jim */ + entry.ut_type = DEAD_PROCESS; + + /* Set current epoch time */ + set_utmp_time (&entry); + + /* Set the line */ + set_utmp_line (mi, &entry); + + /* Use the first 4 chars of the session ID as id */ + set_utmp_id (mi, &entry); + + entry.ut_host[0] = 0; + +#ifdef OVPN_WTMP_DEBUG +fprintf (stderr, "%s: entry.ut_line = %s\n", __FUNCTION__, entry.ut_line); +fprintf (stderr, "%s: entry.ut_id = %s\n", __FUNCTION__, entry.ut_id); +#endif + + updwtmp (mi->context.options.wtmp_file, &entry); +} + + + +/* Build up and store an utmp line */ +static void +set_utmp_line (const struct multi_instance *mi, struct utmp *entry) +{ + unsigned int server_id; + char * line; + + server_id = mi->context.options.wtmp_server_id % 100; + line = format_wtmp_line (mi->context.c1.wtmp_line); + + snprintf (entry->ut_line, UT_LINESIZE, "ovpn%02d-%s", server_id, line); +} + + +/* Store the first 4 chars of the session_id as the utmp id */ +static void +set_utmp_id (const struct multi_instance *mi, struct utmp *entry) +{ + char *line; + + line = format_wtmp_line (mi->context.c1.wtmp_line); + + snprintf (entry->ut_id, 4, "%s", line); +} + + +/* Store a human readable format of the VPN ip address we gave to the client as utmp hostname */ +static void +set_utmp_hostname (const struct multi_instance *mi, struct utmp *entry) +{ + unsigned int ip; + + memset (entry->ut_host, '\0', UT_HOSTSIZE); + + ip = mi->context.c2.push_ifconfig_local; + snprintf (entry->ut_host, UT_HOSTSIZE, "%d.%d.%d.%d", ip >> 24, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip >> 0) & 0xff); +} + + +/* Store the username of the current session in the utmp entry */ +static void +set_utmp_username (const struct multi_instance *mi, struct utmp *entry) +{ + const char *username; + + username = tls_common_name (mi->context.c2.tls_multi, false); + if (username) { + snprintf (entry->ut_user, UT_NAMESIZE, "%s", username); + } else { + snprintf (entry->ut_user, UT_NAMESIZE, "unknown"); + } +} + + +/* Get a sockaddr_in struct containing the real IP of the remote host */ +static struct sockaddr_in * +get_remote_sockaddr_from_multi_instance (const struct multi_instance *mi) +{ + struct buffer buf ; + struct link_socket_actual *lsa; + + lsa = calloc (1, sizeof (struct link_socket_actual)); + if (! lsa) + return NULL; + + link_socket_get_outgoing_addr (&buf, get_link_socket_info (&mi->context), &lsa); + + return &lsa->dest.sa; +} + + +/* + * wtmp line generation + */ + +unsigned long int +gen_wtmp_line () +{ + struct timeval timeval; + + /* Get random seed */ + gettimeofday (&timeval, NULL); + srandom ((unsigned) timeval.tv_sec ^ timeval.tv_usec); + + return random(); +} + +#define MAP_BASE 32 + +char * +format_wtmp_line (const unsigned long int n) +{ + unsigned long int a, b, i; + + char *string; + char digit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'l', 'k' }; + + if (sizeof (digit) / sizeof ((digit)[0]) < MAP_BASE) { + fprintf (stderr, "%s: Not enough digits in 'digit' map.\n"); + return NULL; + } + + /* Allocat string space */ + string = calloc (1, sizeof (n) * 8 + 2); + if (! string) { + return NULL; + } + + /* Beware: This will print the number in base_32 in the wrong direction. + * As this is not relevant here, I won't fix it */ + a = n; + for (i = 0; a != 0; i++) { + b = a % MAP_BASE; + a = a / MAP_BASE; + + string[i] = digit[b]; + } + + return string; +} + + +/* + * Some helper functions, partly stolen from OpenSSH/loginrec.c + */ + + +/* Store the current time in utmp struct */ +static void +set_utmp_time (struct utmp *ut) +{ + struct timeval tv; + + gettimeofday(&ut->ut_tv, NULL); + + ut->ut_time = ut->ut_tv.tv_sec; +} + +#endif /* ENABLE_WTMP */ diff --git a/wtmp.h b/wtmp.h new file mode 100644 index 0000000..f0fb66e --- /dev/null +++ b/wtmp.h @@ -0,0 +1,20 @@ +#ifndef _OPENVPN_WTMP_H +#define _OPENVPN_WTMP_H + +#include + +#include "multi.h" + +enum local_remote { LOCAL, REMOTE }; + +int wtmp_init (const struct options *vpn_options); + +void wtmp_start (const struct multi_instance *mi); +void wtmp_stop (const struct multi_instance *mi); + + +static struct sockaddr_in * get_remote_sockaddr_from_multi_instance (const struct multi_instance *mi); +extern unsigned long int gen_wtmp_line (); +extern char * format_wtmp_line (unsigned long int n); + +#endif /* _OPENVPN_WTMP_H */