2020-03-27 14:04:57 +11:00
#!/usr/bin/env python3
2022-12-19 09:25:10 +11:00
2022-12-19 09:25:24 +11:00
import atexit
import logging
import struct
import sys
import time
import bcrypt
import psycopg2
2020-03-27 14:04:57 +11:00
2022-12-19 09:25:10 +11:00
2020-03-27 14:04:57 +11:00
# Database connection details. The credentials here need access to the Mastodon database, which "ejabberd" is unlikely
# to have on your system by default. You shoud grant SELECT privileges to ejabberd on the "accounts" and "users" tables,
# to play it safe, or include the Mastodon DB user credentials here (don't).
db_host = " localhost "
db_port = 5432
db_user = " ejabberd "
db_pass = " "
db_name = " mastodon "
# This is the query that pulls the password hash for the given user. Mastodon doesn't store the domain for local accounts in
# the database, so we ignore the host component and try to match username where the domain is NULL.
2020-09-21 23:28:38 +10:00
db_query_getpass = " select users.encrypted_password as password from accounts inner join users on accounts.id=users.account_id where lower(accounts.username) = %(user)s and accounts.domain is null "
2020-03-27 14:04:57 +11:00
########################################################################
2022-12-19 09:25:45 +11:00
# Setup
2020-03-27 14:04:57 +11:00
########################################################################
sys . stderr = open ( ' /var/log/ejabberd/extauth_err.log ' , ' a ' )
2022-12-19 09:24:54 +11:00
logging . basicConfig (
level = logging . INFO ,
format = ' %(asctime)s %(levelname)s %(message)s ' ,
filename = ' /var/log/ejabberd/extauth.log ' ,
filemode = ' a ' ,
)
2020-03-27 14:04:57 +11:00
try :
2022-12-19 09:24:48 +11:00
# Connect to the DB, set autocommit and readonly otherwise postgresql seems to have a
# tendency to keep things "idle in transaction" and table locks eventually grind
# Mastodon to a halt. We don't make any changes anyway.
database = psycopg2 . connect ( host = db_host , user = db_user , password = db_pass , database = db_name , port = db_port )
database . set_session ( readonly = True , autocommit = True )
logging . debug ( database . get_dsn_parameters ( ) )
2020-03-27 14:04:57 +11:00
except :
2022-12-19 09:24:48 +11:00
logging . error ( " Unable to initialize database, check settings! " )
time . sleep ( 10 )
sys . exit ( 1 )
2020-03-27 14:04:57 +11:00
2022-12-19 09:25:10 +11:00
2020-03-27 14:04:57 +11:00
@atexit.register
def close_db ( ) :
2022-12-19 09:24:48 +11:00
cursor . close ( )
database . close ( )
2020-03-27 14:04:57 +11:00
2022-12-19 09:25:10 +11:00
2020-03-27 14:04:57 +11:00
logging . info ( ' auth-mastodon script started, waiting for ejabberd requests ' )
2022-12-19 09:25:10 +11:00
2020-03-27 14:04:57 +11:00
class EjabberdInputError ( Exception ) :
2022-12-19 09:25:10 +11:00
2022-12-19 09:24:48 +11:00
def __init__ ( self , value ) :
self . value = value
2022-12-19 09:25:10 +11:00
2022-12-19 09:24:48 +11:00
def __str__ ( self ) :
return repr ( self . value )
2020-03-27 14:04:57 +11:00
########################################################################
2022-12-19 09:25:45 +11:00
# Declarations
2020-03-27 14:04:57 +11:00
########################################################################
def ejabberd_in ( ) :
2022-12-19 09:24:48 +11:00
logging . debug ( " trying to read 2 bytes from ejabberd: " )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
input_length = sys . stdin . buffer . read ( 2 )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
if len ( input_length ) is not 2 :
logging . debug ( " ejabberd sent us wrong things! " )
raise EjabberdInputError ( ' Wrong input from ejabberd! ' )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
logging . debug ( ' got 2 bytes via stdin: %s ' % input_length )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
( size , ) = struct . unpack ( ' >h ' , input_length )
logging . debug ( ' size of data: %i ' % size )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
income = sys . stdin . read ( size )
logging . debug ( " incoming data: %s " % income )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
return income
2020-03-27 14:04:57 +11:00
def ejabberd_out ( bool ) :
2022-12-19 09:24:48 +11:00
logging . debug ( " Ejabberd gets: %s " % bool )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
token = genanswer ( bool )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
logging . debug ( " sent bytes: %#x %#x %#x %#x " % ( token [ 0 ] , token [ 1 ] , token [ 2 ] , token [ 3 ] ) )
2020-03-27 14:04:57 +11:00
2022-12-19 09:24:48 +11:00
sys . stdout . buffer . write ( token )
sys . stdout . buffer . flush ( )
2020-03-27 14:04:57 +11:00
def genanswer ( bool ) :
2022-12-19 09:24:48 +11:00
answer = 0
if bool :
answer = 1
token = struct . pack ( ' >hh ' , 2 , answer )
return token
2020-03-27 14:04:57 +11:00
def get_password ( user , host ) :
2022-12-19 09:24:48 +11:00
# Right now we ignore the host component, as Mastodon doesn't store it for local accounts.
# It may be required one day, so the code to handle passing it to the query is left in for now.
cursor = database . cursor ( )
cursor . execute ( db_query_getpass , { " user " : user . lower ( ) , " host " : host } )
data = cursor . fetchone ( )
cursor . close ( )
return data [ 0 ] if data != None else None
2020-03-27 14:04:57 +11:00
def isuser ( user , host ) :
2022-12-19 09:24:48 +11:00
return get_password ( user , host ) != None
2020-03-27 14:04:57 +11:00
def auth ( user , host , password ) :
2022-12-19 09:24:48 +11:00
db_password = get_password ( user , host )
if db_password == None :
logging . debug ( " Wrong username: %s @ %s " % ( user , host ) )
return False
else :
if bcrypt . checkpw ( password . encode ( ' utf8 ' ) , db_password . encode ( ' utf8 ' ) ) :
logging . debug ( " Validated %s against hash %s " % ( user , db_password ) )
return True
else :
logging . debug ( " Wrong password for user: %s @ %s " % ( user , host ) )
return False
2020-03-27 14:04:57 +11:00
########################################################################
2022-12-19 09:25:45 +11:00
# Main Loop
2020-03-27 14:04:57 +11:00
########################################################################
exitcode = 0
while True :
2022-12-19 09:24:48 +11:00
logging . debug ( " start of infinite loop " )
try :
ejab_request = ejabberd_in ( ) . split ( ' : ' , 3 )
except EOFError :
break
except Exception as e :
logging . exception ( " Exception occured while reading stdin " )
raise
op_result = False
try :
# Only 'auth' and 'isuser' implemented, placeholders left to maybe
# expose other functions later but for now let's not even think about
# modifying the Mastodon DB
if ejab_request [ 0 ] == " auth " :
op_result = auth ( ejab_request [ 1 ] , ejab_request [ 2 ] , ejab_request [ 3 ] )
elif ejab_request [ 0 ] == " isuser " :
op_result = isuser ( ejab_request [ 1 ] , ejab_request [ 2 ] )
elif ejab_request [ 0 ] == " setpass " :
op_result = False
elif ejab_request [ 0 ] == " tryregister " :
op_result = False
elif ejab_request [ 0 ] == " removeuser " :
op_result = False
elif ejab_request [ 0 ] == " removeuser3 " :
op_result = False
except Exception :
logging . exception ( " Exception occured " )
ejabberd_out ( op_result )
logging . info ( " successful " if op_result else " unsuccessful " )
2020-03-27 14:04:57 +11:00
logging . debug ( " end of infinite loop " )
logging . info ( ' extauth script terminating ' )
database . close ( )
sys . exit ( exitcode )