Commit | Line | Data |
---|---|---|
42b8c4c3 P |
1 | #!/usr/bin/env python |
2 | # -*- coding: UTF-8 -*- | |
3 | # Depends: python-mysqldb | |
4a082535 P |
4 | # install -o root -g ejabberd -m 0750 -c auth-mysql.py /etc/ejabberd/ |
5 | # install -o ejabberd -g adm -m 0640 -c /dev/null /var/log/ejabberd/auth-mysql.log | |
6 | # Test: printf '\000\031isuser:moussa.nombre:test' | ./auth-mysql.py | hd | |
7 | ||
42b8c4c3 P |
8 | import sys |
9 | import traceback | |
10 | import struct | |
11 | import time | |
12 | import MySQLdb | |
13 | import crypt | |
14 | ||
15 | # TODO: transformer toute la partie gestion de BdD en une classe | |
16 | # TODO: connexion persistente et gérer les coupures de service MySQL | |
17 | ||
18 | _host = 'nss' | |
19 | _user = 'jabber' | |
20 | _passwd = 'password' | |
4a082535 P |
21 | _db = 'mail' |
22 | _timeout = 2 | |
23 | _query = "SELECT * FROM auforg_virtual WHERE source=%s AND LENGTH(password)>1" | |
f30ec2ce P |
24 | _log_filename = '/var/log/ejabberd/auth-mysql.log' |
25 | _cache_positive_ttl = (15*60) | |
26 | _cache_negative_ttl = (1*60) | |
27 | ||
28 | # ce qui suit est à usage interne, ne pas toucher... | |
29 | _find_user_cache = {} | |
30 | _authenticate_user_cache = {} | |
42b8c4c3 | 31 | |
4a082535 | 32 | def find_user(user, host): |
f30ec2ce P |
33 | """ |
34 | Returns (found, in_cache) | |
35 | where 'found' means the (user,host) was found in the database | |
36 | and 'in_cache' means it was found from the local cache | |
37 | """ | |
38 | now = time.time() | |
39 | ||
40 | global _find_user_cache | |
41 | c = _find_user_cache.get((user,host)) | |
42 | if c != None: | |
43 | if c['found'] and now < (c['time'] + _cache_positive_ttl): | |
44 | return (True, True) | |
45 | if not c['found'] and now < (c['time'] + _cache_negative_ttl): | |
46 | return (False, True) | |
47 | ||
42b8c4c3 P |
48 | global _host, _user, _passwd, _db |
49 | db = MySQLdb.connect(host=_host, user=_user, passwd=_passwd, | |
4a082535 | 50 | db=_db, connect_timeout=_timeout) |
42b8c4c3 | 51 | cur = db.cursor(MySQLdb.cursors.DictCursor) |
4a082535 | 52 | nrows = cur.execute(_query, ('%s@%s' % (user,host), )) |
42b8c4c3 | 53 | del cur, db |
f30ec2ce P |
54 | found = (nrows > 0) |
55 | _find_user_cache[(user,host)] = {'found': found, 'time': now} | |
56 | return (found, False) | |
42b8c4c3 | 57 | |
4a082535 | 58 | def authenticate_user(user, host, password): |
f30ec2ce P |
59 | """ |
60 | Returns (valid, in_cache) | |
61 | where 'valid' means the (user,host,password) was valid in the database | |
62 | and 'in_cache' means it was validated against the local cache | |
63 | """ | |
64 | now = time.time() | |
65 | ||
66 | global _authenticate_user_cache | |
67 | c = _authenticate_user_cache.get((user,host,password)) | |
68 | if c != None: | |
69 | if c['valid'] and now < (c['time'] + _cache_positive_ttl): | |
70 | return (True, True) | |
71 | if not c['valid'] and now < (c['time'] + _cache_negative_ttl): | |
72 | return (False, True) | |
73 | ||
42b8c4c3 P |
74 | global _host, _user, _passwd, _db |
75 | db = MySQLdb.connect(host=_host, user=_user, passwd=_passwd, | |
4a082535 | 76 | db=_db, connect_timeout=_timeout) |
42b8c4c3 | 77 | cur = db.cursor(MySQLdb.cursors.DictCursor) |
4a082535 | 78 | nrows = cur.execute(_query, ('%s@%s' % (user,host), )) |
42b8c4c3 P |
79 | users = cur.fetchall() |
80 | del cur, db | |
f30ec2ce P |
81 | valid = False |
82 | if nrows > 0: | |
83 | for u in users: | |
84 | if crypt.crypt(password, u['password']) == u['password']: | |
85 | valid = True | |
86 | break | |
87 | _authenticate_user_cache[(user,host,password)] = {'valid': valid, 'time': now} | |
88 | return (valid, False) | |
42b8c4c3 P |
89 | |
90 | def main(): | |
f30ec2ce | 91 | log_file = open(_log_filename, 'a') |
42b8c4c3 P |
92 | while True: |
93 | try: | |
94 | nread = sys.stdin.read(2) | |
95 | if len(nread) == 0: | |
96 | time.sleep(0.25) | |
97 | continue | |
98 | now = time.strftime('%Y/%m/%d %H:%M:%S', time.localtime()) | |
99 | if len(nread) < 2: | |
100 | log_file.write('%s bytes_read=%d\n' % (now, len(nread))) | |
101 | log_file.flush() | |
102 | continue | |
103 | size = struct.unpack('>h', nread)[0] | |
104 | data = sys.stdin.read(size) | |
105 | (operation, data) = data.split(':', 1) | |
106 | if operation == 'auth': | |
107 | (user, host, password) = data.split(':', 2) | |
108 | log_file.write('%s operation=%s user=%s host=%s\n' | |
109 | % (now, operation, user, host)) | |
110 | log_file.flush() | |
f30ec2ce | 111 | (result, in_cache) = authenticate_user(user, host, password) |
42b8c4c3 P |
112 | elif operation == 'isuser': |
113 | (user, host) = data.split(':', 1) | |
114 | log_file.write('%s operation=%s user=%s host=%s\n' | |
115 | % (now, operation, user, host)) | |
116 | log_file.flush() | |
f30ec2ce | 117 | (result, in_cache) = find_user(user, host) |
42b8c4c3 P |
118 | elif operation == 'setpass': |
119 | (user, host, password) = data.split(':', 2) | |
120 | log_file.write('%s operation=%s user=%s host=%s\n' | |
121 | % (now, operation, user, host)) | |
122 | log_file.flush() | |
4a082535 | 123 | #result = set_user_password(user, host, password) |
42b8c4c3 | 124 | result = False |
f30ec2ce | 125 | in_cache = False |
42b8c4c3 P |
126 | else: |
127 | result = False | |
f30ec2ce P |
128 | in_cache = False |
129 | if in_cache: | |
130 | in_cache = ' (from cache)' | |
131 | else: | |
132 | in_cache = '' | |
133 | log_file.write('%s => result=%s%s\n' % (now, result, in_cache)) | |
42b8c4c3 P |
134 | log_file.flush() |
135 | sys.stdout.write(struct.pack('>hh', 2, result and 1 or 0)) | |
136 | sys.stdout.flush() | |
137 | except: | |
138 | traceback.print_exc(file=log_file) | |
139 | #sys.exit() | |
140 | ||
141 | if __name__ == '__main__': | |
142 | main() |