| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | __filename__ = "auth.py" | 
					
						
							|  |  |  | __author__ = "Bob Mottram" | 
					
						
							|  |  |  | __license__ = "AGPL3+" | 
					
						
							| 
									
										
										
										
											2021-01-26 10:07:42 +00:00
										 |  |  | __version__ = "1.2.0" | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | __maintainer__ = "Bob Mottram" | 
					
						
							| 
									
										
										
										
											2021-09-10 16:14:50 +00:00
										 |  |  | __email__ = "bob@libreserver.org" | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | __status__ = "Production" | 
					
						
							| 
									
										
										
										
											2021-06-15 15:08:12 +00:00
										 |  |  | __module_group__ = "Security" | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | import base64 | 
					
						
							|  |  |  | import hashlib | 
					
						
							|  |  |  | import binascii | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2020-09-03 18:13:29 +00:00
										 |  |  | import secrets | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  | import datetime | 
					
						
							| 
									
										
										
										
											2020-11-23 09:51:26 +00:00
										 |  |  | from utils import isSystemAccount | 
					
						
							| 
									
										
										
										
											2020-12-23 10:57:44 +00:00
										 |  |  | from utils import hasUsersPath | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-22 18:06:23 +00:00
										 |  |  | def _hashPassword(password: str) -> str: | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     """Hash a password for storing
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') | 
					
						
							|  |  |  |     pwdhash = hashlib.pbkdf2_hmac('sha512', | 
					
						
							|  |  |  |                                   password.encode('utf-8'), | 
					
						
							|  |  |  |                                   salt, 100000) | 
					
						
							|  |  |  |     pwdhash = binascii.hexlify(pwdhash) | 
					
						
							|  |  |  |     return (salt + pwdhash).decode('ascii') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-22 18:06:23 +00:00
										 |  |  | def _getPasswordHash(salt: str, providedPassword: str) -> str: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |     """Returns the hash of a password
 | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     pwdhash = hashlib.pbkdf2_hmac('sha512', | 
					
						
							|  |  |  |                                   providedPassword.encode('utf-8'), | 
					
						
							|  |  |  |                                   salt.encode('ascii'), | 
					
						
							|  |  |  |                                   100000) | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |     return binascii.hexlify(pwdhash).decode('ascii') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 18:13:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  | def constantTimeStringCheck(string1: str, string2: str) -> bool: | 
					
						
							|  |  |  |     """Compares two string and returns if they are the same
 | 
					
						
							|  |  |  |     using a constant amount of time | 
					
						
							|  |  |  |     See https://sqreen.github.io/DevelopersSecurityBestPractices/ | 
					
						
							|  |  |  |     timing-attack/python | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |     # strings must be of equal length | 
					
						
							|  |  |  |     if len(string1) != len(string2): | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |         return False | 
					
						
							|  |  |  |     ctr = 0 | 
					
						
							|  |  |  |     matched = True | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |     for ch in string1: | 
					
						
							|  |  |  |         if ch != string2[ctr]: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |             matched = False | 
					
						
							| 
									
										
										
										
											2020-09-03 18:13:29 +00:00
										 |  |  |         else: | 
					
						
							|  |  |  |             # this is to make the timing more even | 
					
						
							|  |  |  |             # and not provide clues | 
					
						
							|  |  |  |             matched = matched | 
					
						
							| 
									
										
										
										
											2020-09-03 18:07:02 +00:00
										 |  |  |         ctr += 1 | 
					
						
							|  |  |  |     return matched | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-22 18:06:23 +00:00
										 |  |  | def _verifyPassword(storedPassword: str, providedPassword: str) -> bool: | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |     """Verify a stored password against one provided by user
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if not storedPassword: | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  |     if not providedPassword: | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  |     salt = storedPassword[:64] | 
					
						
							|  |  |  |     storedPassword = storedPassword[64:] | 
					
						
							| 
									
										
										
										
											2020-12-22 18:06:23 +00:00
										 |  |  |     pwHash = _getPasswordHash(salt, providedPassword) | 
					
						
							| 
									
										
										
										
											2020-09-03 18:48:32 +00:00
										 |  |  |     return constantTimeStringCheck(pwHash, storedPassword) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | def createBasicAuthHeader(nickname: str, password: str) -> str: | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     """This is only used by tests
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-05-22 11:32:38 +00:00
										 |  |  |     authStr = \ | 
					
						
							|  |  |  |         nickname.replace('\n', '').replace('\r', '') + \ | 
					
						
							|  |  |  |         ':' + \ | 
					
						
							|  |  |  |         password.replace('\n', '').replace('\r', '') | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     return 'Basic ' + base64.b64encode(authStr.encode('utf-8')).decode('utf-8') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def authorizeBasic(baseDir: str, path: str, authHeader: str, | 
					
						
							|  |  |  |                    debug: bool) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     """HTTP basic auth
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if ' ' not in authHeader: | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2021-07-25 21:18:38 +00:00
										 |  |  |             print('DEBUG: basic auth - Authorisation header does not ' + | 
					
						
							| 
									
										
										
										
											2020-03-30 19:09:45 +00:00
										 |  |  |                   'contain a space character') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-12-23 10:57:44 +00:00
										 |  |  |     if not hasUsersPath(path): | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2020-11-23 09:51:26 +00:00
										 |  |  |             print('DEBUG: basic auth - ' + | 
					
						
							|  |  |  |                   'path for Authorization does not contain a user') | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     pathUsersSection = path.split('/users/')[1] | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |     if '/' not in pathUsersSection: | 
					
						
							|  |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2020-11-23 09:51:26 +00:00
										 |  |  |             print('DEBUG: basic auth - this is not a users endpoint') | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     nicknameFromPath = pathUsersSection.split('/')[0] | 
					
						
							| 
									
										
										
										
											2020-11-23 09:51:26 +00:00
										 |  |  |     if isSystemAccount(nicknameFromPath): | 
					
						
							|  |  |  |         print('basic auth - attempted login using system account ' + | 
					
						
							|  |  |  |               nicknameFromPath + ' in path') | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-05-22 11:32:38 +00:00
										 |  |  |     base64Str = \ | 
					
						
							|  |  |  |         authHeader.split(' ')[1].replace('\n', '').replace('\r', '') | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     plain = base64.b64decode(base64Str).decode('utf-8') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     if ':' not in plain: | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2020-11-23 10:18:52 +00:00
										 |  |  |             print('DEBUG: basic auth header does not contain a ":" ' + | 
					
						
							| 
									
										
										
										
											2020-03-30 19:09:45 +00:00
										 |  |  |                   'separator for username:password') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     nickname = plain.split(':')[0] | 
					
						
							| 
									
										
										
										
											2020-11-23 09:51:26 +00:00
										 |  |  |     if isSystemAccount(nickname): | 
					
						
							|  |  |  |         print('basic auth - attempted login using system account ' + nickname + | 
					
						
							|  |  |  |               ' in Auth header') | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     if nickname != nicknameFromPath: | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |             print('DEBUG: Nickname given in the path (' + nicknameFromPath + | 
					
						
							|  |  |  |                   ') does not match the one in the Authorization header (' + | 
					
						
							|  |  |  |                   nickname + ')') | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-06-22 12:29:17 +00:00
										 |  |  |     passwordFile = baseDir + '/accounts/passwords' | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     if not os.path.isfile(passwordFile): | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |         if debug: | 
					
						
							|  |  |  |             print('DEBUG: passwords file missing') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     providedPassword = plain.split(':')[1] | 
					
						
							| 
									
										
										
										
											2021-07-25 22:47:42 +00:00
										 |  |  |     with open(passwordFile, 'r') as passfile: | 
					
						
							|  |  |  |         for line in passfile: | 
					
						
							|  |  |  |             if not line.startswith(nickname + ':'): | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2020-05-22 11:32:38 +00:00
										 |  |  |             storedPassword = \ | 
					
						
							|  |  |  |                 line.split(':')[1].replace('\n', '').replace('\r', '') | 
					
						
							| 
									
										
										
										
											2020-12-22 18:06:23 +00:00
										 |  |  |             success = _verifyPassword(storedPassword, providedPassword) | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |             if not success: | 
					
						
							|  |  |  |                 if debug: | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |                     print('DEBUG: Password check failed for ' + nickname) | 
					
						
							| 
									
										
										
										
											2019-07-04 08:56:15 +00:00
										 |  |  |             return success | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     print('DEBUG: Did not find credentials for ' + nickname + | 
					
						
							|  |  |  |           ' in ' + passwordFile) | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def storeBasicCredentials(baseDir: str, nickname: str, password: str) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-05 09:51:58 +00:00
										 |  |  |     """Stores login credentials to a file
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     if ':' in nickname or ':' in password: | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2020-05-22 11:32:38 +00:00
										 |  |  |     nickname = nickname.replace('\n', '').replace('\r', '').strip() | 
					
						
							|  |  |  |     password = password.replace('\n', '').replace('\r', '').strip() | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     if not os.path.isdir(baseDir + '/accounts'): | 
					
						
							|  |  |  |         os.mkdir(baseDir + '/accounts') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     passwordFile = baseDir + '/accounts/passwords' | 
					
						
							| 
									
										
										
										
											2020-12-22 18:06:23 +00:00
										 |  |  |     storeStr = nickname + ':' + _hashPassword(password) | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     if os.path.isfile(passwordFile): | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |         if nickname + ':' in open(passwordFile).read(): | 
					
						
							| 
									
										
										
										
											2021-07-13 14:40:49 +00:00
										 |  |  |             with open(passwordFile, 'r') as fin: | 
					
						
							| 
									
										
										
										
											2020-08-29 11:14:19 +00:00
										 |  |  |                 with open(passwordFile + '.new', 'w+') as fout: | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |                     for line in fin: | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |                         if not line.startswith(nickname + ':'): | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |                             fout.write(line) | 
					
						
							|  |  |  |                         else: | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |                             fout.write(storeStr + '\n') | 
					
						
							|  |  |  |             os.rename(passwordFile + '.new', passwordFile) | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |         else: | 
					
						
							|  |  |  |             # append to password file | 
					
						
							| 
									
										
										
										
											2021-06-21 22:52:50 +00:00
										 |  |  |             with open(passwordFile, 'a+') as passfile: | 
					
						
							|  |  |  |                 passfile.write(storeStr + '\n') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2021-06-21 22:53:04 +00:00
										 |  |  |         with open(passwordFile, 'w+') as passfile: | 
					
						
							|  |  |  |             passfile.write(storeStr + '\n') | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def removePassword(baseDir: str, nickname: str) -> None: | 
					
						
							| 
									
										
										
										
											2019-07-05 09:51:58 +00:00
										 |  |  |     """Removes the password entry for the given nickname
 | 
					
						
							|  |  |  |     This is called during account removal | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     passwordFile = baseDir + '/accounts/passwords' | 
					
						
							| 
									
										
										
										
											2019-07-05 09:49:57 +00:00
										 |  |  |     if os.path.isfile(passwordFile): | 
					
						
							| 
									
										
										
										
											2021-07-13 14:40:49 +00:00
										 |  |  |         with open(passwordFile, 'r') as fin: | 
					
						
							| 
									
										
										
										
											2020-08-29 11:14:19 +00:00
										 |  |  |             with open(passwordFile + '.new', 'w+') as fout: | 
					
						
							| 
									
										
										
										
											2019-07-05 09:49:57 +00:00
										 |  |  |                 for line in fin: | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |                     if not line.startswith(nickname + ':'): | 
					
						
							| 
									
										
										
										
											2019-07-05 09:49:57 +00:00
										 |  |  |                         fout.write(line) | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |         os.rename(passwordFile + '.new', passwordFile) | 
					
						
							| 
									
										
										
										
											2019-07-05 09:49:57 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def authorize(baseDir: str, path: str, authHeader: str, debug: bool) -> bool: | 
					
						
							| 
									
										
										
										
											2019-07-05 09:51:58 +00:00
										 |  |  |     """Authorize using http header
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     if authHeader.lower().startswith('basic '): | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |         return authorizeBasic(baseDir, path, authHeader, debug) | 
					
						
							| 
									
										
										
										
											2019-07-03 18:24:44 +00:00
										 |  |  |     return False | 
					
						
							| 
									
										
										
										
											2019-07-05 11:27:18 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-13 15:49:29 +00:00
										 |  |  | def createPassword(length: int = 10): | 
					
						
							| 
									
										
										
										
											2020-04-01 19:29:56 +00:00
										 |  |  |     validChars = 'abcdefghijklmnopqrstuvwxyz' + \ | 
					
						
							|  |  |  |         'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' | 
					
						
							| 
									
										
										
										
											2020-07-08 15:09:27 +00:00
										 |  |  |     return ''.join((secrets.choice(validChars) for i in range(length))) | 
					
						
							| 
									
										
										
										
											2021-06-09 14:27:35 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  | def recordLoginFailure(baseDir: str, ipAddress: str, | 
					
						
							|  |  |  |                        countDict: {}, failTime: int, | 
					
						
							|  |  |  |                        logToFile: bool) -> None: | 
					
						
							| 
									
										
										
										
											2021-06-09 14:27:35 +00:00
										 |  |  |     """Keeps ip addresses and the number of times login failures
 | 
					
						
							|  |  |  |     occured for them in a dict | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if not countDict.get(ipAddress): | 
					
						
							|  |  |  |         while len(countDict.items()) > 100: | 
					
						
							|  |  |  |             oldestTime = 0 | 
					
						
							|  |  |  |             oldestIP = None | 
					
						
							|  |  |  |             for ipAddr, ipItem in countDict.items(): | 
					
						
							|  |  |  |                 if oldestTime == 0 or ipItem['time'] < oldestTime: | 
					
						
							|  |  |  |                     oldestTime = ipItem['time'] | 
					
						
							|  |  |  |                     oldestIP = ipAddr | 
					
						
							|  |  |  |             if oldestIP: | 
					
						
							|  |  |  |                 del countDict[oldestIP] | 
					
						
							|  |  |  |         countDict[ipAddress] = { | 
					
						
							|  |  |  |             "count": 1, | 
					
						
							|  |  |  |             "time": failTime | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         countDict[ipAddress]['count'] += 1 | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  |         countDict[ipAddress]['time'] = failTime | 
					
						
							| 
									
										
										
										
											2021-06-09 14:27:35 +00:00
										 |  |  |         failCount = countDict[ipAddress]['count'] | 
					
						
							|  |  |  |         if failCount > 4: | 
					
						
							|  |  |  |             print('WARN: ' + str(ipAddress) + ' failed to log in ' + | 
					
						
							|  |  |  |                   str(failCount) + ' times') | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if not logToFile: | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     failureLog = baseDir + '/accounts/loginfailures.log' | 
					
						
							| 
									
										
										
										
											2021-06-21 22:53:04 +00:00
										 |  |  |     writeType = 'a+' | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  |     if not os.path.isfile(failureLog): | 
					
						
							| 
									
										
										
										
											2021-06-21 22:53:04 +00:00
										 |  |  |         writeType = 'w+' | 
					
						
							| 
									
										
										
										
											2021-06-09 15:19:30 +00:00
										 |  |  |     currTime = datetime.datetime.utcnow() | 
					
						
							| 
									
										
										
										
											2021-06-21 22:53:04 +00:00
										 |  |  |     try: | 
					
						
							|  |  |  |         with open(failureLog, writeType) as fp: | 
					
						
							|  |  |  |             # here we use a similar format to an ssh log, so that | 
					
						
							|  |  |  |             # systems such as fail2ban can parse it | 
					
						
							|  |  |  |             fp.write(currTime.strftime("%Y-%m-%d %H:%M:%SZ") + ' ' + | 
					
						
							|  |  |  |                      'ip-127-0-0-1 sshd[20710]: ' + | 
					
						
							|  |  |  |                      'Disconnecting invalid user epicyon ' + | 
					
						
							|  |  |  |                      ipAddress + ' port 443: ' + | 
					
						
							|  |  |  |                      'Too many authentication failures [preauth]\n') | 
					
						
							|  |  |  |     except BaseException: | 
					
						
							|  |  |  |         pass |