545 * @param string $username The username |
545 * @param string $username The username |
546 * @param string $aes_data The encrypted password, hex-encoded |
546 * @param string $aes_data The encrypted password, hex-encoded |
547 * @param string $aes_key The MD5 hash of the encryption key, hex-encoded |
547 * @param string $aes_key The MD5 hash of the encryption key, hex-encoded |
548 * @param string $challenge The 256-bit MD5 challenge string - first 128 bits should be the hash, the last 128 should be the challenge salt |
548 * @param string $challenge The 256-bit MD5 challenge string - first 128 bits should be the hash, the last 128 should be the challenge salt |
549 * @param int $level The privilege level we're authenticating for, defaults to 0 |
549 * @param int $level The privilege level we're authenticating for, defaults to 0 |
|
550 * @param array $captcha_hash Optional. If we're locked out and the lockout policy is captcha, this should be the identifier for the code. |
|
551 * @param array $captcha_code Optional. If we're locked out and the lockout policy is captcha, this should be the code the user entered. |
550 * @return string 'success' on success, or error string on failure |
552 * @return string 'success' on success, or error string on failure |
551 */ |
553 */ |
552 |
554 |
553 function login_with_crypto($username, $aes_data, $aes_key, $challenge, $level = USER_LEVEL_MEMBER) |
555 function login_with_crypto($username, $aes_data, $aes_key, $challenge, $level = USER_LEVEL_MEMBER, $captcha_hash = false, $captcha_code = false) |
554 { |
556 { |
555 global $db, $session, $paths, $template, $plugins; // Common objects |
557 global $db, $session, $paths, $template, $plugins; // Common objects |
556 |
558 |
557 $privcache = $this->private_key; |
559 $privcache = $this->private_key; |
|
560 |
|
561 // Lockout stuff |
|
562 $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5; |
|
563 $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15; |
|
564 // convert to minutes |
|
565 $duration = $duration * 60; |
|
566 $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout'; |
|
567 if ( $policy == 'captcha' && $captcha_hash && $captcha_code ) |
|
568 { |
|
569 // policy is captcha -- check if it's correct, and if so, bypass lockout check |
|
570 $real_code = $this->get_captcha($captcha_hash); |
|
571 } |
|
572 if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && $real_code == $captcha_code ) ) |
|
573 { |
|
574 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
575 $timestamp_cutoff = time() - $duration; |
|
576 $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;'); |
|
577 $fails = $db->numrows(); |
|
578 if ( $fails > $threshold ) |
|
579 { |
|
580 // ooh boy, somebody's in trouble ;-) |
|
581 $row = $db->fetchrow(); |
|
582 $db->free_result(); |
|
583 return array( |
|
584 'success' => false, |
|
585 'error' => 'locked_out', |
|
586 'lockout_threshold' => $threshold, |
|
587 'lockout_duration' => ( $duration / 60 ), |
|
588 'lockout_fails' => $fails, |
|
589 'lockout_policy' => $policy, |
|
590 'lockout_last_time' => $row['timestamp'] |
|
591 ); |
|
592 } |
|
593 $db->free_result(); |
|
594 } |
558 |
595 |
559 // Instanciate the Rijndael encryption object |
596 // Instanciate the Rijndael encryption object |
560 $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE); |
597 $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE); |
561 |
598 |
562 // Fetch our decryption key |
599 // Fetch our decryption key |
563 |
600 |
564 $aes_key = $this->fetch_public_key($aes_key); |
601 $aes_key = $this->fetch_public_key($aes_key); |
565 if(!$aes_key) |
602 if(!$aes_key) |
566 return 'Couldn\'t look up public key "'.$aes_key.'" for decryption'; |
603 return array( |
|
604 'success' => false, |
|
605 'error' => 'key_not_found' |
|
606 ); |
567 |
607 |
568 // Convert the key to a binary string |
608 // Convert the key to a binary string |
569 $bin_key = hexdecode($aes_key); |
609 $bin_key = hexdecode($aes_key); |
570 |
610 |
571 if(strlen($bin_key) != AES_BITS / 8) |
611 if(strlen($bin_key) != AES_BITS / 8) |
572 return 'The decryption key is the wrong length'; |
612 return array( |
|
613 'success' => false, |
|
614 'error' => 'key_wrong_length' |
|
615 ); |
573 |
616 |
574 // Decrypt our password |
617 // Decrypt our password |
575 $password = $aes->decrypt($aes_data, $bin_key, ENC_HEX); |
618 $password = $aes->decrypt($aes_data, $bin_key, ENC_HEX); |
576 |
619 |
577 // Initialize our success switch |
620 // Initialize our success switch |
583 |
626 |
584 // Select the user data from the table, and decrypt that so we can verify the password |
627 // Select the user data from the table, and decrypt that so we can verify the password |
585 $this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$db_username_lower.'\' OR username=\'' . $db_username . '\';'); |
628 $this->sql('SELECT password,old_encryption,user_id,user_level,theme,style,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$db_username_lower.'\' OR username=\'' . $db_username . '\';'); |
586 if($db->numrows() < 1) |
629 if($db->numrows() < 1) |
587 { |
630 { |
588 return "The username and/or password is incorrect."; |
|
589 // This wasn't logged in <1.0.2, dunno how it slipped through |
631 // This wasn't logged in <1.0.2, dunno how it slipped through |
590 if($level > USER_LEVEL_MEMBER) |
632 if($level > USER_LEVEL_MEMBER) |
591 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
633 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
592 else |
634 else |
593 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
635 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
594 |
636 |
|
637 if ( $policy != 'disable' ) |
|
638 { |
|
639 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
640 // increment fail count |
|
641 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');'); |
|
642 $fails++; |
|
643 // ooh boy, somebody's in trouble ;-) |
|
644 return array( |
|
645 'success' => false, |
|
646 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials', |
|
647 'lockout_threshold' => $threshold, |
|
648 'lockout_duration' => ( $duration / 60 ), |
|
649 'lockout_fails' => $fails, |
|
650 'lockout_policy' => $policy |
|
651 ); |
|
652 } |
|
653 |
|
654 return array( |
|
655 'success' => false, |
|
656 'error' => 'invalid_credentials' |
|
657 ); |
595 } |
658 } |
596 $row = $db->fetchrow(); |
659 $row = $db->fetchrow(); |
597 |
660 |
598 // Check to see if we're logging in using a temporary password |
661 // Check to see if we're logging in using a temporary password |
599 |
662 |
660 $code = $plugins->setHook('login_success'); |
726 $code = $plugins->setHook('login_success'); |
661 foreach ( $code as $cmd ) |
727 foreach ( $code as $cmd ) |
662 { |
728 { |
663 eval($cmd); |
729 eval($cmd); |
664 } |
730 } |
665 return 'success'; |
731 return array( |
|
732 'success' => true |
|
733 ); |
666 } |
734 } |
667 else |
735 else |
668 return 'Your login credentials were correct, but an internal error occurred while registering the session key in the database.'; |
736 return array( |
|
737 'success' => false, |
|
738 'error' => 'backend_fail' |
|
739 ); |
669 } |
740 } |
670 else |
741 else |
671 { |
742 { |
672 if($level > USER_LEVEL_MEMBER) |
743 if($level > USER_LEVEL_MEMBER) |
673 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
744 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
674 else |
745 else |
675 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
746 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
676 |
747 |
677 return 'The username and/or password is incorrect.'; |
748 // Do we also need to increment the lockout countdown? |
|
749 if ( $policy != 'disable' ) |
|
750 { |
|
751 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
752 // increment fail count |
|
753 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');'); |
|
754 $fails++; |
|
755 return array( |
|
756 'success' => false, |
|
757 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials', |
|
758 'lockout_threshold' => $threshold, |
|
759 'lockout_duration' => ( $duration / 60 ), |
|
760 'lockout_fails' => $fails, |
|
761 'lockout_policy' => $policy |
|
762 ); |
|
763 } |
|
764 |
|
765 return array( |
|
766 'success' => false, |
|
767 'error' => 'invalid_credentials' |
|
768 ); |
678 } |
769 } |
679 } |
770 } |
680 |
771 |
681 /** |
772 /** |
682 * Attempts to login without using crypto stuff, mainly for use when the other side doesn't like Javascript |
773 * Attempts to login without using crypto stuff, mainly for use when the other side doesn't like Javascript |
698 if($this->compat) |
789 if($this->compat) |
699 { |
790 { |
700 return $this->login_compat($username, $pass_hashed, $level); |
791 return $this->login_compat($username, $pass_hashed, $level); |
701 } |
792 } |
702 |
793 |
|
794 // Lockout stuff |
|
795 $threshold = ( $_ = getConfig('lockout_threshold') ) ? intval($_) : 5; |
|
796 $duration = ( $_ = getConfig('lockout_duration') ) ? intval($_) : 15; |
|
797 // convert to minutes |
|
798 $duration = $duration * 60; |
|
799 $policy = ( $x = getConfig('lockout_policy') && in_array(getConfig('lockout_policy'), array('lockout', 'disable', 'captcha')) ) ? getConfig('lockout_policy') : 'lockout'; |
|
800 if ( $policy == 'captcha' && $captcha_hash && $captcha_code ) |
|
801 { |
|
802 // policy is captcha -- check if it's correct, and if so, bypass lockout check |
|
803 $real_code = $this->get_captcha($captcha_hash); |
|
804 } |
|
805 if ( $policy != 'disable' && !( $policy == 'captcha' && isset($real_code) && $real_code == $captcha_code ) ) |
|
806 { |
|
807 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
808 $timestamp_cutoff = time() - $duration; |
|
809 $q = $this->sql('SELECT timestamp FROM '.table_prefix.'lockout WHERE timestamp > ' . $timestamp_cutoff . ' AND ipaddr = \'' . $ipaddr . '\' ORDER BY timestamp DESC;'); |
|
810 $fails = $db->numrows(); |
|
811 if ( $fails > $threshold ) |
|
812 { |
|
813 // ooh boy, somebody's in trouble ;-) |
|
814 $row = $db->fetchrow(); |
|
815 $db->free_result(); |
|
816 return array( |
|
817 'success' => false, |
|
818 'error' => 'locked_out', |
|
819 'lockout_threshold' => $threshold, |
|
820 'lockout_duration' => ( $duration / 60 ), |
|
821 'lockout_fails' => $fails, |
|
822 'lockout_policy' => $policy, |
|
823 'lockout_last_time' => $row['timestamp'] |
|
824 ); |
|
825 } |
|
826 $db->free_result(); |
|
827 } |
|
828 |
703 // Instanciate the Rijndael encryption object |
829 // Instanciate the Rijndael encryption object |
704 $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE); |
830 $aes = new AESCrypt(AES_BITS, AES_BLOCKSIZE); |
705 |
831 |
706 // Initialize our success switch |
832 // Initialize our success switch |
707 $success = false; |
833 $success = false; |
708 |
834 |
709 // Retrieve the real password from the database |
835 // Retrieve the real password from the database |
710 $this->sql('SELECT password,old_encryption,user_id,user_level,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$this->prepare_text(strtolower($username)).'\';'); |
836 $this->sql('SELECT password,old_encryption,user_id,user_level,temp_password,temp_password_time FROM '.table_prefix.'users WHERE lcase(username)=\''.$this->prepare_text(strtolower($username)).'\';'); |
711 if($db->numrows() < 1) |
837 if($db->numrows() < 1) |
712 return 'The username and/or password is incorrect.'; |
838 { |
|
839 // This wasn't logged in <1.0.2, dunno how it slipped through |
|
840 if($level > USER_LEVEL_MEMBER) |
|
841 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
842 else |
|
843 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
|
844 |
|
845 // Do we also need to increment the lockout countdown? |
|
846 if ( $policy != 'disable' ) |
|
847 { |
|
848 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
849 // increment fail count |
|
850 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');'); |
|
851 $fails++; |
|
852 return array( |
|
853 'success' => false, |
|
854 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials', |
|
855 'lockout_threshold' => $threshold, |
|
856 'lockout_duration' => ( $duration / 60 ), |
|
857 'lockout_fails' => $fails, |
|
858 'lockout_policy' => $policy |
|
859 ); |
|
860 } |
|
861 |
|
862 return array( |
|
863 'success' => false, |
|
864 'error' => 'invalid_credentials' |
|
865 ); |
|
866 } |
713 $row = $db->fetchrow(); |
867 $row = $db->fetchrow(); |
714 |
868 |
715 // Check to see if we're logging in using a temporary password |
869 // Check to see if we're logging in using a temporary password |
716 |
870 |
717 if((intval($row['temp_password_time']) + 3600*24) > time() ) |
871 if((intval($row['temp_password_time']) + 3600*24) > time() ) |
756 } |
910 } |
757 } |
911 } |
758 if($success) |
912 if($success) |
759 { |
913 { |
760 if((int)$level > (int)$row['user_level']) |
914 if((int)$level > (int)$row['user_level']) |
761 return 'You are not authorized for this level of access.'; |
915 return array( |
|
916 'success' => false, |
|
917 'error' => 'too_big_for_britches' |
|
918 ); |
762 $sess = $this->register_session(intval($row['user_id']), $username, $real_pass, $level); |
919 $sess = $this->register_session(intval($row['user_id']), $username, $real_pass, $level); |
763 if($sess) |
920 if($sess) |
764 { |
921 { |
765 if($level > USER_LEVEL_MEMBER) |
922 if($level > USER_LEVEL_MEMBER) |
766 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_good\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
923 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_good\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
771 foreach ( $code as $cmd ) |
928 foreach ( $code as $cmd ) |
772 { |
929 { |
773 eval($cmd); |
930 eval($cmd); |
774 } |
931 } |
775 |
932 |
776 return 'success'; |
933 return array( |
|
934 'success' => true |
|
935 ); |
777 } |
936 } |
778 else |
937 else |
779 return 'Your login credentials were correct, but an internal error occured while registering the session key in the database.'; |
938 return array( |
|
939 'success' => false, |
|
940 'error' => 'backend_fail' |
|
941 ); |
780 } |
942 } |
781 else |
943 else |
782 { |
944 { |
783 if($level > USER_LEVEL_MEMBER) |
945 if($level > USER_LEVEL_MEMBER) |
784 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
946 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES(\'security\', \'admin_auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
785 else |
947 else |
786 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
948 $this->sql('INSERT INTO '.table_prefix.'logs(log_type,action,time_id,date_string,author,edit_summary) VALUES(\'security\', \'auth_bad\', '.time().', \''.date('d M Y h:i a').'\', \''.$db->escape($username).'\', \''.$db->escape($_SERVER['REMOTE_ADDR']).'\')'); |
787 |
949 |
788 return 'The username and/or password is incorrect.'; |
950 // Do we also need to increment the lockout countdown? |
|
951 if ( $policy != 'disable' ) |
|
952 { |
|
953 $ipaddr = $db->escape($_SERVER['REMOTE_ADDR']); |
|
954 // increment fail count |
|
955 $this->sql('INSERT INTO '.table_prefix.'lockout(ipaddr, timestamp, action) VALUES(\'' . $ipaddr . '\', UNIX_TIMESTAMP(), \'credential\');'); |
|
956 $fails++; |
|
957 return array( |
|
958 'success' => false, |
|
959 'error' => ( $fails >= $threshold ) ? 'locked_out' : 'invalid_credentials', |
|
960 'lockout_threshold' => $threshold, |
|
961 'lockout_duration' => ( $duration / 60 ), |
|
962 'lockout_fails' => $fails, |
|
963 'lockout_policy' => $policy |
|
964 ); |
|
965 } |
|
966 |
|
967 return array( |
|
968 'success' => false, |
|
969 'error' => 'invalid_credentials' |
|
970 ); |
789 } |
971 } |
790 } |
972 } |
791 |
973 |
792 /** |
974 /** |
793 * Attempts to log in using the old table structure and algorithm. |
975 * Attempts to log in using the old table structure and algorithm. |