1
2 '''
3 # DESCRIPTION:
4 # Entropy Object Oriented Interface
5
6 Copyright (C) 2007-2009 Fabio Erculiani
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 '''
22
23 from __future__ import with_statement
24 import os
25 import time
26 import random
27 from entropy.services.skel import Authenticator, RemoteDatabase
28 from entropy.exceptions import *
29 from entropy.const import etpConst
30 from entropy.i18n import _
31
33
34 from entropy import tools as entropyTools
36 Authenticator.__init__(self)
37 RemoteDatabase.__init__(self)
38
39 self.itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
40 self.USER_NORMAL = 0
41 self.USER_INACTIVE = 1
42 self.USER_IGNORE = 2
43 self.USER_FOUNDER = 3
44 self.REGISTERED_USERS_GROUP = 7895
45 self.ADMIN_GROUPS = [7893, 7898]
46 self.MODERATOR_GROUPS = [484]
47 self.DEVELOPER_GROUPS = [7900]
48 self.USERNAME_LENGTH_RANGE = range(3,21)
49 self.PASSWORD_LENGTH_RANGE = range(6,31)
50 self.PRIVMSGS_NO_BOX = -3
51 self.NOTIFY_EMAIL = 0
52 self.FAKE_USERNAME = 'already_authed'
53 self.USER_AGENT = "Entropy/%s (compatible; %s; %s: %s %s %s)" % (
54 etpConst['entropyversion'],
55 "Entropy",
56 "UGC",
57 os.uname()[0],
58 os.uname()[4],
59 os.uname()[2],
60 )
61 self.TABLE_PREFIX = 'phpbb_'
62 self.do_update_session_table = True
63
65 allow_name_chars = self._get_config_value("allow_name_chars")
66 if allow_name_chars == "USERNAME_CHARS_ANY":
67 regex = '.+'
68 elif allow_name_chars == "USERNAME_ALPHA_ONLY":
69 regex = '[A-Za-z0-9]+'
70 elif allow_name_chars == "USERNAME_ALPHA_SPACERS":
71 regex = '[A-Za-z0-9-[\]_+ ]+'
72 elif allow_name_chars == "USERNAME_LETTER_NUM":
73 regex = '[a-zA-Z0-9]+'
74 elif allow_name_chars == "USERNAME_LETTER_NUM_SPACERS":
75 regex = '[-\]_+ [a-zA-Z0-9]+'
76 else:
77 regex = '[\x01-\x7F]+'
78 regex = "^%s$" % (regex,)
79 import re
80 myreg = re.compile(regex)
81 if myreg.match(username):
82 del myreg
83 return True
84 return False
85
87 self.check_connection()
88 self.cursor.execute('SELECT user_id FROM '+self.TABLE_PREFIX+'users WHERE `username_clean` = %s OR LOWER(`username`) = %s', (username_clean,username.lower(),))
89 data = self.cursor.fetchone()
90 if not data: return False
91 if not isinstance(data,dict): return False
92 if not data.has_key('user_id'): return False
93 return True
94
96 self.check_connection()
97 self.cursor.execute('SELECT user_id FROM '+self.TABLE_PREFIX+'users WHERE `user_email` = %s', (email,))
98 data = self.cursor.fetchone()
99 if not data: return False
100 if not isinstance(data,dict): return False
101 if not data.has_key('user_id'): return False
102 return True
103
105 self.check_connection()
106 self.cursor.execute('SELECT disallow_id FROM '+self.TABLE_PREFIX+'disallow WHERE `disallow_username` = %s', (username,))
107 data = self.cursor.fetchone()
108 if not data: return True
109 if not isinstance(data,dict): return True
110 if not data.has_key('disallow_id'): return True
111 return False
112
114
115 try:
116 x = unicode(username.encode('utf-8'),'raw_unicode_escape')
117 del x
118 except (UnicodeDecodeError,UnicodeEncodeError,):
119 return False,'Invalid username'
120 if (""" in username) or ("'" in username) or ('"' in username) or \
121 (" " in username):
122 return False,'Invalid username'
123
124 try:
125 valid = self.validate_username_regex(username)
126 except:
127 return False,'Username contains bad characters'
128 if not valid:
129 return False,'Invalid username'
130
131 exists = self.does_username_exist(username, username_clean)
132 if exists: return False,'Username already taken'
133
134 allowed = self.is_username_allowed(username)
135 if not allowed: return False,'Username not allowed'
136
137 return True,'All fine'
138
140 import binascii
141 return str(binascii.crc32(email.lower())) + str(len(email))
142
144 self.check_connection()
145 self.cursor.execute('UPDATE '+self.TABLE_PREFIX+'users SET user_type = %s WHERE `user_id` = %s', (self.USER_NORMAL,user_id,))
146 return True, user_id
147
149 import re
150 username_clean = username.lower()
151 username_clean = re.sub(r'(?:[\x00-\x1F\x7F]+|(?:\xC2[\x80-\x9F])+)', '', username_clean)
152 username_clean = re.sub(r' {2,}',' ',username_clean)
153 username_clean = username_clean.strip()
154 return username_clean
155
156 - def register_user(self, username, password, email, activate = False):
157
158 if len(username) not in self.USERNAME_LENGTH_RANGE:
159 return False,'Username not in range'
160 if len(password) not in self.PASSWORD_LENGTH_RANGE:
161 return False,'Password not in range'
162 valid = self.entropyTools.is_valid_email(email)
163 if not valid:
164 return False,'Invalid email'
165
166
167 username_clean = self.generate_username_clean(username)
168
169
170 status, err_msg = self.validate_username_string(username, username_clean)
171 if not status: return False,err_msg
172
173
174 exists = self.does_email_exist(email)
175 if exists: return False,'Email already in use'
176
177
178 user_id = self.__register(username, username_clean, password, email, activate)
179
180 return True, user_id
181
182
183 - def __register(self, username, username_clean, password, email, activate):
184
185 email_hash = self._generate_email_hash(email)
186 password_hash = self._get_password_hash(password.encode('utf-8'))
187 time_now = int(time.time())
188
189 user_type = self.USER_INACTIVE
190 if activate: user_type = self.USER_NORMAL
191
192 registration_data = {
193 'username': username,
194 'username_clean': username_clean,
195 'user_password': password_hash,
196 'user_pass_convert': 0,
197 'user_email': email.lower(),
198 'user_email_hash': email_hash,
199 'group_id': self.REGISTERED_USERS_GROUP,
200 'user_type': user_type,
201 'user_permissions': '',
202 'user_timezone': self._get_config_value('board_timezone'),
203 'user_dateformat': self._get_config_value('default_dateformat'),
204 'user_lang': self._get_config_value('default_lang'),
205 'user_style': self._get_config_value('default_style'),
206 'user_actkey': '',
207 'user_ip': '',
208 'user_regdate': time_now,
209 'user_passchg': time_now,
210 'user_options': 895,
211 'user_inactive_reason': 0,
212 'user_inactive_time': 0,
213 'user_lastmark': time_now,
214 'user_lastvisit': 0,
215 'user_lastpost_time': 0,
216 'user_lastpage': '',
217 'user_posts': 0,
218 'user_dst': self._get_config_value('board_dst'),
219 'user_colour': '',
220 'user_occ': '',
221 'user_interests': '',
222 'user_avatar': '',
223 'user_avatar_type': 0,
224 'user_avatar_width': 0,
225 'user_avatar_height': 0,
226 'user_new_privmsg': 0,
227 'user_unread_privmsg': 0,
228 'user_last_privmsg': 0,
229 'user_message_rules': 0,
230 'user_full_folder': self.PRIVMSGS_NO_BOX,
231 'user_emailtime': 0,
232 'user_notify': 0,
233 'user_notify_pm': 1,
234 'user_notify_type': self.NOTIFY_EMAIL,
235 'user_allow_pm': 1,
236 'user_allow_viewonline': 1,
237 'user_allow_viewemail': 1,
238 'user_allow_massemail': 1,
239 'user_sig': '',
240 'user_sig_bbcode_uid': '',
241 'user_sig_bbcode_bitfield': '',
242 'user_form_salt': self._get_unique_id(),
243 }
244
245 sql = self._generate_sql('insert', self.TABLE_PREFIX+'users', registration_data)
246 self.cursor.execute(sql)
247 user_id = self.cursor.lastrowid
248
249
250 group_data = {
251 'user_id': user_id,
252 'group_id': self.REGISTERED_USERS_GROUP,
253 'user_pending': 0,
254 }
255 sql = self._generate_sql('insert', self.TABLE_PREFIX+'user_group', group_data)
256 self.cursor.execute(sql)
257
258
259 self._set_config_value('newest_user_id',user_id)
260 self._set_config_value('newest_username',username)
261 self._set_config_value('num_users',int(self._get_config_value('num_users'))+1)
262 self.cursor.execute('SELECT group_colour FROM '+self.TABLE_PREFIX+'groups WHERE group_id = %s', (group_data['group_id'],))
263 data = self.cursor.fetchone()
264 gcolor = None
265 if isinstance(data,dict):
266 if data.has_key('group_colour'):
267 gcolor = data['group_colour']
268 if gcolor: self._set_config_value('newest_user_colour',gcolor)
269
270 return user_id
271
272
274 self.check_connection()
275 self.check_login_data()
276
277 if not self.login_data.has_key('username'):
278 raise PermissionDenied('PermissionDenied: %s' % (_('no username specified'),))
279 elif not self.login_data.has_key('password'):
280 raise PermissionDenied('PermissionDenied: %s' % (_('no password specified'),))
281
282 if not self.login_data['password']:
283 raise PermissionDenied('PermissionDenied: %s' % (_('empty password'),))
284 elif not self.login_data['username']:
285 raise PermissionDenied('PermissionDenied: %s' % (_('empty username'),))
286
287 self.cursor.execute('SELECT * FROM '+self.TABLE_PREFIX+'users WHERE username = %s', (self.login_data['username'],))
288 data = self.cursor.fetchone()
289 if not data:
290 raise PermissionDenied('PermissionDenied: %s' % (_('user not found'),))
291
292 if data['user_pass_convert']:
293 raise PermissionDenied('PermissionDenied: %s' % (
294 _('you need to login on the website to update your password format'),
295 )
296 )
297
298 valid = self._phpbb3_check_hash(self.login_data['password'], data['user_password'])
299 if not valid:
300 raise PermissionDenied('PermissionDenied: %s' % (_('wrong password'),))
301
302 user_type = data['user_type']
303 if (user_type == self.USER_INACTIVE) or (user_type == self.USER_IGNORE):
304 raise PermissionDenied('PermissionDenied: %s' % (_('user inactive'),))
305
306 banned = self.is_user_banned(data['user_id'])
307 if banned:
308 raise PermissionDenied('PermissionDenied: %s' % (_('user banned'),))
309
310 self.login_data.update(data)
311 self.logged_in = True
312 return self.logged_in
313
318
325
333
335 self.check_connection()
336 self.check_login_data()
337 self.check_logged_in()
338
339 self.cursor.execute('SELECT user_birthday FROM '+self.TABLE_PREFIX+'users WHERE user_id = %s', (self.login_data['user_id'],))
340 bday = self.cursor.fetchone()
341 if not bday:
342 return None
343 elif not bday.has_key('user_birthday'):
344 return None
345 return bday['user_birthday']
346
348 self.check_connection()
349 self.check_login_data()
350 self.check_logged_in()
351
352 self.cursor.execute('SELECT username_clean FROM '+self.TABLE_PREFIX+'users WHERE user_id = %s', (self.login_data['user_id'],))
353 data = self.cursor.fetchone()
354 if not data:
355 return ''
356 elif not data.has_key('username_clean'):
357 return ''
358 return data['username_clean']
359
372
374 self.check_connection()
375 self.check_login_data()
376 self.check_logged_in()
377
378 self.cursor.execute('SELECT user_type FROM '+self.TABLE_PREFIX+'users WHERE user_id = %s', (self.login_data['user_id'],))
379 data = self.cursor.fetchone()
380 if data:
381 if data['user_type'] == self.USER_FOUNDER:
382 return True
383
384
385 groups = self.get_user_groups()
386 for group in groups:
387 if group in self.ADMIN_GROUPS:
388 return True
389
390 return False
391
404
406 self.check_connection()
407 self.check_login_data()
408 self.check_logged_in()
409
410 if self.is_moderator():
411 return False
412 elif self.is_administrator():
413 return False
414 elif self.is_developer():
415 return False
416
417 self.cursor.execute('SELECT user_type,user_id FROM '+self.TABLE_PREFIX+'users WHERE user_id = %s', (self.login_data['user_id'],))
418 data = self.cursor.fetchone()
419 if not data:
420 return False
421 if self.is_user_banned(data['user_id']):
422 return False
423 elif data['user_type'] in [self.USER_NORMAL]:
424 return True
425
426 return False
427
428
430 self.check_connection()
431 self.cursor.execute('SELECT ban_userid FROM '+self.TABLE_PREFIX+'banlist WHERE ban_userid = %s', (user,))
432 data = self.cursor.fetchone()
433 if data:
434 return True
435 return False
436
438 self.check_connection()
439 self.check_login_data()
440 self.check_logged_in()
441 groups = self.get_user_groups()
442 if isinstance(group,int):
443 if group in groups:
444 return True
445 elif isinstance(group,basestring):
446 self.cursor.execute('SELECT group_id FROM '+self.TABLE_PREFIX+'groups WHERE group_name = %s', (group,))
447 data = self.cursor.fetchone()
448 if not data:
449 return False
450 elif data['group_id'] in groups:
451 return True
452
453 return False
454
456 self.check_connection()
457 self.check_login_data()
458 self.check_logged_in()
459
460 self.cursor.execute('SELECT '+self.TABLE_PREFIX+'user_group.group_id,'+self.TABLE_PREFIX+'groups.group_name FROM '+self.TABLE_PREFIX+'user_group,'+self.TABLE_PREFIX+'users,'+self.TABLE_PREFIX+'groups WHERE '+self.TABLE_PREFIX+'users.user_id = %s and '+self.TABLE_PREFIX+'users.user_id = '+self.TABLE_PREFIX+'user_group.user_id and '+self.TABLE_PREFIX+'user_group.group_id = '+self.TABLE_PREFIX+'groups.group_id', (self.login_data['user_id'],))
461 data = self.cursor.fetchall()
462 mydata = {}
463 for mydict in data:
464 mydata[mydict['group_id']] = mydict['group_name']
465
466 return mydata
467
469 self.check_connection()
470 self.check_login_data()
471 self.check_logged_in()
472
473 self.cursor.execute('SELECT group_id FROM '+self.TABLE_PREFIX+'users WHERE user_id = %s', (self.login_data['user_id'],))
474 data = self.cursor.fetchone()
475 if data:
476 if data.has_key('group_id'):
477 return data['group_id']
478
479 return -1
480
486
493
495 self.check_connection()
496 self.check_login_data()
497 self.check_logged_in()
498
499 email_hash = self._generate_email_hash(email)
500 mydata = {
501 'user_email_hash': email_hash,
502 'user_email': email.lower(),
503 }
504
505 try:
506 sql = self._generate_sql("update",self.TABLE_PREFIX+'users', mydata, 'user_id = %s' % (self.login_data['user_id'],))
507 self.cursor.execute(sql)
508 return True
509 except Exception:
510 return False
511
513 self.check_connection()
514 self.check_login_data()
515 self.check_logged_in()
516
517 mydata = {
518 'user_password': password_hash,
519 }
520
521 try:
522 sql = self._generate_sql("update",self.TABLE_PREFIX+'users', mydata, 'user_id = %s' % (self.login_data['user_id'],))
523 self.cursor.execute(sql)
524 return True
525 except Exception:
526 return False
527
529 self.check_connection()
530 self.check_login_data()
531 self.check_logged_in()
532 self.cursor.execute('SELECT user_email FROM '+self.TABLE_PREFIX+'users WHERE user_id = %s', (self.login_data['user_id'],))
533 data = self.cursor.fetchone()
534 if not data:
535 return ''
536 elif not data.has_key('user_email'):
537 return ''
538 return data['user_email']
539
541 self.check_connection()
542 self.check_login_data()
543 self.check_logged_in()
544
545
546 valid_params = [
547 "user_icq","user_yim","user_msnm",
548 "user_jabber","user_website","user_from",
549 "user_interests","user_occ","user_birthday",
550 "user_sig"
551 ]
552
553 my_params = {}
554 for param in valid_params:
555 d = profile_data.get(param)
556 if d == None: continue
557 my_params[param] = d
558
559 if not my_params:
560 return False,'no parameters'
561
562
563
564 b_day = my_params.get('user_birthday')
565 if isinstance(b_day,basestring):
566 import re
567 myre = re.compile("(0[1-9]|[12][0-9]|3[01])[-](0[1-9]|1[012])[-](19|20)\d\d")
568 if not myre.match(b_day):
569 del my_params['user_birthday']
570
571 try:
572 sql = self._generate_sql("update",self.TABLE_PREFIX+'users', my_params, 'user_id = %s' % (self.login_data['user_id'],))
573 self.cursor.execute(sql)
574 return True, None
575 except Exception, e:
576 return False, unicode(e)
577
578
580 self.cursor.execute('UPDATE '+self.TABLE_PREFIX+'config SET config_value = %s WHERE config_name = %s',(data,config_name,))
581
583 self.check_connection()
584 self.cursor.execute('SELECT config_value FROM '+self.TABLE_PREFIX+'config WHERE config_name = %s',(config_name,))
585 myconfig = self.cursor.fetchone()
586 if isinstance(myconfig,dict):
587 if myconfig.has_key('config_value'):
588 return myconfig['config_value']
589 return None
590
592 self.check_connection()
593 time_now = int(time.time())
594 autologin = self._get_config_value("allow_autologin")
595 self.cursor.execute('SELECT user_allow_viewonline FROM '+self.TABLE_PREFIX+'users WHERE user_id = %s', (user_id,))
596 myuserprefs = self.cursor.fetchone()
597 session_admin = 0
598 session_data = {
599 'session_id': None,
600 'session_user_id': user_id,
601 'session_last_visit': time_now,
602 'session_start': time_now,
603 'session_time': time_now,
604 'session_ip': ip_address,
605 'session_browser': self.USER_AGENT,
606 'session_forwarded_for': '',
607 'session_page': 'index.php',
608 'session_viewonline': myuserprefs['user_allow_viewonline'],
609 'session_autologin': autologin,
610 'session_admin': session_admin,
611 'session_forum_id': 0,
612 }
613 import hashlib
614 m = hashlib.md5()
615 m.update(str(user_id)+str(time_now)+str(self.USER_AGENT)+str(ip_address)+str(autologin)+str(myuserprefs['user_allow_viewonline']))
616 session_data['session_id'] = m.hexdigest()
617
618 self.cursor.execute('SELECT * FROM '+self.TABLE_PREFIX+'sessions WHERE session_user_id = %s', (user_id,))
619 mydata = self.cursor.fetchone()
620 do_update = False
621 if mydata:
622 do_update = True
623
624 session_data['session_id'] = mydata['session_id']
625 session_data['session_viewonline'] = mydata['session_viewonline']
626 session_data['session_autologin'] = mydata['session_autologin']
627 session_data['session_forwarded_for'] = mydata['session_forwarded_for']
628 session_data['session_forum_id'] = mydata['session_forum_id']
629 session_data['session_page'] = mydata['session_page']
630 session_data['session_browser'] = mydata['session_browser']
631 session_data['session_admin'] = mydata['session_admin']
632
633 if do_update:
634 where = "session_id = '%s'" % (session_data['session_id'],)
635 del session_data['session_id']
636 sql = self._generate_sql('update', self.TABLE_PREFIX+'sessions', session_data, where)
637 else:
638 sql = self._generate_sql('insert', self.TABLE_PREFIX+'sessions', session_data)
639 if sql:
640 self.cursor.execute(sql)
641 self.dbconn.commit()
642
643
645 self.check_connection()
646 self.cursor.execute('SELECT ban_ip FROM '+self.TABLE_PREFIX+'banlist WHERE ban_ip = %s', (ip,))
647 data = self.cursor.fetchone()
648 if data:
649 return True
650 return False
651
653 import hashlib
654 m = hashlib.md5()
655 rnd = str(abs(hash(os.urandom(1))))
656 m.update(rnd)
657 x = m.hexdigest()[:-16]
658 del m
659 return x
660
662 myrand = 0
663 low_n = 100000
664 high_n = 999999
665 while (myrand < low_n) or (myrand > high_n):
666 try:
667 myrand = hash(os.urandom(1))%high_n
668 except NotImplementedError:
669 random.seed()
670 myrand = random.randint(low_n,high_n)
671 return myrand
672
674
675
676 myrandom = str(self._get_random_number())
677
678 myhash = self._hash_crypt_private(password, self._hash_gensalt_private(myrandom))
679
680 if len(myhash) == 34:
681 return myhash
682
683 import hashlib
684 m = hashlib.md5()
685 m.update(myhash)
686 return m.hexdigest()
687
688
690
691 if (iteration_count_log2 < 4) or (iteration_count_log2 > 31):
692 iteration_count_log2 = 8
693
694 myoutput = '$H$'
695 myoutput += self.itoa64[min(iteration_count_log2 + 5,30)]
696 myoutput += self._hash_encode64(myinput, 6)
697
698 return myoutput
699
701
702 myoutput = '*'
703
704 if setting[:3] != '$H$':
705 return myoutput
706
707 count_log2 = self.itoa64.find(setting[3])
708 if count_log2 == -1: count_log2 = 0
709
710 if (count_log2 < 7) or (count_log2 > 30):
711 return myoutput
712
713 count = 1 << count_log2
714 salt = setting[4:12]
715
716 if len(salt) != 8:
717 return myoutput
718
719 import hashlib
720 m = hashlib.md5()
721 m.update(salt+password)
722 myhash = m.digest()
723 while count:
724 m = hashlib.md5()
725 m.update(myhash+password)
726 myhash = m.digest()
727 count -= 1
728
729 myoutput = setting[:12]
730 myoutput += self._hash_encode64(myhash, 16)
731
732 return myoutput
733
735
736 output = ''
737 i = 0
738 while i < count:
739
740 value = ord(myinput[i])
741 i += 1
742 output += self.itoa64[value & 0x3f]
743 if i < count:
744 value |= ord(myinput[i]) << 8
745
746 output += self.itoa64[(value >> 6) & 0x3f]
747
748 if i >= count:
749 break
750 i += 1
751
752 if i < count:
753 value |= ord(myinput[i]) << 16
754
755 output += self.itoa64[(value >> 12) & 0x3f]
756
757 if (i >= count):
758 break
759 i += 1
760
761 output += self.itoa64[(value >> 18) & 0x3f]
762
763 return output
764
766
767 if len(myhash) == 34:
768 return self._hash_crypt_private(password, myhash) == myhash
769
770 import hashlib
771 m = hashlib.md5()
772 m.update(password)
773 rhash = m.hexdigest()
774 return rhash == myhash
775