From cb1b2093b8c3d553f3930e8434a043889a9b7c41 Mon Sep 17 00:00:00 2001 From: Damien Martins Date: Tue, 31 Jul 2018 15:20:25 +0200 Subject: [PATCH 01/19] First steps to work on migration from ProFTPd Admin 1.0 to 2.X Supports MYSQL PASSWORD() function through passwd_encryption = "MYSQL_Backend"; Updated REAMD.md and config_example.php files TODO: migrate DB from old to new format --- README.md | 47 +++++++++++++++++++++++++++++++++++++- configs/config_example.php | 2 ++ includes/AdminClass.php | 11 ++++++++- includes/mysql_backend.php | 10 ++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 includes/mysql_backend.php diff --git a/README.md b/README.md index c80a28e..391eec7 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This GUI for ProFTPd was written to support a basic user management feature when There is no build-in security, so you have to protect the directory with something else, like Apache Basic Authentication. -It's possible to use either of SHA1 and pbkdf2 with either of MySQL/MariaDB and sqlite3. pbkdf2 is supported since ProFTPd 1.3.5. +It's possible to use either of SHA1, MYSQL_Backend and pbkdf2 with either of MySQL/MariaDB and sqlite3. pbkdf2 is supported since ProFTPd 1.3.5. You can look at some [screenshots](screenshots/README.md) to see if this is the tool you need. @@ -86,6 +86,51 @@ SQLNamedQuery files-in-count UPDATE "files_in_used=files_in_used+1 WHE 8. Start ProFTPd. 9. Go to http://yourwebspace/proftpdadmin/ and start using it! +### Using MySQL backend password hashing + +1. Install ProFTPd with MySQL support + - Debian: apt-get install proftpd-mod-mysql + - Gentoo: USE="mysql" emerge proftpd +2. Create a MySQL database (use something like phpMyAdmin for this), for example: "proftpd". +3. Use tables.sql to populate the database (you can use phpMyAdmin for this). +4. Add the following to your proftpd.conf and sql.conf (edit to your needs): + +``` +CreateHome on 775 +AuthOrder mod_sql.c + +SQLBackend mysql +SQLEngine on +SQLPasswordEngine on +SQLAuthenticate on +SQLAuthTypes Backend + +SQLConnectInfo database@localhost username password +SQLUserInfo users userid passwd uid gid homedir shell +SQLGroupInfo groups groupname gid members +SQLUserWhereClause "disabled != 1" +SQLLog PASS updatecount +SQLNamedQuery updatecount UPDATE "login_count=login_count+1, last_login=now() WHERE userid='%u'" users + + # Used to track xfer traffic per user (without invoking a quota) +SQLLog RETR bytes-out-count +SQLNamedQuery bytes-out-count UPDATE "bytes_out_used=bytes_out_used+%b WHERE userid='%u'" users +SQLLog RETR files-out-count +SQLNamedQuery files-out-count UPDATE "files_out_used=files_out_used+1 WHERE userid='%u'" users + +SQLLog STOR bytes-in-count +SQLNamedQuery bytes-in-count UPDATE "bytes_in_used=bytes_in_used+%b WHERE userid='%u'" users +SQLLog STOR files-in-count +SQLNamedQuery files-in-count UPDATE "files_in_used=files_in_used+1 WHERE userid='%u'" users +``` + +5. Extract all files to your webspace (into a subdirectory like "proftpdadmin"). +6. Secure access to this directory (for example: create a .htaccess file if using apache) +7. Edit the configs/config_example.php file to your needs and rename it to config.php. +8. Start ProFTPd. +9. Go to http://yourwebspace/proftpdadmin/ and start using it! + + ### Using sqlite3 and pbkdf2 1. Install ProFTPd with sqlite3 support diff --git a/configs/config_example.php b/configs/config_example.php index bb6a8cf..30d2c9c 100644 --- a/configs/config_example.php +++ b/configs/config_example.php @@ -44,6 +44,8 @@ // Use either SHA1 or MD5 or any other supported by your MySQL-Server and ProFTPd // "pbkdf2" is supported if you are using ProFTPd 1.3.5. // "crypt" uses the unix crypt() function. +// "MYSQL_Backend" uses the PASSWORD() function from MySQL to hash the password. Useful when migrating from ProFTPd Admin 1.0 +// Emtpy value means cleartext storage // "OpenSSL:sha1" other digest-names also possible; see: http://www.proftpd.org/docs/directives/configuration_full.html#SQLAUTHTYPES $cfg['passwd_encryption'] = "SHA1"; $cfg['min_passwd_length'] = "6"; diff --git a/includes/AdminClass.php b/includes/AdminClass.php index 98b4a4e..0997387 100644 --- a/includes/AdminClass.php +++ b/includes/AdminClass.php @@ -16,6 +16,9 @@ require "hash_pbkdf2_compat.php"; } elseif ($cfg['passwd_encryption'] == "crypt") { require "unix_crypt.php"; +} elseif ($cfg['passwd_encryption'] == "Backend") { + require "mysql_backend.php"; + } include_once "ez_sql_core.php"; @@ -301,13 +304,16 @@ function add_user($userdata) { } else if ($passwd_encryption == 'crypt') { $passwd = unix_crypt($userdata[$field_passwd]); $passwd = '"'.$passwd.'"'; + } else if ($passwd_encryption == 'Backend') { + $passwd = mysql_backend($userdata[$field_passwd]); + $passwd_format = ' %s="%s", '; } else if (strpos($passwd_encryption, "OpenSSL:") === 0) { $passwd_digest = substr($passwd_encryption, strpos($passwd_encryption, ':')+1); $passwd = 'CONCAT("{'.$passwd_digest.'}",TO_BASE64(UNHEX('.$passwd_digest.'("'.$userdata[$field_passwd].'"))))'; } else { $passwd = $passwd_encryption.'("'.$userdata[$field_passwd].'")'; } - $format = 'INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ("%s","%s","%s",%s,"%s","%s","%s","%s","%s","%s","%s","%s","%s")'; + $format = 'INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ("%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s")'; $query = sprintf($format, $this->config['table_users'], $field_userid, $field_uid, @@ -587,6 +593,9 @@ function update_user($userdata) { } else if ($passwd_encryption == 'crypt') { $passwd = unix_crypt($userdata[$field_passwd]); $passwd_format = ' %s="%s", '; + } else if ($passwd_encryption == 'MYSQL_Backend') { + $passwd = mysql_backend($userdata[$field_passwd]); + $passwd_format = ' %s="%s", '; } else if (strpos($passwd_encryption, "OpenSSL:") === 0) { $passwd_digest = substr($passwd_encryption, strpos($passwd_encryption, ':')+1); $passwd = 'CONCAT("{'.$passwd_digest.'}",TO_BASE64(UNHEX('.$passwd_digest.'("'.$userdata[$field_passwd].'"))))'; diff --git a/includes/mysql_backend.php b/includes/mysql_backend.php new file mode 100644 index 0000000..28c5de9 --- /dev/null +++ b/includes/mysql_backend.php @@ -0,0 +1,10 @@ + + */ + +function mysql_backend($password) { + return '*'.strtoupper(hash('sha1',pack('H*',hash('sha1', $password)))); +} From ca9239973e9b72192ae2518c5453127c727a4319 Mon Sep 17 00:00:00 2001 From: Damien Martins Date: Tue, 31 Jul 2018 17:56:03 +0200 Subject: [PATCH 02/19] Migration script added Migration procedure detailled in README file --- README.md | 5 ++++ migrate_proftpd_admin_1_to_2.sh | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 migrate_proftpd_admin_1_to_2.sh diff --git a/README.md b/README.md index 391eec7..3cba906 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,11 @@ You can look at some [screenshots](screenshots/README.md) to see if this is the If you want to upgrade the hashing algorithm you have to change all passwords after changing the configs (both ProFTPd and ProFTPd Admin). +## Migration + +If you want to migrate from ProFTPd Admin 1.0 (http://proftpd-adm.sourceforge.net/), please use migrate_proftpd_admin_1_to_2.sh script (think to edit it to comply with your setup) +Data will be read from original DB and reordered in a new DB. Please follow installation steps before starting the migration script. + ## Installation ### Using MySQL and SHA1 diff --git a/migrate_proftpd_admin_1_to_2.sh b/migrate_proftpd_admin_1_to_2.sh new file mode 100644 index 0000000..e1d4375 --- /dev/null +++ b/migrate_proftpd_admin_1_to_2.sh @@ -0,0 +1,42 @@ +#!/bin/bash +mysql_old_user=CHANGE_ME +mysql_old_password=CHANGE_ME +mysql_old_db=CHANGE_ME +mysql_old_host=CHANGE_ME +mysql_new_user=CHANGE_ME +mysql_new_password=CHANGE_ME +mysql_new_db=CHANGE_ME +mysql_new_host=CHANGE_ME +tmp_file=/tmp/$mysql_old_db_usertable_$$ + +#Dump original data to temporary file +echo "Dumping original data from $mysql_old_db to $tmp_file" +mysql -u $mysql_old_user -p$mysql_old_password -h $mysql_old_host -D $mysql_old_db -e "select * from usertable INTO OUTFILE '$tmp_file' FIELDS TERMINATED BY ','" + +#Read data from temporary file +while read line ; do + userid=$(echo $line | cut -d ',' -f 1) + passwd=$(echo $line | cut -d ',' -f 2) + homedir=$(echo $line | cut -d ',' -f 3) + shell=$(echo $line | cut -d ',' -f 4) + uid=$(echo $line | cut -d ',' -f 5) + gid=$(echo $line | cut -d ',' -f 6) + login_count=$(echo $line | cut -d ',' -f 7) + last_login=$(echo $line | cut -d ',' -f 8) + disabled=$(echo $line | cut -d ',' -f 11) + name=$(echo $line | cut -d ',' -f 12) + email=$(echo $line | cut -d ',' -f 13) + comment='' + title='' + company='' + bytes_in_used=0 + bytes_out_used=0 + files_in_used=0 + files_out_used=0 + last_modified=$(date '+%F %T') +#Write values in destination DB + mysql -u $mysql_new_user -p$mysql_new_password -h $mysql_new_host -D $mysql_new_db << EOF +insert into users values('$uid','$userid','$uid','$gid','$passwd','$homedir','$comment','$disabled','$shell','$email','$name','$title','$company','$bytes_in_used','$bytes_out_used','$files_in_used','$files_out_used','$login_count','$last_login','$last_modified'); +EOF +done <$tmp_file +rm $tmp_file From 5d08bac8dbc709b0ddb291312c88bf8919736be4 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Thu, 29 Nov 2018 15:37:44 +0100 Subject: [PATCH 03/19] Fix add_user : problem when creating a user. Only the Backend mode was operational. --- includes/AdminClass.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/AdminClass.php b/includes/AdminClass.php index 0997387..3f0347b 100644 --- a/includes/AdminClass.php +++ b/includes/AdminClass.php @@ -306,14 +306,14 @@ function add_user($userdata) { $passwd = '"'.$passwd.'"'; } else if ($passwd_encryption == 'Backend') { $passwd = mysql_backend($userdata[$field_passwd]); - $passwd_format = ' %s="%s", '; + $passwd = '"'.$passwd.'"'; } else if (strpos($passwd_encryption, "OpenSSL:") === 0) { $passwd_digest = substr($passwd_encryption, strpos($passwd_encryption, ':')+1); $passwd = 'CONCAT("{'.$passwd_digest.'}",TO_BASE64(UNHEX('.$passwd_digest.'("'.$userdata[$field_passwd].'"))))'; } else { $passwd = $passwd_encryption.'("'.$userdata[$field_passwd].'")'; } - $format = 'INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ("%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s","%s")'; + $format = 'INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ("%s","%s","%s",%s,"%s","%s","%s","%s","%s","%s","%s","%s","%s")'; $query = sprintf($format, $this->config['table_users'], $field_userid, $field_uid, From 76709aacad63c765db64023c0f35ceaf4d9e84b6 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Thu, 29 Nov 2018 17:26:50 +0100 Subject: [PATCH 04/19] When the user database is empty and $cfg ['default_uid'] is not defined. $uid takes the value of min_uid (if $cfg ['min_uid']! = -1) --- add_user.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/add_user.php b/add_user.php index ee890a1..57abc91 100644 --- a/add_user.php +++ b/add_user.php @@ -138,7 +138,12 @@ /* Default values */ $userid = ""; if (empty($cfg['default_uid'])) { - $uid = $ac->get_last_uid() + 1; + $uid = $ac->get_last_uid(); + if ($uid == 0 && $cfg['min_uid'] != -1) { + $uid = $cfg['min_uid']; + } else { + $uid = $uid + 1; + } } else { $uid = $cfg['default_uid']; } From 71001dc1591518eef40affce388ba17d6345893a Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Thu, 29 Nov 2018 18:19:09 +0100 Subject: [PATCH 05/19] Improvement of the information message on the validation of the uid --- add_user.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/add_user.php b/add_user.php index 57abc91..873dfad 100644 --- a/add_user.php +++ b/add_user.php @@ -37,6 +37,16 @@ $errormsg = 'There are no groups in the database; please create at least one group before creating users.'; } +/* find the right message for uid */ +$uidMessage = "Positive integer."; +if ($cfg['max_uid'] != -1 && $cfg['min_uid'] != -1) { + $uidMessage = 'UID must be between ' . $cfg['min_uid'] . ' and ' . $cfg['max_uid'] . '.'; +} else if ($cfg['max_uid'] != -1) { + $uidMessage = 'UID must be at most ' . $cfg['max_uid'] . '.'; +} else if ($cfg['min_uid'] != -1) { + $uidMessage = 'UID must be at least ' . $cfg['min_uid'] . '.'; +} + /* Data validation */ if (empty($errormsg) && !empty($_REQUEST["action"]) && $_REQUEST["action"] == "create") { $errors = array(); @@ -50,14 +60,8 @@ if (empty($_REQUEST[$field_uid]) || !$ac->is_valid_id($_REQUEST[$field_uid])) { array_push($errors, 'Invalid UID; must be a positive integer.'); } - if ($cfg['max_uid'] != -1 && $cfg['min_uid'] != -1) { - if ($_REQUEST[$field_uid] > $cfg['max_uid'] || $_REQUEST[$field_uid] < $cfg['min_uid']) { - array_push($errors, 'Invalid UID; UID must be between ' . $cfg['min_uid'] . ' and ' . $cfg['max_uid'] . '.'); - } - } else if ($cfg['max_uid'] != -1 && $_REQUEST[$field_uid] > $cfg['max_uid']) { - array_push($errors, 'Invalid UID; UID must be at most ' . $cfg['max_uid'] . '.'); - } else if ($cfg['min_uid'] != -1 && $_REQUEST[$field_uid] < $cfg['min_uid']) { - array_push($errors, 'Invalid UID; UID must be at least ' . $cfg['min_uid'] . '.'); + if (($cfg['max_uid'] != -1 && $_REQUEST[$field_uid] > $cfg['max_uid']) or ($cfg['min_uid'] != -1 && $_REQUEST[$field_uid] < $cfg['min_uid'])) { + array_push($errors, 'Invalid UID; '.$uidMessage ); } /* gid validation */ if (empty($_REQUEST[$field_ugid]) || !$ac->is_valid_id($_REQUEST[$field_ugid])) { @@ -192,7 +196,7 @@
-

