Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cb1b209
First steps to work on migration from ProFTPd Admin 1.0 to 2.X
Damien-Martins Jul 31, 2018
ca92399
Migration script added
Damien-Martins Jul 31, 2018
5d08bac
Fix add_user : problem when creating a user. Only the Backend mode wa…
lucguinchard Nov 29, 2018
76709aa
When the user database is empty and $cfg ['default_uid'] is not defin…
lucguinchard Nov 29, 2018
71001dc
Improvement of the information message on the validation of the uid
lucguinchard Nov 29, 2018
0b1808b
Added the get_last_gid method to return the last index number of the …
lucguinchard Nov 30, 2018
f2851b7
When creating a user, if no group exists, a redirection is made to th…
lucguinchard Nov 30, 2018
5b0d3a1
When deleting a group. The 'reallyremove' page redirects directly to …
lucguinchard Nov 30, 2018
e324bd3
The cancel button has been modified by View group
lucguinchard Nov 30, 2018
29ad86f
The cancel button has been modified by "View user"
lucguinchard Nov 30, 2018
06dc60a
When deleting a user. The 'reallyremove' page redirects directly to t…
lucguinchard Nov 30, 2018
676ce29
fix default_gid : add value in config_example.php
lucguinchard Dec 3, 2018
4966b9a
The value of the shell is now in the configuration file.
lucguinchard Dec 3, 2018
5b5a5b7
Added the ability to encrypt passwords by SHA256 and SHA512
lucguinchard Dec 3, 2018
312b57a
Adding a link to the user record by clicking on the user's name.
lucguinchard Dec 6, 2018
95757af
Improvement of the information message on the validation of the gid
lucguinchard Dec 6, 2018
51e0fc3
Improvement of the information message on the validation of the uid
lucguinchard Dec 6, 2018
b9bf32a
Added methods get_gid_message () and get_uid_message () in AdminClass.
lucguinchard Dec 6, 2018
992e0df
Hides password field content
Nov 20, 2019
0475386
Merge pull request #1 from lucguinchard/master
doktoil-makresh May 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -86,6 +91,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
Expand Down
46 changes: 34 additions & 12 deletions add_group.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
$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 = $ac->get_gid_message();

if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "create") {
/* group name validation */
Expand All @@ -37,15 +41,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.');
Expand All @@ -65,6 +64,29 @@
}
}

/* 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'];
}
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");
?>
<?php include ("includes/messages.php"); ?>
Expand All @@ -77,21 +99,21 @@
<div class="panel-body">
<div class="row">
<div class="col-sm-12">
<form role="form" class="form-horizontal" method="post" data-toggle="validator">
<form role="form" class="form-horizontal" method="post" data-toggle="validator" action="<?php echo $action ?>">
<!-- Group name -->
<div class="form-group">
<label for="<?php echo $cfg['field_groupname']; ?>" class="col-sm-4 control-label">Group name</label>
<div class="controls col-sm-8">
<input type="text" class="form-control" id="<?php echo $cfg['field_groupname']; ?>" name="<?php echo $cfg['field_groupname']; ?>" placeholder="Enter a group name" maxlength="<?php echo $cfg['max_groupname_length']; ?>" pattern="<?php echo substr($cfg['groupname_regex'], 2, -3); ?>" required>
<input type="text" class="form-control" id="<?php echo $cfg['field_groupname']; ?>" name="<?php echo $cfg['field_groupname']; ?>" value="<?php echo $groupname; ?>" placeholder="Enter a group name" maxlength="<?php echo $cfg['max_groupname_length']; ?>" pattern="<?php echo substr($cfg['groupname_regex'], 2, -3); ?>" required>
<p class="help-block"><small>Only letters, numbers, hyphens, and underscores. Maximum <?php echo $cfg['max_groupname_length']; ?> characters.</small></p>
</div>
</div>
<!-- GID -->
<div class="form-group">
<label for="<?php echo $cfg['field_gid']; ?>" class="col-sm-4 control-label">GID</label>
<div class="col-sm-8">
<input type="number" class="form-control" id="<?php echo $field_gid; ?>" name="<?php echo $field_gid; ?>" placeholder="Enter the GID" min="1" required>
<p class="help-block"><small>Positive integer.</small></p>
<input type="number" class="form-control" id="<?php echo $field_gid; ?>" name="<?php echo $field_gid; ?>" value="<?php echo $gid; ?>" placeholder="Enter the GID" min="1" required>
<p class="help-block"><small><?php echo $gidMessage; ?></small></p>
</div>
</div>
<!-- Actions -->
Expand Down
46 changes: 32 additions & 14 deletions add_user.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand All @@ -34,9 +35,13 @@
$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 */
$uidMessage = $ac->get_uid_message();

