#!/usr/bin/python2 # # demo-auth-server.py - simplistic demo server for the auth-socket module # # Copyright (C) 2013 David Sommerseth # # 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 . # # 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 " % 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)