Positive integer.

+

From 0b1808b0eecf15d96a5331e74ad5e7497b34db2c Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Fri, 30 Nov 2018 09:37:23 +0100 Subject: [PATCH 06/19] Added the get_last_gid method to return the last index number of the group table. Added information message about the validation of the gid. Retrieving form values in errors to display in the new form. --- add_group.php | 47 +++++++++++++++++++++++++++++++---------- includes/AdminClass.php | 11 ++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/add_group.php b/add_group.php index 334916f..e4bfca5 100644 --- a/add_group.php +++ b/add_group.php @@ -22,6 +22,16 @@ $field_members = $cfg['field_members']; $errors = array(); +/* find the right message for gid */ +$gidMessage = "Positive integer."; +if ($cfg['max_gid'] != -1 && $cfg['min_gid'] != -1) { + $gidMessage = 'GID must be between ' . $cfg['min_gid'] . ' and ' . $cfg['max_gid'] . '.'; +} else if ($cfg['max_gid'] != -1) { + $gidMessage = 'GID must be at most ' . $cfg['max_gid'] . '.'; +} else if ($cfg['min_gid'] != -1) { + $gidMessage = 'GID must be at least ' . $cfg['min_gid'] . '.'; +} + if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "create") { /* group name validation */ if (empty($_REQUEST[$field_groupname]) @@ -37,15 +47,10 @@ if (empty($_REQUEST[$field_gid]) || !$ac->is_valid_id($_REQUEST[$field_gid])) { array_push($errors, 'Invalid GID; GID must be a positive integer.'); } - if ($cfg['max_gid'] != -1 && $cfg['min_gid'] != -1) { - if ($_REQUEST[$field_gid] > $cfg['max_gid'] || $_REQUEST[$field_gid] < $cfg['min_gid']) { - array_push($errors, 'Invalid GID; GID must be between ' . $cfg['min_gid'] . ' and ' . $cfg['max_gid'] . '.'); - } - } else if ($cfg['max_gid'] != -1 && $_REQUEST[$field_gid] > $cfg['max_gid']) { - array_push($errors, 'Invalid GID; GID must be at most ' . $cfg['max_gid'] . '.'); - } else if ($cfg['min_gid'] != -1 && $_REQUEST[$field_gid] < $cfg['min_gid']) { - array_push($errors, 'Invalid GID; GID must be at least ' . $cfg['min_gid'] . '.'); + if (($cfg['max_gid'] != -1 && $_REQUEST[$field_gid] > $cfg['max_gid']) or ($cfg['min_gid'] != -1 && $_REQUEST[$field_gid] < $cfg['min_gid'])) { + array_push($errors, 'Invalid GID; '.$gidMessage); } + /* gid uniqueness validation */ if ($ac->check_gid($_REQUEST[$field_gid])) { array_push($errors, 'GID already exists; GID must be unique.'); @@ -65,6 +70,26 @@ } } +/* Form values */ +if (isset($errormsg)) { + /* This is a failed attempt */ + $groupname = $_REQUEST[$field_groupname]; + $gid = $_REQUEST[$field_gid]; +} else { + /* Default values */ + $groupname = ""; + if (empty($cfg['default_gid'])) { + $gid = $ac->get_last_gid(); + if ($gid == 0 && $cfg['min_gid'] != -1) { + $gid = $cfg['min_gid']; + } else { + $gid = $gid + 1; + } + } else { + $gid = $cfg['default_gid']; + } +} + include ("includes/header.php"); ?> @@ -82,7 +107,7 @@
- +

Only letters, numbers, hyphens, and underscores. Maximum characters.

@@ -90,8 +115,8 @@
- -

Positive integer.

+ +

diff --git a/includes/AdminClass.php b/includes/AdminClass.php index 3f0347b..9c600a6 100644 --- a/includes/AdminClass.php +++ b/includes/AdminClass.php @@ -191,6 +191,17 @@ function get_last_uid() { return $result; } + /** + * returns the last index number of the group table + * @return Integer + */ + function get_last_gid() { + $format = 'SELECT MAX(%s) FROM %s'; + $query = sprintf($format, $this->config['field_gid'], $this->config['table_groups']); + $result = $this->dbConn->get_var($query); + return $result; + } + /** * Checks if the given groupname is already in the database * @param String $groupname From f2851b7732d24076a95d0b1792332eebe1e79ef2 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Fri, 30 Nov 2018 11:47:23 +0100 Subject: [PATCH 07/19] When creating a user, if no group exists, a redirection is made to the form for creating a group. This avoids the NOTICES when displaying the creation form of a user. A redirect message was created: createLoginWithoutGroup --- add_group.php | 6 +++++- add_user.php | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/add_group.php b/add_group.php index e4bfca5..cb92173 100644 --- a/add_group.php +++ b/add_group.php @@ -21,6 +21,7 @@ $field_groupname = $cfg['field_groupname']; $field_members = $cfg['field_members']; $errors = array(); +$action = explode('?', $_SERVER['REQUEST_URI'], 2)[0]; /* find the right message for gid */ $gidMessage = "Positive integer."; @@ -88,6 +89,9 @@ } else { $gid = $cfg['default_gid']; } + if (!empty($_REQUEST["error"]) && $_REQUEST["error"] == "createLoginWithoutGroup") { + $errormsg = "There are no groups in the database; please create at least one group before creating users."; + } } include ("includes/header.php"); @@ -102,7 +106,7 @@
-
+
diff --git a/add_user.php b/add_user.php index 873dfad..fdf06b0 100644 --- a/add_user.php +++ b/add_user.php @@ -34,7 +34,8 @@ $groups = $ac->get_groups(); if (count($groups) == 0) { - $errormsg = 'There are no groups in the database; please create at least one group before creating users.'; + header('Location: add_group.php?error=createLoginWithoutGroup'); + exit(); } /* find the right message for uid */ From 5b0d3a10cd3ba1c98cdefdc92001394293b9061b Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Fri, 30 Nov 2018 14:49:09 +0100 Subject: [PATCH 08/19] When deleting a group. The 'reallyremove' page redirects directly to the list of groups. --- groups.php | 7 +++++++ remove_group.php | 29 ++++------------------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/groups.php b/groups.php index fc964ac..753f2be 100644 --- a/groups.php +++ b/groups.php @@ -19,6 +19,13 @@ $groups = $ac->get_groups(); +if (!empty($_REQUEST["error"]) && $_REQUEST["error"] == "removeGroup") { + $errormsg = 'Group "'.$_REQUEST["groupname"].'" removal failed; see log files for more information.'; +} +if (!empty($_REQUEST["info"]) && $_REQUEST["info"] == "removeGroup") { + $infomsg = 'Group "'.$_REQUEST["groupname"].'" removed successfully.'; +} + include ("includes/header.php"); ?> diff --git a/remove_group.php b/remove_group.php index 74a3e4f..52c0b7c 100644 --- a/remove_group.php +++ b/remove_group.php @@ -51,36 +51,16 @@ if (empty($errormsg) && !empty($_REQUEST["action"]) && $_REQUEST["action"] == "reallyremove") { /* data validation passed */ if ($ac->delete_group_by_gid($gid)) { - $infomsg = 'Group "'.$groupname.'" removed successfully.'; + header('Location: groups.php?info=removeGroup&groupname='.$groupname); } else { - $errormsg = 'Group "'.$groupname.'" removal failed; see log files for more information.'; + header('Location: groups.php?error=removeGroup&groupname='.$groupname); } + exit(); } include ("includes/header.php"); -?> - +include ("includes/messages.php"); ?> - - -
-
-
-
-
- -
- -
-
-
-
-
-
- -
@@ -111,6 +91,5 @@
- From e324bd3975275d1f8cf2e14933f4181265fd0b66 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Fri, 30 Nov 2018 14:51:50 +0100 Subject: [PATCH 09/19] The cancel button has been modified by View group --- remove_group.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remove_group.php b/remove_group.php index 52c0b7c..3220ab1 100644 --- a/remove_group.php +++ b/remove_group.php @@ -81,7 +81,7 @@
- Cancel + View group
From 29ad86fa4ac567f3216d3014be13278724ec716e Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Fri, 30 Nov 2018 15:55:35 +0100 Subject: [PATCH 10/19] The cancel button has been modified by "View user" --- remove_user.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remove_user.php b/remove_user.php index 14b885b..2fb056e 100644 --- a/remove_user.php +++ b/remove_user.php @@ -98,7 +98,7 @@
- Cancel + View user
From 06dc60aaf06ec8bc91703b264c7097671343d645 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Fri, 30 Nov 2018 16:39:48 +0100 Subject: [PATCH 11/19] When deleting a user. The 'reallyremove' page redirects directly to the list of users. --- remove_user.php | 32 ++++++-------------------------- users.php | 11 +++++++++++ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/remove_user.php b/remove_user.php index 2fb056e..97de025 100644 --- a/remove_user.php +++ b/remove_user.php @@ -41,43 +41,24 @@ $groups = $ac->get_groups(); while (list($g_gid, $g_group) = each($groups)) { if (!$ac->remove_user_from_group($userid, $g_gid)) { - $errormsg = 'Cannot remove user "'.$userid.'" from group "'.$g_group.'"; see log files for more information.'; + header('Location: users.php?info=removeUser&userid='.$userid.'&g_group='.$g_group); break; } } if (empty($errormsg)) { if ($ac->remove_user_by_id($id)) { - $infomsg = 'User "'.$userid.'" removed successfully.'; + header('Location: users.php?info=removeUser&userid='.$userid); } else { - $errormsg = 'User "'.$userid.'" removal failed; see log files for more information.'; + header('Location: users.php?error=removeUser&userid='.$userid); } } + exit(); } include ("includes/header.php"); +include ("includes/messages.php"); ?> - - - -
-
-
-
-
- -
- -
-
-
-
-
-
- -
@@ -108,6 +89,5 @@
- - + \ No newline at end of file diff --git a/users.php b/users.php index 440e27b..20352d8 100644 --- a/users.php +++ b/users.php @@ -41,6 +41,17 @@ $all_users = $ac->get_users(); $users = array(); +if (!empty($_REQUEST["error"]) && $_REQUEST["error"] == "removeUser") { + if (!empty($_REQUEST["g_group"]) ) { + $errormsg = 'Cannot remove user "'.$_REQUEST["userid"].'" from group "'.$_REQUEST["g_group"].'"; see log files for more information.'; + } else { + $errormsg = 'User "'.$_REQUEST["userid"].'" removal failed; see log files for more information.'; + } +} +if (!empty($_REQUEST["info"]) && $_REQUEST["info"] == "removeUser") { + $infomsg = 'User "'.$_REQUEST["userid"].'" removed successfully.'; +} + /* parse filter */ $userfilter = array(); $ufilter=""; From 676ce29119e4b454324e9cc5a5627c83543491b8 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Mon, 3 Dec 2018 10:00:03 +0100 Subject: [PATCH 12/19] fix default_gid : add value in config_example.php --- configs/config_example.php | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/config_example.php b/configs/config_example.php index 30d2c9c..e8a834c 100644 --- a/configs/config_example.php +++ b/configs/config_example.php @@ -40,6 +40,7 @@ $cfg['field_members'] = "members"; $cfg['default_uid'] = ""; //if empty next incremental will be default +$cfg['default_gid'] = ""; //if empty next incremental will be default $cfg['default_homedir'] = "/srv/ftp"; // Use either SHA1 or MD5 or any other supported by your MySQL-Server and ProFTPd // "pbkdf2" is supported if you are using ProFTPd 1.3.5. From 4966b9a1c85d3cd7b372bb3b7b6eb875bce38c29 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Mon, 3 Dec 2018 10:01:35 +0100 Subject: [PATCH 13/19] The value of the shell is now in the configuration file. --- add_user.php | 2 +- configs/config_example.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/add_user.php b/add_user.php index fdf06b0..8e24f7d 100644 --- a/add_user.php +++ b/add_user.php @@ -155,7 +155,7 @@ if (empty($infomsg)) { $ugid = ""; $ad_gid = array(); - $shell = "/bin/false"; + $shell = $cfg['default_shell']; } else { $ugid = $_REQUEST[$field_ugid]; $ad_gid = $_REQUEST[$field_ad_gid]; diff --git a/configs/config_example.php b/configs/config_example.php index e8a834c..2b2eab8 100644 --- a/configs/config_example.php +++ b/configs/config_example.php @@ -42,6 +42,7 @@ $cfg['default_uid'] = ""; //if empty next incremental will be default $cfg['default_gid'] = ""; //if empty next incremental will be default $cfg['default_homedir'] = "/srv/ftp"; +$cfg['default_shell'] = "/bin/false"; // Use either SHA1 or MD5 or any other supported by your MySQL-Server and ProFTPd // "pbkdf2" is supported if you are using ProFTPd 1.3.5. // "crypt" uses the unix crypt() function. From 5b5a5b73e809defa7764afdb38173a619e8fe28c Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Mon, 3 Dec 2018 12:38:37 +0100 Subject: [PATCH 14/19] Added the ability to encrypt passwords by SHA256 and SHA512 --- configs/config_example.php | 2 +- includes/AdminClass.php | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/configs/config_example.php b/configs/config_example.php index 2b2eab8..c8e265a 100644 --- a/configs/config_example.php +++ b/configs/config_example.php @@ -43,7 +43,7 @@ $cfg['default_gid'] = ""; //if empty next incremental will be default $cfg['default_homedir'] = "/srv/ftp"; $cfg['default_shell'] = "/bin/false"; -// Use either SHA1 or MD5 or any other supported by your MySQL-Server and ProFTPd +// Use either SHA1 or SHA256 or SHA512 or MD5 or any other supported by your MySQL-Server and ProFTPd // "pbkdf2" is supported if you are using ProFTPd 1.3.5. // "crypt" uses the unix crypt() function. // "MYSQL_Backend" uses the PASSWORD() function from MySQL to hash the password. Useful when migrating from ProFTPd Admin 1.0 diff --git a/includes/AdminClass.php b/includes/AdminClass.php index 9c600a6..9caff2f 100644 --- a/includes/AdminClass.php +++ b/includes/AdminClass.php @@ -321,6 +321,10 @@ function add_user($userdata) { } else if (strpos($passwd_encryption, "OpenSSL:") === 0) { $passwd_digest = substr($passwd_encryption, strpos($passwd_encryption, ':')+1); $passwd = 'CONCAT("{'.$passwd_digest.'}",TO_BASE64(UNHEX('.$passwd_digest.'("'.$userdata[$field_passwd].'"))))'; + } else if ($passwd_encryption == 'SHA256') { + $passwd = "SHA2('$userdata[$field_passwd]', 256)"; + } else if ($passwd_encryption == 'SHA512') { + $passwd = "SHA2('$userdata[$field_passwd]', 512)"; } else { $passwd = $passwd_encryption.'("'.$userdata[$field_passwd].'")'; } @@ -611,6 +615,12 @@ function update_user($userdata) { $passwd_digest = substr($passwd_encryption, strpos($passwd_encryption, ':')+1); $passwd = 'CONCAT("{'.$passwd_digest.'}",TO_BASE64(UNHEX('.$passwd_digest.'("'.$userdata[$field_passwd].'"))))'; $passwd_format = ' %s=%s, '; + } else if ($passwd_encryption == 'SHA256') { + $passwd = "SHA2('$userdata[$field_passwd]', 256)"; + $passwd_format = ' %s=%s, '; + } else if ($passwd_encryption == 'SHA512') { + $passwd = "SHA2('$userdata[$field_passwd]', 512)"; + $passwd_format = ' %s=%s, '; } else { $passwd = $passwd_encryption.'("'.$userdata[$field_passwd].'")'; $passwd_format = ' %s=%s, '; @@ -673,5 +683,5 @@ function generate_random_string($length = 6) { function is_valid_id($id) { return is_numeric($id) && (int)$id > 0 && $id == round($id); } -} + } ?> From 312b57a83f9cbeb6229562e619031e4c0bb5dbfd Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Thu, 6 Dec 2018 12:36:13 +0100 Subject: [PATCH 15/19] Adding a link to the user record by clicking on the user's name. --- edit_group.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit_group.php b/edit_group.php index ff42a4b..62a3cb7 100644 --- a/edit_group.php +++ b/edit_group.php @@ -115,7 +115,7 @@ $user = $ac->get_user_by_id($u_id); ?> - + From 51e0fc34346c53fe2cb01bdd6a7443bfedf17b63 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Thu, 6 Dec 2018 14:28:44 +0100 Subject: [PATCH 17/19] Improvement of the information message on the validation of the uid --- edit_user.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/edit_user.php b/edit_user.php index b18da80..749d17d 100644 --- a/edit_user.php +++ b/edit_user.php @@ -45,6 +45,16 @@ die(); } +/* find the right message for uid */ +$uidMessage = "Positive integer."; +if ($cfg['max_uid'] != -1 && $cfg['min_uid'] != -1) { + $uidMessage = 'UID must be between ' . $cfg['min_uid'] . ' and ' . $cfg['max_uid'] . '.'; +} else if ($cfg['max_uid'] != -1) { + $uidMessage = 'UID must be at most ' . $cfg['max_uid'] . '.'; +} else if ($cfg['min_uid'] != -1) { + $uidMessage = 'UID must be at least ' . $cfg['min_uid'] . '.'; +} + $groups = $ac->get_groups(); $id = $_REQUEST[$field_id]; @@ -77,14 +87,8 @@ if (empty($_REQUEST[$field_uid]) || !$ac->is_valid_id($_REQUEST[$field_uid])) { array_push($errors, 'Invalid UID; must be a positive integer.'); } - if ($cfg['max_uid'] != -1 && $cfg['min_uid'] != -1) { - if ($_REQUEST[$field_uid] > $cfg['max_uid'] || $_REQUEST[$field_uid] < $cfg['min_uid']) { - array_push($errors, 'Invalid UID; UID must be between ' . $cfg['min_uid'] . ' and ' . $cfg['max_uid'] . '.'); - } - } else if ($cfg['max_uid'] != -1 && $_REQUEST[$field_uid] > $cfg['max_uid']) { - array_push($errors, 'Invalid UID; UID must be at most ' . $cfg['max_uid'] . '.'); - } else if ($cfg['min_uid'] != -1 && $_REQUEST[$field_uid] < $cfg['min_uid']) { - array_push($errors, 'Invalid UID; UID must be at least ' . $cfg['min_uid'] . '.'); + if (($cfg['max_uid'] != -1 && $_REQUEST[$field_uid] > $cfg['max_uid']) or ($cfg['min_uid'] != -1 && $_REQUEST[$field_uid] < $cfg['min_uid'])) { + array_push($errors, 'Invalid UID; '.$uidMessage ); } /* gid validation */ if (empty($_REQUEST[$field_ugid]) || !$ac->is_valid_id($_REQUEST[$field_ugid])) { @@ -288,7 +292,7 @@
-

