diff -ur password_policy_orig/constraints/constraint_and.php password_policy/constraints/constraint_and.php
--- password_policy_orig/constraints/constraint_and.php	2007-01-03 21:03:44.000000000 +0200
+++ password_policy/constraints/constraint_and.php	2007-06-07 17:07:51.000000000 +0300
@@ -4,6 +4,8 @@
 class And_Constraint extends Constraint {
 	
 	var $constraints;
+	var $expiration;
+	var $warning;
 	
 	function And_Constraint() {
 		$this->constraints = array();
@@ -54,5 +56,21 @@
 	function getConstraints() {
 		return $this->constraints;
 	}
+
+	function getExpiration() {
+		return $this->expiration;
+	}
+	
+	function setExpiration($expiration) {
+		$this->expiration = $expiration;
+	}
+
+	function getWarning() {
+		return $this->warning;
+	}
+	
+	function setWarning($warning) {
+		$this->warning = $warning;
+	}
 }
-?>
\ No newline at end of file
+?>
diff -ur password_policy_orig/password_policy.install password_policy/password_policy.install
--- password_policy_orig/password_policy.install	2006-09-25 08:01:19.000000000 +0300
+++ password_policy/password_policy.install	2007-06-07 17:07:51.000000000 +0300
@@ -27,4 +27,26 @@
 
 }
 
-?>
\ No newline at end of file
+function password_policy_update_1() {
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli': 
+      $ret[] = update_sql("
+        CREATE TABLE {password_policy_expiration} (
+          uid int(10) unsigned NOT NULL,
+	  warning int(11) default NULL,
+	  blocked int(11) default NULL,
+	  unblocked int(11) default NULL
+	) /*!40100 DEFAULT CHARACTER SET utf8 */;
+      ");
+      $ret[] = update_sql("ALTER TABLE {password_policy} ADD created int(11) default NULL");
+     
+    break;
+  }
+ 
+  return $ret;
+}
+
+?>
diff -ur password_policy_orig/password_policy.module password_policy/password_policy.module
--- password_policy_orig/password_policy.module	2007-01-03 21:06:47.000000000 +0200
+++ password_policy/password_policy.module	2007-06-07 17:07:51.000000000 +0300
@@ -39,6 +39,8 @@
 			t("module can not determine these situations automatically, so be careful during the definition of your policy.") . '</p>';
 
 			break;
+		case "admin/password_policy/list_expired":
+			$output = '<p>'.t('List of accounts which passwords have expired.').'</p>';
 	}
 	return $output;
 }
@@ -108,6 +110,19 @@
       'type' => MENU_DEFAULT_LOCAL_TASK,
       'weight' => -10);
 
+	$items[] = array('path' => 'admin/password_policy/list_expired',
+      'title' => t('expired accounts'),
+      'callback' => 'password_policy_list_expired',
+      'access' => $edit,
+      'type' => MENU_LOCAL_TASK);
+
+	$items[] = array('path' => 'admin/password_policy/unblock/'. $arg3,
+      'title' => t('Unblock'),
+      'callback' => 'password_policy_unblock',
+      'callback arguments' => array('id' => $arg3),
+      'type' => MENU_CALLBACK,
+      'access' => $edit
+	);
 	return $items;
 }
 
@@ -131,7 +146,8 @@
 			$policy = password_policy_load_policy_by_id($pid);
 			if ($policy) {
 				_password_policy_clear_default();
-				db_query("UPDATE {password_policy} SET enabled = %d WHERE id = %d", 1, $pid);
+				$time = time();
+				db_query("UPDATE {password_policy} SET enabled = %d, created = %d WHERE id = %d", 1, $time, $pid);
 				drupal_set_message(t('\'%name\' has been set as the default password policy.', array('%name' => $policy->name)));
 			}
 		}
