diff options
| -rwxr-xr-x | client/client.py | 64 | ||||
| -rwxr-xr-x | pam/pam.py | 40 | ||||
| -rwxr-xr-x | server/mfad.py | 73 |
3 files changed, 143 insertions, 34 deletions
diff --git a/client/client.py b/client/client.py index b2429b6..1c7e155 100755 --- a/client/client.py +++ b/client/client.py | |||
| @@ -4,6 +4,7 @@ import socket | |||
| 4 | import time | 4 | import time |
| 5 | import argparse | 5 | import argparse |
| 6 | import sys | 6 | import sys |
| 7 | import os | ||
| 7 | 8 | ||
| 8 | HEADER_LENGTH = 64 | 9 | HEADER_LENGTH = 64 |
| 9 | KEY_LENGTH = 64 | 10 | KEY_LENGTH = 64 |
| @@ -11,6 +12,7 @@ DISCONNECT_LENGTH = ACK_LENGTH = 3 | |||
| 11 | ACK_MESSAGE = "ACK" | 12 | ACK_MESSAGE = "ACK" |
| 12 | DISCONNECT_MESSAGE = "BYE" | 13 | DISCONNECT_MESSAGE = "BYE" |
| 13 | FORMAT = "utf-8" | 14 | FORMAT = "utf-8" |
| 15 | import configparser | ||
| 14 | 16 | ||
| 15 | def parse_arguments(): | 17 | def parse_arguments(): |
| 16 | parser = argparse.ArgumentParser() | 18 | parser = argparse.ArgumentParser() |
| @@ -18,7 +20,7 @@ def parse_arguments(): | |||
| 18 | parser.add_argument("--port",type=int,help="Port to connect to") | 20 | parser.add_argument("--port",type=int,help="Port to connect to") |
| 19 | parser.add_argument("--config",type=str,help="Path to config file",\ | 21 | parser.add_argument("--config",type=str,help="Path to config file",\ |
| 20 | default="/etc/mfa/mfa.conf") | 22 | default="/etc/mfa/mfa.conf") |
| 21 | parser.add_argument("--key",type=str,help="Client connection key",required=True) | 23 | parser.add_argument("--key",type=str,help="Client connection key") |
| 22 | return parser.parse_args() | 24 | return parser.parse_args() |
| 23 | 25 | ||
| 24 | def prompt_user(prompt): | 26 | def prompt_user(prompt): |
| @@ -53,32 +55,48 @@ def init_connection(mfa_server, client_port, client_key): | |||
| 53 | 55 | ||
| 54 | 56 | ||
| 55 | def read_config(config_file): | 57 | def read_config(config_file): |
| 56 | # Read config file for server and port info | 58 | parser = configparser.ConfigParser(inline_comment_prefixes="#") |
| 57 | # Return tuple (server,port) | 59 | parser.read(config_file) |
| 58 | server = "" | 60 | return parser |
| 59 | port = 0 | 61 | |
| 60 | with open(config_file) as conf: | 62 | |
| 61 | line = None | 63 | def get_vars(args,confparser): |
| 62 | while line != "": | 64 | if not os.path.exists(args.config): |
| 63 | line = conf.readline() | 65 | print("Unable to open config file") |
| 64 | if line.startswith("server ="): | 66 | sys.exit(1) |
| 65 | server = line.split("=")[1].strip() | 67 | |
| 66 | if line.startswith("port ="): | 68 | server = None |
| 67 | port = int(line.split("=")[1].strip()) | 69 | port = None |
| 68 | return (server,port) | 70 | key = None |
| 71 | |||
| 72 | # Set values from config file first | ||
| 73 | if confparser.has_section("client"): | ||
| 74 | server = confparser.get("client","server",fallback=None) | ||
| 75 | port = confparser.get("client","port",fallback=None) | ||
| 76 | key = confparser.get("client","key",fallback=None) | ||
| 77 | |||
| 78 | # Let command line args overwrite any values | ||
| 79 | if args.server: | ||
| 80 | server = args.server | ||
| 81 | if args.port: | ||
| 82 | port = args.port | ||
| 83 | if args.key: | ||
| 84 | key = args.key | ||
| 85 | |||
| 86 | # Exit if any value is null | ||
| 87 | if None in [server,port,key]: | ||
| 88 | print("error: one or more items unspecified") | ||
| 89 | sys.exit(1) | ||
| 90 | |||
| 91 | return server,port,key | ||
| 92 | |||
| 69 | 93 | ||
| 70 | def main(): | 94 | def main(): |
| 71 | # Get arguments, exit if unable to connect | 95 | # Get arguments, exit if unable to connect |
| 72 | args = parse_arguments() | 96 | args = parse_arguments() |
| 73 | client_key = args.key | 97 | confparser = read_config(args.config) |
| 74 | 98 | ||
| 75 | # Read server and port from config file but allow command line options | 99 | mfa_server,client_port,client_key = get_vars(args,confparser) |
| 76 | # to override those settings | ||
| 77 | mfa_server, client_port = read_config(args.config) | ||
| 78 | if args.server != None: | ||
| 79 | mfa_server = args.server | ||
| 80 | if args.port != None: | ||
| 81 | client_port = args.port | ||
| 82 | 100 | ||
| 83 | # Exit if invalid key is provided | 101 | # Exit if invalid key is provided |
| 84 | if len(client_key) != KEY_LENGTH: | 102 | if len(client_key) != KEY_LENGTH: |
| @@ -3,6 +3,8 @@ import socket | |||
| 3 | import argparse | 3 | import argparse |
| 4 | import time | 4 | import time |
| 5 | import sys | 5 | import sys |
| 6 | import configparser | ||
| 7 | import os | ||
| 6 | 8 | ||
| 7 | # Sends authentication request to MFA server | 9 | # Sends authentication request to MFA server |
| 8 | # Receive either pass or fail response from MFA server | 10 | # Receive either pass or fail response from MFA server |
| @@ -40,7 +42,6 @@ def init_connection(mfa_server, pam_port): | |||
| 40 | while connection == None and timeout < timeout_length: | 42 | while connection == None and timeout < timeout_length: |
| 41 | try: | 43 | try: |
| 42 | connection = socket.create_connection((mfa_server,pam_port)) | 44 | connection = socket.create_connection((mfa_server,pam_port)) |
| 43 | print("connected to mfa server") | ||
| 44 | return connection | 45 | return connection |
| 45 | except (ConnectionError,ConnectionRefusedError): | 46 | except (ConnectionError,ConnectionRefusedError): |
| 46 | time.sleep(sleep_length) | 47 | time.sleep(sleep_length) |
| @@ -63,19 +64,54 @@ def read_config(config_file): | |||
| 63 | port = int(line.split("=")[1].strip()) | 64 | port = int(line.split("=")[1].strip()) |
| 64 | return (server,port) | 65 | return (server,port) |
| 65 | 66 | ||
| 67 | |||
| 68 | def read_config(config_file): | ||
| 69 | parser = configparser.ConfigParser(inline_comment_prefixes="#") | ||
| 70 | parser.read(config_file) | ||
| 71 | return parser | ||
| 72 | |||
| 73 | |||
| 74 | def get_vars(args,confparser): | ||
| 75 | if not os.path.exists(args.config): | ||
| 76 | print("Unable to open config file") | ||
| 77 | sys.exit(1) | ||
| 78 | |||
| 79 | server = None | ||
| 80 | port = None | ||
| 81 | |||
| 82 | # Set values from config file first | ||
| 83 | if confparser.has_section("pam"): | ||
| 84 | server = confparser.get("pam","server",fallback=None) | ||
| 85 | port = confparser.get("pam","port",fallback=None) | ||
| 86 | |||
| 87 | # Let command line args overwrite any values | ||
| 88 | if args.server: | ||
| 89 | server = args.server | ||
| 90 | if args.port: | ||
| 91 | port = args.port | ||
| 92 | |||
| 93 | # Exit if any value is null | ||
| 94 | if None in [server,port]: | ||
| 95 | print("error: one or more items unspecified") | ||
| 96 | sys.exit(1) | ||
| 97 | |||
| 98 | return server,port | ||
| 99 | |||
| 100 | |||
| 66 | def main(): | 101 | def main(): |
| 67 | authed = "0" | 102 | authed = "0" |
| 68 | failed = "1" | 103 | failed = "1" |
| 69 | 104 | ||
| 70 | # Get arguments | 105 | # Get arguments |
| 71 | args = parse_arguments() | 106 | args = parse_arguments() |
| 107 | confparser = read_config(args.config) | ||
| 108 | mfa_server,pam_port = get_vars(args,confparser) | ||
| 72 | user = args.user | 109 | user = args.user |
| 73 | service = args.service | 110 | service = args.service |
| 74 | 111 | ||
| 75 | # Compile data to send to server | 112 | # Compile data to send to server |
| 76 | # Read server and port from config file but allow command line options | 113 | # Read server and port from config file but allow command line options |
| 77 | # to override those settings | 114 | # to override those settings |
| 78 | mfa_server, pam_port = read_config(args.config) | ||
| 79 | if args.server != None: | 115 | if args.server != None: |
| 80 | mfa_server = args.server | 116 | mfa_server = args.server |
| 81 | if args.port != None: | 117 | if args.port != None: |
diff --git a/server/mfad.py b/server/mfad.py index d045e14..46fc0cc 100755 --- a/server/mfad.py +++ b/server/mfad.py | |||
| @@ -7,6 +7,8 @@ import threading | |||
| 7 | import pyotp | 7 | import pyotp |
| 8 | import sqlite3 | 8 | import sqlite3 |
| 9 | import re | 9 | import re |
| 10 | import configparser | ||
| 11 | import argparse | ||
| 10 | 12 | ||
| 11 | ## Listens for authentication request from PAM module | 13 | ## Listens for authentication request from PAM module |
| 12 | ## Recevies connection from client | 14 | ## Recevies connection from client |
| @@ -16,7 +18,7 @@ import re | |||
| 16 | ## Return pass or fail response to PAM moudle | 18 | ## Return pass or fail response to PAM moudle |
| 17 | 19 | ||
| 18 | 20 | ||
| 19 | DB_NAME = "mfa.db" | 21 | DB_NAME = "" |
| 20 | HEADER_LENGTH = 64 | 22 | HEADER_LENGTH = 64 |
| 21 | KEY_LENGTH = 64 | 23 | KEY_LENGTH = 64 |
| 22 | DISCONNECT_LENGTH = ACK_LENGTH = 3 | 24 | DISCONNECT_LENGTH = ACK_LENGTH = 3 |
| @@ -41,6 +43,22 @@ CLIENT_SECRET_INDEX = 2 | |||
| 41 | # key and a tuple of (socket,(addr,port)) as the value | 43 | # key and a tuple of (socket,(addr,port)) as the value |
| 42 | client_connections = dict() | 44 | client_connections = dict() |
| 43 | 45 | ||
| 46 | def parse_arguments(): | ||
| 47 | parser = argparse.ArgumentParser() | ||
| 48 | parser.add_argument("--address",type=str,help="Bind Address") | ||
| 49 | parser.add_argument("--pam-port",type=int,help="Port to listen for PAM requests") | ||
| 50 | parser.add_argument("--client-port",type=int,help="Port for client connections") | ||
| 51 | parser.add_argument("--database",type=str,help="Path to alternate database file") | ||
| 52 | parser.add_argument("--config",type=str,help="Alternate config file location",\ | ||
| 53 | default="/etc/mfa/mfa.conf") | ||
| 54 | return parser.parse_args() | ||
| 55 | |||
| 56 | |||
| 57 | def read_config(config): | ||
| 58 | parser = configparser.ConfigParser(inline_comment_prefixes="#") | ||
| 59 | parser.read(config) | ||
| 60 | return parser | ||
| 61 | |||
| 44 | 62 | ||
| 45 | def eval_mfa(client_key, mfa_methods, client_response): | 63 | def eval_mfa(client_key, mfa_methods, client_response): |
| 46 | print("response: " + client_response) | 64 | print("response: " + client_response) |
| @@ -228,14 +246,14 @@ def listen_pam(addr, port): | |||
| 228 | 246 | ||
| 229 | ################################################################################ | 247 | ################################################################################ |
| 230 | 248 | ||
| 231 | def create_db(): | 249 | def create_db(db): |
| 232 | with sqlite3.connect(DB_NAME) as conn: | 250 | with sqlite3.connect(db) as conn: |
| 233 | c = conn.cursor() | 251 | c = conn.cursor() |
| 234 | c.execute("""CREATE TABLE applications ( | 252 | c.execute("""CREATE TABLE applications ( |
| 235 | username text, | 253 | username text, |
| 236 | hostname text, | 254 | hostname text, |
| 237 | service text, | 255 | service text, |
| 238 | client_key text, | 256 | alias text, |
| 239 | mfa_methods text | 257 | mfa_methods text |
| 240 | )""") | 258 | )""") |
| 241 | c.execute("""CREATE TABLE clients ( | 259 | c.execute("""CREATE TABLE clients ( |
| @@ -243,16 +261,53 @@ def create_db(): | |||
| 243 | key text, | 261 | key text, |
| 244 | totp_secret text | 262 | totp_secret text |
| 245 | )""") | 263 | )""") |
| 264 | conn.commit() | ||
| 265 | |||
| 266 | |||
| 267 | def get_vars(args,confparser): | ||
| 268 | if not os.path.exists(args.config): | ||
| 269 | print("Unable to open config file") | ||
| 270 | sys.exit(1) | ||
| 271 | |||
| 272 | bind_addr = None | ||
| 273 | client_port = None | ||
| 274 | pam_port = None | ||
| 275 | database = None | ||
| 276 | |||
| 277 | # Set values from config file first | ||
| 278 | if confparser.has_section("mfad"): | ||
| 279 | bind_addr = confparser.get("mfad","address",fallback=None) | ||
| 280 | client_port = confparser.get("mfad","client-port",fallback=None) | ||
| 281 | pam_port = confparser.get("mfad","pam-port",fallback=None) | ||
| 282 | database = confparser.get("mfad","database",fallback=None) | ||
| 283 | |||
| 284 | # Let command line args overwrite any values | ||
| 285 | if args.address: | ||
| 286 | bind_addr = args.address | ||
| 287 | if args.client_port: | ||
| 288 | client_port = args.client_port | ||
| 289 | if args.pam_port: | ||
| 290 | pam_port = args.pam_port | ||
| 291 | if args.database: | ||
| 292 | database = args.database | ||
| 293 | |||
| 294 | # Exit if any value is null | ||
| 295 | if None in [bind_addr,client_port,pam_port,database]: | ||
| 296 | print("error: one or more items unspecified") | ||
| 297 | sys.exit(1) | ||
| 298 | |||
| 299 | return bind_addr, int(client_port), int(pam_port), database | ||
| 246 | 300 | ||
| 247 | 301 | ||
| 248 | def main(): | 302 | def main(): |
| 249 | global connection_list | 303 | args = parse_arguments() |
| 250 | bind_addr = "127.0.0.1" | 304 | confparser = read_config(args.config) |
| 251 | pam_port = 8000 | 305 | |
| 252 | client_port = 8001 | 306 | bind_addr, client_port, pam_port, DB_NAME = get_vars(args,confparser) |
| 253 | 307 | ||
| 254 | if not os.path.exists(DB_NAME): | 308 | if not os.path.exists(DB_NAME): |
| 255 | create_db() | 309 | print("Creating DB") |
| 310 | create_db(DB_NAME) | ||
| 256 | 311 | ||
| 257 | clients = threading.Thread(target=listen_client,args=(bind_addr,client_port)) | 312 | clients = threading.Thread(target=listen_client,args=(bind_addr,client_port)) |
| 258 | pam = threading.Thread(target=listen_pam,args=(bind_addr,pam_port)) | 313 | pam = threading.Thread(target=listen_pam,args=(bind_addr,pam_port)) |