Positive integer.

+

From b9bf32a5faba5df9f647a5156c479858420908a7 Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Thu, 6 Dec 2018 15:16:34 +0100 Subject: [PATCH 18/19] Added methods get_gid_message () and get_uid_message () in AdminClass. Use these two methods in adding / editing groups and users. --- add_group.php | 9 +-------- add_user.php | 9 +-------- edit_group.php | 9 +-------- edit_user.php | 9 +-------- includes/AdminClass.php | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/add_group.php b/add_group.php index cb92173..2693ff3 100644 --- a/add_group.php +++ b/add_group.php @@ -24,14 +24,7 @@ $action = explode('?', $_SERVER['REQUEST_URI'], 2)[0]; /* find the right message for gid */ -$gidMessage = "Positive integer."; -if ($cfg['max_gid'] != -1 && $cfg['min_gid'] != -1) { - $gidMessage = 'GID must be between ' . $cfg['min_gid'] . ' and ' . $cfg['max_gid'] . '.'; -} else if ($cfg['max_gid'] != -1) { - $gidMessage = 'GID must be at most ' . $cfg['max_gid'] . '.'; -} else if ($cfg['min_gid'] != -1) { - $gidMessage = 'GID must be at least ' . $cfg['min_gid'] . '.'; -} +$gidMessage = $ac->get_gid_message(); if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "create") { /* group name validation */ diff --git a/add_user.php b/add_user.php index 8e24f7d..bd82897 100644 --- a/add_user.php +++ b/add_user.php @@ -39,14 +39,7 @@ } /* find the right message for uid */ -$uidMessage = "Positive integer."; -if ($cfg['max_uid'] != -1 && $cfg['min_uid'] != -1) { - $uidMessage = 'UID must be between ' . $cfg['min_uid'] . ' and ' . $cfg['max_uid'] . '.'; -} else if ($cfg['max_uid'] != -1) { - $uidMessage = 'UID must be at most ' . $cfg['max_uid'] . '.'; -} else if ($cfg['min_uid'] != -1) { - $uidMessage = 'UID must be at least ' . $cfg['min_uid'] . '.'; -} +$uidMessage = $ac->get_uid_message(); /* Data validation */ if (empty($errormsg) && !empty($_REQUEST["action"]) && $_REQUEST["action"] == "create") { diff --git a/edit_group.php b/edit_group.php index 8c0f420..c84e215 100644 --- a/edit_group.php +++ b/edit_group.php @@ -46,14 +46,7 @@ } /* find the right message for gid */ -$gidMessage = "Positive integer."; -if ($cfg['max_gid'] != -1 && $cfg['min_gid'] != -1) { - $gidMessage = 'GID must be between ' . $cfg['min_gid'] . ' and ' . $cfg['max_gid'] . '.'; -} else if ($cfg['max_gid'] != -1) { - $gidMessage = 'GID must be at most ' . $cfg['max_gid'] . '.'; -} else if ($cfg['min_gid'] != -1) { - $gidMessage = 'GID must be at least ' . $cfg['min_gid'] . '.'; -} +$gidMessage = $ac->get_gid_message(); if (empty($errormsg) && !empty($_REQUEST["action"]) && $_REQUEST["action"] == "update") { $errors = array(); diff --git a/edit_user.php b/edit_user.php index 749d17d..11692cc 100644 --- a/edit_user.php +++ b/edit_user.php @@ -46,14 +46,7 @@ } /* find the right message for uid */ -$uidMessage = "Positive integer."; -if ($cfg['max_uid'] != -1 && $cfg['min_uid'] != -1) { - $uidMessage = 'UID must be between ' . $cfg['min_uid'] . ' and ' . $cfg['max_uid'] . '.'; -} else if ($cfg['max_uid'] != -1) { - $uidMessage = 'UID must be at most ' . $cfg['max_uid'] . '.'; -} else if ($cfg['min_uid'] != -1) { - $uidMessage = 'UID must be at least ' . $cfg['min_uid'] . '.'; -} +$uidMessage = $ac->get_uid_message(); $groups = $ac->get_groups(); diff --git a/includes/AdminClass.php b/includes/AdminClass.php index 9caff2f..8e01b26 100644 --- a/includes/AdminClass.php +++ b/includes/AdminClass.php @@ -202,6 +202,40 @@ function get_last_gid() { return $result; } + /** + * returns the right message for uid + * @return String + */ + function get_uid_message() { + /* find the right message for uid */ + $uidMessage = "Positive integer."; + if ($this->config['max_uid'] != -1 && $this->config['min_uid'] != -1) { + $uidMessage = 'UID must be between ' . $this->config['min_uid'] . ' and ' . $this->config['max_uid'] . '.'; + } else if ($this->config['max_uid'] != -1) { + $uidMessage = 'UID must be at most ' . $this->config['max_uid'] . '.'; + } else if ($this->config['min_uid'] != -1) { + $uidMessage = 'UID must be at least ' . $this->config['min_uid'] . '.'; + } + return $uidMessage; + } + + /** + * returns the right message for gid + * @return String + */ + function get_gid_message() { + /* find the right message for gid */ + $gidMessage = "Positive integer."; + if ($this->config['max_gid'] != -1 && $this->config['min_gid'] != -1) { + $gidMessage = 'GID must be between ' . $this->config['min_gid'] . ' and ' . $this->config['max_gid'] . '.'; + } else if ($this->config['max_gid'] != -1) { + $gidMessage = 'GID must be at most ' . $this->config['max_gid'] . '.'; + } else if ($this->config['min_gid'] != -1) { + $gidMessage = 'GID must be at least ' . $this->config['min_gid'] . '.'; + } + return $gidMessage; + } + /** * Checks if the given groupname is already in the database * @param String $groupname From 992e0df354daa1f07b666458c0d767880c96ad8a Mon Sep 17 00:00:00 2001 From: mdmien Date: Wed, 20 Nov 2019 21:32:38 +0100 Subject: [PATCH 19/19] Hides password field content Requires password validation --- add_user.php | 19 +++++++++++++++++-- edit_user.php | 15 ++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/add_user.php b/add_user.php index ee890a1..83a24a0 100644 --- a/add_user.php +++ b/add_user.php @@ -22,6 +22,7 @@ $field_ugid = $cfg['field_ugid']; $field_ad_gid = 'ad_gid'; $field_passwd = $cfg['field_passwd']; +$field_passwd2 = $cfg['field_passwd2']; $field_homedir = $cfg['field_homedir']; $field_shell = $cfg['field_shell']; $field_title = $cfg['field_title']; @@ -67,6 +68,11 @@ if (strlen($_REQUEST[$field_passwd]) < $cfg['min_passwd_length']) { array_push($errors, 'Password is too short; minimum length is '.$cfg['min_passwd_length'].' characters.'); } + /* password confirmation validation */ + if ($_REQUEST[$field_passwd] != $_REQUEST[$field_passwd2]) { + array_push($errors, 'Passwords are not matching'); + } + /* home directory validation */ if (strlen($_REQUEST[$field_homedir]) <= 1) { array_push($errors, 'Invalid home directory; home directory cannot be empty.'); @@ -90,6 +96,7 @@ $field_uid => $_REQUEST[$field_uid], $field_ugid => $_REQUEST[$field_ugid], $field_passwd => $_REQUEST[$field_passwd], + $field_passwd2 => $_REQUEST[$field_passwd2], $field_homedir => $_REQUEST[$field_homedir], $field_shell => $_REQUEST[$field_shell], $field_title => $_REQUEST[$field_title], @@ -126,6 +133,7 @@ $ugid = $_REQUEST[$field_ugid]; $ad_gid = $_REQUEST[$field_ad_gid]; $passwd = $_REQUEST[$field_passwd]; + $passwd2 = $_REQUEST[$field_passwd2]; $homedir = $_REQUEST[$field_homedir]; $shell = $_REQUEST[$field_shell]; $title = $_REQUEST[$field_title]; @@ -151,7 +159,7 @@ $ad_gid = $_REQUEST[$field_ad_gid]; $shell = $_REQUEST[$field_shell]; } - $passwd = $ac->generate_random_string((int) $cfg['min_passwd_length']); + //$passwd = $ac->generate_random_string((int) $cfg['min_passwd_length']); $homedir = $cfg['default_homedir']; $title = "m"; $name = ""; @@ -216,10 +224,17 @@
- +

