diff options
Diffstat (limited to 'pam')
-rwxr-xr-x | pam/pam.py | 100 | ||||
-rw-r--r-- | pam/pam_mfa.c | 60 |
2 files changed, 160 insertions, 0 deletions
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 @@ | |||
1 | #!/usr/bin/python3 | ||
2 | import socket | ||
3 | import argparse | ||
4 | import time | ||
5 | import sys | ||
6 | |||
7 | # Sends authentication request to MFA server | ||
8 | # Receive either pass or fail response from MFA server | ||
9 | # Returns status to PAM | ||
10 | |||
11 | HEADER_LENGTH = 64 | ||
12 | KEY_LENGTH = 64 | ||
13 | ACK_LENGTH = 3 | ||
14 | DISCONNECT_MESSAGE = "DISCONNECT" | ||
15 | FORMAT = "utf-8" | ||
16 | RESPONSE_LENGTH = 1 | ||
17 | PAM_SUCCESS = 0 | ||
18 | PAM_AUTH_ERR = 7 | ||
19 | |||
20 | def parse_arguments(): | ||
21 | # Parse command line arguments | ||
22 | parser = argparse.ArgumentParser() | ||
23 | parser.add_argument("--user",type=str,help="PAM username",required=True) | ||
24 | parser.add_argument("--service",type=str,help="PAM service",required=True) | ||
25 | return parser.parse_args() | ||
26 | |||
27 | def init_connection(mfa_server, pam_port): | ||
28 | # Attempts to connect to MFA server with provided address and port | ||
29 | # Repeats connection attempts once per second until timeout is reached | ||
30 | # Returns the socket if connection was successful or None otherwise | ||
31 | connection = None | ||
32 | timeout = 0 | ||
33 | timeout_length = 5 | ||
34 | sleep_length = 1 | ||
35 | while connection == None and timeout < timeout_length: | ||
36 | try: | ||
37 | connection = socket.create_connection((mfa_server,pam_port)) | ||
38 | print("connected to mfa server") | ||
39 | return connection | ||
40 | except (ConnectionError,ConnectionRefusedError): | ||
41 | time.sleep(sleep_length) | ||
42 | timeout += sleep_length | ||
43 | return None | ||
44 | |||
45 | |||
46 | def read_config(config_file="/etc/mfa/mfa.conf"): | ||
47 | # Read config file for server and port info | ||
48 | # Return tuple (server,port) | ||
49 | server = "" | ||
50 | port = 0 | ||
51 | with open(config_file) as conf: | ||
52 | line = None | ||
53 | while line != "": | ||
54 | line = conf.readline() | ||
55 | if line.startswith("server ="): | ||
56 | server = line.split("=")[1].strip() | ||
57 | if line.startswith("port ="): | ||
58 | port = int(line.split("=")[1].strip()) | ||
59 | return (server,port) | ||
60 | |||
61 | def main(): | ||
62 | authed = "0" | ||
63 | failed = "1" | ||
64 | |||
65 | # Get arguments | ||
66 | args = parse_arguments() | ||
67 | user = args.user | ||
68 | service = args.service | ||
69 | |||
70 | # Compile data to send to server | ||
71 | mfa_server, pam_port = read_config() | ||
72 | hostname = None | ||
73 | with open("/etc/hostname") as f: | ||
74 | hostname = f.read().strip() | ||
75 | data = user + "," + hostname + "," + service | ||
76 | |||
77 | |||
78 | # Initalize connection to MFA server. Quit if unable to connect. | ||
79 | connection = init_connection(mfa_server,pam_port) | ||
80 | if connection == None: | ||
81 | print(failed) | ||
82 | sys.exit(1) | ||
83 | |||
84 | # Send authentication data to MFA server | ||
85 | data_length = len(data) | ||
86 | length_msg = str(data_length) | ||
87 | length_msg += ' ' * (HEADER_LENGTH - len(length_msg)) | ||
88 | connection.send(length_msg.encode(FORMAT)) | ||
89 | connection.send(data.encode(FORMAT)) | ||
90 | |||
91 | # Listen for response from MFA server | ||
92 | # Response will be 0 for authenticated and 1 for denied | ||
93 | response = int(connection.recv(RESPONSE_LENGTH).decode(FORMAT)) | ||
94 | |||
95 | # Print success/failure for PAM module | ||
96 | print(str(response)) | ||
97 | |||
98 | if __name__ == '__main__': | ||
99 | main() | ||
100 | |||
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 @@ | |||
1 | #include <stdlib.h> | ||
2 | #include <unistd.h> | ||
3 | #include <errno.h> | ||
4 | #include <pwd.h> | ||
5 | #include <string.h> | ||
6 | #include <stdio.h> | ||
7 | #include <stdbool.h> | ||
8 | #include <syslog.h> | ||
9 | |||
10 | #include <security/pam_modutil.h> | ||
11 | #include <security/pam_modules.h> | ||
12 | #include <security/pam_ext.h> | ||
13 | |||
14 | #define PAMPY "python3 /usr/bin/openmfa/pam/pam.py" | ||
15 | |||
16 | int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char** argv) { | ||
17 | int retval; | ||
18 | const char *user = NULL; | ||
19 | const char *service; | ||
20 | FILE *fp; | ||
21 | |||
22 | // Get user and service | ||
23 | pam_get_item(pamh, PAM_SERVICE, (const void **) &service); | ||
24 | pam_get_user(pamh, &user, NULL); | ||
25 | |||
26 | // Build command line | ||
27 | int cmdsize = 256; | ||
28 | char cmd[cmdsize]; | ||
29 | cmd[0] = '\0'; | ||
30 | strcat(cmd, PAMPY); | ||
31 | strcat(cmd," --user "); | ||
32 | strcat(cmd,user); | ||
33 | strcat(cmd," --service "); | ||
34 | strcat(cmd,service); | ||
35 | pam_syslog(pamh,LOG_INFO,cmd); | ||
36 | |||
37 | // Execute pam.py | ||
38 | if ((fp = popen(cmd,"r")) == NULL) { | ||
39 | pam_syslog(pamh,LOG_ERR,"Error opening pipe"); | ||
40 | return PAM_AUTH_ERR; | ||
41 | } | ||
42 | |||
43 | // Get output and return authentication status | ||
44 | int size = 32; | ||
45 | char result[size]; | ||
46 | fgets(result,size,fp); | ||
47 | pam_syslog(pamh,LOG_INFO,result); | ||
48 | pclose(fp); | ||
49 | if (atoi(result) == 0) { | ||
50 | pam_syslog(pamh,LOG_INFO,"auth success"); | ||
51 | return PAM_SUCCESS; | ||
52 | } else { | ||
53 | pam_syslog(pamh,LOG_ERR,"auth error"); | ||
54 | return PAM_AUTH_ERR; | ||
55 | } | ||
56 | } | ||
57 | |||
58 | int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char** argv) { | ||
59 | return PAM_IGNORE; | ||
60 | } | ||