@@ -163,6 +179,10 @@
 		$desc = !$constraints ? t('This policy has no constraints set.  You can add constraints by ') . $editURL . '.'
 		: t('This policy has the constraints listed below.  You can change the constraints by ') . $editURL . '.</br>' .  $policy->getValidationErrorMessage();
 		$output = "<p>$desc</p>";
+
+                $expiration = $policy->expiration;
+		$desc = $expiration > 0 ? t('The passwords expire after %number %days.', array('%number' => $expiration, '%days' => format_plural($expiration, t('day'), t('days')))) : t('The passwords never expire.');
+		$output .= "<p>$desc</p>";
 		return $output;
 
 	}
@@ -178,6 +198,7 @@
 			$options[$id] = '';
 			if ($summary['enabled']) {
 				$default_id = $id;
+			        $form[$name]['created'] = array('#value' => format_date($summary['created'], 'custom', 'm/d/y H:i:s'));
 			}
 			$form[$name]['id'] = array('#value' => $id);
 			$form[$name]['view'] = array('#value' => l(t('view'), 'admin/password_policy/'. $id));
@@ -206,6 +227,7 @@
 			$rows[] = array(
 			form_render($form['default'][$element['id']['#value']]),
 			check_plain($name),
+			$element['created']['#value'],
 			form_render($form[$name]['view']),
 			form_render($form[$name]['edit']),
 			form_render($form[$name]['delete'])
@@ -213,7 +235,7 @@
 			unset($form[$name]);
 		}
 	}
-	$header = array(t('Default'), t('Name'), array('data' => t('Operations'), 'colspan' => 3));
+	$header = array(t('Default'), t('Name'), t('Enabled'), array('data' => t('Operations'), 'colspan' => 3));
 	$output = theme('table', $header, $rows);
 	$output .= form_render($form);
 
@@ -318,18 +340,47 @@
 		$policy = password_policy_load_policy_by_id($pid);
 	}
 
-	$form['name'] = array('#type' => 'textfield',
+        $form['general'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('General Settings'),
+    '#collapsible' => FALSE,
+    '#collapsed' => FALSE
+        );
+
+	$form['general']['name'] = array('#type' => 'textfield',
     '#title' => t('Name'),
     '#default_value' => $policy->name,
     '#maxlength' => 64,
     '#required' => TRUE,
 	);
 
-	$form['description'] = array('#type' => 'textarea',
+	$form['general']['description'] = array('#type' => 'textarea',
     '#title' => t('Description'),
     '#default_value' => $policy->description,
 	);
 
+	$form['general']['expiration'] = array('#type' => 'textfield',
+    '#title' => t('Password Expiration'),
+    '#default_value' => $policy->expiration,
+    '#size' => 5,
+    '#maxlength' => 5,
+    '#description' => t('The passwords will expire after this number of days. The users with expired passwords will be blocked. Leaving this field empty won\'t put any password expiration constraints.'),
+	);
+
+	$form['general']['warning'] = array('#type' => 'textfield',
+    '#title' => t('Password Expiration Warning'),
+    '#default_value' => $policy->warning,
+    '#size' => 10,
+    '#description' => t('The comma separated list of days. The warning about expiration of the password will be sent out on those days before the expiration. Leaving this field empty won\'t send out or display any warnings.'),
+	);
+
+        $form['constraints'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Password Constraints'),
+    '#collapsible' => FALSE,
+    '#collapsed' => FALSE
+        );
+
 	// Get an array of the valid constraint types available
 	$constraintTypes = _password_policy_get_valid_constraints();
 
@@ -382,6 +433,8 @@
 	$policy = new And_Constraint();
 	$policy->setName($form_values['name']);
 	$policy->setDescription($form_values['description']);
+	$policy->setExpiration(trim($form_values['expiration']));
+	$policy->setWarning(str_replace(' ', '', $form_values['warning']));
 	foreach ($form_values as $key => $value) {
 		
 		// if we have no form value, then we have no constraint to set.
@@ -428,6 +481,9 @@
 				// as long as the password policy module is enabled, we will track the hashed password values which 
 				// can then be used in the history constraint.
 				if ($user->uid) _password_policy_store_password($user->uid, $edit['pass']);
+				// if user successfully changed his password we will unblock the account
+                                db_query("UPDATE {users} SET status = '1' WHERE uid = %d", $user->uid);
+                                db_query("DELETE FROM {password_policy_expiration} WHERE uid = '%d'", $user->uid);
 			
 			}
 		}
@@ -438,6 +494,62 @@
 			if ($user->uid) _password_policy_store_password($user->uid, $edit['pass']);
 		}
 	}
