mirror of
https://github.com/postfixadmin/postfixadmin.git
synced 2024-09-20 03:36:20 +02:00
Merge pull request #200 from doktoil-makresh/master
Support for password expiration, managed in PostFix Admin
This commit is contained in:
commit
69e234f668
48
README.password_expiration
Normal file
48
README.password_expiration
Normal file
@ -0,0 +1,48 @@
|
||||
*Description
|
||||
|
||||
This extension adds support for password expiration.
|
||||
It is designed to have expiration on users passwords. An email is sent when the password is expiring in 30 days, then 14 days, then 7 days.
|
||||
It is strongly inspired by https://abridge2devnull.com/posts/2014/09/29/dovecot-user-password-expiration-notifications-updated-4122015/, and adapted to fit with Postfix Admin & Roundcube's password plugin
|
||||
Expiration unit is day
|
||||
Expiration value for domain is set through Postfix Admin GUI
|
||||
|
||||
*Installation
|
||||
|
||||
Perform the following changes:
|
||||
|
||||
**Changes in MySQL/MariaDB mailbox table (as defined in $CONF['database_tables'] from config.inc.php):
|
||||
|
||||
You are invited to backup your DB first, and ensure the table name is correct.
|
||||
|
||||
Execute the attached SQL script (password_expiration.sql) that will add the required columns. The expiration value for existing users will be set to 90 days. If you want a different value, edit line 2 in the script and replace 90 by the required value.
|
||||
|
||||
**Changes in Postfix Admin :
|
||||
|
||||
To enable password expiration, add the following to your config.inc.php file:
|
||||
$CONF['password_expiration_enabled'] = 'YES';
|
||||
|
||||
All my tests are performed using $CONF['encrypt'] = 'md5crypt';
|
||||
|
||||
**If you are using Roundcube's password plugin, you should also adapt the $config['password_query'] value.
|
||||
|
||||
I recommend to use:
|
||||
|
||||
$config['password_query'] = 'UPDATE mailbox SET password=%c, modified = now(), password_expiry = now() + interval 90 day';
|
||||
|
||||
of cource you may adapt to the expected expiration value
|
||||
|
||||
All my tests are performed using $config['password_algorithm'] = 'md5-crypt';
|
||||
|
||||
**Changes in Dovecot (adapt if you use another LDA)
|
||||
|
||||
Edit dovecot-mysql.conf file, and replace the user_query (and only this one) by this query:
|
||||
|
||||
password_query = SELECT username as user, password, concat('/var/vmail/', maildir) as userdb_var, concat('maildir:/var/vmail/', maildir) as userdb_mail, 20001 as userdb_uid, 20001 as userdb_gid, m.domain FROM mailbox m, domain d where d.domain = m.domain and m.username = '%u' AND m.active = '1' AND (m.pw_expires_on > now() or d.password_expiration_value = 0)
|
||||
|
||||
Of course you may require to adapt the uid, gid, maildir and table to your setup
|
||||
|
||||
**Changes in system
|
||||
|
||||
You need to have a script running on a daily basis to check password expiration and send emails 30, 14 and 7 days before password expiration (script attached: check_mailpass_expiration.sh).
|
||||
Edit the script to adapt the variables to your setup.
|
||||
This script is using postfixadmin.my.cnf to read credentials. Edit this file to enter a DB user that is allowed to access (read-write) your database. This file should be protected from any user (chmod 400).
|
20
check_mailpass_expiration.sh
Normal file
20
check_mailpass_expiration.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
#Adapt to your setup
|
||||
|
||||
POSTFIX_DB="postfix_test"
|
||||
MYSQL_CREDENTIALS_FILE="postfixadmin.my.cnf"
|
||||
|
||||
REPLY_ADDRESS=noreply@example.com
|
||||
|
||||
# Change this list to change notification times and when ...
|
||||
for INTERVAL in 30 14 7
|
||||
do
|
||||
LOWER=$(( $INTERVAL - 1 ))
|
||||
|
||||
QUERY="SELECT username,password_expiry FROM mailbox WHERE password_expiry > now() + interval $LOWER DAY AND password_expiry < NOW() + interval $INTERVAL DAY"
|
||||
|
||||
mysql --defaults-extra-file="$MYSQL_CREDENTIALS_FILE" "$POSTFIX_DB" -B -e "$QUERY" | while read -a RESULT ; do
|
||||
echo -e "Dear User, \n Your password will expire on ${RESULT[1]}" | mail -s "Password 30 days before expiration notication" -r $REPLY_ADDRESS ${RESULT[0]}
|
||||
done
|
||||
|
||||
done
|
@ -516,6 +516,16 @@ $CONF['show_undeliverable']='YES';
|
||||
$CONF['show_undeliverable_color']='tomato';
|
||||
// mails to these domains will never be flagged as undeliverable
|
||||
$CONF['show_undeliverable_exceptions']=array("unixmail.domain.ext","exchangeserver.domain.ext");
|
||||
// show mailboxes with expired password
|
||||
$CONF['show_expired']='YES';
|
||||
$CONF['show_expired_color']='orange';
|
||||
// show vacation enabled mailboxes
|
||||
$CONF['show_vacation']='YES';
|
||||
$CONF['show_vacation_color']='turquoise';
|
||||
// show disabled accounts
|
||||
$CONF['show_disabled']='YES';
|
||||
$CONF['show_disabled_color']='grey';
|
||||
// show POP/IMAP mailboxes
|
||||
$CONF['show_popimap']='YES';
|
||||
$CONF['show_popimap_color']='darkgrey';
|
||||
// you can assign special colors to some domains. To do this,
|
||||
@ -661,6 +671,11 @@ $CONF['theme_custom_css'] = '';
|
||||
// change to boolean true to enable xmlrpc
|
||||
$CONF['xmlrpc_enabled'] = false;
|
||||
|
||||
//Account expiration info
|
||||
//If you want to display the password expiracy status of the accounts (read-only)
|
||||
//More details in README.password_expiration
|
||||
$CONF['password_expiration_enable'] = 'YES';
|
||||
|
||||
// If you want to keep most settings at default values and/or want to ensure
|
||||
// that future updates work without problems, you can use a separate config
|
||||
// file (config.local.php) instead of editing this file and override some
|
||||
|
@ -260,6 +260,18 @@ function check_domain($domain) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get password expiration value for a domain
|
||||
* @param string $domain - a string that may be a domain
|
||||
* @return int password expiration value for this domain (DAYS, or zero if not enabled)
|
||||
*/
|
||||
function get_password_expiration_value ($domain) {
|
||||
$table_domain = table_by_key('domain');
|
||||
$query = "SELECT password_expiry FROM $table_domain WHERE domain='$domain'";
|
||||
$result = db_query ($query);
|
||||
$password_expiration_value = db_array ($result['result']);
|
||||
return $password_expiration_value[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* check_email
|
||||
@ -1871,7 +1883,7 @@ function db_delete($table, $where, $delete, $additionalwhere='') {
|
||||
* @param array $timestamp (optional) - array of fields to set to now() - default: array('created', 'modified')
|
||||
* @return int - number of inserted rows
|
||||
*/
|
||||
function db_insert($table, $values, $timestamp = array('created', 'modified')) {
|
||||
function db_insert ($table, $values, $timestamp = array('created', 'modified'), $timestamp_expiration = array('password_expiry') ) {
|
||||
$table = table_by_key($table);
|
||||
|
||||
foreach (array_keys($values) as $key) {
|
||||
@ -1886,6 +1898,19 @@ function db_insert($table, $values, $timestamp = array('created', 'modified')) {
|
||||
}
|
||||
}
|
||||
|
||||
global $CONF;
|
||||
if ($CONF['password_expiration_enabled'] == 'YES') {
|
||||
if ($table == 'mailbox') {
|
||||
$domain_dirty = $values['domain'];
|
||||
$domain = substr($domain_dirty, 1, -1); // really the update to the mailbox password_expiry should be based on a trigger, or a query like :
|
||||
// .... NOW() + INTERVAL domain.password_expiry DAY
|
||||
$password_expiration_value = get_password_expiration_value($domain);
|
||||
foreach($timestamp_expiration as $key) {
|
||||
$values[$key] = "now() + interval " . $password_expiration_value . " day";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sql_values = "(" . implode(",", escape_string(array_keys($values))).") VALUES (".implode(",", $values).")";
|
||||
|
||||
$result = db_query("INSERT INTO $table $sql_values");
|
||||
@ -1934,6 +1959,19 @@ function db_update_q($table, $where, $values, $timestamp = array('modified')) {
|
||||
}
|
||||
}
|
||||
|
||||
global $CONF;
|
||||
if ($CONF['password_expiration_enabled'] == 'YES') {
|
||||
$where_type = explode('=',$where);
|
||||
$email = ($where_type[1]);
|
||||
$domain_dirty = explode('@',$email)[1];
|
||||
$domain = substr($domain_dirty, 0, -1);
|
||||
if ($table == 'mailbox') {
|
||||
$password_expiration_value = get_password_expiration_value($domain);
|
||||
$key = 'password_expiry';
|
||||
$sql_values[$key] = $key . " = now() + interval " . $password_expiration_value . " day";
|
||||
}
|
||||
}
|
||||
|
||||
$sql="UPDATE $table SET " . implode(",", $sql_values) . " WHERE $where";
|
||||
|
||||
$result = db_query($sql);
|
||||
@ -2190,6 +2228,36 @@ function gen_show_status($show_alias) {
|
||||
}
|
||||
}
|
||||
|
||||
// Vacation CHECK
|
||||
if ( $CONF['show_vacation'] == 'YES' ) {
|
||||
$stat_result = db_query ("SELECT * FROM ". $CONF['database_tables']['vacation'] ." WHERE email = '" . $show_alias . "' AND active = 1");
|
||||
if ($stat_result['rows'] == 1) {
|
||||
$stat_string .= "<span style='background-color:" . $CONF['show_vacation_color'] . "'>" . $CONF['show_status_text'] . "</span> ";
|
||||
} else {
|
||||
$stat_string .= $CONF['show_status_text'] . " ";
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled CHECK
|
||||
if ( $CONF['show_disabled'] == 'YES' ) {
|
||||
$stat_result = db_query ("SELECT * FROM ". $CONF['database_tables']['mailbox'] ." WHERE username = '" . $show_alias . "' AND active = 0");
|
||||
if ($stat_result['rows'] == 1) {
|
||||
$stat_string .= "<span style='background-color:" . $CONF['show_disabled_color'] . "'>" . $CONF['show_status_text'] . "</span> ";
|
||||
} else {
|
||||
$stat_string .= $CONF['show_status_text'] . " ";
|
||||
}
|
||||
}
|
||||
|
||||
// Expired CHECK
|
||||
if ( $CONF['show_expired'] == 'YES' ) {
|
||||
$stat_result = db_query ("SELECT * FROM ". $CONF['database_tables']['mailbox'] ." WHERE username = '" . $show_alias . "' AND password_expiry <= now()");
|
||||
if ($stat_result['rows'] == 1) {
|
||||
$stat_string .= "<span style='background-color:" . $CONF['show_expired_color'] . "'>" . $CONF['show_status_text'] . "</span> ";
|
||||
} else {
|
||||
$stat_string .= $CONF['show_status_text'] . " ";
|
||||
}
|
||||
}
|
||||
|
||||
// POP/IMAP CHECK
|
||||
if ($CONF['show_popimap'] == 'YES') {
|
||||
$stat_delimiter = "";
|
||||
|
@ -348,6 +348,10 @@ $PALANG['broadcast_mailboxes_only'] = 'Only send to mailboxes';
|
||||
$PALANG['broadcast_to_domains'] = 'Send to domains:';
|
||||
|
||||
$PALANG['pStatus_undeliverable'] = 'maybe UNDELIVERABLE ';
|
||||
$PALANG['pStatus_disabled'] = 'Account disabled ';
|
||||
$PALANG['pStatus_expired'] = 'Password expired ';
|
||||
$PALANG['pStatus_vacation'] = 'Vacation enabled ';
|
||||
|
||||
$PALANG['pStatus_custom'] = 'Delivers to ';
|
||||
$PALANG['pStatus_popimap'] = 'POP/IMAP ';
|
||||
|
||||
@ -407,6 +411,7 @@ $PALANG['pFetchmail_desc_returned_text'] = 'Text message from last polling';
|
||||
|
||||
$PALANG['dateformat_pgsql'] = 'YYYY-mm-dd'; # translators: rearrange to your local date format, but make sure it's a valid PostgreSQL date format
|
||||
$PALANG['dateformat_mysql'] = '%Y-%m-%d'; # translators: rearrange to your local date format, but make sure it's a valid MySQL date format
|
||||
$PALANG['password_expiration'] = 'Pass expires';
|
||||
|
||||
$PALANG['please_keep_this_as_last_entry'] = ''; # needed for language-check.sh
|
||||
/* vim: set expandtab ft=php softtabstop=3 tabstop=3 shiftwidth=3: */
|
||||
|
@ -342,6 +342,9 @@ $PALANG['pBroadcast_error_empty'] = 'Les champs "Nom", "Sujet" et "Message" ne p
|
||||
$PALANG['broadcast_mailboxes_only'] = 'Only send to mailboxes'; # XXX
|
||||
$PALANG['broadcast_to_domains'] = 'Send to domains:'; # XXX
|
||||
$PALANG['pStatus_undeliverable'] = 'Non délivrable ';
|
||||
$PALANG['pStatus_vacation'] = 'Répondeur activé ';
|
||||
$PALANG['pStatus_disabled'] = 'Compte désactivé ';
|
||||
$PALANG['pStatus_expired'] = 'Mot de passe expiré ';
|
||||
$PALANG['pStatus_custom'] = 'Délivré à ';
|
||||
$PALANG['pStatus_popimap'] = 'POP/IMAP ';
|
||||
$PALANG['password_too_short'] = 'Mot de passe trop court. - %s caractères minimum';
|
||||
@ -398,6 +401,7 @@ $PALANG['pFetchmail_desc_date'] = 'Date de la dernière vérification/changement
|
||||
$PALANG['pFetchmail_desc_returned_text'] = 'Message de la dernière vérification';
|
||||
$PALANG['dateformat_pgsql'] = 'dd-mm-YYYY';
|
||||
$PALANG['dateformat_mysql'] = '%d-%m-%Y';
|
||||
$PALANG['password_expiration'] = 'Expiration du mot de passe';
|
||||
|
||||
$PALANG['please_keep_this_as_last_entry'] = ''; # needed for language-check.sh
|
||||
/* vim: set expandtab ft=php softtabstop=3 tabstop=3 shiftwidth=3: */
|
||||
|
@ -94,6 +94,7 @@ class DomainHandler extends PFAHandler {
|
||||
'default_aliases' => pacol($this->new, $this->new, 0, 'bool', 'pAdminCreate_domain_defaultaliases', '' , 1,'', /*not in db*/ 1 ),
|
||||
'created' => pacol(0, 0, 0, 'ts', 'created' , '' ),
|
||||
'modified' => pacol(0, 0, $super, 'ts', 'last_modified' , '' ),
|
||||
'password_expiry' => pacol($super, $super, $super, 'num', 'password_expiration' , 'password_expiration_desc', ''),
|
||||
'_can_edit' => pacol(0, 0, 1, 'int', '' , '' , 0 ,
|
||||
/*options*/ '',
|
||||
/*not_in_db*/ 0,
|
||||
|
@ -49,6 +49,7 @@ class MailboxHandler extends PFAHandler {
|
||||
'token_validity' => pacol(1, 0, 0, 'ts', '' , '', date("Y-m-d H:i:s",time())),
|
||||
'created' => pacol(0, 0, 1, 'ts', 'created' , '' ),
|
||||
'modified' => pacol(0, 0, 1, 'ts', 'last_modified' , '' ),
|
||||
'password_expiry' => pacol(0, 0, 1, 'ts', 'password_expiration' , '' ),
|
||||
# TODO: add virtual 'notified' column and allow to display who received a vacation response?
|
||||
);
|
||||
|
||||
|
3
password_expiration.sql
Normal file
3
password_expiration.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE mailbox ADD COLUMN password_expiry TIMESTAMP DEFAULT now() not null;
|
||||
UPDATE mailbox set password_expiry = now() + interval 90 day;
|
||||
ALTER TABLE domain ADD COLUMN password_expiry int DEFAULT 0;
|
3
postfixadmin.my.cnf
Normal file
3
postfixadmin.my.cnf
Normal file
@ -0,0 +1,3 @@
|
||||
[client]
|
||||
user=postfix_read_write_account
|
||||
password=strong_password
|
@ -165,6 +165,7 @@ $tAlias = $handler->result();
|
||||
#
|
||||
|
||||
$display_mailbox_aliases = Config::bool('alias_control_admin');
|
||||
$password_expiration = Config::bool('password_expiration');
|
||||
|
||||
# build the sql query
|
||||
$sql_select = "SELECT $table_mailbox.* ";
|
||||
@ -190,6 +191,10 @@ if ($display_mailbox_aliases) {
|
||||
$sql_join .= " LEFT JOIN $table_alias ON $table_mailbox.username=$table_alias.address ";
|
||||
}
|
||||
|
||||
if ($password_expiration) {
|
||||
$sql_select .= ", $table_mailbox.password_expiry as password_expiration ";
|
||||
}
|
||||
|
||||
if (Config::bool('vacation_control_admin')) {
|
||||
$table_vacation = table_by_key('vacation');
|
||||
$sql_select .= ", $table_vacation.active AS v_active ";
|
||||
|
@ -88,11 +88,11 @@ function _upgrade_filter_function($name) {
|
||||
return preg_match('/upgrade_[\d]+(_mysql|_pgsql|_sqlite|_mysql_pgsql)?$/', $name) == 1;
|
||||
}
|
||||
|
||||
function _db_add_field($table, $field, $fieldtype, $after) {
|
||||
function _db_add_field($table, $field, $fieldtype, $after = '') {
|
||||
global $CONF;
|
||||
|
||||
$query = "ALTER TABLE " . table_by_key($table) . " ADD COLUMN $field $fieldtype";
|
||||
if ($CONF['database_type'] == 'mysql') {
|
||||
if ($CONF['database_type'] == 'mysql' && !empty($after)) {
|
||||
$query .= " AFTER $after "; # PgSQL does not support to specify where to add the column, MySQL does
|
||||
}
|
||||
|
||||
@ -1760,3 +1760,8 @@ function upgrade_1841_sqlite() {
|
||||
_db_add_field($table, 'token_validity', '{DATETIME}', 'token');
|
||||
}
|
||||
}
|
||||
|
||||
function upgrade_1842() {
|
||||
_db_add_field('mailbox', 'password_expiry', "{DATETIME}"); // when a specific mailbox password expires
|
||||
_db_add_field('domain', 'password_expiry', 'int DEFAULT 0'); // expiry applied to mailboxes within that domain
|
||||
}
|
||||
|
@ -71,6 +71,16 @@
|
||||
{if $CONF.show_undeliverable===YES}
|
||||
<span style='background-color:{$CONF.show_undeliverable_color};'>{$CONF.show_status_text}</span>={$PALANG.pStatus_undeliverable}
|
||||
{/if}
|
||||
{if $CONF.show_vacation===YES}
|
||||
<span style='background-color:{$CONF.show_vacation_color};'>{$CONF.show_status_text}</span>={$PALANG.pStatus_vacation}
|
||||
{/if}
|
||||
{if $CONF.show_disabled===YES}
|
||||
<span style='background-color:{$CONF.show_disabled_color};'>{$CONF.show_status_text}</span>={$PALANG.pStatus_disabled}
|
||||
{/if}
|
||||
{if $CONF.show_expired===YES}
|
||||
<span style='background-color:{$CONF.show_expired_color};'>{$CONF.show_status_text}</span>={$PALANG.pStatus_expired}
|
||||
{/if}
|
||||
|
||||
{if $CONF.show_popimap===YES}
|
||||
<span style='background-color:{$CONF.show_popimap_color};'>{$CONF.show_status_text}</span>={$PALANG.pStatus_popimap}
|
||||
{/if}
|
||||
|
@ -13,6 +13,9 @@
|
||||
<td>{$PALANG.name}</td>
|
||||
{if $CONF.quota===YES}<td>{$PALANG.pOverview_mailbox_quota}</td>{/if}
|
||||
<td>{$PALANG.last_modified}</td>
|
||||
{if $CONF.password_expiration===YES}
|
||||
<td>{$PALANG.password_expiration}</td>
|
||||
{/if}
|
||||
<td>{$PALANG.active}</td>
|
||||
{assign var="colspan" value="`$colspan-6`"}
|
||||
<td colspan="{$colspan}"> </td>
|
||||
@ -74,6 +77,9 @@
|
||||
</td>
|
||||
{/if}
|
||||
<td>{$item.modified}</td>
|
||||
{if $CONF.password_expiration===YES}
|
||||
<td>{$item.password_expiration}</td>
|
||||
{/if}
|
||||
<td><a href="{#url_editactive#}mailbox&id={$item.username|escape:"url"}&active={if ($item.active==0)}1{else}0{/if}&token={$smarty.session.PFA_token|escape:"url"}"
|
||||
>{if $item.active==1}{$PALANG.YES}{else}{$PALANG.NO}{/if}</a></td>
|
||||
{if $CONF.vacation_control_admin===YES && $CONF.vacation===YES}
|
||||
|
Loading…
Reference in New Issue
Block a user