Move all files from plugins/ directory to the root default tip
authorDan Fuhry <dan@enanocms.org>
Fri, 30 Jun 2017 17:49:12 -0400
changeset 39 6212d849ab08
parent 38 d109af008343
Move all files from plugins/ directory to the root
Yubikey.php
plugins/Yubikey.php
plugins/yubikey/admincp.php
plugins/yubikey/auth.php
plugins/yubikey/corelib.php
plugins/yubikey/field.png
plugins/yubikey/formicon.gif
plugins/yubikey/usercp.php
plugins/yubikey/yubikey.css
plugins/yubikey/yubikey.js
yubikey/admincp.php
yubikey/auth.php
yubikey/corelib.php
yubikey/field.png
yubikey/formicon.gif
yubikey/usercp.php
yubikey/yubikey.css
yubikey/yubikey.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Yubikey.php	Fri Jun 30 17:49:12 2017 -0400
@@ -0,0 +1,188 @@
+<?php
+/**!info**
+{
+	"Plugin Name"  : "Yubikey authentication",
+	"Plugin URI"   : "http://enanocms.org/plugin/yubikey",
+	"Description"  : "Allows authentication to Enano via Yubico's Yubikey, a one-time password device.",
+	"Author"       : "Dan Fuhry",
+	"Version"      : "1.1.7",
+	"Author URI"   : "http://enanocms.org/",
+	"Auth plugin"  : true
+}
+**!*/
+
+// Include files
+require( ENANO_ROOT . '/plugins/yubikey/corelib.php' );
+require( ENANO_ROOT . '/plugins/yubikey/admincp.php' );
+
+if ( getConfig('yubikey_enable', '1') == '1' )
+{
+	require( ENANO_ROOT . '/plugins/yubikey/auth.php' );
+	require( ENANO_ROOT . '/plugins/yubikey/usercp.php' );
+}
+
+// Install schema: MySQL
+/**!install dbms="mysql"; **
+CREATE TABLE {{TABLE_PREFIX}}yubikey(
+	yubi_id int(12) NOT NULL auto_increment,
+	user_id mediumint(8) NOT NULL DEFAULT 1,
+	yubi_uid char(12) NOT NULL DEFAULT '____________',
+	PRIMARY KEY ( yubi_id )
+) ENGINE `MyISAM` CHARACTER SET `utf8` COLLATE `utf8_bin`;
+
+ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_yubikey_flags smallint(3) NOT NULL DEFAULT 0;
+**!*/
+
+// Install schema: PostgreSQL
+/**!install dbms="postgresql"; **
+CREATE TABLE {{TABLE_PREFIX}}yubikey(
+	yubi_id SERIAL,
+	user_id int NOT NULL DEFAULT 1,
+	yubi_uid char(12) NOT NULL DEFAULT '____________',
+	PRIMARY KEY ( yubi_id )
+);
+
+ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_yubikey_flags smallint NOT NULL DEFAULT 0;
+**!*/
+
+// Uninstall schema
+/**!uninstall**
+DROP TABLE {{TABLE_PREFIX}}yubikey;
+ALTER TABLE {{TABLE_PREFIX}}users DROP user_yubikey_flags;
+**!*/
+
+/**!language**
+
+The following text up to the closing comment tag is JSON language data.
+It is not PHP code but your editor or IDE may highlight it as such. This
+data is imported when the plugin is loaded for the first time; it provides
+the strings displayed by this plugin's interface.
+
+You should copy and paste this block when you create your own plugins so
+that these comments and the basic structure of the language data is
+preserved. All language data is in the same format as the Enano core
+language files in the /language/* directories. See the Enano Localization
+Guide and Enano API Documentation for further information on the format of
+language files.
+
+The exception in plugin language file format is that multiple languages
+may be specified in the language block. This should be done by way of making
+the top-level elements each a JSON language object, with elements named
+according to the ISO-639-1 language they are representing. The path should be:
+
+	root => language ID => categories array, ( strings object => category \
+	objects => strings )
+
+All text leading up to first curly brace is stripped by the parser; using
+a code tag makes jEdit and other editors do automatic indentation and
+syntax highlighting on the language data. The use of the code tag is not
+necessary; it is only included as a tool for development.
+
+<code>
+{
+	// english
+	eng: {
+		categories: [ 'meta', 'yubiauth', 'yubiucp', 'yubiacp' ],
+		strings: {
+			meta: {
+				yubiauth: 'Yubikey authentication messages',
+				yubiucp: 'Yubikey user CP',
+				yubiacp: 'Yubikey admin CP',
+			},
+			yubiauth: {
+				msg_please_touch_key: 'Please touch your Yubikey',
+				msg_close_instructions: 'or press <tt>Esc</tt>',
+				msg_invalid_chars: 'OTP contains invalid characters',
+				msg_too_long: 'OTP is too long',
+				msg_validating_otp: 'Validating OTP...',
+				msg_otp_valid: 'OTP validated',
+				btn_enter_otp: 'Log in with Yubikey',
+				lbl_otp_field: 'Yubikey OTP:',
+				
+				ctl_btn_change_key: 'Change key',
+				ctl_btn_clear: 'Clear',
+				ctl_btn_enroll: 'Enroll',
+				ctl_status_enrolled_pending: 'Enrolled (pending)',
+				ctl_status_empty: 'Not enrolled',
+				ctl_status_remove_pending: 'Removed (pending)',
+				ctl_status_enrolled: 'Enrolled',
+				
+				err_invalid_otp: 'Your login was rejected because the Yubikey OTP you entered contains invalid characters.',
+				err_invalid_auth_url: 'Login with Yubikey was rejected because the URL to the authentication server is not valid.',
+				err_nothing_provided: 'You did not provide a Yubikey OTP or a username. One of these is required for login to work.',
+				err_must_have_otp: 'Please provide a Yubikey OTP to log in to this account.',
+				err_must_have_username: 'Please provide your username.',
+				err_must_have_password: 'Please enter your password in addition to your username and Yubikey.',
+				err_key_not_authorized: 'This Yubikey is not authorized on this site.',
+				err_otp_invalid_chars: '%this.yubiauth_err_invalid_otp%',
+				err_http_failed: 'Your OTP could not be validated because the authentication server could not be contacted. Technical error message: %http_error%',
+				err_missing_api_key: 'Your OTP could not be validated because no Yubico API key is registered on this site.',
+				err_http_response_error: 'Your OTP could not be validated because the Yubico authentication server reported an error.',
+				err_malformed_response: 'Your OTP could not be validated because the Yubico authentication server returned an unexpected response.',
+				err_timestamp_check_failed: 'Your OTP could not be validated because the timestamp of the response from the Yubico authentication server was out of bounds.',
+				err_uid_mismatch: 'This Yubikey is registered to a different user account than the one you are trying to log into.',
+				err_response_missing_sig: 'Your OTP could not be validated because the Yubico authentication server did not sign its response.',
+				err_response_invalid_sig: 'Your OTP could not be validated because the signature of the authentication response was invalid.',
+				err_response_missing_status: '%this.yubiauth_err_malformed_response%',
+				err_response_ok: 'OTP is OK',
+				err_response_bad_otp: 'Authentication failed because the Yubikey OTP is invalid.',
+				err_response_replayed_otp: 'Authentication failed because the Yubikey OTP you entered has been used before.',
+				err_response_bad_signature: 'Authentication failed because the Yubico authentication server reported an invalid signature.',
+				err_response_missing_parameter: 'Authentication failed because of a Dan Fuhry error.',
+				err_response_no_such_client: 'Authentication failed because the Yubikey you used is not registered with Yubico.',
+				err_response_operation_not_allowed: 'Authentication failed because the Enano server was denied the request to validate the OTP.',
+				err_response_backend_error: 'Authentication failed because an unexpected problem happened with the Yubico server.',
+				err_response_security_error: 'Authentication failed because the Yubico authentication server reported an unknown security error.',
+				
+				specialpage_yubikey: 'Yubikey API'
+			},
+			yubiucp: {
+				panel_title: 'Yubikey settings',
+				
+				msg_save_title: 'Yubikey preferences saved',
+				msg_save_body: 'Your preferences have been saved. You will be transferred back to the User CP momentarily.',
+				
+				field_enable_title: 'Enable Yubikey support on my account:',
+				field_enable_hint: 'Disabling support will remove any keys that are enrolled for your account.',
+				field_enable: 'Enabled',
+				field_keys_title: 'Enrolled Yubikeys:',
+				field_keys_hint: 'Enroll a Yubikey to allow it to log into your account.',
+				field_keys_maximum: 'You can enroll up to %max% Yubikeys.',
+				field_normal_flags: 'When logging in, ask me for:',
+				field_elev_flags: 'When performing sensitive operations, require:',
+				field_flags_keyonly: 'Just my Yubikey',
+				field_flags_username: 'My Yubikey and username',
+				field_flags_userandpw: 'My <acronym title="Two factor authentication">Yubikey, username and password</acronym>',
+				field_allow_plain_login: 'Allow me to log in without my Yubikey',
+				field_allow_plain_login_hint: 'If this option is turned off, you will be unable to access your account if all of your enrolled Yubikeys become lost or broken. However, turning this option off provides greater security.',
+				err_double_enrollment: 'One of the Yubikeys you tried to enroll is already enrolled on another account on this website. A single Yubikey can only be associated with one account at a time.',
+				err_double_enrollment_single: 'The Yubikey you tried to enroll is already enrolled on another account on this website. A single Yubikey can only be associated with one account at a time.',
+				
+				reg_field_otp: 'Enroll a <a href="http://www.yubico.com/products/yubikey" onclick="window.open(this.href); return false;">Yubikey</a>:',
+				reg_field_otp_hint_optional: 'If you have a Yubikey, you can authorize it for use in your new account here.',
+				reg_field_otp_hint_required: 'Please enroll a Yubikey here to create an account. This is a required step.',
+				reg_err_otp_required: 'Please enroll a Yubikey to register on this site.',
+				reg_err_otp_invalid: 'Your Yubikey OTP failed to validate.'
+			},
+			yubiacp: {
+				th: 'Yubikey authentication',
+				field_enable_title: 'Yubikey support:',
+				field_enable: 'Enable Yubikey authentication',
+				field_api_key: 'Yubico API key:',
+				field_api_key_id: 'Yubico numeric ID:',
+				field_auth_server: 'Authentication server URL:',
+				field_enroll_limit: 'Number of enrolled keys permitted per account:',
+				field_reg_require_otp_title: 'Yubikey required for registration:',
+				field_reg_require_otp_hint: 'If this is enabled, users will be asked to enroll a Yubikey during registration. The enrolled Yubikey will be authorized for the new account.',
+				field_reg_require_otp: 'Require Yubikey during registration',
+				field_use_local_pre: 'Or:',
+				field_use_local: 'Use local YMS',
+				
+				err_invalid_auth_server: 'The URL to the Yubikey authentication server that you entered is invalid.'
+			}
+		}
+	}
+}
+</code>
+**!*/
+
--- a/plugins/Yubikey.php	Fri Nov 11 00:33:28 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-<?php
-/**!info**
-{
-	"Plugin Name"  : "Yubikey authentication",
-	"Plugin URI"   : "http://enanocms.org/plugin/yubikey",
-	"Description"  : "Allows authentication to Enano via Yubico's Yubikey, a one-time password device.",
-	"Author"       : "Dan Fuhry",
-	"Version"      : "1.1.7",
-	"Author URI"   : "http://enanocms.org/",
-	"Auth plugin"  : true
-}
-**!*/
-
-// Include files
-require( ENANO_ROOT . '/plugins/yubikey/corelib.php' );
-require( ENANO_ROOT . '/plugins/yubikey/admincp.php' );
-
-if ( getConfig('yubikey_enable', '1') == '1' )
-{
-	require( ENANO_ROOT . '/plugins/yubikey/auth.php' );
-	require( ENANO_ROOT . '/plugins/yubikey/usercp.php' );
-}
-
-// Install schema: MySQL
-/**!install dbms="mysql"; **
-CREATE TABLE {{TABLE_PREFIX}}yubikey(
-	yubi_id int(12) NOT NULL auto_increment,
-	user_id mediumint(8) NOT NULL DEFAULT 1,
-	yubi_uid char(12) NOT NULL DEFAULT '____________',
-	PRIMARY KEY ( yubi_id )
-) ENGINE `MyISAM` CHARACTER SET `utf8` COLLATE `utf8_bin`;
-
-ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_yubikey_flags smallint(3) NOT NULL DEFAULT 0;
-**!*/
-
-// Install schema: PostgreSQL
-/**!install dbms="postgresql"; **
-CREATE TABLE {{TABLE_PREFIX}}yubikey(
-	yubi_id SERIAL,
-	user_id int NOT NULL DEFAULT 1,
-	yubi_uid char(12) NOT NULL DEFAULT '____________',
-	PRIMARY KEY ( yubi_id )
-);
-
-ALTER TABLE {{TABLE_PREFIX}}users ADD COLUMN user_yubikey_flags smallint NOT NULL DEFAULT 0;
-**!*/
-
-// Uninstall schema
-/**!uninstall**
-DROP TABLE {{TABLE_PREFIX}}yubikey;
-ALTER TABLE {{TABLE_PREFIX}}users DROP user_yubikey_flags;
-**!*/
-
-/**!language**
-
-The following text up to the closing comment tag is JSON language data.
-It is not PHP code but your editor or IDE may highlight it as such. This
-data is imported when the plugin is loaded for the first time; it provides
-the strings displayed by this plugin's interface.
-
-You should copy and paste this block when you create your own plugins so
-that these comments and the basic structure of the language data is
-preserved. All language data is in the same format as the Enano core
-language files in the /language/* directories. See the Enano Localization
-Guide and Enano API Documentation for further information on the format of
-language files.
-
-The exception in plugin language file format is that multiple languages
-may be specified in the language block. This should be done by way of making
-the top-level elements each a JSON language object, with elements named
-according to the ISO-639-1 language they are representing. The path should be:
-
-	root => language ID => categories array, ( strings object => category \
-	objects => strings )
-
-All text leading up to first curly brace is stripped by the parser; using
-a code tag makes jEdit and other editors do automatic indentation and
-syntax highlighting on the language data. The use of the code tag is not
-necessary; it is only included as a tool for development.
-
-<code>
-{
-	// english
-	eng: {
-		categories: [ 'meta', 'yubiauth', 'yubiucp', 'yubiacp' ],
-		strings: {
-			meta: {
-				yubiauth: 'Yubikey authentication messages',
-				yubiucp: 'Yubikey user CP',
-				yubiacp: 'Yubikey admin CP',
-			},
-			yubiauth: {
-				msg_please_touch_key: 'Please touch your Yubikey',
-				msg_close_instructions: 'or press <tt>Esc</tt>',
-				msg_invalid_chars: 'OTP contains invalid characters',
-				msg_too_long: 'OTP is too long',
-				msg_validating_otp: 'Validating OTP...',
-				msg_otp_valid: 'OTP validated',
-				btn_enter_otp: 'Log in with Yubikey',
-				lbl_otp_field: 'Yubikey OTP:',
-				
-				ctl_btn_change_key: 'Change key',
-				ctl_btn_clear: 'Clear',
-				ctl_btn_enroll: 'Enroll',
-				ctl_status_enrolled_pending: 'Enrolled (pending)',
-				ctl_status_empty: 'Not enrolled',
-				ctl_status_remove_pending: 'Removed (pending)',
-				ctl_status_enrolled: 'Enrolled',
-				
-				err_invalid_otp: 'Your login was rejected because the Yubikey OTP you entered contains invalid characters.',
-				err_invalid_auth_url: 'Login with Yubikey was rejected because the URL to the authentication server is not valid.',
-				err_nothing_provided: 'You did not provide a Yubikey OTP or a username. One of these is required for login to work.',
-				err_must_have_otp: 'Please provide a Yubikey OTP to log in to this account.',
-				err_must_have_username: 'Please provide your username.',
-				err_must_have_password: 'Please enter your password in addition to your username and Yubikey.',
-				err_key_not_authorized: 'This Yubikey is not authorized on this site.',
-				err_otp_invalid_chars: '%this.yubiauth_err_invalid_otp%',
-				err_http_failed: 'Your OTP could not be validated because the authentication server could not be contacted. Technical error message: %http_error%',
-				err_missing_api_key: 'Your OTP could not be validated because no Yubico API key is registered on this site.',
-				err_http_response_error: 'Your OTP could not be validated because the Yubico authentication server reported an error.',
-				err_malformed_response: 'Your OTP could not be validated because the Yubico authentication server returned an unexpected response.',
-				err_timestamp_check_failed: 'Your OTP could not be validated because the timestamp of the response from the Yubico authentication server was out of bounds.',
-				err_uid_mismatch: 'This Yubikey is registered to a different user account than the one you are trying to log into.',
-				err_response_missing_sig: 'Your OTP could not be validated because the Yubico authentication server did not sign its response.',
-				err_response_invalid_sig: 'Your OTP could not be validated because the signature of the authentication response was invalid.',
-				err_response_missing_status: '%this.yubiauth_err_malformed_response%',
-				err_response_ok: 'OTP is OK',
-				err_response_bad_otp: 'Authentication failed because the Yubikey OTP is invalid.',
-				err_response_replayed_otp: 'Authentication failed because the Yubikey OTP you entered has been used before.',
-				err_response_bad_signature: 'Authentication failed because the Yubico authentication server reported an invalid signature.',
-				err_response_missing_parameter: 'Authentication failed because of a Dan Fuhry error.',
-				err_response_no_such_client: 'Authentication failed because the Yubikey you used is not registered with Yubico.',
-				err_response_operation_not_allowed: 'Authentication failed because the Enano server was denied the request to validate the OTP.',
-				err_response_backend_error: 'Authentication failed because an unexpected problem happened with the Yubico server.',
-				err_response_security_error: 'Authentication failed because the Yubico authentication server reported an unknown security error.',
-				
-				specialpage_yubikey: 'Yubikey API'
-			},
-			yubiucp: {
-				panel_title: 'Yubikey settings',
-				
-				msg_save_title: 'Yubikey preferences saved',
-				msg_save_body: 'Your preferences have been saved. You will be transferred back to the User CP momentarily.',
-				
-				field_enable_title: 'Enable Yubikey support on my account:',
-				field_enable_hint: 'Disabling support will remove any keys that are enrolled for your account.',
-				field_enable: 'Enabled',
-				field_keys_title: 'Enrolled Yubikeys:',
-				field_keys_hint: 'Enroll a Yubikey to allow it to log into your account.',
-				field_keys_maximum: 'You can enroll up to %max% Yubikeys.',
-				field_normal_flags: 'When logging in, ask me for:',
-				field_elev_flags: 'When performing sensitive operations, require:',
-				field_flags_keyonly: 'Just my Yubikey',
-				field_flags_username: 'My Yubikey and username',
-				field_flags_userandpw: 'My <acronym title="Two factor authentication">Yubikey, username and password</acronym>',
-				field_allow_plain_login: 'Allow me to log in without my Yubikey',
-				field_allow_plain_login_hint: 'If this option is turned off, you will be unable to access your account if all of your enrolled Yubikeys become lost or broken. However, turning this option off provides greater security.',
-				err_double_enrollment: 'One of the Yubikeys you tried to enroll is already enrolled on another account on this website. A single Yubikey can only be associated with one account at a time.',
-				err_double_enrollment_single: 'The Yubikey you tried to enroll is already enrolled on another account on this website. A single Yubikey can only be associated with one account at a time.',
-				
-				reg_field_otp: 'Enroll a <a href="http://www.yubico.com/products/yubikey" onclick="window.open(this.href); return false;">Yubikey</a>:',
-				reg_field_otp_hint_optional: 'If you have a Yubikey, you can authorize it for use in your new account here.',
-				reg_field_otp_hint_required: 'Please enroll a Yubikey here to create an account. This is a required step.',
-				reg_err_otp_required: 'Please enroll a Yubikey to register on this site.',
-				reg_err_otp_invalid: 'Your Yubikey OTP failed to validate.'
-			},
-			yubiacp: {
-				th: 'Yubikey authentication',
-				field_enable_title: 'Yubikey support:',
-				field_enable: 'Enable Yubikey authentication',
-				field_api_key: 'Yubico API key:',
-				field_api_key_id: 'Yubico numeric ID:',
-				field_auth_server: 'Authentication server URL:',
-				field_enroll_limit: 'Number of enrolled keys permitted per account:',
-				field_reg_require_otp_title: 'Yubikey required for registration:',
-				field_reg_require_otp_hint: 'If this is enabled, users will be asked to enroll a Yubikey during registration. The enrolled Yubikey will be authorized for the new account.',
-				field_reg_require_otp: 'Require Yubikey during registration',
-				field_use_local_pre: 'Or:',
-				field_use_local: 'Use local YMS',
-				
-				err_invalid_auth_server: 'The URL to the Yubikey authentication server that you entered is invalid.'
-			}
-		}
-	}
-}
-</code>
-**!*/
-
--- a/plugins/yubikey/admincp.php	Fri Nov 11 00:33:28 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-<?php
-
-$plugins->attachHook('acp_general_users', 'yubikey_admin_cp_ui();');
-$plugins->attachHook('acp_general_save', 'yubikey_admin_cp_save();');
-
-function yubikey_admin_cp_ui()
-{
-	global $lang;
-	?>
-		<tr>
-			<th colspan="2" class="subhead">
-				<?php echo $lang->get('yubiacp_th'); ?>
-			</th>
-		</tr>
-		
-		<tr>
-			<td class="row1">
-				<?php echo $lang->get('yubiacp_field_enable_title'); ?>
-			</td>
-			<td class="row1">
-				<label>
-					<input type="checkbox" name="yubikey_enable" <?php if ( getConfig('yubikey_enable', '1') == '1' ) echo 'checked="checked" '; ?>/>
-					<?php echo $lang->get('yubiacp_field_enable'); ?>
-				</label>
-			</td>
-		</tr>
-		
-		<tr>
-			<td class="row2">
-				<?php echo $lang->get('yubiacp_field_api_key'); ?>
-			</td>
-			<td class="row2">
-				<input type="text" name="yubikey_api_key" value="<?php echo htmlspecialchars(getConfig('yubikey_api_key', '')); ?>" size="30" />
-			</td>
-		</tr>
-		
-		<tr>
-			<td class="row1">
-				<?php echo $lang->get('yubiacp_field_api_key_id'); ?>
-			</td>
-			<td class="row1">
-				<input type="text" name="yubikey_api_key_id" value="<?php echo strval(intval(getConfig('yubikey_api_key_id', ''))); ?>" size="5" />
-			</td>
-		</tr>
-		
-		<tr>
-			<td class="row2">
-				<?php echo $lang->get('yubiacp_field_auth_server'); ?>
-			</td>
-			<td class="row2">
-				<input type="text" name="yubikey_auth_server" value="<?php echo htmlspecialchars(getConfig('yubikey_auth_server', YK_DEFAULT_VERIFY_URL)); ?>" size="30" />
-				<?php
-				if ( defined('YMS_INSTALLED') )
-				{
-					echo '<br />';
-					echo $lang->get('yubiacp_field_use_local_pre');
-					?> <label><input type="checkbox" name="yubikey_use_local_yms" <?php echo getConfig('yubikey_use_local_yms', 0) ? 'checked="checked" ' : ''; ?>/><?php
-					echo $lang->get('yubiacp_field_use_local');
-					echo '</label>';
-				}
-				?>
-			</td>
-		</tr>
-		
-		<tr>
-			<td class="row1">
-				<?php echo $lang->get('yubiacp_field_enroll_limit'); ?>
-			</td>
-			<td class="row1">
-				<input type="text" name="yubikey_enroll_limit" value="<?php echo strval(intval(getConfig('yubikey_enroll_limit', '3'))); ?>" size="5" />
-			</td>
-		</tr>
-		
-		<tr>
-			<td class="row2">
-				<?php echo $lang->get('yubiacp_field_reg_require_otp_title'); ?><br />
-				<small><?php echo $lang->get('yubiacp_field_reg_require_otp_hint'); ?></small>
-			</td>
-			<td class="row2">
-				<label>
-					<input type="checkbox" name="yubikey_reg_require_otp" <?php if ( getConfig('yubikey_reg_require_otp', '0') == '1' ) echo 'checked="checked" '; ?>/>
-					<?php echo $lang->get('yubiacp_field_reg_require_otp'); ?>
-				</label>
-			</td>
-		</tr>
-		
-	<?php
-}
-
-function yubikey_admin_cp_save()
-{
-	global $lang;
-	
-	// yubikey_enable, yubikey_api_key, yubikey_api_key_id, yubikey_auth_server, yubikey_enroll_limit
-	setConfig('yubikey_enable', isset($_POST['yubikey_enable']) ? '1' : '0');
-	setConfig('yubikey_api_key', $_POST['yubikey_api_key']);
-	setConfig('yubikey_api_key_id', intval($_POST['yubikey_api_key_id']));
-	setConfig('yubikey_enroll_limit', intval($_POST['yubikey_enroll_limit']));
-	setConfig('yubikey_reg_require_otp', isset($_POST['yubikey_reg_require_otp']) ? '1' : '0');
-	setConfig('yubikey_use_local_yms', isset($_POST['yubikey_use_local_yms']) && defined('YMS_INSTALLED') ? '1' : '0');
-	
-	if ( preg_match('#^(?:https?://)?(\[?[a-z0-9-:]+(?:\.[a-z0-9-:]+\]?)*)(/.*)$#', $_POST['yubikey_auth_server']) )
-		setConfig('yubikey_auth_server', $_POST['yubikey_auth_server']);
-	else
-		echo '<div class="error-box">' . $lang->get('yubiacp_err_invalid_auth_server') . '</div>';
-}
-
--- a/plugins/yubikey/auth.php	Fri Nov 11 00:33:28 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,325 +0,0 @@
-<?php
-
-if ( getConfig('yubikey_enable', '1') != '1' )
-		return true;
-
-// hook into auth
-$plugins->attachHook('login_process_userdata_json', 'return yubikey_auth_hook_json($userinfo, $req["level"], @$req["remember"]);');
-// hook into special page init
-$plugins->attachHook('session_started', 'yubikey_add_special_pages();');
-// session key security
-$plugins->attachHook('session_key_calc', 'yubikey_sk_calc($user_id, $key_pieces, $sk_mode);');
-
-function yubikey_auth_hook_json(&$userdata, $level, $remember)
-{
-	global $db, $session, $paths, $template, $plugins; // Common objects
-	global $lang;
-	
-	$do_validate_otp = false;
-	$do_validate_user = false;
-	$do_validate_pass = false;
-	
-	$user_flag = ( $level >= USER_LEVEL_CHPREF ) ? YK_SEC_ELEV_USERNAME : YK_SEC_NORMAL_USERNAME;
-	$pass_flag = ( $level >= USER_LEVEL_CHPREF ) ? YK_SEC_ELEV_PASSWORD : YK_SEC_NORMAL_PASSWORD;
-	
-	$auth_log_prefix = ( $level >= USER_LEVEL_CHPREF ) ? 'admin_' : '';
-	
-	// Sort of a hack: if the password looks like an OTP and the OTP field is empty, use the password as the OTP
-	if ( empty($userdata['yubikey_otp']) && preg_match('/^[cbdefghijklnrtuv]{44}$/', $userdata['password'] ) )
-	{
-		$userdata['yubikey_otp'] = $userdata['password'];
-	}
-	
-	// Lockouts removed from here - they're done during preprocessing now.
-	
-	if ( !empty($userdata['username']) )
-	{
-		// get flags
-		$q = $db->sql_query('SELECT user_id, user_yubikey_flags FROM ' . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '" . $db->escape(strtolower($userdata['username'])) . "';");
-		if ( !$q )
-			$db->die_json();
-		
-		if ( $db->numrows() < 1 )
-		{
-			// Username not found - let the main login function handle it
-			$db->free_result();
-			return null;
-		}
-		list($user_id, $flags) = $db->fetchrow_num();
-		$flags = intval($flags);
-		// At the point the username is validated.
-		$do_validate_user = false;
-		$do_validate_pass = $flags & $pass_flag;
-		if ( empty($userdata['yubikey_otp']) )
-		{
-			// no OTP was provided
-			// make sure the user has allowed logging in with no OTP
-			if ( !($flags & YK_SEC_ALLOW_NO_OTP) )
-			{
-				// We also might have no Yubikeys enrolled.
-				$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yubikey WHERE user_id = $user_id;");
-				if ( !$q )
-					$db->die_json();
-				
-				if ( $db->numrows() > 0 )
-				{
-					// Yep at least one key is enrolled.
-					// I don't think these should be logged because they'll usually just be innocent mistakes.
-					$db->free_result();
-					return array(
-							'mode' => 'error',
-							'error' => 'yubiauth_err_must_have_otp'
-						);
-				}
-				// Nope, no keys enrolled, user hasn't enabled Yubikey support
-				$db->free_result();
-			}
-			// we're ok, use normal password auth
-			return null;
-		}
-		else
-		{
-			// user did enter an OTP; make sure it's associated with the username
-			$yubi_uid = $db->escape(substr($userdata['yubikey_otp'], 0, 12));
-			$q = $db->sql_query('SELECT user_id FROM ' . table_prefix . 'yubikey WHERE yubi_uid = \'' . $yubi_uid . '\';');
-			if ( !$q )
-				$db->die_json();
-			if ( $db->numrows() < 1 )
-			{
-				$db->free_result();
-				return array(
-						'mode' => 'error',
-						'error' => 'yubiauth_err_key_not_authorized'
-					);
-			}
-			list($yubi_pair_uid) = $db->fetchrow_num();
-			if ( $yubi_pair_uid !== $user_id )
-			{
-				return array(
-						'mode' => 'error',
-						'error' => 'yubiauth_err_uid_mismatch'
-					);
-			}
-			$db->free_result();
-			$do_validate_otp = true;
-		}
-	}
-	else if ( !empty($userdata['yubikey_otp']) )
-	{
-		// we have an OTP, but no username to work with
-		$yubi_uid = substr($userdata['yubikey_otp'], 0, 12);
-		if ( !preg_match('/^[cbdefghijklnrtuv]{12}$/', $yubi_uid ) )
-		{
-			return array(
-					'mode' => 'error',
-					'error' => 'yubiauth_err_invalid_otp'
-				);
-		}
-		$q = $db->sql_query('SELECT u.user_id, u.username, u.user_yubikey_flags FROM ' . table_prefix . "users AS u\n"
-											. "  LEFT JOIN " . table_prefix . "yubikey AS y\n"
-											. "    ON ( y.user_id = u.user_id )\n"
-											. "  WHERE y.yubi_uid = '$yubi_uid'\n"
-											. "  GROUP BY u.user_yubikey_flags;");
-		if ( !$q )
-			$db->_die();
-		
-		if ( $db->numrows() < 1 )
-		{
-			if ( !$do_validate_pass )
-				$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
- 									. '  (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \'DEPRECATED\', \'(Yubikey)\', '
-											. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
-			
-			return array(
-					'mode' => 'error',
-					'error' => 'yubiauth_err_key_not_authorized'
-				);
-		}
-		
-		list($user_id, $username, $flags) = $db->fetchrow_num();
-		
-		if ( $level > USER_LEVEL_MEMBER )
-		{
-			$session->start();
-			if ( $session->user_logged_in && ($session->user_id !== $user_id) )
-			{
-				return array(
-						'mode' => 'error',
-						'error' => 'yubiauth_err_uid_mismatch'
-					);
-			}
-		}
-		
-		$do_validate_otp = true;
-		$do_validate_user = $flags & $user_flag;
-		$do_validate_pass = $flags & $pass_flag;
-		// to complete security logs later
-		$userdata['username'] = $username;
-	}
-	else
-	{
-		// Nothing - no username or OTP. This request can't be used; throw it out.
-		return array(
-				'mode' => 'error',
-				'error' => 'yubiauth_err_nothing_provided'
-			);
-	}
-	
-	if ( $do_validate_otp )
-	{
-		// We need to validate the OTP.
-		$otp_check = yubikey_validate_otp($userdata['yubikey_otp']);
-		if ( !$otp_check['success'] )
-		{
-			if ( !$do_validate_pass )
-				$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
- 									. '  (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \'DEPRECATED\', \'(Yubikey)\', '
-											. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
-			
-			if ( $otp_check['error'] === 'http_failed' )
-			{
-				return array(
-						'mode' => 'error',
-						'error' => 'yubiauth_err_' . $otp_check['error'],
-						'http_error' => $otp_check['http_error']
-					);
-			}
-			return array(
-					'mode' => 'error',
-					'error' => 'yubiauth_err_' . $otp_check['error']
-				);
-		}
-	}
-	if ( $do_validate_user )
-	{
-		if ( empty($username) )
-		{
-			return array(
-					'mode' => 'error',
-					'error' => 'yubiauth_err_must_have_username'
-				);
-		}
-		if ( strtolower($username) !== strtolower($userdata['username']) )
-		{
-			// Username incorrect
-			if ( !$do_validate_pass )
-				$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
- 									. '  (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \'DEPRECATED\', \'(Yubikey)\', '
-											. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
-			return array(
-					'mode' => 'error',
-					'error' => 'invalid_credentials'
-				);
-		}
-	}
-	// Do we need to have the password validated?
-	if ( $do_validate_pass )
-	{
-		if ( empty($userdata['password']) )
-		{
-			return array(
-					'mode' => 'error',
-					'error' => 'yubiauth_err_must_have_password'
-				);
-		}
-		// Yes; return and let the login API continue
-		return null;
-	}
-	else
-	{
-		// No password required; validated, issue session key
-		$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
- 									. '  (\'security\', \'' . $auth_log_prefix . 'auth_good\', '.time().', \'DEPRECATED\', \'' . $db->escape($userdata['username']) . '\', '
-											. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
-				
-		$q = $db->sql_query('SELECT password FROM ' . table_prefix . "users WHERE user_id = $user_id;");
-		if ( !$q )
-			$db->_die();
-		
-		list($password) = $db->fetchrow_num();
-		$db->free_result();
-		
-		$session->register_session($user_id, $userdata['username'], $password, intval($level), $remember);
-		return true;
-	}
-}
-
-function yubikey_add_special_pages()
-{
-	global $db, $session, $paths, $template, $plugins; // Common objects
-	global $lang;
-	
-	if ( getConfig('yubikey_enable', '1') != '1' )
-		return true;
-	
-	$paths->add_page(array(
-			'name' => $lang->get('yubiauth_specialpage_yubikey'),
-			'urlname' => 'Yubikey',
-			'namespace' => 'Special',
-			'visible' => 0, 'protected' => 0, 'comments_on' => 0, 'special' => 0
-		));
-}
-
-function yubikey_sk_calc($user_id, &$key_pieces, &$sk_mode)
-{
-	global $db, $session, $paths, $template, $plugins; // Common objects
-	// hash the user's yubikeys
-	$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE user_id = $user_id;");
-	if ( !$q )
-		$db->_die();
-	
-	while ( $row = $db->fetchrow() )
-	{
-		$key_pieces[] = $row['yubi_uid'];
-	}
-}
-
-function page_Special_Yubikey()
-{
-	global $db, $session, $paths, $template, $plugins; // Common objects
-	
-	header('Content-type: text/javascript');
-	/*
-	if ( isset($_GET['validate_otp']) )
-	{
-		echo enano_json_encode(yubikey_validate_otp($_GET['validate_otp']));
-		return true;
-	}
-	*/
-	if ( isset($_GET['get_flags']) || isset($_POST['get_flags']) )
-	{
-		$yubi_uid = substr($_REQUEST['get_flags'], 0, 12);
-		if ( !preg_match('/^[cbdefghijklnrtuv]{12}$/', $yubi_uid) )
-		{
-			return print enano_json_encode(array(
-					'mode' => 'error',
-					'error' => 'invalid_otp'
-				));
-		}
-		$q = $db->sql_query('SELECT u.user_yubikey_flags FROM ' . table_prefix . "users AS u\n"
-											. "  LEFT JOIN " . table_prefix . "yubikey AS y\n"
-											. "    ON ( y.user_id = u.user_id )\n"
-											. "  WHERE y.yubi_uid = '$yubi_uid'\n"
-											. "  GROUP BY u.user_yubikey_flags;");
-		if ( !$q )
-			$db->_die();
-		
-		if ( $db->numrows() < 1 )
-		{
-			return print enano_json_encode(array(
-					'mode' => 'error',
-					'error' => 'key_not_authorized'
-				));
-		}
-		
-		list($flags) = $db->fetchrow_num();
-		
-		echo enano_json_encode(array(
-				// We strip YK_SEC_ALLOW_NO_OTP here for security reasons.
-				'flags' => intval($flags & ~YK_SEC_ALLOW_NO_OTP)
-			));
-		
-		return true;
-	}
-}
-
--- a/plugins/yubikey/corelib.php	Fri Nov 11 00:33:28 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,300 +0,0 @@
-<?php
-
-define('YK_SEC_NORMAL_USERNAME', 1);
-define('YK_SEC_NORMAL_PASSWORD', 2);
-define('YK_SEC_ELEV_USERNAME', 4);
-define('YK_SEC_ELEV_PASSWORD', 8);
-define('YK_SEC_ALLOW_NO_OTP', 16);
-
-define('YK_DEFAULT_VERIFY_URL', 'http://api.yubico.com/wsapi/verify');
-
-function generate_yubikey_field($name = 'yubikey_otp', $value = false)
-{
-	global $lang;
-	
-	$fid = substr(sha1(microtime() . mt_rand()), 0, 12);
-	$class = $value ? 'wasfull' : 'wasempty';
-	$html = '<input id="yubifield' . $fid . '" class="' . $class . '" type="hidden" name="' . $name . '" value="' . ( is_string($value) ? $value : '' ) . '" />';
-	$html .= '<noscript><input type="text" name="' . $name . '" class="yubikey_noscript" value="' . ( is_string($value) ? $value : '' ) . '" /> </noscript>';
-	if ( $value )
-	{
-		$html .= '<span id="yubistat' . $fid . '" class="yubikey_status enrolled">' . $lang->get('yubiauth_ctl_status_enrolled') . '</span>';
-		$atext = $lang->get('yubiauth_ctl_btn_change_key');
-		$classadd = ' abutton_green';
-	}
-	else
-	{
-		$html .= '<span id="yubistat' . $fid . '" class="yubikey_status empty">' . $lang->get('yubiauth_ctl_status_empty') . '</span>';
-		$atext = $lang->get('yubiauth_ctl_btn_enroll');
-		$classadd = '';
-	}
-	
-	$html .= ' <span class="yubikey_pubkey">';
-	if ( !empty($value) )
-		$html .= htmlspecialchars(substr($value, 0, 12));
-	$html .= '</span> ';
-	
-	$html .= ' <a class="abutton' . $classadd . ' yubikey_enroll" onclick="yk_mb_init(\'yubifield' . $fid . '\', \'yubistat' . $fid . '\'); return false;" href="#enroll">' . $atext . '</a>';
-	if ( $value )
-	{
-		$html .= ' <a class="abutton abutton_red yubikey_enroll" onclick="yk_clear(\'yubifield' . $fid . '\', \'yubistat' . $fid . '\'); return false;" href="#enroll">'
- 						. $lang->get('yubiauth_ctl_btn_clear') .
- 						'</a>';
-	}
-	
-	return $html;
-}
-
-function yubikey_validate_otp($otp)
-{
-	$api_key = getConfig('yubikey_api_key');
-	$api_id  = getConfig('yubikey_api_key_id');
-	// Don't require an API key or user ID to be installed if we're using local YMS
-	if ( !(getConfig('yubikey_use_local_yms', 0) && defined('YMS_INSTALLED')) && (!$api_key || !$api_id) )
-	{
-		return array(
-				'success' => false,
-				'error' => 'missing_api_key'
-			);
-	}
-	if ( !preg_match('/^[cbdefghijklnrtuv]{44}$/', $otp) )
-	{
-		return array(
-				'success' => false,
-				'error' => 'otp_invalid_chars'
-			);
-	}
-	// are we using local YMS?
-	if ( getConfig('yubikey_use_local_yms', 0) && defined('YMS_INSTALLED') )
-	{
-		$result = yms_validate_otp($otp, $api_id);
-		if ( $result == 'OK' )
-		{
-			return array(
-					'success' => true
-				);
-		}
-		else
-		{
-			return array(
-				'success' => false,
-				'error' => strtolower("response_{$result}")
-			);
-		}
-	}
-	// make HTTP request
-	require_once( ENANO_ROOT . '/includes/http.php' );
-	$auth_url = getConfig('yubikey_auth_server', YK_DEFAULT_VERIFY_URL);
-	$auth_url = preg_replace('#^https?://#i', '', $auth_url);
-	if ( !preg_match('#^(\[?[a-z0-9-:]+(?:\.[a-z0-9-:]+\]?)*)(?::([0-9]+))?(/.*)$#U', $auth_url, $match) )
-	{
-		return array(
-				'success' => false,
-				'error' => 'invalid_auth_url'
-			);
-	}
-	$auth_server =& $match[1];
-	$auth_port = ( !empty($match[2]) ) ? intval($match[2]) : 80;
-	$auth_uri =& $match[3];
-	try
-	{
-		$req = new Request_HTTP($auth_server, $auth_uri, 'GET', $auth_port);
-		$req->add_get('id', strval($api_id));
-		$req->add_get('otp', $otp);
-		$req->add_get('h', yubikey_sign($req->parms_get));
-	
-		$response = $req->get_response_body();
-	}
-	catch ( Exception $e )
-	{
-		return array(
-				'success' => false,
-				'error' => 'http_failed',
-				'http_error' => $e->getMessage()
-			);
-	}
-	
-	if ( $req->response_code != HTTP_OK )
-	{
-		return array(
-				'success' => false,
-				'error' => 'http_response_error'
-			);
-	}
-	$response = trim($response);
-	if ( !preg_match_all('/^([a-z0-9_]+)=(.*?)\r?$/m', $response, $matches) )
-	{
-		return array(
-				'success' => false,
-				'error' => 'malformed_response'
-			);
-	}
-	$response = array();
-	foreach ( $matches[0] as $i => $_ )
-	{
-		$response[$matches[1][$i]] = $matches[2][$i];
-	}
-	// make sure we have a status
-	if ( !isset($response['status']) )
-	{
-		return array(
-				'success' => false,
-				'error' => 'response_missing_status'
-			);
-	}
-	// verify response signature
-	// MISSING_PARAMETER is the ONLY situation under which an unsigned response is acceptable
-	if ( $response['status'] !== 'MISSING_PARAMETER' )
-	{
-		if ( !isset($response['h']) )
-		{
-			return array(
-					'success' => false,
-					'error' => 'response_missing_sig'
-				);
-		}
-		if ( yubikey_sign($response) !== $response['h'] )
-		{
-			return array(
-					'success' => false,
-					'error' => 'response_invalid_sig'
-				);
-		}
-	}
-	if ( $response['status'] === 'OK' )
-	{
-		if ( yubikey_verify_timestamp($response['t']) )
-		{
-			return array(
-					'success' => true
-				);
-		}
-		else
-		{
-			return array(
-					'success' => false,
-					'error' => 'timestamp_check_failed'
-				);
-		}
-	}
-	else
-	{
-		return array(
-				'success' => false,
-				'error' => strtolower("response_{$response['status']}")
-			);
-	}
-}
-
-function yubikey_sign($arr, $use_api_key = false)
-{
-	static $api_key = false;
-	
-	ksort($arr);
-	
-	if ( !$use_api_key )
-	{
-		if ( !$api_key )
-		{
-			$api_key = getConfig('yubikey_api_key');
-			$api_key = hexencode(base64_decode($api_key), '', '');
-		}
-		$use_api_key = $api_key;
-	}
-	/*
-	else
-	{
-		$use_api_key = hexencode(base64_decode($use_api_key), '', '');
-	}
-	*/
-	
-	foreach ( array('h', 'title', 'auth', 'do') as $key )
-	{
-		if ( isset($arr[$key]) )
-			unset($arr[$key]);
-	}
-	
-	$req = array();
-	foreach ( $arr as $key => $val )
-	{
-		$req[] = "$key=$val";
-	}
-	$req = implode('&', $req);
-	
-	$sig = hmac_sha1($req, $use_api_key);
-	$sig = hexdecode($sig);
-	$sig = base64_encode($sig);
-	
-	return $sig;
-}
-
-/**
- * Validate the timestamp returned in a Yubico API response. Borrowed from Drupal and backported for friendliness with earlier versions of PHP.
- * @param string Yubico timestamp
- * @return bool True if valid, false otherwise
- */
-
-function yubikey_verify_timestamp($timestamp)
-{
-	$tolerance = intval(getConfig('yubikey_api_ts_tolerance', 150));
-	
-	$now = time();
-	$timestamp_seconds = yk_strtotime($timestamp);
-
-	if ( !$timestamp || !$now || !$timestamp_seconds )
-	{
-		return false;
-	}
-
-	if ( ( $timestamp_seconds + $tolerance ) > $now && ( $timestamp_seconds - $tolerance ) < $now )
-	{
-		return true;
-	}
-
-	return false;
-}
-
-function yk_strtotime($timestamp)
-{
-	if ( !preg_match('/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:Z[0-9]+)?$/', $timestamp, $match) )
-		return 0;
-	
-	$hour = intval($match[4]);
-	$minute = intval($match[5]);
-	$second = intval($match[6]);
-	$month = intval($match[2]);
-	$day = intval($match[3]);
-	$year = intval($match[1]);
-	
-	return gmmktime($hour, $minute, $second, $month, $day, $year);
-}
-
-$plugins->attachHook('compile_template', 'yubikey_attach_headers($this);');
-
-function yubikey_attach_headers(&$template)
-{
-	global $db, $session, $paths, $template, $plugins; // Common objects
-	
-	if ( getConfig('yubikey_enable', '1') != '1' )
-		return true;
-	
-	$template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/yubikey/yubikey.js"></script>');
-	$template->add_header('<link rel="stylesheet" type="text/css" href="' . scriptPath . '/plugins/yubikey/yubikey.css" />');
-	// config option for all users have yubikey
-	$user_flags = 0;
-	$yk_enabled = 0;
-	if ( $session->user_logged_in )
-	{
-		$q = $db->sql_query('SELECT COUNT(y.yubi_uid) > 0, u.user_yubikey_flags FROM ' . table_prefix . "yubikey AS y LEFT JOIN " . table_prefix . "users AS u ON ( u.user_id = y.user_id ) WHERE y.user_id = {$session->user_id} GROUP BY u.user_id, u.user_yubikey_flags;");
-		if ( !$q )
-			$db->_die();
-		
-		list($yk_enabled, $user_flags) = $db->fetchrow_num();
-		$db->free_result();
-	}
-	$yk_enabled = intval($yk_enabled);
-	$user_flags = intval($user_flags);
-	
-	$template->add_header('<script type="text/javascript">var yk_reg_require_otp = ' . getConfig('yubikey_reg_require_otp', '0') . '; var yk_user_enabled = ' . $yk_enabled . '; var yk_user_flags = ' . $user_flags . ';</script>');
-}
-
Binary file plugins/yubikey/field.png has changed
Binary file plugins/yubikey/formicon.gif has changed
--- a/plugins/yubikey/usercp.php	Fri Nov 11 00:33:28 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,385 +0,0 @@
-<?php
-
-if ( getConfig('yubikey_enable', '1') != '1' )
-	return true;
-
-$plugins->attachHook("userprefs_jbox", "yubikey_ucp_setup();");
-$plugins->attachHook("userprefs_body", "return yubikey_user_cp(\$section);");
-$plugins->attachHook("login_form_html", "yubikey_inject_html_login();");
-$plugins->attachHook("ucp_register_form", "yubikey_inject_registration_form();");
-$plugins->attachHook("ucp_register_validate", "yubikey_register_validate(\$error);");
-$plugins->attachHook("user_registered", "yubikey_register_insert_key(\$user_id);");
-
-function yubikey_ucp_setup()
-{
-	userprefs_menu_add('usercp_sec_profile', 'yubiucp_panel_title', makeUrlNS('Special', 'Preferences/Yubikey') . '" onclick="ajaxLoginNavTo(\'Special\', \'Preferences/Yubikey\', '.USER_LEVEL_CHPREF.'); return false;');
-}
-
-function yubikey_user_cp($section)
-{
-	global $db, $session, $paths, $template, $plugins; // Common objects
-	global $lang;
-	
-	if ( $section !== 'Yubikey' )
-		return false;
-	
-	if ( $session->auth_level < USER_LEVEL_CHPREF )
-	{
-		redirect(makeUrlNS('Special', 'Login/' . $paths->fullpage, 'level=' . USER_LEVEL_CHPREF, true), 'Authentication required', 'You need to re-authenticate to access this page.', 0);
-	}
-	
-	$count_enabled = intval(getConfig('yubikey_enroll_limit', '3'));
-	
-	if ( isset($_POST['submit']) )
-	{
-		csrf_request_confirm();
-		
-		$keys = array();
-		if ( isset($_POST['yubikey_enable']) )
-		{
-			for ( $i = 0; $i < $count_enabled; $i++ )
-			{
-				if ( !empty($_POST["yubikey_otp_$i"]) )
-				{
-					$ckey =& $_POST["yubikey_otp_$i"];
-					if ( preg_match('/^[cbdefghijklnrtuv]{12,44}$/', $ckey) )
-					{
-						$ckey = substr($ckey, 0, 12);
-						$keys[] = $ckey;
-					}
-					unset($ckey);
-				}
-			}
-		}
-		// Check for double enrollment
-		$keys_check = "yubi_uid = '" . implode("' OR yubi_uid = '", $keys) . "'";
-		$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE ( $keys_check ) AND user_id != {$session->user_id};");
-		if ( !$q )
-			$db->_die();
-		
-		if ( $db->numrows() > 0 )
-		{
-			echo '<div class="error-box" style="margin: 0 0 10px 0;">' . $lang->get('yubiucp_err_double_enrollment') . '</div>';
-			while ( $row = $db->fetchrow() )
-			{
-				foreach ( $keys as $i => $key )
-				{
-					if ( $key == $row['yubi_uid'] )
-					{
-						unset($keys[$i]);
-					}
-				}
-			}
-			$keys = array_values($keys);
-		}
-		$db->free_result();
-		
-		// Remove all currently registered keys
-		$q = $db->sql_query('DELETE FROM ' . table_prefix . "yubikey WHERE user_id = {$session->user_id};");
-		if ( !$q )
-			$db->_die();
-		
-		// Enroll any new keys
-		if ( !empty($keys) )
-		{
-			$query = 'INSERT INTO ' . table_prefix . "yubikey(user_id, yubi_uid) VALUES\n  " .
- 								"( $session->user_id, '" . implode("' ),\n  ( $session->user_id, '", $keys) . "' );";
-			if ( !$db->sql_query($query) )
-				$db->_die();
-		}
-		
-		// Calculate flags
-		$yubi_flags = 0;
-		$yubi_flags |= intval($_POST['login_normal_flags']);
-		$yubi_flags |= intval($_POST['login_elev_flags']);
-		$yubi_flags |= ( isset($_POST['allow_no_yubikey']) ) ? YK_SEC_ALLOW_NO_OTP : 0;
-		
-		// update flags
-		$q = $db->sql_query('UPDATE ' . table_prefix . "users SET user_yubikey_flags = $yubi_flags WHERE user_id = {$session->user_id};");
-		if ( !$q )
-			$db->_die();
-		
-		// regenerate session
-		$q = $db->sql_query('SELECT password FROM ' . table_prefix . "users WHERE user_id = {$session->user_id};");
-		if ( !$q )
-			$db->_die();
-		list($password_hmac) = $db->fetchrow_num();
-		
-		@$session->register_session($session->user_id, $session->username, $password_hmac, USER_LEVEL_MEMBER, false);
-		$session->logout(USER_LEVEL_CHPREF);
-		
-		// redirect back to normal CP
-		// if OB-ing isn't enabled, require a JS redirect (hey, not many other options...)
-		if ( @ob_get_contents() )
-		{
-			@ob_end_clean();
-			redirect(makeUrlNS('Special', 'Preferences'), $lang->get('yubiucp_msg_save_title'), $lang->get('yubiucp_msg_save_body'), 3);
-		}
-		else
-		{
-			echo '<h3>' . $lang->get('yubiucp_msg_save_title') . '</h3>';
-			echo '<p>' . $lang->get('yubiucp_msg_save_body') . '</p>';
-			// not much choice here, i'm resorting to javascript because the user CP always
-			// sends headers :-/
-			echo '<script type="text/javascript">
-				addOnloadHook(function()
-				{' .
-				// note: $_COOKIE['sid'] has just been assigned by $session->register_session() - so it's safe to use here.
-				'
-					createCookie(\'sid\', \'' . $_COOKIE['sid'] . '\');
-					window.location = makeUrlNS(\'Special\', \'Preferences\');
-				});
-			</script>';
-			return true;
-		}
-	}
-	else
-	{
-		// Fetch flags
-		$q = $db->sql_query('SELECT user_yubikey_flags FROM ' . table_prefix . "users WHERE user_id = {$session->user_id};");
-		if ( !$q )
-			$db->_die();
-		
-		list($yubi_flags) = $db->fetchrow_num();
-		$yubi_flags = intval($yubi_flags);
-		// Fetch user's authorized keys from the DB
-		$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE user_id = {$session->user_id};");
-		if ( !$q )
-			$db->_die();
-		
-		$keys = array();
-		while ( $row = $db->fetchrow() )
-		{
-			$keys[] = $row['yubi_uid'];
-		}
-		$db->free_result();
-	}
-	
-	while ( count($keys) < $count_enabled )
-	{
-		$keys[] = false;
-	}
-	
-	$enable_checked = ( $keys[0] === false && !isset($_POST['yubikey_enable']) ) ? '' : 'checked="checked"';
-	$displaytable = ( $keys[0] === false && !isset($_POST['yubikey_enable']) ) ? 'none' : 'block';
-	
-	$check_normal_keyonly = ( !($yubi_flags & YK_SEC_NORMAL_USERNAME) && !($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
-	$check_normal_username = ( ($yubi_flags & YK_SEC_NORMAL_USERNAME) && !($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
-	$check_normal_userandpw = ( ($yubi_flags & YK_SEC_NORMAL_USERNAME) && ($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
-
-	$check_elev_keyonly = ( !($yubi_flags & YK_SEC_ELEV_USERNAME) && !($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';
-	$check_elev_username = ( ($yubi_flags & YK_SEC_ELEV_USERNAME) && !($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';
-	$check_elev_userandpw = ( ($yubi_flags & YK_SEC_ELEV_USERNAME) && ($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';  
-	
-	?>
-	<h3 style="margin-top: 0;"><?php echo $lang->get('yubiucp_panel_title'); ?></h3>
-	
-	<form action="<?php echo makeUrlNS('Special', 'Preferences/Yubikey'); ?>" method="post">
-	
-	<div>
-		<table border="0" cellpadding="4" width="100%">
-			<tr>
-				<td style="width: 50%; text-align: right;">
-					<?php echo $lang->get('yubiucp_field_enable_title'); ?><br />
-					<small><?php echo $lang->get('yubiucp_field_enable_hint'); ?></small>
-				</td>
-				<td style="width: 50%;">
-					<label>
-						<input type="checkbox" name="yubikey_enable" onclick="if ( $(this).attr('checked') ) $('#yk_useroptions').show('blind'); else $('#yk_useroptions').hide('blind');" <?php echo $enable_checked; ?> />
-						<?php echo $lang->get('yubiucp_field_enable'); ?>
-					</label>
-				</td>
-			</tr>
-		</table>
-		<table border="0" cellpadding="4" width="100%" id="yk_useroptions" style="display: <?php echo $displaytable ?>;">
-			<tr class="yk_alt1">
-			<td style="width: 50%; text-align: right;">
-					<?php echo $lang->get('yubiucp_field_keys_title'); ?><br />
-					<small><?php
-					echo $lang->get('yubiucp_field_keys_hint');
-					if ( $count_enabled > 1 )
-					{
-						echo ' ';
-						echo $lang->get('yubiucp_field_keys_maximum', array('max' => $count_enabled));
-					}
-					?></small>
-				</td>
-				<td style="width: 50%;">
-					<?php
-					for ( $i = 0; $i < $count_enabled; $i++ )
-					{
-						echo '<p>' . generate_yubikey_field('yubikey_otp_' . $i, $keys[$i]) . '</p>';
-					}
-					?>
-				</td>
-			</tr>
-			<tr>
-				<td style="width: 50%; text-align: right;">
-					<?php echo $lang->get('yubiucp_field_normal_flags'); ?>
-				</td>
-				<td>
-					<label>
-						<input type="radio" name="login_normal_flags" value="0" <?php echo $check_normal_keyonly; ?>/>
-						<?php echo $lang->get('yubiucp_field_flags_keyonly'); ?>
-					</label>
-					
-					<br />
-					
-					<label>
-						<input type="radio" name="login_normal_flags" value="<?php echo strval(YK_SEC_NORMAL_USERNAME); ?>" <?php echo $check_normal_username; ?>/>
-						<?php echo $lang->get('yubiucp_field_flags_username'); ?>
-					</label>
-					
-					<br />
-					
-					<label>
-						<input type="radio" name="login_normal_flags" value="<?php echo strval(YK_SEC_NORMAL_USERNAME | YK_SEC_NORMAL_PASSWORD); ?>" <?php echo $check_normal_userandpw; ?>/>
-						<?php echo $lang->get('yubiucp_field_flags_userandpw'); ?>
-					</label>
-				</td>
-			</tr>
-			<tr class="yk_alt1">
-				<td style="width: 50%; text-align: right;">
-					<?php echo $lang->get('yubiucp_field_elev_flags'); ?>
-				</td>
-				<td>
-					<label>
-						<input type="radio" name="login_elev_flags" value="0" <?php echo $check_elev_keyonly; ?>/>
-						<?php echo $lang->get('yubiucp_field_flags_keyonly'); ?>
-					</label>
-					
-					<br />
-					
-					<label>
-						<input type="radio" name="login_elev_flags" value="<?php echo strval(YK_SEC_ELEV_USERNAME); ?>" <?php echo $check_elev_username; ?>/>
-						<?php echo $lang->get('yubiucp_field_flags_username'); ?>
-					</label>
-					
-					<br />
-					
-					<label>
-						<input type="radio" name="login_elev_flags" value="<?php echo strval(YK_SEC_ELEV_USERNAME | YK_SEC_ELEV_PASSWORD); ?>" <?php echo $check_elev_userandpw; ?>/>
-						<?php echo $lang->get('yubiucp_field_flags_userandpw'); ?>
-					</label>
-				</td>
-			</tr>
-			<tr>
-				<td>
-				</td>
-				<td>
-					<label>
-						<input type="checkbox" name="allow_no_yubikey" <?php if ( $yubi_flags & YK_SEC_ALLOW_NO_OTP ) echo 'checked="checked" '; ?>/>
-						<?php echo $lang->get('yubiucp_field_allow_plain_login'); ?>
-					</label>
-					<br />
-					<small>
-						<?php echo $lang->get('yubiucp_field_allow_plain_login_hint'); ?>
-					</small>
-				</td>
-			</tr>
-		</table>
-		<table border="0" cellpadding="4" width="100%">
-			<tr class="yk_alt1">
-				<td colspan="2" style="text-align: center;">
-					<input type="submit" name="submit" value="<?php echo $lang->get('etc_save_changes'); ?>" />
-				</td>
-			</tr>
-		</table>
-	</div>
-	
-	<input type="hidden" name="cstok" value="<?php echo $session->csrf_token; ?>" />
-	
-	</form>
-	<?php
-	
-	return true;
-}
-
-function yubikey_inject_html_login()
-{
-	global $lang;
-	?>
-	<tr>
-		<td class="row2">
-			<?php echo $lang->get('yubiauth_lbl_otp_field'); ?>
-		</td>
-		<td class="row1" colspan="2">
-			<input type="text" size="40" class="yubikey_noscript" name="yubikey_otp" />
-		</td>
-	</tr>
-	<?php
-}
-
-function yubikey_inject_registration_form()
-{
-	global $lang;
-	
-	$preset_otp = isset($_POST['yubikey_otp']) ? $_POST['yubikey_otp'] : false;
-	?>
-	<tr>
-		<td class="row1">
-			<?php echo $lang->get('yubiucp_reg_field_otp'); ?><br />
-			<small><?php
-				if ( getConfig('yubikey_reg_require_otp', '0') == '1' )
-					echo $lang->get('yubiucp_reg_field_otp_hint_required');
-				else
-					echo $lang->get('yubiucp_reg_field_otp_hint_optional');
-			?></small>
-		</td>
-		<td class="row1">
-			<?php
-			echo generate_yubikey_field('yubikey_otp', $preset_otp);
-			?>
-		</td>
-		<td class="row1">
-		</td>
-	</tr>
-	<?php
-}
-
-function yubikey_register_validate(&$error)
-{
-	global $db, $session, $paths, $template, $plugins; // Common objects
-	global $lang;
-	
-	$otp_required = getConfig('yubikey_reg_require_otp', '0') == '1';
-	$have_otp = !empty($_POST['yubikey_otp']);
-	if ( $otp_required && !$have_otp )
-	{
-		$error = $lang->get('yubiucp_reg_err_otp_required');
-		return false;
-	}
-	if ( $have_otp )
-	{
-		$result = yubikey_validate_otp($_POST['yubikey_otp']);
-		if ( !$result['success'] )
-		{
-			$error = '<b>' . $lang->get('yubiucp_reg_err_otp_invalid') . '</b><br />' . $lang->get("yubiauth_err_{$result['error']}");
-			return false;
-		}
-		// check for double enrollment
-		$yubi_uid = substr($_POST['yubikey_otp'], 0, 12);
-		// Note on SQL injection: yubikey_validate_otp() has already ensured that this is safe
-		$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yubikey WHERE yubi_uid = '$yubi_uid';");
-		if ( !$q )
-			$db->_die();
-		if ( $db->numrows() > 0 )
-		{
-			$error = '<b>' . $lang->get('yubiucp_reg_err_otp_invalid') . '</b><br />' . $lang->get('yubiucp_err_double_enrollment_single');
-			return false;
-		}
-		$db->free_result();
-	}
-}
-
-function yubikey_register_insert_key($user_id)
-{
-	global $db, $session, $paths, $template, $plugins; // Common objects
-	if ( !empty($_POST['yubikey_otp']) )
-	{
-		$yubi_uid = $db->escape(substr($_POST['yubikey_otp'], 0, 12));
-		$q = $db->sql_query('INSERT INTO ' . table_prefix . "yubikey ( user_id, yubi_uid ) VALUES ( $user_id, '$yubi_uid' );");
-		if ( !$q )
-			$db->_die();
-	}
-}
--- a/plugins/yubikey/yubikey.css	Fri Nov 11 00:33:28 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-span.yubikey_status {
-  font-weight: bold;
-}
-
-span.yubikey_status.empty {
-  color: #6699CC;
-}
-
-span.yubikey_status.savepending {
-  color: #f5ac00;
-}
-
-span.yubikey_status.rmpending {
-  color: #aa0000;
-}
-
-span.yubikey_status.enrolled {
-  color: #008000;
-}
-
-a.yubikey_enroll {
-  font-size: smaller;
-}
-
-span.yubikey_pubkey {
-  font-size: smaller;
-  color: #666;
-}
-
-tr.yk_alt1 td {
-  background-color: #f8f8f8;
-}
-
-input.yubikey_noscript {
-  background-color: white;
-  background-image: url(./formicon.gif);
-  background-position: 1px 1px;
-  background-repeat: no-repeat;
-  padding-left: 18px;
-}
-
-div.yubikey_bar {
-  width: 88px;
-  height: 88px;
-  margin: 4px auto;
-  text-align: center;
-  background-image: url(./field.png);
-  background-repeat: no-repeat;
-}
-
-div.yubikey_bar > img {
-  width: 0px;
-  height: 88px;
-  background-image: url(./field.png);
-  background-repeat: no-repeat;
-  background-position: 0px -88px;
-}
-
-div.yubikey_bar > img.yubikey_bar_error {
-  width: 88px !important;
-  background-position: 0px -176px !important;
-}
--- a/plugins/yubikey/yubikey.js	Fri Nov 11 00:33:28 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,305 +0,0 @@
-// sample OTP:
-// ttttvvvvvvcurikvhjcvnlnbecbkubjvuittbifhndhn
-// charset: cbdefghijklnrtuv
-
-var yk_interval = false;
-
-var YK_SEC_NORMAL_USERNAME = 1;
-var YK_SEC_NORMAL_PASSWORD = 2;
-var YK_SEC_ELEV_USERNAME = 4;
-var YK_SEC_ELEV_PASSWORD = 8;
-
-var yubikey_otp_current = false;
-
-function yk_mb_init(fieldid, statid)
-{
-  load_component(['messagebox', 'fadefilter', 'flyin', 'jquery', 'jquery-ui', 'l10n']);
-  var mp = miniPrompt(yk_mb_construct);
-  if ( typeof(fieldid) == 'function' )
-  {
-    var input = mp.getElementsByTagName('input')[0];
-    input.submit_func = fieldid;
-  }
-  else if ( fieldid && statid )
-  {
-    var input = mp.getElementsByTagName('input')[0];
-    input.yk_field_id = fieldid;
-    input.yk_status_id = statid;
-  }
-}
-
-function yk_mb_construct(mp)
-{
-  mp.innerHTML = '';
-  mp.style.textAlign = 'center';
-  mp.innerHTML = '<h3>' + $lang.get('yubiauth_msg_please_touch_key') + '</h3>';
-  var progress = document.createElement('div');
-  $(progress).addClass('yubikey_bar').css('text-align', 'left');
-  var progimg = document.createElement('img');
-  progimg.src = cdnPath + '/images/spacer.gif';
-  progress.appendChild(progimg);
-  mp.appendChild(progress);
-  var ta = document.createElement('input');
-  ta.submitted = false;
-  $(ta)
-    .css('background-color', 'transparent')
-    .css('border-width', '0px')
-    .css('color', '#fff')
-    .css('font-size', '1px')
-    .css('padding', '0')
-    .css('opacity', '0')
-    .attr('size', '1')
-    .keyup(function(e)
-      {
-        if ( e.keyCode == 27 )
-        {
-          window.clearInterval(yk_interval);
-          miniPromptDestroy(this);
-        }
-        // 0.3: submit only upon a keycode 13
-        else if ( e.keyCode == 13 )
-        {
-          this.submitted = true;
-          yk_handle_submit(this);
-        }
-        else
-        {
-          $('div.yubikey_bar > img', this.parentNode)
-            .css('width', String(this.value.length * 2) + 'px')
-            //.css('background-position', String((this.value.length > 44 ? 44 : this.value.length) - 44) + 'px -88px');
-        }
-        e.preventDefault();
-        e.stopPropagation();
-      });
-  mp.appendChild(ta);
-  setTimeout(function()
-    {
-      window.yk_interval = setInterval(function()
-        {
-          ta.focus();
-        }, 50);
-    }, 750);
-  var info = document.createElement('p');
-  $(info)
-    .append('<span style="color: #ffffff; font-size: smaller;">' + $lang.get('yubiauth_msg_close_instructions') + '</span>&nbsp;&nbsp;')
-    .append('<a class="abutton abutton_green" href="#" onclick="miniPromptDestroy(this); return false;">' + $lang.get('etc_cancel') + '</a>&nbsp;&nbsp;')
-    //.append('<br />')
-    .append('<span style="color: #909090; font-size: smaller;">' + $lang.get('yubiauth_msg_close_instructions') + '</span>')
-    .css('margin-top', '0');
-  mp.appendChild(info);
-}
-
-function yk_handle_submit(ta)
-{
-  if ( ta.value.length > 44 || !ta.value.match(/^[cbdefghijklnrtuv]+$/) )
-  {
-    // report "invalid characters"
-    setTimeout(function()
-      {
-        var parent = ta.parentNode;
-        var tabackup = {
-          field_id: ta.yk_field_id,
-          status_id: ta.yk_status_id,
-          submit_func: ta.submit_func
-        };
-        yk_mb_construct(parent);
-        var input = parent.getElementsByTagName('input')[0];
-        if ( tabackup.field_id )
-          input.yk_field_id = tabackup.field_id;
-        if ( tabackup.status_id )
-          input.yk_status_id = tabackup.status_id;
-        if ( tabackup.submit_func )
-          input.submit_func = tabackup.submit_func;
-      }, 1000);
-    $('h3', ta.parentNode).text($lang.get(ta.value.length > 44 ? 'yubiauth_msg_too_long' : 'yubiauth_msg_invalid_chars'));
-    $('div.yubikey_bar > img', this.parentNode).addClass('yubikey_bar_error');
-    return false;
-  }
-  
-  window.clearInterval(yk_interval);
-  
-  if ( ta.yk_field_id && ta.yk_status_id )
-  {
-    var field = document.getElementById(ta.yk_field_id);
-    var status = document.getElementById(ta.yk_status_id);
-    if ( $(status).hasClass('empty') || $(status).hasClass('rmpending') )
-    {
-      $(status)
-      .next('span.yubikey_pubkey')
-        .text(ta.value.substr(0, 12))
-      .next('a.yubikey_enroll')
-        .text($lang.get('yubiauth_ctl_btn_change_key'))
-        .addClass('abutton_green')
-        .after(' <a class="abutton abutton_red yubikey_enroll" href="#yk_clear" onclick="yk_clear(\'' + ta.yk_field_id + '\', \'' + ta.yk_status_id + '\'); return false;">'
-               + $lang.get('yubiauth_ctl_btn_clear') +
-               '</a>');
-    }
-    $(status).removeClass('empty').removeClass('enrolled').removeClass('rmpending').addClass('savepending').html($lang.get('yubiauth_ctl_status_enrolled_pending'));
-    $(status).next('span.yubikey_pubkey').text(ta.value.substr(0, 12));
-    field.value = ta.value;
-    miniPromptDestroy(ta);
-    return true;
-  }
-  else if ( ta.submit_func )
-  {
-    ta.submit_func(ta);
-  }
-  else
-  {
-    miniPromptDestroy(ta);
-  }
-}
-
-function yk_login_validate_reqs(ta)
-{
-  $(ta.parentNode).remove('p');
-  yubikey_otp_current = ta.value;
-  
-  miniPromptDestroy(ta, true);
-  
-  if ( logindata )
-  {
-    if ( logindata.mb_object )
-    {
-      // login window is open
-      if ( user_level == USER_LEVEL_GUEST )
-      {
-        // for guests, get the user's yubikey auth flags
-        // we're still ok to submit, so make sure twofactor isn't enabled
-        // as we are a guest, we have to get the flags for the user from the server
-        var ajax = ajaxMakeXHR();
-        var uri = makeUrlNS('Special', 'Yubikey', 'get_flags=' + ta.value.substr(0, 12));
-        var flags = 0;
-        try
-        {
-          ajax.open('GET', uri, false);
-          ajax.send(null);
-          
-          if ( ajax.readyState == 4 && ajax.status == 200 )
-          {
-            // we got it
-            var response = String(ajax.responseText + '');
-            if ( check_json_response(response) )
-            {
-              response = parseJSON(response);
-              flags = response.flags || 0;
-            }
-          }
-        }
-        catch ( e )
-        {
-          ajaxLoginSetStatus(AJAX_STATUS_ERROR);
-          return false;
-        }
-        var show_username = flags & YK_SEC_NORMAL_USERNAME;
-        var show_password = flags & YK_SEC_NORMAL_PASSWORD;
-      }
-      else
-      {
-        var show_username = window.yk_user_flags & YK_SEC_ELEV_USERNAME;
-        var show_password = window.yk_user_flags & YK_SEC_ELEV_PASSWORD;
-      }
-      if ( !show_username )
-        $('#ajax_login_field_username').parent('td').hide().prev().hide();
-      if ( !show_password )
-        $('#ajax_login_field_password').parent('td').hide().prev().hide();
-      
-      var can_submit = true;
-      if ( show_username && !$('#ajax_login_field_username').attr('value') )
-      {
-        $('#ajax_login_field_username').focus();
-        
-        if ( !show_password )
-          $('#ajax_login_field_username').keyup(function(e)
-            {
-              // assign press of Enter in username field to submit
-              if ( e.keyCode == 13 )
-              {
-                $('#messageBoxButtons input:button:first').click();
-              }
-            });
-        
-        can_submit = false;
-      }
-      if ( show_password && !$('#ajax_login_field_password').attr('value') )
-      {
-        if ( can_submit )
-        {
-          // can_submit only true if show_username false
-          $('#ajax_login_field_password').focus();
-        }
-        can_submit = false;
-      }
-      
-      if ( can_submit )
-      {
-        $('#messageBoxButtons input:button:first').click();
-      }
-    }
-  }
-}
-
-function yk_clear(field_id, status_id)
-{
-  var field = document.getElementById(field_id);
-  var status = document.getElementById(status_id);
-  
-  var was_pending = $(field).hasClass('wasempty');
-  
-  $(field).attr('value', '');
-  $(status)
-    .removeClass('savepending')
-    .removeClass('enrolled')
-    .addClass( was_pending ? 'empty' : 'rmpending' )
-    .text( was_pending ? $lang.get('yubiauth_ctl_status_empty') : $lang.get('yubiauth_ctl_status_remove_pending') )
-    .next('span.yubikey_pubkey')
-      .text('')
-    .next('a')
-      .text($lang.get('yubiauth_ctl_btn_enroll'))
-      .removeClass('abutton_green')
-    .next('a')
-      .remove();
-}
-
-addOnloadHook(function()
-  {
-    if ( is_iPhone )
-      // kinda can't plug a yubikey into an iPhone
-      // ... yet?
-      return;
-    
-    attachHook('login_build_form', 'yk_login_dlg_hook(table, data);');
-    attachHook('login_build_userinfo', 'if ( window.yubikey_otp_current ) userinfo.yubikey_otp = window.yubikey_otp_current;');
-    if ( title == namespace_list.Special + 'Preferences/Yubikey' )
-    {
-      load_component(['jquery', 'jquery-ui', 'expander']);
-    }
-  });
-
-function yk_login_dlg_hook(table, data)
-{
-  window.yubikey_otp_current = false;
-  var tr = document.createElement('tr');
-  var td = document.createElement('td');
-  $(td)
-    .attr('colspan', '2')
-    .css('text-align', 'center')
-    .css('font-size', 'smaller')
-    .css('font-weight', 'bold')
-    .html('<a href="#" onclick="yk_mb_init(yk_login_validate_reqs); return false;" style="color: #6fa202">' + $lang.get('yubiauth_btn_enter_otp') + '</a>');
-  $('a', td).blur(function(e)
-    {
-      $('#messageBoxButtons input:button:first').focus();
-      $('#ajax_login_field_captcha').focus();
-    });
-  if ( ( window.yk_reg_require_otp || window.yk_user_enabled ) && !data.locked_out.locked_out )
-  {
-    setTimeout(function()
-      {
-        yk_mb_init(yk_login_validate_reqs);
-      }, 750);
-  }
-  tr.appendChild(td);
-  table.appendChild(tr);
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/yubikey/admincp.php	Fri Jun 30 17:49:12 2017 -0400
@@ -0,0 +1,107 @@
+<?php
+
+$plugins->attachHook('acp_general_users', 'yubikey_admin_cp_ui();');
+$plugins->attachHook('acp_general_save', 'yubikey_admin_cp_save();');
+
+function yubikey_admin_cp_ui()
+{
+	global $lang;
+	?>
+		<tr>
+			<th colspan="2" class="subhead">
+				<?php echo $lang->get('yubiacp_th'); ?>
+			</th>
+		</tr>
+		
+		<tr>
+			<td class="row1">
+				<?php echo $lang->get('yubiacp_field_enable_title'); ?>
+			</td>
+			<td class="row1">
+				<label>
+					<input type="checkbox" name="yubikey_enable" <?php if ( getConfig('yubikey_enable', '1') == '1' ) echo 'checked="checked" '; ?>/>
+					<?php echo $lang->get('yubiacp_field_enable'); ?>
+				</label>
+			</td>
+		</tr>
+		
+		<tr>
+			<td class="row2">
+				<?php echo $lang->get('yubiacp_field_api_key'); ?>
+			</td>
+			<td class="row2">
+				<input type="text" name="yubikey_api_key" value="<?php echo htmlspecialchars(getConfig('yubikey_api_key', '')); ?>" size="30" />
+			</td>
+		</tr>
+		
+		<tr>
+			<td class="row1">
+				<?php echo $lang->get('yubiacp_field_api_key_id'); ?>
+			</td>
+			<td class="row1">
+				<input type="text" name="yubikey_api_key_id" value="<?php echo strval(intval(getConfig('yubikey_api_key_id', ''))); ?>" size="5" />
+			</td>
+		</tr>
+		
+		<tr>
+			<td class="row2">
+				<?php echo $lang->get('yubiacp_field_auth_server'); ?>
+			</td>
+			<td class="row2">
+				<input type="text" name="yubikey_auth_server" value="<?php echo htmlspecialchars(getConfig('yubikey_auth_server', YK_DEFAULT_VERIFY_URL)); ?>" size="30" />
+				<?php
+				if ( defined('YMS_INSTALLED') )
+				{
+					echo '<br />';
+					echo $lang->get('yubiacp_field_use_local_pre');
+					?> <label><input type="checkbox" name="yubikey_use_local_yms" <?php echo getConfig('yubikey_use_local_yms', 0) ? 'checked="checked" ' : ''; ?>/><?php
+					echo $lang->get('yubiacp_field_use_local');
+					echo '</label>';
+				}
+				?>
+			</td>
+		</tr>
+		
+		<tr>
+			<td class="row1">
+				<?php echo $lang->get('yubiacp_field_enroll_limit'); ?>
+			</td>
+			<td class="row1">
+				<input type="text" name="yubikey_enroll_limit" value="<?php echo strval(intval(getConfig('yubikey_enroll_limit', '3'))); ?>" size="5" />
+			</td>
+		</tr>
+		
+		<tr>
+			<td class="row2">
+				<?php echo $lang->get('yubiacp_field_reg_require_otp_title'); ?><br />
+				<small><?php echo $lang->get('yubiacp_field_reg_require_otp_hint'); ?></small>
+			</td>
+			<td class="row2">
+				<label>
+					<input type="checkbox" name="yubikey_reg_require_otp" <?php if ( getConfig('yubikey_reg_require_otp', '0') == '1' ) echo 'checked="checked" '; ?>/>
+					<?php echo $lang->get('yubiacp_field_reg_require_otp'); ?>
+				</label>
+			</td>
+		</tr>
+		
+	<?php
+}
+
+function yubikey_admin_cp_save()
+{
+	global $lang;
+	
+	// yubikey_enable, yubikey_api_key, yubikey_api_key_id, yubikey_auth_server, yubikey_enroll_limit
+	setConfig('yubikey_enable', isset($_POST['yubikey_enable']) ? '1' : '0');
+	setConfig('yubikey_api_key', $_POST['yubikey_api_key']);
+	setConfig('yubikey_api_key_id', intval($_POST['yubikey_api_key_id']));
+	setConfig('yubikey_enroll_limit', intval($_POST['yubikey_enroll_limit']));
+	setConfig('yubikey_reg_require_otp', isset($_POST['yubikey_reg_require_otp']) ? '1' : '0');
+	setConfig('yubikey_use_local_yms', isset($_POST['yubikey_use_local_yms']) && defined('YMS_INSTALLED') ? '1' : '0');
+	
+	if ( preg_match('#^(?:https?://)?(\[?[a-z0-9-:]+(?:\.[a-z0-9-:]+\]?)*)(/.*)$#', $_POST['yubikey_auth_server']) )
+		setConfig('yubikey_auth_server', $_POST['yubikey_auth_server']);
+	else
+		echo '<div class="error-box">' . $lang->get('yubiacp_err_invalid_auth_server') . '</div>';
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/yubikey/auth.php	Fri Jun 30 17:49:12 2017 -0400
@@ -0,0 +1,325 @@
+<?php
+
+if ( getConfig('yubikey_enable', '1') != '1' )
+		return true;
+
+// hook into auth
+$plugins->attachHook('login_process_userdata_json', 'return yubikey_auth_hook_json($userinfo, $req["level"], @$req["remember"]);');
+// hook into special page init
+$plugins->attachHook('session_started', 'yubikey_add_special_pages();');
+// session key security
+$plugins->attachHook('session_key_calc', 'yubikey_sk_calc($user_id, $key_pieces, $sk_mode);');
+
+function yubikey_auth_hook_json(&$userdata, $level, $remember)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	global $lang;
+	
+	$do_validate_otp = false;
+	$do_validate_user = false;
+	$do_validate_pass = false;
+	
+	$user_flag = ( $level >= USER_LEVEL_CHPREF ) ? YK_SEC_ELEV_USERNAME : YK_SEC_NORMAL_USERNAME;
+	$pass_flag = ( $level >= USER_LEVEL_CHPREF ) ? YK_SEC_ELEV_PASSWORD : YK_SEC_NORMAL_PASSWORD;
+	
+	$auth_log_prefix = ( $level >= USER_LEVEL_CHPREF ) ? 'admin_' : '';
+	
+	// Sort of a hack: if the password looks like an OTP and the OTP field is empty, use the password as the OTP
+	if ( empty($userdata['yubikey_otp']) && preg_match('/^[cbdefghijklnrtuv]{44}$/', $userdata['password'] ) )
+	{
+		$userdata['yubikey_otp'] = $userdata['password'];
+	}
+	
+	// Lockouts removed from here - they're done during preprocessing now.
+	
+	if ( !empty($userdata['username']) )
+	{
+		// get flags
+		$q = $db->sql_query('SELECT user_id, user_yubikey_flags FROM ' . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '" . $db->escape(strtolower($userdata['username'])) . "';");
+		if ( !$q )
+			$db->die_json();
+		
+		if ( $db->numrows() < 1 )
+		{
+			// Username not found - let the main login function handle it
+			$db->free_result();
+			return null;
+		}
+		list($user_id, $flags) = $db->fetchrow_num();
+		$flags = intval($flags);
+		// At the point the username is validated.
+		$do_validate_user = false;
+		$do_validate_pass = $flags & $pass_flag;
+		if ( empty($userdata['yubikey_otp']) )
+		{
+			// no OTP was provided
+			// make sure the user has allowed logging in with no OTP
+			if ( !($flags & YK_SEC_ALLOW_NO_OTP) )
+			{
+				// We also might have no Yubikeys enrolled.
+				$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yubikey WHERE user_id = $user_id;");
+				if ( !$q )
+					$db->die_json();
+				
+				if ( $db->numrows() > 0 )
+				{
+					// Yep at least one key is enrolled.
+					// I don't think these should be logged because they'll usually just be innocent mistakes.
+					$db->free_result();
+					return array(
+							'mode' => 'error',
+							'error' => 'yubiauth_err_must_have_otp'
+						);
+				}
+				// Nope, no keys enrolled, user hasn't enabled Yubikey support
+				$db->free_result();
+			}
+			// we're ok, use normal password auth
+			return null;
+		}
+		else
+		{
+			// user did enter an OTP; make sure it's associated with the username
+			$yubi_uid = $db->escape(substr($userdata['yubikey_otp'], 0, 12));
+			$q = $db->sql_query('SELECT user_id FROM ' . table_prefix . 'yubikey WHERE yubi_uid = \'' . $yubi_uid . '\';');
+			if ( !$q )
+				$db->die_json();
+			if ( $db->numrows() < 1 )
+			{
+				$db->free_result();
+				return array(
+						'mode' => 'error',
+						'error' => 'yubiauth_err_key_not_authorized'
+					);
+			}
+			list($yubi_pair_uid) = $db->fetchrow_num();
+			if ( $yubi_pair_uid !== $user_id )
+			{
+				return array(
+						'mode' => 'error',
+						'error' => 'yubiauth_err_uid_mismatch'
+					);
+			}
+			$db->free_result();
+			$do_validate_otp = true;
+		}
+	}
+	else if ( !empty($userdata['yubikey_otp']) )
+	{
+		// we have an OTP, but no username to work with
+		$yubi_uid = substr($userdata['yubikey_otp'], 0, 12);
+		if ( !preg_match('/^[cbdefghijklnrtuv]{12}$/', $yubi_uid ) )
+		{
+			return array(
+					'mode' => 'error',
+					'error' => 'yubiauth_err_invalid_otp'
+				);
+		}
+		$q = $db->sql_query('SELECT u.user_id, u.username, u.user_yubikey_flags FROM ' . table_prefix . "users AS u\n"
+											. "  LEFT JOIN " . table_prefix . "yubikey AS y\n"
+											. "    ON ( y.user_id = u.user_id )\n"
+											. "  WHERE y.yubi_uid = '$yubi_uid'\n"
+											. "  GROUP BY u.user_yubikey_flags;");
+		if ( !$q )
+			$db->_die();
+		
+		if ( $db->numrows() < 1 )
+		{
+			if ( !$do_validate_pass )
+				$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
+ 									. '  (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \'DEPRECATED\', \'(Yubikey)\', '
+											. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
+			
+			return array(
+					'mode' => 'error',
+					'error' => 'yubiauth_err_key_not_authorized'
+				);
+		}
+		
+		list($user_id, $username, $flags) = $db->fetchrow_num();
+		
+		if ( $level > USER_LEVEL_MEMBER )
+		{
+			$session->start();
+			if ( $session->user_logged_in && ($session->user_id !== $user_id) )
+			{
+				return array(
+						'mode' => 'error',
+						'error' => 'yubiauth_err_uid_mismatch'
+					);
+			}
+		}
+		
+		$do_validate_otp = true;
+		$do_validate_user = $flags & $user_flag;
+		$do_validate_pass = $flags & $pass_flag;
+		// to complete security logs later
+		$userdata['username'] = $username;
+	}
+	else
+	{
+		// Nothing - no username or OTP. This request can't be used; throw it out.
+		return array(
+				'mode' => 'error',
+				'error' => 'yubiauth_err_nothing_provided'
+			);
+	}
+	
+	if ( $do_validate_otp )
+	{
+		// We need to validate the OTP.
+		$otp_check = yubikey_validate_otp($userdata['yubikey_otp']);
+		if ( !$otp_check['success'] )
+		{
+			if ( !$do_validate_pass )
+				$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
+ 									. '  (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \'DEPRECATED\', \'(Yubikey)\', '
+											. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
+			
+			if ( $otp_check['error'] === 'http_failed' )
+			{
+				return array(
+						'mode' => 'error',
+						'error' => 'yubiauth_err_' . $otp_check['error'],
+						'http_error' => $otp_check['http_error']
+					);
+			}
+			return array(
+					'mode' => 'error',
+					'error' => 'yubiauth_err_' . $otp_check['error']
+				);
+		}
+	}
+	if ( $do_validate_user )
+	{
+		if ( empty($username) )
+		{
+			return array(
+					'mode' => 'error',
+					'error' => 'yubiauth_err_must_have_username'
+				);
+		}
+		if ( strtolower($username) !== strtolower($userdata['username']) )
+		{
+			// Username incorrect
+			if ( !$do_validate_pass )
+				$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
+ 									. '  (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \'DEPRECATED\', \'(Yubikey)\', '
+											. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
+			return array(
+					'mode' => 'error',
+					'error' => 'invalid_credentials'
+				);
+		}
+	}
+	// Do we need to have the password validated?
+	if ( $do_validate_pass )
+	{
+		if ( empty($userdata['password']) )
+		{
+			return array(
+					'mode' => 'error',
+					'error' => 'yubiauth_err_must_have_password'
+				);
+		}
+		// Yes; return and let the login API continue
+		return null;
+	}
+	else
+	{
+		// No password required; validated, issue session key
+		$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n"
+ 									. '  (\'security\', \'' . $auth_log_prefix . 'auth_good\', '.time().', \'DEPRECATED\', \'' . $db->escape($userdata['username']) . '\', '
+											. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')');
+				
+		$q = $db->sql_query('SELECT password FROM ' . table_prefix . "users WHERE user_id = $user_id;");
+		if ( !$q )
+			$db->_die();
+		
+		list($password) = $db->fetchrow_num();
+		$db->free_result();
+		
+		$session->register_session($user_id, $userdata['username'], $password, intval($level), $remember);
+		return true;
+	}
+}
+
+function yubikey_add_special_pages()
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	global $lang;
+	
+	if ( getConfig('yubikey_enable', '1') != '1' )
+		return true;
+	
+	$paths->add_page(array(
+			'name' => $lang->get('yubiauth_specialpage_yubikey'),
+			'urlname' => 'Yubikey',
+			'namespace' => 'Special',
+			'visible' => 0, 'protected' => 0, 'comments_on' => 0, 'special' => 0
+		));
+}
+
+function yubikey_sk_calc($user_id, &$key_pieces, &$sk_mode)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	// hash the user's yubikeys
+	$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE user_id = $user_id;");
+	if ( !$q )
+		$db->_die();
+	
+	while ( $row = $db->fetchrow() )
+	{
+		$key_pieces[] = $row['yubi_uid'];
+	}
+}
+
+function page_Special_Yubikey()
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	header('Content-type: text/javascript');
+	/*
+	if ( isset($_GET['validate_otp']) )
+	{
+		echo enano_json_encode(yubikey_validate_otp($_GET['validate_otp']));
+		return true;
+	}
+	*/
+	if ( isset($_GET['get_flags']) || isset($_POST['get_flags']) )
+	{
+		$yubi_uid = substr($_REQUEST['get_flags'], 0, 12);
+		if ( !preg_match('/^[cbdefghijklnrtuv]{12}$/', $yubi_uid) )
+		{
+			return print enano_json_encode(array(
+					'mode' => 'error',
+					'error' => 'invalid_otp'
+				));
+		}
+		$q = $db->sql_query('SELECT u.user_yubikey_flags FROM ' . table_prefix . "users AS u\n"
+											. "  LEFT JOIN " . table_prefix . "yubikey AS y\n"
+											. "    ON ( y.user_id = u.user_id )\n"
+											. "  WHERE y.yubi_uid = '$yubi_uid'\n"
+											. "  GROUP BY u.user_yubikey_flags;");
+		if ( !$q )
+			$db->_die();
+		
+		if ( $db->numrows() < 1 )
+		{
+			return print enano_json_encode(array(
+					'mode' => 'error',
+					'error' => 'key_not_authorized'
+				));
+		}
+		
+		list($flags) = $db->fetchrow_num();
+		
+		echo enano_json_encode(array(
+				// We strip YK_SEC_ALLOW_NO_OTP here for security reasons.
+				'flags' => intval($flags & ~YK_SEC_ALLOW_NO_OTP)
+			));
+		
+		return true;
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/yubikey/corelib.php	Fri Jun 30 17:49:12 2017 -0400
@@ -0,0 +1,300 @@
+<?php
+
+define('YK_SEC_NORMAL_USERNAME', 1);
+define('YK_SEC_NORMAL_PASSWORD', 2);
+define('YK_SEC_ELEV_USERNAME', 4);
+define('YK_SEC_ELEV_PASSWORD', 8);
+define('YK_SEC_ALLOW_NO_OTP', 16);
+
+define('YK_DEFAULT_VERIFY_URL', 'http://api.yubico.com/wsapi/verify');
+
+function generate_yubikey_field($name = 'yubikey_otp', $value = false)
+{
+	global $lang;
+	
+	$fid = substr(sha1(microtime() . mt_rand()), 0, 12);
+	$class = $value ? 'wasfull' : 'wasempty';
+	$html = '<input id="yubifield' . $fid . '" class="' . $class . '" type="hidden" name="' . $name . '" value="' . ( is_string($value) ? $value : '' ) . '" />';
+	$html .= '<noscript><input type="text" name="' . $name . '" class="yubikey_noscript" value="' . ( is_string($value) ? $value : '' ) . '" /> </noscript>';
+	if ( $value )
+	{
+		$html .= '<span id="yubistat' . $fid . '" class="yubikey_status enrolled">' . $lang->get('yubiauth_ctl_status_enrolled') . '</span>';
+		$atext = $lang->get('yubiauth_ctl_btn_change_key');
+		$classadd = ' abutton_green';
+	}
+	else
+	{
+		$html .= '<span id="yubistat' . $fid . '" class="yubikey_status empty">' . $lang->get('yubiauth_ctl_status_empty') . '</span>';
+		$atext = $lang->get('yubiauth_ctl_btn_enroll');
+		$classadd = '';
+	}
+	
+	$html .= ' <span class="yubikey_pubkey">';
+	if ( !empty($value) )
+		$html .= htmlspecialchars(substr($value, 0, 12));
+	$html .= '</span> ';
+	
+	$html .= ' <a class="abutton' . $classadd . ' yubikey_enroll" onclick="yk_mb_init(\'yubifield' . $fid . '\', \'yubistat' . $fid . '\'); return false;" href="#enroll">' . $atext . '</a>';
+	if ( $value )
+	{
+		$html .= ' <a class="abutton abutton_red yubikey_enroll" onclick="yk_clear(\'yubifield' . $fid . '\', \'yubistat' . $fid . '\'); return false;" href="#enroll">'
+ 						. $lang->get('yubiauth_ctl_btn_clear') .
+ 						'</a>';
+	}
+	
+	return $html;
+}
+
+function yubikey_validate_otp($otp)
+{
+	$api_key = getConfig('yubikey_api_key');
+	$api_id  = getConfig('yubikey_api_key_id');
+	// Don't require an API key or user ID to be installed if we're using local YMS
+	if ( !(getConfig('yubikey_use_local_yms', 0) && defined('YMS_INSTALLED')) && (!$api_key || !$api_id) )
+	{
+		return array(
+				'success' => false,
+				'error' => 'missing_api_key'
+			);
+	}
+	if ( !preg_match('/^[cbdefghijklnrtuv]{44}$/', $otp) )
+	{
+		return array(
+				'success' => false,
+				'error' => 'otp_invalid_chars'
+			);
+	}
+	// are we using local YMS?
+	if ( getConfig('yubikey_use_local_yms', 0) && defined('YMS_INSTALLED') )
+	{
+		$result = yms_validate_otp($otp, $api_id);
+		if ( $result == 'OK' )
+		{
+			return array(
+					'success' => true
+				);
+		}
+		else
+		{
+			return array(
+				'success' => false,
+				'error' => strtolower("response_{$result}")
+			);
+		}
+	}
+	// make HTTP request
+	require_once( ENANO_ROOT . '/includes/http.php' );
+	$auth_url = getConfig('yubikey_auth_server', YK_DEFAULT_VERIFY_URL);
+	$auth_url = preg_replace('#^https?://#i', '', $auth_url);
+	if ( !preg_match('#^(\[?[a-z0-9-:]+(?:\.[a-z0-9-:]+\]?)*)(?::([0-9]+))?(/.*)$#U', $auth_url, $match) )
+	{
+		return array(
+				'success' => false,
+				'error' => 'invalid_auth_url'
+			);
+	}
+	$auth_server =& $match[1];
+	$auth_port = ( !empty($match[2]) ) ? intval($match[2]) : 80;
+	$auth_uri =& $match[3];
+	try
+	{
+		$req = new Request_HTTP($auth_server, $auth_uri, 'GET', $auth_port);
+		$req->add_get('id', strval($api_id));
+		$req->add_get('otp', $otp);
+		$req->add_get('h', yubikey_sign($req->parms_get));
+	
+		$response = $req->get_response_body();
+	}
+	catch ( Exception $e )
+	{
+		return array(
+				'success' => false,
+				'error' => 'http_failed',
+				'http_error' => $e->getMessage()
+			);
+	}
+	
+	if ( $req->response_code != HTTP_OK )
+	{
+		return array(
+				'success' => false,
+				'error' => 'http_response_error'
+			);
+	}
+	$response = trim($response);
+	if ( !preg_match_all('/^([a-z0-9_]+)=(.*?)\r?$/m', $response, $matches) )
+	{
+		return array(
+				'success' => false,
+				'error' => 'malformed_response'
+			);
+	}
+	$response = array();
+	foreach ( $matches[0] as $i => $_ )
+	{
+		$response[$matches[1][$i]] = $matches[2][$i];
+	}
+	// make sure we have a status
+	if ( !isset($response['status']) )
+	{
+		return array(
+				'success' => false,
+				'error' => 'response_missing_status'
+			);
+	}
+	// verify response signature
+	// MISSING_PARAMETER is the ONLY situation under which an unsigned response is acceptable
+	if ( $response['status'] !== 'MISSING_PARAMETER' )
+	{
+		if ( !isset($response['h']) )
+		{
+			return array(
+					'success' => false,
+					'error' => 'response_missing_sig'
+				);
+		}
+		if ( yubikey_sign($response) !== $response['h'] )
+		{
+			return array(
+					'success' => false,
+					'error' => 'response_invalid_sig'
+				);
+		}
+	}
+	if ( $response['status'] === 'OK' )
+	{
+		if ( yubikey_verify_timestamp($response['t']) )
+		{
+			return array(
+					'success' => true
+				);
+		}
+		else
+		{
+			return array(
+					'success' => false,
+					'error' => 'timestamp_check_failed'
+				);
+		}
+	}
+	else
+	{
+		return array(
+				'success' => false,
+				'error' => strtolower("response_{$response['status']}")
+			);
+	}
+}
+
+function yubikey_sign($arr, $use_api_key = false)
+{
+	static $api_key = false;
+	
+	ksort($arr);
+	
+	if ( !$use_api_key )
+	{
+		if ( !$api_key )
+		{
+			$api_key = getConfig('yubikey_api_key');
+			$api_key = hexencode(base64_decode($api_key), '', '');
+		}
+		$use_api_key = $api_key;
+	}
+	/*
+	else
+	{
+		$use_api_key = hexencode(base64_decode($use_api_key), '', '');
+	}
+	*/
+	
+	foreach ( array('h', 'title', 'auth', 'do') as $key )
+	{
+		if ( isset($arr[$key]) )
+			unset($arr[$key]);
+	}
+	
+	$req = array();
+	foreach ( $arr as $key => $val )
+	{
+		$req[] = "$key=$val";
+	}
+	$req = implode('&', $req);
+	
+	$sig = hmac_sha1($req, $use_api_key);
+	$sig = hexdecode($sig);
+	$sig = base64_encode($sig);
+	
+	return $sig;
+}
+
+/**
+ * Validate the timestamp returned in a Yubico API response. Borrowed from Drupal and backported for friendliness with earlier versions of PHP.
+ * @param string Yubico timestamp
+ * @return bool True if valid, false otherwise
+ */
+
+function yubikey_verify_timestamp($timestamp)
+{
+	$tolerance = intval(getConfig('yubikey_api_ts_tolerance', 150));
+	
+	$now = time();
+	$timestamp_seconds = yk_strtotime($timestamp);
+
+	if ( !$timestamp || !$now || !$timestamp_seconds )
+	{
+		return false;
+	}
+
+	if ( ( $timestamp_seconds + $tolerance ) > $now && ( $timestamp_seconds - $tolerance ) < $now )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+function yk_strtotime($timestamp)
+{
+	if ( !preg_match('/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:Z[0-9]+)?$/', $timestamp, $match) )
+		return 0;
+	
+	$hour = intval($match[4]);
+	$minute = intval($match[5]);
+	$second = intval($match[6]);
+	$month = intval($match[2]);
+	$day = intval($match[3]);
+	$year = intval($match[1]);
+	
+	return gmmktime($hour, $minute, $second, $month, $day, $year);
+}
+
+$plugins->attachHook('compile_template', 'yubikey_attach_headers($this);');
+
+function yubikey_attach_headers(&$template)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	
+	if ( getConfig('yubikey_enable', '1') != '1' )
+		return true;
+	
+	$template->add_header('<script type="text/javascript" src="' . scriptPath . '/plugins/yubikey/yubikey.js"></script>');
+	$template->add_header('<link rel="stylesheet" type="text/css" href="' . scriptPath . '/plugins/yubikey/yubikey.css" />');
+	// config option for all users have yubikey
+	$user_flags = 0;
+	$yk_enabled = 0;
+	if ( $session->user_logged_in )
+	{
+		$q = $db->sql_query('SELECT COUNT(y.yubi_uid) > 0, u.user_yubikey_flags FROM ' . table_prefix . "yubikey AS y LEFT JOIN " . table_prefix . "users AS u ON ( u.user_id = y.user_id ) WHERE y.user_id = {$session->user_id} GROUP BY u.user_id, u.user_yubikey_flags;");
+		if ( !$q )
+			$db->_die();
+		
+		list($yk_enabled, $user_flags) = $db->fetchrow_num();
+		$db->free_result();
+	}
+	$yk_enabled = intval($yk_enabled);
+	$user_flags = intval($user_flags);
+	
+	$template->add_header('<script type="text/javascript">var yk_reg_require_otp = ' . getConfig('yubikey_reg_require_otp', '0') . '; var yk_user_enabled = ' . $yk_enabled . '; var yk_user_flags = ' . $user_flags . ';</script>');
+}
+
Binary file yubikey/field.png has changed
Binary file yubikey/formicon.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/yubikey/usercp.php	Fri Jun 30 17:49:12 2017 -0400
@@ -0,0 +1,385 @@
+<?php
+
+if ( getConfig('yubikey_enable', '1') != '1' )
+	return true;
+
+$plugins->attachHook("userprefs_jbox", "yubikey_ucp_setup();");
+$plugins->attachHook("userprefs_body", "return yubikey_user_cp(\$section);");
+$plugins->attachHook("login_form_html", "yubikey_inject_html_login();");
+$plugins->attachHook("ucp_register_form", "yubikey_inject_registration_form();");
+$plugins->attachHook("ucp_register_validate", "yubikey_register_validate(\$error);");
+$plugins->attachHook("user_registered", "yubikey_register_insert_key(\$user_id);");
+
+function yubikey_ucp_setup()
+{
+	userprefs_menu_add('usercp_sec_profile', 'yubiucp_panel_title', makeUrlNS('Special', 'Preferences/Yubikey') . '" onclick="ajaxLoginNavTo(\'Special\', \'Preferences/Yubikey\', '.USER_LEVEL_CHPREF.'); return false;');
+}
+
+function yubikey_user_cp($section)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	global $lang;
+	
+	if ( $section !== 'Yubikey' )
+		return false;
+	
+	if ( $session->auth_level < USER_LEVEL_CHPREF )
+	{
+		redirect(makeUrlNS('Special', 'Login/' . $paths->fullpage, 'level=' . USER_LEVEL_CHPREF, true), 'Authentication required', 'You need to re-authenticate to access this page.', 0);
+	}
+	
+	$count_enabled = intval(getConfig('yubikey_enroll_limit', '3'));
+	
+	if ( isset($_POST['submit']) )
+	{
+		csrf_request_confirm();
+		
+		$keys = array();
+		if ( isset($_POST['yubikey_enable']) )
+		{
+			for ( $i = 0; $i < $count_enabled; $i++ )
+			{
+				if ( !empty($_POST["yubikey_otp_$i"]) )
+				{
+					$ckey =& $_POST["yubikey_otp_$i"];
+					if ( preg_match('/^[cbdefghijklnrtuv]{12,44}$/', $ckey) )
+					{
+						$ckey = substr($ckey, 0, 12);
+						$keys[] = $ckey;
+					}
+					unset($ckey);
+				}
+			}
+		}
+		// Check for double enrollment
+		$keys_check = "yubi_uid = '" . implode("' OR yubi_uid = '", $keys) . "'";
+		$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE ( $keys_check ) AND user_id != {$session->user_id};");
+		if ( !$q )
+			$db->_die();
+		
+		if ( $db->numrows() > 0 )
+		{
+			echo '<div class="error-box" style="margin: 0 0 10px 0;">' . $lang->get('yubiucp_err_double_enrollment') . '</div>';
+			while ( $row = $db->fetchrow() )
+			{
+				foreach ( $keys as $i => $key )
+				{
+					if ( $key == $row['yubi_uid'] )
+					{
+						unset($keys[$i]);
+					}
+				}
+			}
+			$keys = array_values($keys);
+		}
+		$db->free_result();
+		
+		// Remove all currently registered keys
+		$q = $db->sql_query('DELETE FROM ' . table_prefix . "yubikey WHERE user_id = {$session->user_id};");
+		if ( !$q )
+			$db->_die();
+		
+		// Enroll any new keys
+		if ( !empty($keys) )
+		{
+			$query = 'INSERT INTO ' . table_prefix . "yubikey(user_id, yubi_uid) VALUES\n  " .
+ 								"( $session->user_id, '" . implode("' ),\n  ( $session->user_id, '", $keys) . "' );";
+			if ( !$db->sql_query($query) )
+				$db->_die();
+		}
+		
+		// Calculate flags
+		$yubi_flags = 0;
+		$yubi_flags |= intval($_POST['login_normal_flags']);
+		$yubi_flags |= intval($_POST['login_elev_flags']);
+		$yubi_flags |= ( isset($_POST['allow_no_yubikey']) ) ? YK_SEC_ALLOW_NO_OTP : 0;
+		
+		// update flags
+		$q = $db->sql_query('UPDATE ' . table_prefix . "users SET user_yubikey_flags = $yubi_flags WHERE user_id = {$session->user_id};");
+		if ( !$q )
+			$db->_die();
+		
+		// regenerate session
+		$q = $db->sql_query('SELECT password FROM ' . table_prefix . "users WHERE user_id = {$session->user_id};");
+		if ( !$q )
+			$db->_die();
+		list($password_hmac) = $db->fetchrow_num();
+		
+		@$session->register_session($session->user_id, $session->username, $password_hmac, USER_LEVEL_MEMBER, false);
+		$session->logout(USER_LEVEL_CHPREF);
+		
+		// redirect back to normal CP
+		// if OB-ing isn't enabled, require a JS redirect (hey, not many other options...)
+		if ( @ob_get_contents() )
+		{
+			@ob_end_clean();
+			redirect(makeUrlNS('Special', 'Preferences'), $lang->get('yubiucp_msg_save_title'), $lang->get('yubiucp_msg_save_body'), 3);
+		}
+		else
+		{
+			echo '<h3>' . $lang->get('yubiucp_msg_save_title') . '</h3>';
+			echo '<p>' . $lang->get('yubiucp_msg_save_body') . '</p>';
+			// not much choice here, i'm resorting to javascript because the user CP always
+			// sends headers :-/
+			echo '<script type="text/javascript">
+				addOnloadHook(function()
+				{' .
+				// note: $_COOKIE['sid'] has just been assigned by $session->register_session() - so it's safe to use here.
+				'
+					createCookie(\'sid\', \'' . $_COOKIE['sid'] . '\');
+					window.location = makeUrlNS(\'Special\', \'Preferences\');
+				});
+			</script>';
+			return true;
+		}
+	}
+	else
+	{
+		// Fetch flags
+		$q = $db->sql_query('SELECT user_yubikey_flags FROM ' . table_prefix . "users WHERE user_id = {$session->user_id};");
+		if ( !$q )
+			$db->_die();
+		
+		list($yubi_flags) = $db->fetchrow_num();
+		$yubi_flags = intval($yubi_flags);
+		// Fetch user's authorized keys from the DB
+		$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE user_id = {$session->user_id};");
+		if ( !$q )
+			$db->_die();
+		
+		$keys = array();
+		while ( $row = $db->fetchrow() )
+		{
+			$keys[] = $row['yubi_uid'];
+		}
+		$db->free_result();
+	}
+	
+	while ( count($keys) < $count_enabled )
+	{
+		$keys[] = false;
+	}
+	
+	$enable_checked = ( $keys[0] === false && !isset($_POST['yubikey_enable']) ) ? '' : 'checked="checked"';
+	$displaytable = ( $keys[0] === false && !isset($_POST['yubikey_enable']) ) ? 'none' : 'block';
+	
+	$check_normal_keyonly = ( !($yubi_flags & YK_SEC_NORMAL_USERNAME) && !($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
+	$check_normal_username = ( ($yubi_flags & YK_SEC_NORMAL_USERNAME) && !($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
+	$check_normal_userandpw = ( ($yubi_flags & YK_SEC_NORMAL_USERNAME) && ($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
+
+	$check_elev_keyonly = ( !($yubi_flags & YK_SEC_ELEV_USERNAME) && !($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';
+	$check_elev_username = ( ($yubi_flags & YK_SEC_ELEV_USERNAME) && !($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';
+	$check_elev_userandpw = ( ($yubi_flags & YK_SEC_ELEV_USERNAME) && ($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';  
+	
+	?>
+	<h3 style="margin-top: 0;"><?php echo $lang->get('yubiucp_panel_title'); ?></h3>
+	
+	<form action="<?php echo makeUrlNS('Special', 'Preferences/Yubikey'); ?>" method="post">
+	
+	<div>
+		<table border="0" cellpadding="4" width="100%">
+			<tr>
+				<td style="width: 50%; text-align: right;">
+					<?php echo $lang->get('yubiucp_field_enable_title'); ?><br />
+					<small><?php echo $lang->get('yubiucp_field_enable_hint'); ?></small>
+				</td>
+				<td style="width: 50%;">
+					<label>
+						<input type="checkbox" name="yubikey_enable" onclick="if ( $(this).attr('checked') ) $('#yk_useroptions').show('blind'); else $('#yk_useroptions').hide('blind');" <?php echo $enable_checked; ?> />
+						<?php echo $lang->get('yubiucp_field_enable'); ?>
+					</label>
+				</td>
+			</tr>
+		</table>
+		<table border="0" cellpadding="4" width="100%" id="yk_useroptions" style="display: <?php echo $displaytable ?>;">
+			<tr class="yk_alt1">
+			<td style="width: 50%; text-align: right;">
+					<?php echo $lang->get('yubiucp_field_keys_title'); ?><br />
+					<small><?php
+					echo $lang->get('yubiucp_field_keys_hint');
+					if ( $count_enabled > 1 )
+					{
+						echo ' ';
+						echo $lang->get('yubiucp_field_keys_maximum', array('max' => $count_enabled));
+					}
+					?></small>
+				</td>
+				<td style="width: 50%;">
+					<?php
+					for ( $i = 0; $i < $count_enabled; $i++ )
+					{
+						echo '<p>' . generate_yubikey_field('yubikey_otp_' . $i, $keys[$i]) . '</p>';
+					}
+					?>
+				</td>
+			</tr>
+			<tr>
+				<td style="width: 50%; text-align: right;">
+					<?php echo $lang->get('yubiucp_field_normal_flags'); ?>
+				</td>
+				<td>
+					<label>
+						<input type="radio" name="login_normal_flags" value="0" <?php echo $check_normal_keyonly; ?>/>
+						<?php echo $lang->get('yubiucp_field_flags_keyonly'); ?>
+					</label>
+					
+					<br />
+					
+					<label>
+						<input type="radio" name="login_normal_flags" value="<?php echo strval(YK_SEC_NORMAL_USERNAME); ?>" <?php echo $check_normal_username; ?>/>
+						<?php echo $lang->get('yubiucp_field_flags_username'); ?>
+					</label>
+					
+					<br />
+					
+					<label>
+						<input type="radio" name="login_normal_flags" value="<?php echo strval(YK_SEC_NORMAL_USERNAME | YK_SEC_NORMAL_PASSWORD); ?>" <?php echo $check_normal_userandpw; ?>/>
+						<?php echo $lang->get('yubiucp_field_flags_userandpw'); ?>
+					</label>
+				</td>
+			</tr>
+			<tr class="yk_alt1">
+				<td style="width: 50%; text-align: right;">
+					<?php echo $lang->get('yubiucp_field_elev_flags'); ?>
+				</td>
+				<td>
+					<label>
+						<input type="radio" name="login_elev_flags" value="0" <?php echo $check_elev_keyonly; ?>/>
+						<?php echo $lang->get('yubiucp_field_flags_keyonly'); ?>
+					</label>
+					
+					<br />
+					
+					<label>
+						<input type="radio" name="login_elev_flags" value="<?php echo strval(YK_SEC_ELEV_USERNAME); ?>" <?php echo $check_elev_username; ?>/>
+						<?php echo $lang->get('yubiucp_field_flags_username'); ?>
+					</label>
+					
+					<br />
+					
+					<label>
+						<input type="radio" name="login_elev_flags" value="<?php echo strval(YK_SEC_ELEV_USERNAME | YK_SEC_ELEV_PASSWORD); ?>" <?php echo $check_elev_userandpw; ?>/>
+						<?php echo $lang->get('yubiucp_field_flags_userandpw'); ?>
+					</label>
+				</td>
+			</tr>
+			<tr>
+				<td>
+				</td>
+				<td>
+					<label>
+						<input type="checkbox" name="allow_no_yubikey" <?php if ( $yubi_flags & YK_SEC_ALLOW_NO_OTP ) echo 'checked="checked" '; ?>/>
+						<?php echo $lang->get('yubiucp_field_allow_plain_login'); ?>
+					</label>
+					<br />
+					<small>
+						<?php echo $lang->get('yubiucp_field_allow_plain_login_hint'); ?>
+					</small>
+				</td>
+			</tr>
+		</table>
+		<table border="0" cellpadding="4" width="100%">
+			<tr class="yk_alt1">
+				<td colspan="2" style="text-align: center;">
+					<input type="submit" name="submit" value="<?php echo $lang->get('etc_save_changes'); ?>" />
+				</td>
+			</tr>
+		</table>
+	</div>
+	
+	<input type="hidden" name="cstok" value="<?php echo $session->csrf_token; ?>" />
+	
+	</form>
+	<?php
+	
+	return true;
+}
+
+function yubikey_inject_html_login()
+{
+	global $lang;
+	?>
+	<tr>
+		<td class="row2">
+			<?php echo $lang->get('yubiauth_lbl_otp_field'); ?>
+		</td>
+		<td class="row1" colspan="2">
+			<input type="text" size="40" class="yubikey_noscript" name="yubikey_otp" />
+		</td>
+	</tr>
+	<?php
+}
+
+function yubikey_inject_registration_form()
+{
+	global $lang;
+	
+	$preset_otp = isset($_POST['yubikey_otp']) ? $_POST['yubikey_otp'] : false;
+	?>
+	<tr>
+		<td class="row1">
+			<?php echo $lang->get('yubiucp_reg_field_otp'); ?><br />
+			<small><?php
+				if ( getConfig('yubikey_reg_require_otp', '0') == '1' )
+					echo $lang->get('yubiucp_reg_field_otp_hint_required');
+				else
+					echo $lang->get('yubiucp_reg_field_otp_hint_optional');
+			?></small>
+		</td>
+		<td class="row1">
+			<?php
+			echo generate_yubikey_field('yubikey_otp', $preset_otp);
+			?>
+		</td>
+		<td class="row1">
+		</td>
+	</tr>
+	<?php
+}
+
+function yubikey_register_validate(&$error)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	global $lang;
+	
+	$otp_required = getConfig('yubikey_reg_require_otp', '0') == '1';
+	$have_otp = !empty($_POST['yubikey_otp']);
+	if ( $otp_required && !$have_otp )
+	{
+		$error = $lang->get('yubiucp_reg_err_otp_required');
+		return false;
+	}
+	if ( $have_otp )
+	{
+		$result = yubikey_validate_otp($_POST['yubikey_otp']);
+		if ( !$result['success'] )
+		{
+			$error = '<b>' . $lang->get('yubiucp_reg_err_otp_invalid') . '</b><br />' . $lang->get("yubiauth_err_{$result['error']}");
+			return false;
+		}
+		// check for double enrollment
+		$yubi_uid = substr($_POST['yubikey_otp'], 0, 12);
+		// Note on SQL injection: yubikey_validate_otp() has already ensured that this is safe
+		$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yubikey WHERE yubi_uid = '$yubi_uid';");
+		if ( !$q )
+			$db->_die();
+		if ( $db->numrows() > 0 )
+		{
+			$error = '<b>' . $lang->get('yubiucp_reg_err_otp_invalid') . '</b><br />' . $lang->get('yubiucp_err_double_enrollment_single');
+			return false;
+		}
+		$db->free_result();
+	}
+}
+
+function yubikey_register_insert_key($user_id)
+{
+	global $db, $session, $paths, $template, $plugins; // Common objects
+	if ( !empty($_POST['yubikey_otp']) )
+	{
+		$yubi_uid = $db->escape(substr($_POST['yubikey_otp'], 0, 12));
+		$q = $db->sql_query('INSERT INTO ' . table_prefix . "yubikey ( user_id, yubi_uid ) VALUES ( $user_id, '$yubi_uid' );");
+		if ( !$q )
+			$db->_die();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/yubikey/yubikey.css	Fri Jun 30 17:49:12 2017 -0400
@@ -0,0 +1,62 @@
+span.yubikey_status {
+  font-weight: bold;
+}
+
+span.yubikey_status.empty {
+  color: #6699CC;
+}
+
+span.yubikey_status.savepending {
+  color: #f5ac00;
+}
+
+span.yubikey_status.rmpending {
+  color: #aa0000;
+}
+
+span.yubikey_status.enrolled {
+  color: #008000;
+}
+
+a.yubikey_enroll {
+  font-size: smaller;
+}
+
+span.yubikey_pubkey {
+  font-size: smaller;
+  color: #666;
+}
+
+tr.yk_alt1 td {
+  background-color: #f8f8f8;
+}
+
+input.yubikey_noscript {
+  background-color: white;
+  background-image: url(./formicon.gif);
+  background-position: 1px 1px;
+  background-repeat: no-repeat;
+  padding-left: 18px;
+}
+
+div.yubikey_bar {
+  width: 88px;
+  height: 88px;
+  margin: 4px auto;
+  text-align: center;
+  background-image: url(./field.png);
+  background-repeat: no-repeat;
+}
+
+div.yubikey_bar > img {
+  width: 0px;
+  height: 88px;
+  background-image: url(./field.png);
+  background-repeat: no-repeat;
+  background-position: 0px -88px;
+}
+
+div.yubikey_bar > img.yubikey_bar_error {
+  width: 88px !important;
+  background-position: 0px -176px !important;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/yubikey/yubikey.js	Fri Jun 30 17:49:12 2017 -0400
@@ -0,0 +1,305 @@
+// sample OTP:
+// ttttvvvvvvcurikvhjcvnlnbecbkubjvuittbifhndhn
+// charset: cbdefghijklnrtuv
+
+var yk_interval = false;
+
+var YK_SEC_NORMAL_USERNAME = 1;
+var YK_SEC_NORMAL_PASSWORD = 2;
+var YK_SEC_ELEV_USERNAME = 4;
+var YK_SEC_ELEV_PASSWORD = 8;
+
+var yubikey_otp_current = false;
+
+function yk_mb_init(fieldid, statid)
+{
+  load_component(['messagebox', 'fadefilter', 'flyin', 'jquery', 'jquery-ui', 'l10n']);
+  var mp = miniPrompt(yk_mb_construct);
+  if ( typeof(fieldid) == 'function' )
+  {
+    var input = mp.getElementsByTagName('input')[0];
+    input.submit_func = fieldid;
+  }
+  else if ( fieldid && statid )
+  {
+    var input = mp.getElementsByTagName('input')[0];
+    input.yk_field_id = fieldid;
+    input.yk_status_id = statid;
+  }
+}
+
+function yk_mb_construct(mp)
+{
+  mp.innerHTML = '';
+  mp.style.textAlign = 'center';
+  mp.innerHTML = '<h3>' + $lang.get('yubiauth_msg_please_touch_key') + '</h3>';
+  var progress = document.createElement('div');
+  $(progress).addClass('yubikey_bar').css('text-align', 'left');
+  var progimg = document.createElement('img');
+  progimg.src = cdnPath + '/images/spacer.gif';
+  progress.appendChild(progimg);
+  mp.appendChild(progress);
+  var ta = document.createElement('input');
+  ta.submitted = false;
+  $(ta)
+    .css('background-color', 'transparent')
+    .css('border-width', '0px')
+    .css('color', '#fff')
+    .css('font-size', '1px')
+    .css('padding', '0')
+    .css('opacity', '0')
+    .attr('size', '1')
+    .keyup(function(e)
+      {
+        if ( e.keyCode == 27 )
+        {
+          window.clearInterval(yk_interval);
+          miniPromptDestroy(this);
+        }
+        // 0.3: submit only upon a keycode 13
+        else if ( e.keyCode == 13 )
+        {
+          this.submitted = true;
+          yk_handle_submit(this);
+        }
+        else
+        {
+          $('div.yubikey_bar > img', this.parentNode)
+            .css('width', String(this.value.length * 2) + 'px')
+            //.css('background-position', String((this.value.length > 44 ? 44 : this.value.length) - 44) + 'px -88px');
+        }
+        e.preventDefault();
+        e.stopPropagation();
+      });
+  mp.appendChild(ta);
+  setTimeout(function()
+    {
+      window.yk_interval = setInterval(function()
+        {
+          ta.focus();
+        }, 50);
+    }, 750);
+  var info = document.createElement('p');
+  $(info)
+    .append('<span style="color: #ffffff; font-size: smaller;">' + $lang.get('yubiauth_msg_close_instructions') + '</span>&nbsp;&nbsp;')
+    .append('<a class="abutton abutton_green" href="#" onclick="miniPromptDestroy(this); return false;">' + $lang.get('etc_cancel') + '</a>&nbsp;&nbsp;')
+    //.append('<br />')
+    .append('<span style="color: #909090; font-size: smaller;">' + $lang.get('yubiauth_msg_close_instructions') + '</span>')
+    .css('margin-top', '0');
+  mp.appendChild(info);
+}
+
+function yk_handle_submit(ta)
+{
+  if ( ta.value.length > 44 || !ta.value.match(/^[cbdefghijklnrtuv]+$/) )
+  {
+    // report "invalid characters"
+    setTimeout(function()
+      {
+        var parent = ta.parentNode;
+        var tabackup = {
+          field_id: ta.yk_field_id,
+          status_id: ta.yk_status_id,
+          submit_func: ta.submit_func
+        };
+        yk_mb_construct(parent);
+        var input = parent.getElementsByTagName('input')[0];
+        if ( tabackup.field_id )
+          input.yk_field_id = tabackup.field_id;
+        if ( tabackup.status_id )
+          input.yk_status_id = tabackup.status_id;
+        if ( tabackup.submit_func )
+          input.submit_func = tabackup.submit_func;
+      }, 1000);
+    $('h3', ta.parentNode).text($lang.get(ta.value.length > 44 ? 'yubiauth_msg_too_long' : 'yubiauth_msg_invalid_chars'));
+    $('div.yubikey_bar > img', this.parentNode).addClass('yubikey_bar_error');
+    return false;
+  }
+  
+  window.clearInterval(yk_interval);
+  
+  if ( ta.yk_field_id && ta.yk_status_id )
+  {
+    var field = document.getElementById(ta.yk_field_id);
+    var status = document.getElementById(ta.yk_status_id);
+    if ( $(status).hasClass('empty') || $(status).hasClass('rmpending') )
+    {
+      $(status)
+      .next('span.yubikey_pubkey')
+        .text(ta.value.substr(0, 12))
+      .next('a.yubikey_enroll')
+        .text($lang.get('yubiauth_ctl_btn_change_key'))
+        .addClass('abutton_green')
+        .after(' <a class="abutton abutton_red yubikey_enroll" href="#yk_clear" onclick="yk_clear(\'' + ta.yk_field_id + '\', \'' + ta.yk_status_id + '\'); return false;">'
+               + $lang.get('yubiauth_ctl_btn_clear') +
+               '</a>');
+    }
+    $(status).removeClass('empty').removeClass('enrolled').removeClass('rmpending').addClass('savepending').html($lang.get('yubiauth_ctl_status_enrolled_pending'));
+    $(status).next('span.yubikey_pubkey').text(ta.value.substr(0, 12));
+    field.value = ta.value;
+    miniPromptDestroy(ta);
+    return true;
+  }
+  else if ( ta.submit_func )
+  {
+    ta.submit_func(ta);
+  }
+  else
+  {
+    miniPromptDestroy(ta);
+  }
+}
+
+function yk_login_validate_reqs(ta)
+{
+  $(ta.parentNode).remove('p');
+  yubikey_otp_current = ta.value;
+  
+  miniPromptDestroy(ta, true);
+  
+  if ( logindata )
+  {
+    if ( logindata.mb_object )
+    {
+      // login window is open
+      if ( user_level == USER_LEVEL_GUEST )
+      {
+        // for guests, get the user's yubikey auth flags
+        // we're still ok to submit, so make sure twofactor isn't enabled
+        // as we are a guest, we have to get the flags for the user from the server
+        var ajax = ajaxMakeXHR();
+        var uri = makeUrlNS('Special', 'Yubikey', 'get_flags=' + ta.value.substr(0, 12));
+        var flags = 0;
+        try
+        {
+          ajax.open('GET', uri, false);
+          ajax.send(null);
+          
+          if ( ajax.readyState == 4 && ajax.status == 200 )
+          {
+            // we got it
+            var response = String(ajax.responseText + '');
+            if ( check_json_response(response) )
+            {
+              response = parseJSON(response);
+              flags = response.flags || 0;
+            }
+          }
+        }
+        catch ( e )
+        {
+          ajaxLoginSetStatus(AJAX_STATUS_ERROR);
+          return false;
+        }
+        var show_username = flags & YK_SEC_NORMAL_USERNAME;
+        var show_password = flags & YK_SEC_NORMAL_PASSWORD;
+      }
+      else
+      {
+        var show_username = window.yk_user_flags & YK_SEC_ELEV_USERNAME;
+        var show_password = window.yk_user_flags & YK_SEC_ELEV_PASSWORD;
+      }
+      if ( !show_username )
+        $('#ajax_login_field_username').parent('td').hide().prev().hide();
+      if ( !show_password )
+        $('#ajax_login_field_password').parent('td').hide().prev().hide();
+      
+      var can_submit = true;
+      if ( show_username && !$('#ajax_login_field_username').attr('value') )
+      {
+        $('#ajax_login_field_username').focus();
+        
+        if ( !show_password )
+          $('#ajax_login_field_username').keyup(function(e)
+            {
+              // assign press of Enter in username field to submit
+              if ( e.keyCode == 13 )
+              {
+                $('#messageBoxButtons input:button:first').click();
+              }
+            });
+        
+        can_submit = false;
+      }
+      if ( show_password && !$('#ajax_login_field_password').attr('value') )
+      {
+        if ( can_submit )
+        {
+          // can_submit only true if show_username false
+          $('#ajax_login_field_password').focus();
+        }
+        can_submit = false;
+      }
+      
+      if ( can_submit )
+      {
+        $('#messageBoxButtons input:button:first').click();
+      }
+    }
+  }
+}
+
+function yk_clear(field_id, status_id)
+{
+  var field = document.getElementById(field_id);
+  var status = document.getElementById(status_id);
+  
+  var was_pending = $(field).hasClass('wasempty');
+  
+  $(field).attr('value', '');
+  $(status)
+    .removeClass('savepending')
+    .removeClass('enrolled')
+    .addClass( was_pending ? 'empty' : 'rmpending' )
+    .text( was_pending ? $lang.get('yubiauth_ctl_status_empty') : $lang.get('yubiauth_ctl_status_remove_pending') )
+    .next('span.yubikey_pubkey')
+      .text('')
+    .next('a')
+      .text($lang.get('yubiauth_ctl_btn_enroll'))
+      .removeClass('abutton_green')
+    .next('a')
+      .remove();
+}
+
+addOnloadHook(function()
+  {
+    if ( is_iPhone )
+      // kinda can't plug a yubikey into an iPhone
+      // ... yet?
+      return;
+    
+    attachHook('login_build_form', 'yk_login_dlg_hook(table, data);');
+    attachHook('login_build_userinfo', 'if ( window.yubikey_otp_current ) userinfo.yubikey_otp = window.yubikey_otp_current;');
+    if ( title == namespace_list.Special + 'Preferences/Yubikey' )
+    {
+      load_component(['jquery', 'jquery-ui', 'expander']);
+    }
+  });
+
+function yk_login_dlg_hook(table, data)
+{
+  window.yubikey_otp_current = false;
+  var tr = document.createElement('tr');
+  var td = document.createElement('td');
+  $(td)
+    .attr('colspan', '2')
+    .css('text-align', 'center')
+    .css('font-size', 'smaller')
+    .css('font-weight', 'bold')
+    .html('<a href="#" onclick="yk_mb_init(yk_login_validate_reqs); return false;" style="color: #6fa202">' + $lang.get('yubiauth_btn_enter_otp') + '</a>');
+  $('a', td).blur(function(e)
+    {
+      $('#messageBoxButtons input:button:first').focus();
+      $('#ajax_login_field_captcha').focus();
+    });
+  if ( ( window.yk_reg_require_otp || window.yk_user_enabled ) && !data.locked_out.locked_out )
+  {
+    setTimeout(function()
+      {
+        yk_mb_init(yk_login_validate_reqs);
+      }, 750);
+  }
+  tr.appendChild(td);
+  table.appendChild(tr);
+}
+