From 570d0da295f3e2fcd7b8c80ae2e6c42fc365abdd Mon Sep 17 00:00:00 2001 From: Sam Chudnick Date: Mon, 27 Jun 2022 20:41:01 -0400 Subject: Initial commit --- pam/pam.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pam/pam_mfa.c | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100755 pam/pam.py create mode 100644 pam/pam_mfa.c (limited to 'pam') diff --git a/pam/pam.py b/pam/pam.py new file mode 100755 index 0000000..28450ee --- /dev/null +++ b/pam/pam.py @@ -0,0 +1,100 @@ +#!/usr/bin/python3 +import socket +import argparse +import time +import sys + +# Sends authentication request to MFA server +# Receive either pass or fail response from MFA server +# Returns status to PAM + +HEADER_LENGTH = 64 +KEY_LENGTH = 64 +ACK_LENGTH = 3 +DISCONNECT_MESSAGE = "DISCONNECT" +FORMAT = "utf-8" +RESPONSE_LENGTH = 1 +PAM_SUCCESS = 0 +PAM_AUTH_ERR = 7 + +def parse_arguments(): + # Parse command line arguments + parser = argparse.ArgumentParser() + parser.add_argument("--user",type=str,help="PAM username",required=True) + parser.add_argument("--service",type=str,help="PAM service",required=True) + return parser.parse_args() + +def init_connection(mfa_server, pam_port): + # Attempts to connect to MFA server with provided address and port + # Repeats connection attempts once per second until timeout is reached + # Returns the socket if connection was successful or None otherwise + connection = None + timeout = 0 + timeout_length = 5 + sleep_length = 1 + while connection == None and timeout < timeout_length: + try: + connection = socket.create_connection((mfa_server,pam_port)) + print("connected to mfa server") + return connection + except (ConnectionError,ConnectionRefusedError): + time.sleep(sleep_length) + timeout += sleep_length + return None + + +def read_config(config_file="/etc/mfa/mfa.conf"): + # Read config file for server and port info + # Return tuple (server,port) + server = "" + port = 0 + with open(config_file) as conf: + line = None + while line != "": + line = conf.readline() + if line.startswith("server ="): + server = line.split("=")[1].strip() + if line.startswith("port ="): + port = int(line.split("=")[1].strip()) + return (server,port) + +def main(): + authed = "0" + failed = "1" + + # Get arguments + args = parse_arguments() + user = args.user + service = args.service + + # Compile data to send to server + mfa_server, pam_port = read_config() + hostname = None + with open("/etc/hostname") as f: + hostname = f.read().strip() + data = user + "," + hostname + "," + service + + + # Initalize connection to MFA server. Quit if unable to connect. + connection = init_connection(mfa_server,pam_port) + if connection == None: + print(failed) + sys.exit(1) + + # Send authentication data to MFA server + data_length = len(data) + length_msg = str(data_length) + length_msg += ' ' * (HEADER_LENGTH - len(length_msg)) + connection.send(length_msg.encode(FORMAT)) + connection.send(data.encode(FORMAT)) + + # Listen for response from MFA server + # Response will be 0 for authenticated and 1 for denied + response = int(connection.recv(RESPONSE_LENGTH).decode(FORMAT)) + + # Print success/failure for PAM module + print(str(response)) + +if __name__ == '__main__': + main() + diff --git a/pam/pam_mfa.c b/pam/pam_mfa.c new file mode 100644 index 0000000..7e71856 --- /dev/null +++ b/pam/pam_mfa.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define PAMPY "python3 /usr/bin/openmfa/pam/pam.py" + +int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char** argv) { + int retval; + const char *user = NULL; + const char *service; + FILE *fp; + + // Get user and service + pam_get_item(pamh, PAM_SERVICE, (const void **) &service); + pam_get_user(pamh, &user, NULL); + + // Build command line + int cmdsize = 256; + char cmd[cmdsize]; + cmd[0] = '\0'; + strcat(cmd, PAMPY); + strcat(cmd," --user "); + strcat(cmd,user); + strcat(cmd," --service "); + strcat(cmd,service); + pam_syslog(pamh,LOG_INFO,cmd); + + // Execute pam.py + if ((fp = popen(cmd,"r")) == NULL) { + pam_syslog(pamh,LOG_ERR,"Error opening pipe"); + return PAM_AUTH_ERR; + } + + // Get output and return authentication status + int size = 32; + char result[size]; + fgets(result,size,fp); + pam_syslog(pamh,LOG_INFO,result); + pclose(fp); + if (atoi(result) == 0) { + pam_syslog(pamh,LOG_INFO,"auth success"); + return PAM_SUCCESS; + } else { + pam_syslog(pamh,LOG_ERR,"auth error"); + return PAM_AUTH_ERR; + } +} + +int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char** argv) { + return PAM_IGNORE; +} -- cgit v1.2.3