Minimum length characters.

+ +
+ +
+ +
+
diff --git a/edit_user.php b/edit_user.php index b18da80..0cfe8bf 100644 --- a/edit_user.php +++ b/edit_user.php @@ -23,6 +23,7 @@ $field_ugid = $cfg['field_ugid']; $field_ad_gid = 'ad_gid'; $field_passwd = $cfg['field_passwd']; +$field_passwd2 = $cfg['field_passwd2']; $field_homedir = $cfg['field_homedir']; $field_shell = $cfg['field_shell']; $field_title = $cfg['field_title']; @@ -94,6 +95,11 @@ if (strlen($_REQUEST[$field_passwd]) > 0 && strlen($_REQUEST[$field_passwd]) < $cfg['min_passwd_length']) { array_push($errors, 'Password is too short; minimum length is '.$cfg['min_passwd_length'].' characters.'); } + /* password confirmation validation */ + if ($_REQUEST[$field_passwd] != $_REQUEST[$field_passwd2]) { + array_push($errors, 'Passwords are not matching'); +} + /* home directory validation */ if (strlen($_REQUEST[$field_homedir]) <= 1) { array_push($errors, 'Invalid home directory; home directory cannot be empty.'); @@ -317,10 +323,17 @@
- +

Minimum length characters.

+ +
+ +
+ +
+