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