This repository has been archived by the owner on Mar 3, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
heartbleed.module
214 lines (187 loc) · 6.46 KB
/
heartbleed.module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<?php
/**
* @file
* Prompt users for password reset.
*/
/**
* The default reset date: April 7, 2014.
*/
define('HEARTBLEED_DEFAULT_RESET_DATE', 1396828800);
/**
* Implements hook_menu().
*/
function heartbleed_menu() {
$items = array();
$items['user/heartbleed'] = array(
'page callback' => 'heartbleed_page',
'access arguments' => array('access content'),
);
$items['admin/config/people/heartbleed'] = array(
'title' => 'Heartbleed',
'page callback' => 'drupal_get_form',
'page arguments' => array('heartbleed_admin_form'),
'access arguments' => array('administer users'),
);
return $items;
}
/**
* Admin config form.
*/
function heartbleed_admin_form($form, &$form_state) {
$form = array();
$form['heartbleed_password_reset_date'] = array(
'#type' => 'textfield',
'#title' => t('Password reset date'),
'#default_value' => variable_get('heartbleed_password_reset_date', HEARTBLEED_DEFAULT_RESET_DATE),
'#description' => t('Any users that existed in the database BEFORE this date will be prompted to update their password next time they log in. Users will receive this prompt until they update their password. Look up the unix timestamp here: <a href="@unixtimestamp">unixtimestamp.com</a>.',
array('@unixtimestamp' => 'http://unixtimestamp.com/')),
);
$form['heartbleed_message'] = array(
'#type' => 'textarea',
'#title' => t('Heartbleed Message'),
'#description' => t('Message to be displayed to users'),
'#default_value' => variable_get('heartbleed_message', t('Please reset your password. More information: <a href="@heartbleed">@heartbleed-text</a> and <a href="@dhs">@dhs-text</a>.',
array(
'@heartbleed' => 'http://heartbleed.com/',
'@heartbleed-text' => 'heartbleed.com',
'@dhs' => 'http://www.dhs.gov/stopthinkconnect-heartbleed',
'@dhs-text' => 'dhs.gov/stopthinkconnect-heartbleed'))),
);
return system_settings_form($form);
}
/**
* Implements hook_form_alter().
*/
function heartbleed_form_alter(&$form, &$form_state, $form_id) {
// Hook into user login forms to add our submit handler after user module's
// submit handler.
if ($form_id == 'user_login' || $form_id == 'user_login_block') {
$form['#submit'][] = 'heartbleed_login_submit';
}
// Alter user_pass form to display heartbleed info if we're on heartbleed
// path. If we're not on heartbleed page (e.g. if we're on the standard
// user/password page) do nothing.
if ($form_id == 'user_pass') {
$args = arg();
$path = "{$args[0]}/{$args[1]}";
$path = strtolower("{$args[0]}/{$args[1]}");
if ($path == 'user/heartbleed') {
// We're on heartbleed page. Add heartbleed info to form.
$form['heartbleed_message'] = array(
'#type' => 'item',
'#title' => t('Please reset your password'),
'#description' => variable_get('heartbleed_message', ''),
'#weight' => 0,
'#prefix' => '<div class="messages warning">',
'#suffix' => '</div>',
);
}
}
}
/**
* Submit handler for heartbleed reset.
*
* Upon successful login, force logout and redirect user to /heartbleed page
* where they will receive instructions on how to reset their password.
*/
function heartbleed_login_submit($form_id, &$form_state) {
// Check if user needs reset.
$force_reset = heartbleed_force_reset();
if (!$force_reset) {
// Nothing to do here.
return;
}
// Copied from user_logout().
global $user;
watchdog('user', 'Session closed for %name.', array('%name' => $user->name));
module_invoke_all('user_logout', $user);
// Destroy the current session, and reset $user to the anonymous user.
session_destroy();
// Do not obey Drupal's destination if set. Otherwise user logged out and
// redirected and they don't know why or how to fix it.
$_GET['destination'] = 'user/heartbleed';
drupal_goto('user/heartbleed');
}
/**
* Page callback at user/heartbleed. See hook_menu above.
*
* This looks just like user/password, with some extra info included in the form
* by a form_alter.
*/
function heartbleed_page() {
module_load_include('inc', 'user', 'user.pages');
return drupal_get_form('user_pass');
}
/**
* Check if user needs to reset password.
*
* @return bool
* TRUE if the active user needs a password reset or FALSE if not.
*/
function heartbleed_force_reset() {
global $user;
// Get password reset date.
$password_reset_date = variable_get('heartbleed_password_reset_date', HEARTBLEED_DEFAULT_RESET_DATE);
// Get date user's password was last modified. Since this information is only
// tracked after Heartbleed module is enabled, $last_pass_update is 0 for all
// users when the module is first enabled.
$last_pass_update = heartbleed_get_last_pass_update($user->uid);
if ($user->created > $password_reset_date) {
// If user was created AFTER password reset date, do not force reset.
$needs_reset = FALSE;
}
elseif ($last_pass_update < $password_reset_date) {
// If user was created BEFORE reset date AND the password has not been reset
// since the reset date, user needs to reset password.
$needs_reset = TRUE;
}
else {
$needs_reset = FALSE;
}
return $needs_reset;
}
/**
* Implements hook_user_update().
*/
function heartbleed_user_update(&$edit, $account, $category) {
// Is user updating password? If so, log event in heartbleed table.
if (!empty($edit['pass'])) {
// Pass property is only set when password is being update. Proceed.
db_merge('heartbleed')
->key(array('uid' => $account->uid))
->fields(array(
'uid' => $account->uid,
'pass_last_updated' => time(),
))
->execute();
}
}
/**
* Provide last password reset timestamp.
*
* @param int $uid
* User id.
*
* @return int
* Timestamp for when user last reset pass if availble. False if not.
*/
function heartbleed_get_last_pass_update($uid) {
$query = db_query('SELECT * FROM {heartbleed} WHERE uid = :uid', array(
':uid' => $uid,
));
$result = $query->fetchAll();
if (count($result) < 1) {
// No results. User hasn't updated password since Heartbleed was installed.
$pass_last_updated = 0;
}
elseif (count($result) == 1) {
// Return pass_last_updated timestamp.
$pass_last_updated = $result[0]->pass_last_updated;
}
else {
// Something is wrong.
$pass_last_updated = 0;
watchdog('heartbleed', 'Something is wrong. The same UID is in the heartbleed table more than once.', WATCHDOG_ERROR);
}
return $pass_last_updated;
}