+
+	if ($type == 'login') {
+	    $constraint = password_policy_load_active_policy();
+	    // $edit['name'] is NULL for a one time login
+	    if($constraint && ($user->uid > 1 || variable_get('password_policy_admin', false)) && !empty($edit['name'])) {
+	        $expiration = $constraint->getExpiration();
+	        $warning = max(explode(',', $constraint->getWarning()));
+	        
+		$expiration_seconds = $expiration*60*60*24;
+	        $warning_seconds = $warning*60*60*24;
+
+                $policy_enabled = _password_policy_enebled($expiration_seconds);
+            }
+	    if(!empty($expiration)) {
+                $result = db_query("SELECT * FROM {password_policy_users} WHERE uid = %d ORDER BY created DESC LIMIT 1", $user->uid);
+                if ($row = db_fetch_object($result)) {
+                    $last_change = $row->created;
+                }
+                else {
+		    // user has not changed his pwd after this module had been enabled
+	            $last_change = $user->created;
+	        }
+
+                $time = time();
+		if($time > max($policy_enabled, $last_change) + $expiration_seconds) {
+                    db_query("UPDATE {users} SET status = '0' WHERE uid = '%d'", $user->uid);
+                    $result = db_query("SELECT * FROM {password_policy_expiration} WHERE uid = '%d'", $user->uid);
+                    if($row = db_fetch_array($result)) {
+                      db_query("UPDATE {password_policy_expiration} SET blocked = '%d' WHERE uid = %d", $time, $user->uid);
+		    }
+		    else {
+                      db_query("INSERT INTO {password_policy_expiration} (uid, blocked) VALUES ('%d', '%d')", $user->uid, $time);
+		    }
+		    watchdog('password_policy', t('Password for user %name has expired.', array('%name' => theme('placeholder', $user->name))), WATCHDOG_NOTICE, l(t('edit'), 'user/'.$user->uid.'/edit'));
+	            if(variable_get('password_policy_block', 0) == 0) {
+		        user_logout();
+		    }
+		    else {
+		        drupal_set_message(t('Your password has expired. You have to change it now or you won\'t be able to login again.'), 'error');
+		        unset($_REQUEST['destination']);
+                        drupal_goto('user/'.$user->uid.'/edit');
+                    }
+		}
+		elseif ($time > max($policy_enabled, $last_change) + $expiration_seconds - $warning_seconds) {
+		    $days_left = ceil((max($policy_enabled, $last_change) + $expiration_seconds - $time)/(60*60*24));
+	            drupal_set_message(t('Your password will expire in less than %number %days. Please change it.', array('%number' => $days_left, '%days' => format_plural($days_left, t('day'), t('days')))));
+		    unset($_REQUEST['destination']);
+		    drupal_goto('user/'.$user->uid.'/edit');
+		}
+            }
+	}
+
+        if($type == 'delete') {
+          db_query("DELETE FROM {password_policy_users} WHERE uid = '%d'", $user->uid);
+          db_query("DELETE FROM {password_policy_expiration} WHERE uid = '%d'", $user->uid);
+	}
 }
 
 /**
@@ -495,7 +607,7 @@
  * 		An array of associative arrays.
  */
 function _password_policy_load_policy_summaries() {
-	$result = db_query('SELECT id, name, enabled, description FROM {password_policy} p ORDER BY name');
+	$result = db_query('SELECT id, name, enabled, description, created FROM {password_policy} p ORDER BY name');
 	while ($ary = db_fetch_array($result)) {
 		$summaries[] = $ary;
 	}
@@ -566,4 +678,246 @@
 	return array_keys($tests);
 }
 
