Python3 PAM Modules Added
This commit is contained in:
parent
0a0f7ea77f
commit
9b0f0260d7
2 changed files with 494 additions and 0 deletions
248
ehcp/etc/pam/pam_dbauth_smtp_python3.py
Normal file
248
ehcp/etc/pam/pam_dbauth_smtp_python3.py
Normal file
|
@ -0,0 +1,248 @@
|
|||
# vim: noai:ts=2:sw=2:set expandtab:
|
||||
#
|
||||
# pam_dbauth.py
|
||||
# Performs salted-hash authentication from a database
|
||||
#
|
||||
"""
|
||||
Author: Eric Windisch <eric@windisch.us>
|
||||
Copyright: 2010, Eric Windisch <eric@grokthis.net>, VPS Village
|
||||
License: EPL v1.0
|
||||
|
||||
Contributor: Eric Arnol-Martin <earnolmartin@gmail.com>
|
||||
Added MySQL Password() Function Support
|
||||
- Requires passlib (sudo pip install passlib)
|
||||
- Fixed port integer bug MySQL
|
||||
- Added logic to handle an already encrypted password being sent in
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
Add to PAM configuration with:
|
||||
auth required pam_python.so pam_dbauth.py
|
||||
|
||||
|
||||
Requires configuration file, /etc/security/pam_dbauth.conf,
|
||||
Example:
|
||||
|
||||
[database]
|
||||
host=localhost
|
||||
user=myuser
|
||||
password=mypass
|
||||
db=myuser_db
|
||||
port=XXXX
|
||||
engine=mysqldb
|
||||
; engine=psycopg2
|
||||
; engine=redis
|
||||
|
||||
[query]
|
||||
; SQL Example
|
||||
select_statement=select password from users where username=%s
|
||||
|
||||
; Redis example:
|
||||
; select_statement=users:%s:password
|
||||
|
||||
; ----------------------------------------------------------------
|
||||
; Support forcing or defaulting hashtypes,
|
||||
; ONLY effective if stored password does not start with {hashtype}.
|
||||
; ----------------------------------------------------------------
|
||||
; hashtype_force=sha1
|
||||
;
|
||||
; ----------------------------------------------------------------
|
||||
; Default type to be used if all auto-detection fails (unlikely)
|
||||
; ----------------------------------------------------------------
|
||||
; hashtype_default=md5
|
||||
;
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import syslog
|
||||
import hashlib
|
||||
import base64
|
||||
import string
|
||||
import traceback
|
||||
import configparser
|
||||
from passlib.hash import mysql41
|
||||
import crypt
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read('/etc/security/pam_dbauth_smtp.conf')
|
||||
|
||||
dbengine=config.get('database','engine')
|
||||
if dbengine == 'mysqldb':
|
||||
import MySQLdb
|
||||
dbengineClass=MySQLdb
|
||||
elif dbengine == 'pyscopg2':
|
||||
import psycopg2
|
||||
dbengineClass=psycopg2
|
||||
elif dbengine == 'redis':
|
||||
import redis
|
||||
dbengineClass=redis
|
||||
else:
|
||||
syslog.syslog ("pam_dbauth.py - Unknown or unspecified database engine")
|
||||
sys.exit(1)
|
||||
|
||||
def pam_sm_authenticate(pamh, flags, argv):
|
||||
resp=pamh.conversation(
|
||||
pamh.Message(pamh.PAM_PROMPT_ECHO_OFF,"Password")
|
||||
)
|
||||
|
||||
try:
|
||||
user = pamh.get_user(None)
|
||||
except pamh.exception as e:
|
||||
return e.pam_result
|
||||
if user == None:
|
||||
return pamh.PAM_USER_UNKNOWN
|
||||
|
||||
try:
|
||||
def safeConfigGet(sect,key):
|
||||
if config.has_option(sect,key):
|
||||
return config.get(sect,key)
|
||||
else:
|
||||
None
|
||||
|
||||
connargs={
|
||||
# Each engine has its own connection string type.
|
||||
# perhaps I might abstract this further into a class,
|
||||
# but for now, this module is light enough, that I
|
||||
# simply won't bother.
|
||||
'mysqldb': {
|
||||
'host': safeConfigGet('database','host'),
|
||||
'user': safeConfigGet('database','user'),
|
||||
'passwd': safeConfigGet('database','password'),
|
||||
'port': int(safeConfigGet('database','port')),
|
||||
'db': safeConfigGet('database','db')
|
||||
},
|
||||
'pyscopg2': {
|
||||
'host': safeConfigGet('database','host'),
|
||||
'user': safeConfigGet('database','user'),
|
||||
'password': safeConfigGet('database','password'),
|
||||
'port': safeConfigGet('database','port'),
|
||||
'db': safeConfigGet('database','db')
|
||||
},
|
||||
'redis': {
|
||||
'host': safeConfigGet('database','host'),
|
||||
'password': safeConfigGet('database','password'),
|
||||
'port': safeConfigGet('database','port'),
|
||||
'db': safeConfigGet('database','db')
|
||||
}
|
||||
}[dbengine]
|
||||
|
||||
# Filter out None vars.
|
||||
for k in connargs.keys():
|
||||
if connargs[k] is None:
|
||||
del connargs[k]
|
||||
|
||||
# Connect to database... finally!
|
||||
db=dbengineClass.connect( **connargs )
|
||||
|
||||
# Query the DB.
|
||||
# All but Redis (so-far) are SQL-based...
|
||||
if dbengine != 'redis':
|
||||
cursor=db.cursor()
|
||||
query=str(config.get('query','select_statement')).format(user)
|
||||
#syslog.syslog ("query is " + query)
|
||||
cursor.execute(query)
|
||||
pass_raw=cursor.fetchone()[0]
|
||||
else:
|
||||
pass_raw=db.get(config.get('query','select_statement' % (user)))
|
||||
|
||||
# Initalize pass_stored to pass_raw... we might change it
|
||||
pass_stored=pass_raw
|
||||
|
||||
# We search for a {} section containing the hashtype
|
||||
htindex=str.find(pass_raw,"}")
|
||||
if htindex > 0:
|
||||
# password contained a hashtype
|
||||
hashtype=pass_raw[1:htindex]
|
||||
# Remove the hashtype indicator
|
||||
pass_stored=pass_raw[htindex:]
|
||||
elif config.has_option('query','hashtype_force'):
|
||||
# if a hashtype is forced on us
|
||||
hashtype=config.get('query','hashtype_force')
|
||||
elif len(pass_raw) == 16:
|
||||
# assume 16-byte length is md5
|
||||
hashtype='md5'
|
||||
elif len(pass_raw) == 20:
|
||||
# assume 20-byte length is sha-1
|
||||
hashtype='ssha1'
|
||||
elif config.has_option('query','hashtype_default'):
|
||||
# attempt to fall back...
|
||||
hashtype=config.get('query','hashtype_default')
|
||||
elif len(pass_raw) == 41:
|
||||
# MySQL Password() function hash
|
||||
hashtype='mysql_password_function'
|
||||
elif pass_stored == resp.resp: # Perhaps an already encrypted password is being sent in like in the case of SMTP sasl auth...
|
||||
return pamh.PAM_SUCCESS
|
||||
elif pass_stored == crypt.crypt(resp.resp, "ehcp"):
|
||||
return pamh.PAM_SUCCESS
|
||||
else:
|
||||
#syslog.syslog ("Unable to determine hash type... the length is " + str(len(pass_raw)))
|
||||
return pamh.PAM_SERVICE_ERR
|
||||
|
||||
if hashtype != 'mysql_password_function':
|
||||
|
||||
pass_decoded=base64.b64decode(pass_stored)
|
||||
|
||||
# Set the hashlib
|
||||
hl={
|
||||
'ssha1': hashlib.sha1(),
|
||||
'sha1': hashlib.sha1(),
|
||||
'md5': hashlib.md5(),
|
||||
}[hashtype]
|
||||
|
||||
pass_base=pass_decoded[:hl.digest_size]
|
||||
pass_salt=pass_decoded[hl.digest_size:]
|
||||
|
||||
hl.update(resp.resp)
|
||||
hl.update(pass_salt)
|
||||
|
||||
hashedrep = base64.b64encode(hl.digest())
|
||||
|
||||
if hl.digest() == pass_base:
|
||||
#syslog.syslog ("pam-dbauth.py hashes match")
|
||||
return pamh.PAM_SUCCESS
|
||||
else:
|
||||
#syslog.syslog ("hashes do not match: " + hl.digest() + " not equal to " + pass_base)
|
||||
pamh.PAM_AUTH_ERR
|
||||
else:
|
||||
if pass_stored == mysql41.encrypt(resp.resp):
|
||||
#syslog.syslog ("pam-dbauth.py hashes match")
|
||||
return pamh.PAM_SUCCESS
|
||||
else:
|
||||
#syslog.syslog ("hashes do not match: " + hl.digest() + " not equal to " + pass_base)
|
||||
pamh.PAM_AUTH_ERR
|
||||
|
||||
except Exception as e:
|
||||
#traceback.print_exc()
|
||||
#syslog.syslog ("pam-dbauth.py exception triggered " + str(e))
|
||||
return pamh.PAM_SERVICE_ERR
|
||||
|
||||
return pamh.PAM_SERVICE_ERR
|
||||
|
||||
def pam_sm_setcred(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
def pam_sm_acct_mgmt(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
def pam_sm_open_session(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
def pam_sm_close_session(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
def pam_sm_chauthtok(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
#if __name__ == '__main__':
|
||||
# pam_sm_authenticate(pamh, flags, argv):
|
||||
|
||||
|
||||
"""
|
||||
Author: Eric Windisch <eric@windisch.us>
|
||||
Copyright: 2010, Eric Windisch <eric@grokthis.net>, VPS Village
|
||||
License: EPL v1.0
|
||||
"""
|
||||
|
246
ehcp/etc/pam/pam_dbauth_vsftpd_python3.py
Normal file
246
ehcp/etc/pam/pam_dbauth_vsftpd_python3.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
# vim: noai:ts=2:sw=2:set expandtab:
|
||||
#
|
||||
# pam_dbauth.py
|
||||
# Performs salted-hash authentication from a database
|
||||
#
|
||||
"""
|
||||
Author: Eric Windisch <eric@windisch.us>
|
||||
Copyright: 2010, Eric Windisch <eric@grokthis.net>, VPS Village
|
||||
License: EPL v1.0
|
||||
|
||||
Contributor: Eric Arnol-Martin <earnolmartin@gmail.com>
|
||||
Added MySQL Password() Function Support
|
||||
- Requires passlib (sudo pip install passlib)
|
||||
- Fixed port integer bug MySQL
|
||||
- Added logic to handle an already encrypted password being sent in
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
Add to PAM configuration with:
|
||||
auth required pam_python.so pam_dbauth.py
|
||||
|
||||
|
||||
Requires configuration file, /etc/security/pam_dbauth.conf,
|
||||
Example:
|
||||
|
||||
[database]
|
||||
host=localhost
|
||||
user=myuser
|
||||
password=mypass
|
||||
db=myuser_db
|
||||
port=XXXX
|
||||
engine=mysqldb
|
||||
; engine=psycopg2
|
||||
; engine=redis
|
||||
|
||||
[query]
|
||||
; SQL Example
|
||||
select_statement=select password from users where username=%s
|
||||
|
||||
; Redis example:
|
||||
; select_statement=users:%s:password
|
||||
|
||||
; ----------------------------------------------------------------
|
||||
; Support forcing or defaulting hashtypes,
|
||||
; ONLY effective if stored password does not start with {hashtype}.
|
||||
; ----------------------------------------------------------------
|
||||
; hashtype_force=sha1
|
||||
;
|
||||
; ----------------------------------------------------------------
|
||||
; Default type to be used if all auto-detection fails (unlikely)
|
||||
; ----------------------------------------------------------------
|
||||
; hashtype_default=md5
|
||||
;
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import syslog
|
||||
import hashlib
|
||||
import base64
|
||||
import string
|
||||
import traceback
|
||||
import configparser
|
||||
from passlib.hash import mysql41
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read('/etc/security/pam_dbauth_vsftpd.conf')
|
||||
|
||||
dbengine=config.get('database','engine')
|
||||
if dbengine == 'mysqldb':
|
||||
import MySQLdb
|
||||
dbengineClass=MySQLdb
|
||||
elif dbengine == 'pyscopg2':
|
||||
import psycopg2
|
||||
dbengineClass=psycopg2
|
||||
elif dbengine == 'redis':
|
||||
import redis
|
||||
dbengineClass=redis
|
||||
else:
|
||||
syslog.syslog ("pam_dbauth.py - Unknown or unspecified database engine")
|
||||
sys.exit(1)
|
||||
|
||||
def pam_sm_authenticate(pamh, flags, argv):
|
||||
resp=pamh.conversation(
|
||||
pamh.Message(pamh.PAM_PROMPT_ECHO_OFF,"Password")
|
||||
)
|
||||
|
||||
try:
|
||||
user = pamh.get_user(None)
|
||||
except pamh.exception as e:
|
||||
return e.pam_result
|
||||
if user == None:
|
||||
return pamh.PAM_USER_UNKNOWN
|
||||
|
||||
try:
|
||||
def safeConfigGet(sect,key):
|
||||
if config.has_option(sect,key):
|
||||
return config.get(sect,key)
|
||||
else:
|
||||
None
|
||||
|
||||
connargs={
|
||||
# Each engine has its own connection string type.
|
||||
# perhaps I might abstract this further into a class,
|
||||
# but for now, this module is light enough, that I
|
||||
# simply won't bother.
|
||||
'mysqldb': {
|
||||
'host': safeConfigGet('database','host'),
|
||||
'user': safeConfigGet('database','user'),
|
||||
'passwd': safeConfigGet('database','password'),
|
||||
'port': int(safeConfigGet('database','port')),
|
||||
'db': safeConfigGet('database','db')
|
||||
},
|
||||
'pyscopg2': {
|
||||
'host': safeConfigGet('database','host'),
|
||||
'user': safeConfigGet('database','user'),
|
||||
'password': safeConfigGet('database','password'),
|
||||
'port': safeConfigGet('database','port'),
|
||||
'db': safeConfigGet('database','db')
|
||||
},
|
||||
'redis': {
|
||||
'host': safeConfigGet('database','host'),
|
||||
'password': safeConfigGet('database','password'),
|
||||
'port': safeConfigGet('database','port'),
|
||||
'db': safeConfigGet('database','db')
|
||||
}
|
||||
}[dbengine]
|
||||
|
||||
# Filter out None vars.
|
||||
for k in connargs.keys():
|
||||
if connargs[k] is None:
|
||||
del connargs[k]
|
||||
|
||||
# Connect to database... finally!
|
||||
db=dbengineClass.connect( **connargs )
|
||||
|
||||
# Query the DB.
|
||||
# All but Redis (so-far) are SQL-based...
|
||||
if dbengine != 'redis':
|
||||
cursor=db.cursor()
|
||||
query=str(config.get('query','select_statement')).format(user)
|
||||
#syslog.syslog ("query is " + query)
|
||||
cursor.execute(query)
|
||||
pass_raw=cursor.fetchone()[0]
|
||||
else:
|
||||
pass_raw=db.get(config.get('query','select_statement' % (user)))
|
||||
|
||||
# Initalize pass_stored to pass_raw... we might change it
|
||||
pass_stored=pass_raw
|
||||
|
||||
# We search for a {} section containing the hashtype
|
||||
htindex=str.find(pass_raw,"}")
|
||||
if htindex > 0:
|
||||
# password contained a hashtype
|
||||
hashtype=pass_raw[1:htindex]
|
||||
# Remove the hashtype indicator
|
||||
pass_stored=pass_raw[htindex:]
|
||||
elif config.has_option('query','hashtype_force'):
|
||||
# if a hashtype is forced on us
|
||||
hashtype=config.get('query','hashtype_force')
|
||||
elif len(pass_raw) == 16:
|
||||
# assume 16-byte length is md5
|
||||
hashtype='md5'
|
||||
elif len(pass_raw) == 20:
|
||||
# assume 20-byte length is sha-1
|
||||
hashtype='ssha1'
|
||||
elif config.has_option('query','hashtype_default'):
|
||||
# attempt to fall back...
|
||||
hashtype=config.get('query','hashtype_default')
|
||||
elif len(pass_raw) == 41:
|
||||
# MySQL Password() function hash
|
||||
hashtype='mysql_password_function'
|
||||
elif pass_stored == resp.resp: # Perhaps an already encrypted password is being sent in like in the case of SMTP sasl auth...
|
||||
return pamh.PAM_SUCCESS
|
||||
else:
|
||||
#syslog.syslog ("Unable to determine hash type... the length is " + str(len(pass_raw)))
|
||||
return pamh.PAM_SERVICE_ERR
|
||||
|
||||
if hashtype != 'mysql_password_function':
|
||||
|
||||
pass_decoded=base64.b64decode(pass_stored)
|
||||
|
||||
# Set the hashlib
|
||||
hl={
|
||||
'ssha1': hashlib.sha1(),
|
||||
'sha1': hashlib.sha1(),
|
||||
'md5': hashlib.md5(),
|
||||
}[hashtype]
|
||||
|
||||
pass_base=pass_decoded[:hl.digest_size]
|
||||
pass_salt=pass_decoded[hl.digest_size:]
|
||||
|
||||
hl.update(resp.resp)
|
||||
hl.update(pass_salt)
|
||||
|
||||
hashedrep = base64.b64encode(hl.digest())
|
||||
|
||||
if hl.digest() == pass_base:
|
||||
#syslog.syslog ("pam-dbauth.py hashes match")
|
||||
return pamh.PAM_SUCCESS
|
||||
else:
|
||||
#syslog.syslog ("hashes do not match: " + hl.digest() + " not equal to " + pass_base)
|
||||
pamh.PAM_AUTH_ERR
|
||||
else:
|
||||
if pass_stored == mysql41.encrypt(resp.resp):
|
||||
#syslog.syslog ("pam-dbauth.py hashes match")
|
||||
return pamh.PAM_SUCCESS
|
||||
else:
|
||||
#syslog.syslog ("hashes do not match: " + hl.digest() + " not equal to " + pass_base)
|
||||
pamh.PAM_AUTH_ERR
|
||||
|
||||
except Exception as e:
|
||||
#traceback.print_exc()
|
||||
#syslog.syslog ("pam-dbauth.py exception triggered " + str(e))
|
||||
return pamh.PAM_SERVICE_ERR
|
||||
|
||||
return pamh.PAM_SERVICE_ERR
|
||||
|
||||
def pam_sm_setcred(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
def pam_sm_acct_mgmt(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
def pam_sm_open_session(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
def pam_sm_close_session(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
def pam_sm_chauthtok(pamh, flags, argv):
|
||||
return pamh.PAM_SUCCESS
|
||||
|
||||
#if __name__ == '__main__':
|
||||
# pam_sm_authenticate(pamh, flags, argv):
|
||||
|
||||
|
||||
"""
|
||||
Author: Eric Windisch <eric@windisch.us>
|
||||
Copyright: 2010, Eric Windisch <eric@grokthis.net>, VPS Village
|
||||
License: EPL v1.0
|
||||
"""
|
||||
|
||||
|
Loading…
Reference in a new issue