summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Sommerseth <dazo@users.sourceforge.net>2013-06-08 02:32:34 +0200
committerDavid Sommerseth <dazo@users.sourceforge.net>2013-06-09 01:03:29 +0200
commit6554cba9441de42e0c5c455b16fb5f6b39c19e28 (patch)
tree027ed605d3c5556bba853018fd6eb6cf18effc24
parentd7b486079a77beb883e3c5e39842dd5c180f3b7b (diff)
downloadeurephia-6554cba9441de42e0c5c455b16fb5f6b39c19e28.tar.gz
eurephia-6554cba9441de42e0c5c455b16fb5f6b39c19e28.tar.xz
eurephia-6554cba9441de42e0c5c455b16fb5f6b39c19e28.zip
auth: Added socket-auth module
This can authenticate username/passwords via a file socket to an authentication service. A simple authentication service written in Python is added as well. Signed-off-by: David Sommerseth <dazo@users.sourceforge.net>
-rw-r--r--CMakeLists.txt2
-rw-r--r--auth/socket/CMakeLists.txt35
-rwxr-xr-xauth/socket/demo-auth-server.py222
-rw-r--r--auth/socket/socket-auth.c224
4 files changed, 482 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1297b7f..3a07c83 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -181,7 +181,7 @@ SET_PROPERTY(TARGET common PROPERTY IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/common
# Start the building. First build the common library, and then the requested eurephia modules
IF(DATABASE OR PLUGIN OR FIREWALL OR EUREPHIADM)
- SUBDIRS(common auth/dummy auth/flatfile ${subdirs})
+ SUBDIRS(common auth/dummy auth/flatfile auth/socket ${subdirs})
ENDIF(DATABASE OR PLUGIN OR FIREWALL OR EUREPHIADM)
# Compile Doxygen docs at the end if requested
diff --git a/auth/socket/CMakeLists.txt b/auth/socket/CMakeLists.txt
new file mode 100644
index 0000000..63da08a
--- /dev/null
+++ b/auth/socket/CMakeLists.txt
@@ -0,0 +1,35 @@
+# cmake rules for eurephia - dummy-auth plug-in
+#
+# This module is only for testing purpose.
+# DO NO USE THIS PLUG-IN IN A PRODUCTION ENVIRONMENT
+#
+# GPLv2 only - Copyright (C) 2008 - 2012
+# David Sommerseth <dazo@users.sourceforge.net>
+#
+# 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; version 2
+# of the License.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+PROJECT(socket-auth C)
+cmake_minimum_required(VERSION 2.6)
+
+# Compiler settigns
+INCLUDE_DIRECTORIES(. ../../common .. ../../database)
+
+ADD_LIBRARY(socket-auth SHARED
+ socket-auth.c
+)
+TARGET_LINK_LIBRARIES(socket-auth common)
+SET_TARGET_PROPERTIES(socket-auth PROPERTIES COMPILE_FLAGS -fPIC)
+SET_TARGET_PROPERTIES(socket-auth PROPERTIES OUTPUT_NAME socket-auth PREFIX "")
+
diff --git a/auth/socket/demo-auth-server.py b/auth/socket/demo-auth-server.py
new file mode 100755
index 0000000..d985fa3
--- /dev/null
+++ b/auth/socket/demo-auth-server.py
@@ -0,0 +1,222 @@
+#!/usr/bin/python2
+#
+# demo-auth-server.py - simplistic demo server for the auth-socket module
+#
+# Copyright (C) 2013 David Sommerseth <dazo@users.sourceforge.net>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+# DESCRIPTION:
+#
+# This is a fairly simple eurephia socket-auth authentication service
+#
+# The eurephiaAuthService class can be reused and the real demo
+# program in the '__main__' section provides a small example how
+# to use this service class.
+#
+# This demo takes one argument, which is the file path of the socket to create.
+# This server needs to be started before OpenVPN+eurephia when the auth-socket
+# module is used.
+#
+
+import socket, os, os.path, sys, signal
+
+STATUS_PASS = 1
+STATUS_FAIL = 2
+STATUS_ERROR = 3
+
+class eurephiaAuthService(object):
+ """Simple socket based authentication service for eurephia.
+This is to be used together with the eurephia socket-auth.so module"""
+
+ def __init__(self, socketname, auth_callback_fnc, socket_umask=007):
+ """Initiates the authentication service. The socketname variable
+is a filename to where the file based socket will be created for socket-auth.so
+to connect to. The auth_callback_fnc is the function which will be called
+when a authentication request arrives."""
+ self.__socketname = socketname
+ self.__umask = socket_umask
+ self.__authcallback = auth_callback_fnc
+ self.__socket = None
+ self.__keeprunning = None
+
+
+ def __debug(self, msg):
+ if self.__dbg:
+ sys.stdout.write(msg)
+ sys.stdout.flush()
+
+
+ def __prepare_socket(self):
+ # Clean up old sockets, if present
+ if os.path.exists(self.__socketname):
+ os.remove(self.__socketname)
+
+ # Create new socket
+ prev_umask = os.umask(self.__umask)
+ self.__socket = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM )
+ self.__socket.bind(self.__socketname)
+ os.umask(prev_umask)
+
+ # Prepare for just a single connection
+ self.__socket.listen(1)
+
+
+ def __get_connection(self):
+ while True:
+ try:
+ self.__restart = False
+ self.__debug("Waiting for eurephia socket-auth.so to connect ... ")
+ conn, client = self.__socket.accept()
+ self.__debug("Connected\n")
+ return conn
+ except socket.error, e:
+ if self.__keeprunning and not self.__restart:
+ raise e
+ if not self.__keeprunning:
+ self.__debug(" Aborting\n")
+ return None
+
+
+ def __read_data(self, conn):
+ try:
+ # First byte is the length of the data
+ msglen = conn.recv( 1 )
+ if not msglen:
+ return None
+
+ # Read the data
+ msg = conn.recv( ord(msglen[0]) )
+
+ if msg and msg == "***SHUTDOWN***":
+ self.__debug("OpenVPN is disonnecting\n")
+ self.__restart = True
+ return None
+ return msg
+
+ except socket.error, e:
+ if self.__keeprunning and not self.__restart:
+ raise e
+ if not self.__keeprunning:
+ self.__debug(" Aborting\n")
+ return None
+
+
+ def __send_response(self, conn, status):
+ # A response consist only of 4 bytes,
+ if status == STATUS_PASS:
+ self.__debug("PASS\n")
+ conn.send("PASS")
+ elif status == STATUS_FAIL:
+ self.__debug("FAIL\n")
+ conn.send("FAIL")
+ elif status == STATUS_ERROR:
+ self.__debug("Internal error\n")
+ conn.send("IERR")
+ else:
+ self.__debug("FATAL\n")
+ raise ValueError("Invalid status code")
+
+ def __main_loop(self):
+ self.__restart = False
+ conn = self.__get_connection()
+ if not conn:
+ return
+ while self.__keeprunning and not self.__restart:
+ username = self.__read_data(conn)
+ if not username:
+ if self.__keeprunning and not self.__restart:
+ self.__debug("** ERROR ** Failed to read username. Aborting\n")
+ break
+
+ passwd = self.__read_data(conn)
+ if not passwd:
+ if self.__keeprunning and not self.__restart:
+ self.__debug("** ERROR ** Failed to read password for '%s'. Aborting\n" % username)
+ break
+
+ self.__debug("Authenticating '%s' ... " % username)
+ res = self.__authcallback(username, passwd)
+ self.__send_response(conn, res)
+ conn.close()
+ self.__debug("Closed connection\n")
+
+
+ def __sighandler(self, sig, frame):
+ if sig == signal.SIGINT or sig == signal.SIGTERM:
+ self.__keeprunning = False
+ signal.signal(signal.SIGINT, self.__sighandler)
+ signal.signal(signal.SIGTERM, self.__sighandler)
+ elif sig == signal.SIGHUP:
+ self.__debug("Caught SIGHUP\n")
+ self.__restart = True
+ signal.signal(signal.SIGHUP, self.__sighandler)
+
+
+ def Run(self, debug = False):
+ "Starts the authentication service and loops until a shutdown signal is caught"
+ self.__dbg = debug
+ self.__prepare_socket()
+ self.__keeprunning = True
+
+ # Prepare signal handling
+ signal.signal(signal.SIGINT, self.__sighandler)
+ signal.signal(signal.SIGTERM, self.__sighandler)
+ signal.signal(signal.SIGHUP, self.__sighandler)
+
+ while self.__keeprunning:
+ try:
+ self.__main_loop()
+ except KeyboardInterrupt:
+ # Fallback if the signal handler doesn't catch it
+ self.__debug("\nCaught SIGINT\n")
+
+ # Complete the shutdown by removing the socket file
+ self.__socket.close()
+ os.remove(self.__socketname)
+
+
+if __name__ == "__main__":
+ #
+ #
+ # Demo authentication service. This SHOULD NOT be used in production,
+ # but can be used as a template on how to use socket-auth.so and
+ # the eurephiaAuthentication class.
+ #
+ #
+
+
+ # Authentication callback - used by eurephiaAuthService
+ def auth_callback(username, password):
+ "Stupid authentication callback demo"
+
+ print " [auth_callback('%s', '%s')] " % (username, password),
+
+ if username == 'foo' and password == 'bar':
+ return STATUS_PASS
+ else:
+ return STATUS_FAIL
+
+ # Simple arugment parser ... takes only one argument, the socket file
+ # eurephia socket-auth.so is supposed to connect to
+ if len(sys.argv) != 2:
+ print "Usage: %s <auth-socket-path>" % sys.argv[0]
+ sys.exit(1)
+ socketname = sys.argv[1]
+
+ # Prepare authentication service, using the callback function above
+ authserv = eurephiaAuthService(socketname, auth_callback)
+ # Start running with some debug info
+ authserv.Run(debug=True)
+
diff --git a/auth/socket/socket-auth.c b/auth/socket/socket-auth.c
new file mode 100644
index 0000000..41396fb
--- /dev/null
+++ b/auth/socket/socket-auth.c
@@ -0,0 +1,224 @@
+/* socket-auth.c -- Authenticates user/password against a socket service
+ *
+ * The socket service retrieves the username and password and replies with
+ * PASS or FAIL. See the demo-auth-server.py for more info
+ *
+ * Copyright (C) 2013 David Sommerseth <dazo@users.sourceforge.net>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * @file socket-auth.c
+ * @author David Sommerseth <dazo@users.sourceforge.net>
+ * @date 2013-06-08
+ *
+ * @brief Authenticate users using a socket based auth server
+ */
+
+/*
+ * The wire protocol for the socket service is:
+ *
+ * The client sends:
+ * 1 byte - lenght of username
+ * x bytes - the username
+ * 1 byte - lenght of password
+ * x bytes - the password
+ *
+ * And now the server is expected to reply with:
+ * 4 bytes - containing the string FAIL or PASS
+ *
+ */
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <eurephia_nullsafe.h>
+#include <eurephia_context.h>
+#include <eurephia_log.h>
+#include <eurephia_authplugin_driver.h>
+
+/*
+ * INTERNAL VARIABLES AND FUNCTIONS
+ */
+
+static int authservfd = -1;
+static struct sockaddr_un sockaddr;
+
+/**
+ * Send data to the remote end according to the wire protocol
+ * where the first byte is the length of data to be sent. Maximum
+ * 127 bytes can be transferred.
+ *
+ * @param fd Socket fd to send the data to
+ * @param msg Pointer to the string to be sent
+ *
+ * @return Returns 1 on success, otherwise 0
+ */
+static int send_data(int fd, const char *msg)
+{
+ int msglen = strlen(msg);
+ char buf[130];
+
+ memset(&buf, 0, 130);
+ if( msglen > 127 ) {
+ msglen = 127;
+ }
+ snprintf(buf, msglen+1, "%c", (int) strlen(msg));
+ if (write(fd, buf, strlen(buf)) != strlen(buf)) {
+ return 0;
+ }
+
+ snprintf(buf, msglen+1, "%s", msg);
+ if (write(fd, buf, strlen(buf)) != strlen(buf)) {
+ return 0;
+ }
+ return 1;
+}
+
+
+/**
+ * Reads the server response and parses it. Anything except of 'PASS' is
+ * handled as a failure. But it expects the response to alwyas be 4 bytes.
+ *
+ * @param fd Socket fd where to read the response from
+ *
+ * @returns Returns 1 on successful authentication, otherwise 0. If less than 4 bytes
+ * was read it will return -1. In this case, it might be wise to reconnect to
+ * the server.
+ */
+static int parse_result(int fd)
+{
+ char buf[6];
+
+ memset(&buf, 0, 6);
+ int r = read(fd, &buf, 4);
+
+ if( r == 4 ) {
+ return strcmp(buf, "PASS") == 0;
+ }
+ return -1;
+}
+
+
+/**
+ * Sends username and password via the socket to the authentication server and
+ * parses the result back.
+ *
+ * @param fd Socket fd to the authentication server
+ * @param username Username to be authenticated
+ * @param passwd Matching password to the username
+ *
+ * @returns See parse_result() for valid result codes.
+ */
+static int authenticate_user(int fd, const char *username, const char *passwd)
+{
+ if( !send_data(fd, username) ) {
+ return 0;
+ }
+ if( !send_data(fd, passwd) ) {
+ return 0;
+ }
+ return parse_result(fd);
+}
+
+
+/*
+ * Required plug-in functions
+ */
+
+static ePluginInfo pluginfo = { .name = "socket-auth plug-in",
+ .version = "1.0",
+ .copyright = "2013 (C) David Sommerseth <dazo@users.sourceforge.net>",
+ .pluginType = eptAUTH,
+ .APIversion = 1 };
+
+
+/**
+ * @copydoc PluginInfo()
+ */
+ePluginInfo * PluginInfo()
+{
+ return &pluginfo;
+}
+
+/**
+ * @copydoc PluginInit()
+ */
+int PluginInit(eurephiaCTX *ctx, const char *args)
+{
+ authservfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if ( authservfd == -1 ) {
+ eurephia_log(ctx, LOG_FATAL, 0, "Failed to initialise socket to the socket-auth server");
+ return 0;
+ }
+
+ memset(&sockaddr, 0, sizeof(struct sockaddr_un));
+ sockaddr.sun_family = AF_UNIX;
+ strncpy(sockaddr.sun_path, args, sizeof(sockaddr.sun_path)-1);
+
+ if( connect(authservfd, (struct sockaddr*)&sockaddr, sizeof(struct sockaddr_un)) == -1) {
+ eurephia_log(ctx, LOG_FATAL, 0, "Failed to connect to the socket-auth server");
+ close(authservfd);
+ authservfd = -1;
+ return 0;
+ }
+
+ eurephia_log(ctx, LOG_INFO, 0, "socket-auth connected via %s", args);
+ return 1;
+}
+
+
+/**
+ * @copydoc PluginClose()
+ */
+void PluginClose(eurephiaCTX *ctx)
+{
+ send_data(authservfd, "***SHUTDOWN***");
+ close(authservfd);
+ eurephia_log(ctx, LOG_INFO, 0, "Disconnected from auth-socket");
+}
+
+
+/**
+ * @copydoc AuthenticateUser()
+ */
+eAuthResult * AuthenticateUser(eurephiaCTX *ctx, const char *username, const char *passwd)
+{
+ eAuthResult *ret = NULL;
+ int authres = -2;
+
+ DEBUG(ctx, 20, "socket-auth:AuthenticateUser('%s', xxxxxx)", username);
+
+ ret = malloc_nullsafe(ctx, sizeof(eAuthResult)+2);
+ authres = authenticate_user(authservfd, username, passwd);
+ if( authres < 0 ) {
+ ret->status = eAUTH_PLGERROR;
+ ret->msg = strdup("Failed communicating with auth-server");
+ } else if( authres == 0 ) {
+ ret->status = eAUTH_FAILED;
+ ret->msg = strdup("Wrong password");
+ } else if( authres == 1 ) {
+ ret->status = eAUTH_SUCCESS;
+ } else {
+ // This should never ever happen
+ ret->status = eAUTH_PLGERROR;
+ ret->msg = strdup("Unknown result code");
+ }
+ return ret;
+}