/* Data validation */
if (empty($errormsg) && !empty($_REQUEST["action"]) && $_REQUEST["action"] == "create") {
$errors = array();
Expand All @@ -50,14 +55,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])) {
Expand All @@ -67,6 +66,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.');
Expand All @@ -90,6 +94,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],
Expand Down Expand Up @@ -126,6 +131,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];
Expand All @@ -138,20 +144,25 @@
/* 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'];
}
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];
$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 = "";
Expand Down Expand Up @@ -187,7 +198,7 @@
<label for="<?php echo $field_uid; ?>" class="col-sm-4 control-label">UID</label>
<div class="controls col-sm-8">
<input type="number" class="form-control" id="<?php echo $field_uid; ?>" name="<?php echo $field_uid; ?>" value="<?php echo $uid; ?>" min="1" placeholder="Enter a UID" required />
<p class="help-block"><small>Positive integer.</small></p>
<p class="help-block"><small><?php echo $uidMessage; ?></small></p>
</div>
</div>
<!-- Main group -->
Expand Down Expand Up @@ -216,10 +227,17 @@
<div class="form-group">
<label for="<?php echo $field_passwd; ?>" class="col-sm-4 control-label">Password</label>
<div class="controls col-sm-8">
<input type="text" class="form-control" id="<?php echo $field_passwd; ?>" name="<?php echo $field_passwd; ?>" value="<?php echo $passwd; ?>" placeholder="Enter a password" minlength="<?php echo $cfg['min_passwd_length']; ?>" required />
<input type="password" class="form-control" id="<?php echo $field_passwd; ?>" name="<?php echo $field_passwd; ?>" value="" placeholder="Enter a password" minlength="<?php echo $cfg['min_passwd_length']; ?>" required />
<p class="help-block"><small>Minimum length <?php echo $cfg['min_passwd_length']; ?> characters.</small></p>
</div>
</div>
<!-- Password confirmation -->
<div class="form-group">
<label for="<?php echo $field_passwd2; ?>" class="col-sm-4 control-label">Confirm password</label>
<div class="controls col-sm-8">
<input type="password" class="form-control" id="<?php echo $field_passwd2; ?>" name="<?php echo $field_passwd2; ?>" value "" placeholder="Confirm password" />
</div>
</div>
<!-- Home directory -->
<div class="form-group">
<label for="<?php echo $field_homedir; ?>" class="col-sm-4 control-label">Home directory</label>
Expand Down
6 changes: 5 additions & 1 deletion configs/config_example.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@
$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
$cfg['default_shell'] = "/bin/false";
// 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
// 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";
Expand Down
17 changes: 7 additions & 10 deletions edit_group.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
}
}

/* find the right message for gid */
$gidMessage = $ac->get_gid_message();

if (empty($errormsg) && !empty($_REQUEST["action"]) && $_REQUEST["action"] == "update") {
$errors = array();
/* gid validation */
Expand All @@ -57,14 +60,8 @@
array_push($errors, 'GID already exists; GID must be unique.');
}
/* gid range validation */
if ($cfg['max_gid'] != -1 && $cfg['min_gid'] != -1) {
if ($_REQUEST[$field_newgid] > $cfg['max_gid'] || $_REQUEST[$field_newgid] < $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_newgid] > $cfg['max_gid']) {
array_push($errors, 'Invalid GID; GID must be at most ' . $cfg['max_gid'] . '.');
} else if ($cfg['min_gid'] != -1 && $_REQUEST[$field_newgid] < $cfg['min_gid']) {
array_push($errors, 'Invalid GID; GID must be at least ' . $cfg['min_gid'] . '.');
if (($cfg['max_gid'] != -1 && $_REQUEST[$field_newgid] > $cfg['max_gid']) or ($cfg['min_gid'] != -1 && $_REQUEST[$field_newgid] < $cfg['min_gid'])) {
array_push($errors, 'Invalid GID; '.$gidMessage);
}
if (count($errors) == 0) {
/* data validation passed */
Expand Down Expand Up @@ -115,7 +112,7 @@
$user = $ac->get_user_by_id($u_id); ?>
<tr>
<td class="pull-middle"><?php echo $user[$field_uid]; ?></td>
<td class="pull-middle"><?php echo $u_userid; ?></td>
<td class="pull-middle"><a href="edit_user.php?action=show&<?php echo $field_id; ?>=<?php echo $u_id; ?>"><?php echo $u_userid; ?></a></td>
<td class="pull-middle"><?php echo ($user[$field_disabled] ? 'Yes' : 'No'); ?></td>
<td class="pull-middle">
<div class="btn-toolbar pull-right" role="toolbar">
Expand Down Expand Up @@ -185,7 +182,7 @@
<label for="<?php echo $cfg['field_gid']; ?>" class="col-sm-4 control-label">New GID</label>
<div class="col-sm-8">
<input type="number" class="form-control" id="new_<?php echo $cfg['field_gid']; ?>" name="new_<?php echo $cfg['field_gid']; ?>" value="<?php echo $gid; ?>" placeholder="Enter the new GID" min="1" required />
<p class="help-block"><small>Positive integer.</small></p>
<p class="help-block"><small><?php echo $gidMessage; ?></small></p>
</div>
</div>
<!-- Actions -->
Expand Down
Loading