+/**
+ * Implementation of hook_settings 
+ */
+function password_policy_settings() {
+  $form['expiration'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Expiration Settings'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE
+  );
+  $form['expiration']['password_policy_admin'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Admin (UID=1) password expires.'),
+    '#default_value' => variable_get('password_policy_admin', false),
+    '#description' => t('Admin account password will obey expiration policy.'),
+  );
+  $form['expiration']['password_policy_begin'] = array(
+    '#type' => 'radios',
+    '#title' => t('Beginning of password expirations'),
+    '#default_value' => variable_get('password_policy_begin', 0),
+    '#options' => array('0' => t('After expiration time from setting a default policy (all passwords are valid during the expiration time from setting the default policy, and after that older than expiration time passwords expire).'), '1' => t('Setting a default policy (passwords older than expiration time expire after setting the default policy, retroactive behaviour).')),
+  );
+  $form['expiration']['password_policy_block'] = array(
+    '#type' => 'radios',
+    '#title' => t('Blocking expired accounts'),
+    '#default_value' => variable_get('password_policy_block', 0),
+    '#options' => array('0' => t('Expired accounts are blocked. Only administrators can unblock them.'), '1' => t('The user with expired account is not blocked, but sent to a change password page. If the password is not changed, the account is blocked and the user cannot login again.')),
+  );
+
+  // E-mail notification settings.
+  $form['email'] = array(
+    '#type' => 'fieldset', 
+    '#title' => t('E-mail notification settings'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE
+  );
+  $form['email']['password_policy_mail_warning_subject'] = array(
+    '#type' => 'textfield', 
+    '#title' => t('Subject of warning e-mail'), 
+    '#default_value' => _password_policy_mail_text('warning_subject'), 
+    '#maxlength' => 180, 
+    '#description' => t('Customize the subject of the warning e-mail message, which is sent to remind of password expiration.') .' '. t('Available variables are:') .' %username, %site, %uri, %uri_brief, %mailto, %date, %login_uri, %edit_uri, %days.',
+  );
+  $form['email']['password_policy_mail_warning_body'] = array(
+    '#type' => 'textarea', 
+    '#title' => t('Body of warning e-mail'), 
+    '#default_value' => _password_policy_mail_text('warning_body'), 
+    '#rows' => 15, 
+    '#description' => t('Customize the body of the warning e-mail message, which is sent to remind of password expiration.') .' '. t('Available variables are:') .' %username, %site, %uri, %uri_brief, %mailto, %date, %login_uri, %edit_uri, %days.',
+  );
+
+  return $form;
+}
+
+/**
+ * List all expired accounts 
+ */
+function password_policy_list_expired() {
+  $header[] = array('data' => t('Blocked'), 'field' => 'blocked', 'sort' => 'desc');
+  $header[] = array('data' => t('Username'), 'field' => 'name');
+  $header[] = array('data' => t('Unblocked'), 'field' => 'unblocked');
+  $header[] = array('data' => t('Action'));
+
+  $max_pages = 20;
+  $result = pager_query("SELECT p.*, u.name FROM {password_policy_expiration} p INNER JOIN {users} u ON p.uid = u.uid WHERE p.blocked > 0".tablesort_sql($header), $max_pages, 0, NULL);
+  while($row = db_fetch_object($result)) {
+    $entry[$row->uid]['blocked'] = format_date($row->blocked, 'custom', 'm/d/y H:i:s');
+    $entry[$row->uid]['name'] = l($row->name, 'user/'.$row->uid);
+    $entry[$row->uid]['unblocked'] = $row->unblocked < $row->blocked ? '' : format_date($row->unblocked, 'custom', 'm/d/y H:i:s');
+    $entry[$row->uid]['action'] = $row->unblocked < $row->blocked ? l(t('unblock'), 'admin/password_policy/unblock/'.$row->uid) : '';
+  }
+  if(!isset($entry)) {
+    $colspan = '4';
+    $entry[] = array(array('data' => t('No entries'), 'colspan' => $colspan));
+  }
+
+  $page = theme_table($header, $entry);
+  $page .= theme_pager(array(), $max_pages, 0);
+
+
+  return $page;
+}
+
+/**
+ * Unblocks the expired account
+ */
+function password_policy_unblock($uid = NULL) {
+  if($uid) {
+    db_query("UPDATE {users} SET status = '1' WHERE uid = %d", $uid);
+    db_query("UPDATE {password_policy_expiration} SET unblocked = '%d' WHERE uid = %d", time(), $uid);
+
+    if($account = user_load(array('uid' => $uid, 'status' => 1))) {
+      password_policy_send_login($account);
+      drupal_set_message(t('The user %name has been unblocked.', array('%name' => theme('placeholder', $account->name))));
+    }
+  }
+
+  drupal_goto('admin/password_policy/list_expired');
+}
+
+/**
+ * Sends one time login url to the user
+ * based on 'user_pass_submit'
+ */
+function password_policy_send_login($account = NUL) {
+  global $base_url;
+
+  $from = variable_get('site_mail', ini_get('sendmail_from'));
+
+  // Mail one time login URL and instructions.
+  $variables = array('%username' => $account->name, '%site' => variable_get('site_name', 'drupal'), '%login_url' => user_pass_reset_url($account), '%uri' => $base_url, '%uri_brief' => substr($base_url, strlen('http://')), '%mailto' => $account->mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE));
+  $subject = _user_mail_text('pass_subject', $variables);
+  $body = _user_mail_text('pass_body', $variables);
+  $headers = "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from";
+  $mail_success = user_mail($account->mail, $subject, $body, $headers);
+
+  if ($mail_success) {
+    watchdog('password_policy', t('Password reset instructions mailed to %name at %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', $account->mail))));
+    drupal_set_message(t('Further instructions have been sent to %name e-mail address.', array('%name' => theme('placeholder', $account->name))));
+  }
+  else {
+    watchdog('password_policy', t('Error mailing password reset instructions to %name at %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', $account->mail))), WATCHDOG_ERROR);
+    drupal_set_message(t('Unable to send mail. Please contact the site admin.'));
+  }
+}
+
+/**
+ * Implementation of hook_cron
+ *
+ */
+function password_policy_cron() {
+  $constraint = password_policy_load_active_policy();
+  if($constraint) {
+    $expiration = $constraint->getExpiration();
+    $warnings = explode(',', $constraint->getWarning());
+
+    if(!empty($expiration)) {
+      // Get all users' last password change time. We don't touch blocked accounts
+      $result = db_query("SELECT u.*, u.created created_u, p.created created_p, e.warning warning, e.unblocked unblocked FROM {users} u LEFT JOIN {password_policy_users} p ON u.uid = p.uid LEFT JOIN {password_policy_expiration} e ON u.uid = e.uid WHERE u.uid > 0 AND u.status = '1' ORDER BY p.created ASC");
+      while($row = db_fetch_object($result)) {
+        if($row->uid == 1 && !variable_get('password_policy_admin', false)) { continue; }
+        $accounts[$row->uid] = empty($row->created_p) ? $row->created_u : $row->created_p;
+        $warns[$row->uid] = $row->warning;
+        $unblocks[$row->uid] = $row->unblocked;
+      }
+
+      $expiration_seconds = $expiration*60*60*24;
+      $policy_enabled = _password_policy_enebled($expiration_seconds);
+      rsort($warnings, SORT_NUMERIC);
+      $time = time();
+      foreach($accounts as $uid => $last_change) {
+        foreach($warnings as $warning) {
+          if(!empty($warning)) {
+            $warning_seconds = $warning*60*60*24;
+            $start_period = max($policy_enabled, $last_change) + $expiration_seconds - $warning_seconds;
+            $end_period = $start_period + 60*60*24;
+            if($warns[$uid] > $start_period && $warns[$uid] < $end_period) {
+              // a warning was already mailed out
+              continue;
+            }
+            if($time > $start_period && $time < $end_period) {
+              // we're sending a warning
+              global $base_url;
+
+              $from = variable_get('site_mail', ini_get('sendmail_from'));
+              $account = user_load(array('uid' => $uid));
+              $variables = array('%username' => $account->name, '%site' => variable_get('site_name', 'drupal'), '%uri' => $base_url, '%uri_brief' => substr($base_url, strlen('http://')), '%mailto' => $account->mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE), '%days' => $warning);
+              $subject = _password_policy_mail_text('warning_subject', $variables);
+              $body = _password_policy_mail_text('warning_body', $variables);
+              $headers = "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from";
+              $mail_success = user_mail($account->mail, $subject, $body, $headers);
+
+              if ($mail_success) {
+                watchdog('password_policy', t('Password expiration warning mailed to %username at %email.', array('%username' => theme('placeholder', $account->name), '%email' => theme('placeholder', $account->mail))));
+              }
+              else {
+                watchdog('password_policy', t('Error mailing password expiration warning to %username at %email.', array('%username' => theme('placeholder', $account->name), '%email' => theme('placeholder', $account->mail))));
+              }
+
+              if(!empty($warns[$uid])) {
+                db_query("UPDATE {password_policy_expiration} SET warning = '%d' WHERE uid = %d", $time, $uid);
+              }
+              else {
+                db_query("INSERT INTO {password_policy_expiration} (uid, warning) VALUES ('%d', '%d')", $uid, $time);
+              }
+	    }
+          }
+        }
+
+        // Block expired accounts. Unblocked accounts are not blocked for 24h.
+        if($time > max($policy_enabled, $last_change) + $expiration_seconds && $time > $unblocks[$uid] + 60*60*24 && variable_get('password_policy_block', 0) == 0) {
+          db_query("UPDATE {users} SET status = '0' WHERE uid = '%d'", $uid);
+          if(!empty($warns[$uid])) {
+            db_query("UPDATE {password_policy_expiration} SET blocked = '%d' WHERE uid = %d", $time, $uid);
+          }
+          else {
+            db_query("INSERT INTO {password_policy_expiration} (uid, blocked) VALUES ('%d', '%d')", $uid, $time);
+          }
+
+          $account = user_load(array('uid' => $uid));
+          watchdog('password_policy', t('Password for user %name has expired.', array('%name' => theme('placeholder', $account->name))), WATCHDOG_NOTICE, l(t('edit'), 'user/'.$account->uid.'/edit'));
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Loads default or saved mail text
+ */
+function _password_policy_mail_text($messageid, $variables = array()) {
+  // Check if an admin setting overrides the default string.
+  if ($admin_setting = variable_get('password_policy_mail_' . $messageid, '')) {
+    return strtr($admin_setting, $variables);
+  }
+  // No override, return with default strings.
+  else {
+    switch ($messageid) {
+      case 'warning_subject':
+        return t('Password expiration warning for %username at %site', $variables);
+      case 'warning_body':
+        return t("%username,\n\nYour password at %site will expire in less than %days day(s).\n\nPlease go to %edit_uri to change your password.", $variables);
+    }
+  }
+}
+
+/**
+ * Returns starting point of active policy
+ */
+function _password_policy_enebled($expiration_seconds = 0) {
+  $result = db_query("SELECT * FROM {password_policy} WHERE enabled = '1' ORDER BY enabled DESC LIMIT 1");
+  if ($row = db_fetch_object($result)) {
+    $policy_enabled = $row->created;
+  }
+  if(variable_get('password_policy_begin', 0) == 1) {
+    // password older than expiration time expires starting from setting the policy
+    $policy_enabled -= $expiration_seconds;
+  }
+
+  return $policy_enabled;
+}
+
 ?>
