list2form : gestion des erreurs et des petits fichiers (merci cgi.py…)
[progfou.git] / ejabberd / auth-mysql.py
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3 # Depends: python-mysqldb
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
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'
21 _db = 'mail'
22 _timeout = 2
23 _query = "SELECT * FROM auforg_virtual WHERE source=%s AND LENGTH(password)>1"
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 = {}
31
32 def find_user(user, host):
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
48 global _host, _user, _passwd, _db
49 db = MySQLdb.connect(host=_host, user=_user, passwd=_passwd,
50 db=_db, connect_timeout=_timeout)
51 cur = db.cursor(MySQLdb.cursors.DictCursor)
52 nrows = cur.execute(_query, ('%s@%s' % (user,host), ))
53 del cur, db
54 found = (nrows > 0)
55 _find_user_cache[(user,host)] = {'found': found, 'time': now}
56 return (found, False)
57
58 def authenticate_user(user, host, password):
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
74 global _host, _user, _passwd, _db
75 db = MySQLdb.connect(host=_host, user=_user, passwd=_passwd,
76 db=_db, connect_timeout=_timeout)
77 cur = db.cursor(MySQLdb.cursors.DictCursor)
78 nrows = cur.execute(_query, ('%s@%s' % (user,host), ))
79 users = cur.fetchall()
80 del cur, db
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)
89
90 def main():
91 log_file = open(_log_filename, 'a')
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()
111 (result, in_cache) = authenticate_user(user, host, password)
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()
117 (result, in_cache) = find_user(user, host)
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()
123 #result = set_user_password(user, host, password)
124 result = False
125 in_cache = False
126 else:
127 result = False
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))
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()