Compare commits

..

97 commits
master ... dev

Author SHA1 Message Date
Oliver Hartl
f4c9f4c98a Not maintained anymore 2017-09-14 05:12:24 +02:00
Oliver Hartl
1426b5840c Fix getting server ip 2016-09-08 21:51:07 +02:00
Oliver Hartl
3594c6e236 Running webmum with php built-in server 2016-09-08 21:50:15 +02:00
Oliver Hartl
a7c3a4f271 Implementation of the WebMUM installer (#52)
* Preparation for installer and discontinue support of old config style.

* Adding installer with step 0

* Fixing travis config, because example config isn't loaded anymore if normal config does not exist.

* Adding installer step 1: database connection

* Adding installer step 2: database schema

* Adding installer step 3: Your first admin user

* Minor fix on step 3

* Adding installer step 4: general settings

* Adding installer step 5: optional features

* Adding final installation step 7: writing config & finish

* Display the current installation progress

* Fixing installation finish

* Disable direct access to installer

* Change colors of "notifying" text

* Add check and cross marks to requirements page of the installer

* Change how config directory is checked, no more writable check but error if writing fails.

* Execute post-receice..
2016-06-06 18:49:21 +02:00
ohartl
dbb66ee3aa Fix column doesn't exist problem on redirects 2016-05-31 19:16:41 +02:00
ohartl
2e2ac12073 Fix column doesn't exist problem 2016-05-31 19:11:05 +02:00
ohartl
2926d3fab2 Add own exception class for auth, so catching of auth exceptions wont catch all exceptions anymore 2016-05-31 19:01:26 +02:00
ohartl
29dc137f0e Adding readme text for user redirects, this will close #27 2016-05-19 13:24:29 +02:00
ohartl
99925c690e phpdoc 2016-05-17 23:36:03 +02:00
ohartl
171f2c7f11 Fix Database init parameter handling 2016-05-16 19:45:13 +02:00
ohartl
5ba6def517 Fixing example schema for tests 2016-05-10 00:26:40 +02:00
ohartl
f50b8e1360 Fixing some PHP strict complain 2016-05-10 00:21:17 +02:00
ohartl
ecd556cdba Adding optional feature so "users can create redirects on their own" (amount can be limited by admin) #27 2016-05-10 00:18:21 +02:00
ohartl
38715854b1 Minor UI improvements 2016-05-10 00:15:17 +02:00
ohartl
09c0a7c248 Adding helper for singular/plural text 2016-05-10 00:14:31 +02:00
ohartl
23f1cd5b06 Fix every value of a model was casted to string by getter method 2016-05-10 00:12:35 +02:00
ohartl
410fe43330 Improvements in handling exceptions, database now showing all errors through exceptions. 2016-05-10 00:11:09 +02:00
ohartl
1f44769030 Closes #44 2016-05-05 14:33:13 +02:00
ohartl
4d8143eb0d Weakening email validation so redirects on tld will work again, users should know what they are doing 2016-05-05 13:55:39 +02:00
ohartl
6b9605db73 Fix faulty redirect count on domains 2016-05-05 13:48:06 +02:00
ohartl
5109879ab6 Fix multi source redirects bug 2016-04-30 00:48:36 +02:00
ohartl
25ff5bf759 Changing texts, since ownership changed 2016-04-30 00:23:55 +02:00
Oliver Hartl
a5ec3e879e Better random number gen and fix for travis (#50) 2016-04-21 22:11:45 +02:00
Thomas Leister
e3d634a8a3 Update README.md 2016-04-21 15:41:46 +02:00
ohartl
03ca96edaf Sort emails in alphabetical order 2016-03-19 01:28:38 +01:00
ohartl
6642705d55 Prepare Database class for tests 2016-03-07 23:48:54 +01:00
ohartl
8f2661e199 Make old config warning visible for everybody 2016-03-06 18:57:02 +01:00
ohartl
d47ae1b9ae Remove pointless singleton testing 2016-03-05 16:02:51 +01:00
ohartl
800777a4e4 Add test for Router, and making some minor fixes on Router 2016-03-05 15:58:09 +01:00
ohartl
a0c88ff757 Move error templates in another location 2016-03-04 11:47:11 +01:00
ohartl
79d1ba2d6a Add test for Message 2016-03-03 01:41:29 +01:00
Oliver Hartl
283b4b9d25 Fix travis-ci badge 2016-03-02 10:04:48 +01:00
Thomas Leister
c6d5191e76 Adds TravicCI badge 2016-03-02 10:03:05 +01:00
ohartl
0368a99c80 Fix travis mysql charset 2016-03-02 09:48:23 +01:00
ohartl
c59789f425 Add test for Auth 2016-03-02 09:28:28 +01:00
ohartl
9f985600c0 Add test for Config 2016-03-02 09:28:17 +01:00
ohartl
ffbf29eb4c Add TestCase base class that will setup users, and in future also domains, .. 2016-03-02 09:28:00 +01:00
ohartl
c3171b972f Add phpunit and travis ci config 2016-03-02 09:26:52 +01:00
ohartl
e60420ce79 readme spelling 2016-03-01 15:37:39 +01:00
ohartl
bf1c6b7972 readme 2016-03-01 15:31:35 +01:00
ohartl
8cd5baeee2 Update readme for new config style 2016-03-01 15:30:31 +01:00
ohartl
fadbd4a6b4 Changes to work with new config and some minor fixes 2016-03-01 15:12:28 +01:00
ohartl
25f08f5e76 Add new config style and Config class for better configuration. 2016-03-01 14:56:32 +01:00
ohartl
3be0a51c75 Protect config files from public access 2016-03-01 14:38:12 +01:00
ohartl
d3003715dc Remove autogenerated comment 2016-03-01 11:00:21 +01:00
ohartl
2d3ae751f7 Fix for php 5.4 2016-02-29 11:25:48 +01:00
ohartl
7ef1e6d53a Refactoring database specific functionality to Database class 2016-02-28 00:35:32 +01:00
ohartl
34f1b7d5f1 Refactoring Auth 2016-02-28 00:31:53 +01:00
ohartl
7336611dd2 Refactoring Message 2016-02-28 00:30:14 +01:00
ohartl
cc998c9dfb Fix autoload 2016-02-27 12:33:17 +01:00
ohartl
300dadbab6 Refactoring, move messages to Message class 2016-02-27 11:49:58 +01:00
ohartl
5e72d0e715 Refactoring on url and redirect methods, moving them to Router 2016-02-26 22:47:02 +01:00
ohartl
b8486843a8 Change padding on footer 2016-02-25 01:27:44 +01:00
ohartl
93f023d81a Add redirect count to users list and improve getting a users redirects 2016-02-25 01:23:15 +01:00
ohartl
4dc50750c9 Add a page to list redirects to the own mailbox 2016-02-25 00:53:21 +01:00
Oliver Hartl
dda397074c Merge pull request #37 from ohartl/feature-limit-admin-domains
Adding feature to limit domains an admin can manage
2016-02-24 23:55:51 +01:00
ohartl
9c175037fe Merge branch 'dev' into feature-limit-admin-domains
# Conflicts:
#	include/php/models/AbstractRedirect.php
#	include/php/models/User.php
#	include/php/pages/admin/listredirects.php
#	include/php/pages/admin/listusers.php
2016-02-24 23:52:49 +01:00
ohartl
7090bef419 Add warnings for mailboxes overridden by redirects 2016-02-24 04:58:55 +01:00
ohartl
e9f8d850f0 Use new display error and fix some redirects 2016-02-23 04:45:57 +01:00
ohartl
1f736a9746 Move logout to a routing callback 2016-02-23 04:42:08 +01:00
ohartl
39a38be904 Add error display method to Router and ability to add callbacks to routing 2016-02-23 04:40:49 +01:00
ohartl
f68e75f801 Merge branch 'dev' into feature-limit-admin-domains 2016-02-23 04:14:24 +01:00
ohartl
40d5b4141f Replace old routing with new Router class, also adding a routes.inc.php for configuring the routes 2016-02-23 03:37:40 +01:00
ohartl
19ec250851 Fix sql 2016-02-22 01:12:09 +01:00
ohartl
ce380e587b Add optional support for admin domain limits 2016-02-22 01:07:21 +01:00
ohartl
3c9b0c6415 Spelling 2016-02-22 00:47:37 +01:00
ohartl
342e0d510f Fix frontend email text separator 2016-02-21 18:16:37 +01:00
ohartl
0b1c3788ab Fix password changing 2016-02-21 04:33:37 +01:00
ohartl
d3c56b815c No more need to set the SUBDIR in config, its getting extracted from the FRONTEND_BASE_PATH 2016-02-20 23:34:30 +01:00
ohartl
7dfaa0c22e Fix urls to root 2016-02-20 22:40:43 +01:00
ohartl
70b6463501 Fix change password 2016-02-20 22:36:04 +01:00
ohartl
75598698d1 Fix duplicate source addresses 2016-02-20 22:21:43 +01:00
ohartl
c4fa44cff5 Fix problems with output send before headers and some refactoring 2016-02-20 21:32:29 +01:00
ohartl
82de30f636 Fix line-height being to height for table cells 2016-02-20 20:45:51 +01:00
ohartl
084c2abd0e Fix mysqli::fetch_all not supported in some php version 2016-02-20 20:41:41 +01:00
ohartl
2cf74eafc7 Merge branch 'dev' of https://github.com/ThomasLeister/webmum into dev 2016-02-20 00:22:25 +01:00
ohartl
2a62fcb377 Fix session problems 2016-02-20 00:14:53 +01:00
Thomas Leister
8954a01b69 Updates footer 2016-02-19 16:44:49 +01:00
Thomas Leister
a5a6931236 Merge pull request #35 from ohartl/readme-update
Adding contributors to readme
2016-02-19 16:28:33 +01:00
ohartl
88e701b23d Update readme 2016-02-19 16:24:02 +01:00
ohartl
c182551e8f Change all pages to use models 2016-02-19 15:47:38 +01:00
ohartl
3964e5331b Multiple little fixes, typos and reparation for some future code 2016-02-19 15:08:38 +01:00
ohartl
32d0216a83 Use models for authentication 2016-02-19 13:18:00 +01:00
ohartl
297cebd57b Add models for every case, so much work.. 2016-02-19 13:15:48 +01:00
ohartl
a1e13a9919 Add own ORM without any dependencies, yay 2016-02-19 13:13:36 +01:00
ohartl
3b8466ab7f Add dependency injection to load classes 2016-02-18 19:27:06 +01:00
ohartl
da0fb4738f Protect the codebase by denying direct access and executing of php scripts in /include/php 2016-02-18 18:04:29 +01:00
ohartl
6bd36d6623 Use role constants for roles 2016-02-18 17:51:10 +01:00
ohartl
d0b38d17b1 Moving authentication relevant things to its own class replacing checkpermission and old user.class.php, and a modern approach on validation without global variables 2016-02-18 17:44:27 +01:00
ohartl
838188174e Add model for user as a start of a more modern mvc pattern like approach on architecture 2016-02-18 17:41:01 +01:00
ohartl
f6952b44fd Closing mysqli db connections at the end of execution is unnecessary since its closed by php automatically there anyways 2016-02-18 17:37:46 +01:00
ohartl
db0520368d Fix email validation 2016-02-18 15:36:39 +01:00
ohartl
35a2b9cec2 Merge branch 'patch-redirect' into dev 2016-02-18 15:23:49 +01:00
ohartl
4b833acddf Minor changes in README 2016-02-18 15:14:04 +01:00
Thomas Leister
9dad71e647 Adds config file notice 2016-02-18 11:41:14 +01:00
Thomas Leister
ac7b4172a9 Fix things 2016-02-18 11:37:58 +01:00
Thomas Leister
fa68acc1e4 Adds config.inc.php.example as a config template to be copied to config.inc.php 2016-02-18 11:35:14 +01:00
74 changed files with 7435 additions and 1479 deletions

2
.gitignore vendored
View file

@ -1 +1 @@
config/config_override.inc.php
config/config.php

26
.travis.yml Normal file
View file

@ -0,0 +1,26 @@
language: php
php:
- '5.4'
- '5.5'
- '5.6'
- '7.0'
- hhvm
- nightly
matrix:
allow_failures:
- php: hhvm
- php: nightly
services:
- mysql
before_install:
# Create example schema
- mysql -u root -e "CREATE USER 'vmail'@'localhost' IDENTIFIED BY 'vmail';"
- mysql -u root -e "CREATE DATABASE IF NOT EXISTS vmail;"
- mysql -u root -e "GRANT ALL PRIVILEGES ON vmail.* TO 'vmail'@'localhost'"
- mysql -u root -e "CREATE TABLE vmail.domains (id int(10) unsigned NOT NULL AUTO_INCREMENT, domain varchar(128) NOT NULL, PRIMARY KEY (domain), UNIQUE KEY id (id)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;"
- mysql -u root -e "CREATE TABLE vmail.users (id int(10) unsigned NOT NULL AUTO_INCREMENT, username varchar(128) NOT NULL DEFAULT '', domain varchar(128) NOT NULL DEFAULT '', password varchar(128) NOT NULL DEFAULT '', mailbox_limit int(10) NOT NULL DEFAULT '128', max_user_redirects int(10) NOT NULL DEFAULT '0', PRIMARY KEY (username,domain), UNIQUE KEY id (id)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;"
- mysql -u root -e "CREATE TABLE vmail.aliases (id int(10) unsigned NOT NULL AUTO_INCREMENT, source varchar(128) NOT NULL, destination text NOT NULL, multi_source varchar(32) DEFAULT NULL, is_created_by_user int(1) NOT NULL DEFAULT '0', PRIMARY KEY (source), UNIQUE KEY id (id)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;"
# Copy the example config
- cp config/config.php.example config/config.php
notifications:
email: false

296
README.md
View file

@ -1,5 +1,9 @@
:exclamation: This project **isn't maintained** anymore. New maintainers welcome, just contact [@ohartl](https://github.com/ohartl) . Please consider using alternatives like https://github.com/Andreas-Bresch/vmailManage/ :exclamation:
# WebMUM - Web Mailserver User Manager
[![Build Status](https://travis-ci.org/ohartl/webmum.svg)](https://travis-ci.org/ohartl/webmum)
***WebMUM is not compatible with the [new Mailserver-HowTo](https://thomas-leister.de/allgemein/sicherer-mailserver-dovecot-postfix-virtuellen-benutzern-mysql-ubuntu-server-xenial/)!,*** but we will try to implement the changes for the release of version 1.0.0.
WebMUM is a web frontend based on PHP which helps you to manage e-mail server via MySQL. This software is licensed under the MIT license.
@ -9,19 +13,25 @@ Founder of this project is [ThomasLeister](https://github.com/ThomasLeister), a
Feel free to send in issues and pull requests, your support for this project is much appreciated!
## Installation
Clone the WebMUM Repository to your webserver's virtual host root directory:
```php
```bash
git clone https://github.com/ohartl/webmum
```
A update / upgrade guide can be found [here](#update--upgrade-webmum).
### Webserver
Now configure your webserver. URL rewriting to index.php is required.
### Nginx
#### Nginx
Nginx config examples following, but you still need to change domain and path in config as explained in WebMum Config -> Paths.
Nginx config examples following, but you still need to change domain and path in config as explained [here](#paths).
With subdirectory `webmum/` in URL (e.g. `http://mydomain.tld/webmum/`):
@ -41,7 +51,17 @@ server {
}
location /webmum {
try_files $uri $uri/ /webmum/index.php?$args;
try_files $uri $uri/ /webmum/index.php?$args;
}
# protect the codebase by denying direct access
location ^~ /webmum/include/php {
deny all;
return 403;
}
location ^~ /webmum/config {
deny all;
return 403;
}
}
```
@ -64,14 +84,24 @@ server {
}
location / {
try_files $uri $uri/ /index.php?$args;
try_files $uri $uri/ /index.php?$args;
}
# protect the codebase by denying direct access
location ^~ /include/php {
deny all;
return 403;
}
location ^~ /config {
deny all;
return 403;
}
}
```
### Apache
#### Apache
Apache config examples following, but you still need to change domain and path in config as explained in WebMum Config -> Paths.
Apache config examples following, but you still need to change domain and path in config as explained [here](#paths).
Please note: mod_rewrite must be enabled for URL rewriting:
@ -86,11 +116,11 @@ With subdirectory `webmum/` in URL (e.g. `http://mydomain.tld/webmum/`):
ServerName domain.tld
DocumentRoot /var/www/domain.tld
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^\/webmum/(.*)\.css$ /webmum/$1.css [L]
RewriteRule ^\/webmum/(.*)$ /webmum/index.php [L,QSA]
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^\/webmum/(.*)\.css$ /webmum/$1.css [L]
RewriteRule ^\/webmum/(.*)$ /webmum/index.php [L,QSA]
</VirtualHost>
```
@ -101,92 +131,61 @@ Without subdirectory in URL (e.g. `http://webmum.mydomain.tld/`):
ServerName webmum.domain.tld
DocumentRoot /var/www/domain.tld/webmum
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule (.*)\.css$ $1.css [L]
RewriteRule ^(.*)$ /index.php [L,QSA]
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule (.*)\.css$ $1.css [L]
RewriteRule ^(.*)$ /index.php [L,QSA]
</VirtualHost>
```
Access to the codebase is denied with a `.htaccess` file, that can be found in `/include/php`.
## WebMUM Configuration
Configure WebMUM via the configuration file at `config/config.inc.php`.
Configure WebMUM via the configuration file at `config/config.inc.php`.
### MySQL
At first the database access has to be configured.
At first the database access has to be configured under the config key `mysql`.
Check if you've got the same database schema as configured in the config key `schema`.
### Mailbox limit (Optional)
If you want to use your "mailbox_limit" column to limit the size of your users' mailboxes, just enable mailbox limit in the options.
```php
/*
* MySQL server and database settings
*/
define("MYSQL_HOST", "localhost");
define("MYSQL_USER", "vmail");
define("MYSQL_PASSWORD", "vmail");
define("MYSQL_DATABASE", "vmail");
'options' => array(
...
'enable_mailbox_limits' => true,
...
),
```
... then define the table names according to your own setup:
```php
/*
* Database table names
*/
// Table names
define("DBT_USERS", "users");
define("DBT_DOMAINS", "domains");
define("DBT_ALIASES", "aliases");
```
... and finally the table column names:
```php
// Users table columns
define("DBC_USERS_ID", "id");
define("DBC_USERS_USERNAME", "username");
define("DBC_USERS_DOMAIN", "domain");
define("DBC_USERS_PASSWORD", "password");
//define("DBC_USERS_MAILBOXLIMIT", "mailbox_limit");
WebMUM will then show a new field "Mailbox limit" in the frontend.
// Domains table columns
define("DBC_DOMAINS_ID", "id");
define("DBC_DOMAINS_DOMAIN", "domain");
// Aliases table columns
define("DBC_ALIASES_ID", "id");
define("DBC_ALIASES_SOURCE", "source");
define("DBC_ALIASES_DESTINATION", "destination");
//define("DBC_ALIASES_MULTI_SOURCE", "multi_source");
```
### Mailbox limit
If you have a "mailbox_limit" column to limit the size of your users' mailboxes, just comment in the line
```php
define("DBC_USERS_MAILBOXLIMIT", "mailbox_limit");
```
in your configuration. WebMUM will then show a new field "Mailbox limit" in the frontend.
### Multiple source redirect support
### Multiple source redirect support (Optional)
As mailservers can only process a single source address for redirects the database table for aliases / redirects can only hold a single source address in a row.
WebMum will, if you enabled the multiple source redirect support, do some magic so there is only a single address in a row even though multiple adresses where entered.
To make this work another column in the database table is required, which holds an identifier for the list of source adresses, so they can be edited like normal redirects.
WebMum will, if you enabled the multiple source redirect support, do some magic so there is only a single address in a row even though multiple addresses where entered.
To make this work another column in the database table is required, which holds an identifier for the list of source addresses, so they can be edited like normal redirects.
By default you can only redirect a single address to a single or multiple destinations.
If you want to enable support for redirecting multiple source adresses to a destionation, just comment in the line
If you want to enable support for redirecting multiple source addresses to a destination, just enable it in the options:
```php
define("DBC_ALIASES_MULTI_SOURCE", "multi_source");
'options' => array(
...
'enable_multi_source_redirects' => true,
...
),
```
in your configuration. And add the following column to your database table for aliases / redirects:
And add the following column to your database table for aliases / redirects:
```sql
ALTER TABLE `aliases` ADD COLUMN `multi_source` VARCHAR(32) NULL DEFAULT NULL;
@ -194,38 +193,81 @@ ALTER TABLE `aliases` ADD COLUMN `multi_source` VARCHAR(32) NULL DEFAULT NULL;
WebMUM will then show a larger field for source addresses in the frontend and you can not list emails in source field.
### Admin domain limits (Optional)
If you share your mailserver with others, host their domains and they should be able to manage their domains, but not all domains on that mailserver then this is the right option for you.
You have to add that user to the `admins` array in your configuration and enable admin domain limits in the options:
```php
'options' => array(
...
'enable_admin_domain_limits' => true,
...
),
```
also you have to make an entry in the `admin_domain_limits` array, for example `peter@his.tld` should be able to manage his domains `his.tld` and `his-company.tld` then configure the following:
```php
'admin_domain_limits' => array(
'peter@his.tld' => array('his.tld', 'his-company.tld'),
);
```
Admins that have been listed in `admin_domain_limits` don't have access to the "Manage domains" pages, otherwise they could delete domains they are managing, but maybe someone else owns.
### Users redirects (Optional)
If you want to enable some users to create redirects on their own, either limited by a maximum number of redirects or unlimited, this is the right option for you.
You have to enable this feature in the options:
```php
'options' => array(
...
'enable_user_redirects' => true,
...
),
```
And add the following columns to your database tables for aliases / redirects and users:
```sql
ALTER TABLE `aliases` ADD COLUMN `is_created_by_user` INT(1) NOT NULL DEFAULT '0';
```
Note: By choosing a default value for the `max_user_redirects` column on the users table, you can set the default state of user redirects for new users. `0` = unlimited, `-1` = disabled and a number larger than 0 will limit user redirects.
```sql
ALTER TABLE `users` ADD COLUMN `max_user_redirects` INT(10) NOT NULL DEFAULT '-1';
```
### Paths
(#path-config)
Define the URL of the web application, and it's subfolder:
The `base_url` is the URL your WebMUM installation is accessible from outside, this also includes subdirectories if you installed it in a subdirectory for that specific domain.
```php
/*
* Frontend paths
*/
define("FRONTEND_BASE_PATH", "http://mydomain.tld/webmum/");
define("SUBDIR", "webmum/");
'base_url' => 'http://localhost/webmum',
```
In the example above, WebMUM is located in a subfolder named "webmum/". If you don't want to use a subfolder, but install WebMUM directly into the domain root,
set the settings like this:
In the example above, WebMUM is located in a subdirectory named "webmum/". If your WebMUM installation is directly accessible from a domain (has its own domain), then set the `base_url` to something like this:
```php
define("FRONTEND_BASE_PATH", "http://webmum.mydomain.tld/");
define("SUBDIR", "");
'base_url' => 'http://webmum.mydomain.tld',
```
### Admin e-mail address
Only users with one of the specified email addresses will have access to the administrator's dashboard and will be able to create, edit and delete users, domains and redirects.
```php
/*
* Admin e-mail address
*/
$admins = array("admin@domain.tld");
'admins' = array(
'admin@domain.tld',
);
```
Admin email accounts must exist in the virtual user database on your own server. (=> an e-mail account on a foreign server won't give you access!). You can then login into the admin dashboard with that e-mail address and the corresponding password.
@ -233,25 +275,32 @@ Admin email accounts must exist in the virtual user database on your own server.
### Minimal required password length
```php
/*
* Minimal password length
*/
define("MIN_PASS_LENGTH", 8);
'password' => array(
...
'min_length' => 8,
...
),
```
### Logfile
When logging is enabled, WebMUM will write messages into a file "webmum.log" in a specified directory (e.g. when a login attempt fails).
To enable logging, comment in the lines
Enable logging by setting it to enabled in the options:
```php
# define("WRITE_LOG", true);
# define("WRITE_LOG_PATH","/var/www/webmum/log/");
'options' => array(
...
'enable_logging' => true,
...
),
```
... and make sure that PHP has permissions to write the log file to the directory defined in WRITE_LOG_PATH.
... and set a log path where the PHP user has permission to write the log file:
```php
'log_path' => '/var/www/webmum/log/',
```
"Login-failed-messages" have the following scheme:
@ -268,27 +317,32 @@ If you want to use **Fail2Ban** with WebMUM, the filter has to be:
failregex = ^(.*)\: WebMUM login failed for IP <HOST>$
```
### General options
To **restrict source adresses to managed domains only**, which is totally optional but recommended, just uncomment the following line
### Validate that source addresses of redirects must be from the managed domains only
```php
define("VALIDATE_ALIASES_SOURCE_DOMAIN_ENABLED", true);
'options' => array(
...
'enable_validate_aliases_source_domain' => true,
...
),
```
### Frontend options
Choose delimiter beteween multiple email adresses: comma, semicolon or new line separated.
Choose delimiter between multiple email addresses: comma, semicolon or new line separated.
**Tip:** new line is helpfull for long lists of addresses.
**Tip:** new line is helpful for long lists of addresses.
```php
define("FRONTEND_EMAIL_SEPARATOR_TEXT", ', '); // possible values: ', ' (default), '; ', PHP_EOL (newline)
define("FRONTEND_EMAIL_SEPARATOR_FORM", ','); // possible values: ',' (default), ';', PHP_EOL (newline)
'frontend_options' => array(
// Separator for email lists
'email_separator_text' => ', ', // possible values: ', ' (default), '; ', PHP_EOL (newline)
'email_separator_form' => ',', // possible values: ',' (default), ';', PHP_EOL (newline)
),
```
The input for addresses can be separated by `,`, `;`, `:`, `|`, `newline` and combinations since all of them will result in a valid list of adresses in database, magic.
The input for addresses can be separated by `,`, `;`, `:`, `|`, `newline` and combinations since all of them will result in a valid list of addresses in database, magic.
## Update / Upgrade WebMUM
@ -300,26 +354,26 @@ git stash
git pull origin master
git stash pop
```
... and you are ready to go. Git might complain about conflicting files - you will have to resolve the merge conflict manually then.
If you downloaded WebMUM as a ZIP package, you have to update WebMUM manually.
**After every update:**
**After every update:**
Please check if your config.inc.php fits the current requirements by comparing your version of the file with the config.inc.php in the repository.
## FAQ
### Which password scheme does WebMUM use?
### Which password hash algorithm does WebMUM use?
By default WebMUM uses SHA512-CRYPT password scheme. It cloud be change in the config file to SHA256-CRYPT or BLOWFISH-CRYPT.
By default WebMUM uses the `SHA-512` hash algorithm for passwords. You can also choose between the alternatives `SHA-256` or `BLOWFISH` in the config.
```php
/*
* Select one of the following algorithms
* SHA-512, SHA-256, BLOWFISH
*/
define("PASS_HASH_SCHEMA", "SHA-512");
'password' => array(
...
'hash_algorithm' => 'SHA-512', // Supported algorithms: SHA-512, SHA-256, BLOWFISH
...
),
```
### "login/ cannot be found"

1
config/.htaccess Normal file
View file

@ -0,0 +1 @@
Deny from all

View file

@ -1,99 +0,0 @@
<?php
/*
* MySQL server and database settings
*/
define("MYSQL_HOST", "localhost");
define("MYSQL_USER", "vmail");
define("MYSQL_PASSWORD", "vmail");
define("MYSQL_DATABASE", "vmail");
/*
* Database table names
*/
// Table names
define("DBT_USERS", "users");
define("DBT_DOMAINS", "domains");
define("DBT_ALIASES", "aliases");
// Users table columns
define("DBC_USERS_ID", "id");
define("DBC_USERS_USERNAME", "username");
define("DBC_USERS_DOMAIN", "domain");
define("DBC_USERS_PASSWORD", "password");
// define("DBC_USERS_MAILBOXLIMIT", "mailbox_limit"); // (Optional)
// Domains table columns
define("DBC_DOMAINS_ID", "id");
define("DBC_DOMAINS_DOMAIN", "domain");
// Aliases table columns
define("DBC_ALIASES_ID", "id");
define("DBC_ALIASES_SOURCE", "source");
define("DBC_ALIASES_DESTINATION", "destination");
// Enable multi source redirects, needs a new column in aliase table with VARCHAR(32)
//define("DBC_ALIASES_MULTI_SOURCE", "multi_source"); // (Optional, Recommended)
/*
* General options
*/
// Enable validating that the source addresses are ending with domain from domains
//define("VALIDATE_ALIASES_SOURCE_DOMAIN_ENABLED", true); // (Optional, Recommended)
/*
* Frontend paths
*/
define("FRONTEND_BASE_PATH", "http://localhost/webmum/");
define("SUBDIR", "webmum/");
/*
* Admin e-mail address
*/
// outdated: define("ADMIN_EMAIL", "admin@domain.tld");
// new: $admins = array("admin1@server.tld", "admin2@server.tld");
$admins = array("admin@domain.tld");
/*
* Password
*/
/*
* Select on of the following schemas (only these are supported)
* SHA-512, SHA-256, BLOWFISH
*/
define("PASS_HASH_SCHEMA", "SHA-512");
//minimum password length
define("MIN_PASS_LENGTH", 8);
/*
* Write log file? Failed login attempts will be written to the logfile.
* You can mointor the logfile with fail2ban and ban attackers' IP-addresses.
* Make sure that PHP has permission to create the log directory and webmum.log (write permissions for php user)
*
* Default: Do not write logfile
*/
// define("WRITE_LOG", true);
// define("WRITE_LOG_PATH","/var/www/webmum/log/");
/*
* Frontend options
*/
// Separator for email lists
define("FRONTEND_EMAIL_SEPARATOR_TEXT", ', '); // possible values: ', ' (default), '; ', PHP_EOL (newline)
define("FRONTEND_EMAIL_SEPARATOR_FORM", ','); // possible values: ',' (default), ';', PHP_EOL (newline)
?>

211
config/config.php.example Normal file
View file

@ -0,0 +1,211 @@
<?php
//////////////////////////////////////////////////////////////////////////
// //
// DO NOT EDIT THIS FILE! //
// //
// Instead, copy this config file to config.php and make your changes //
// in the copied version. This is just a template! //
// //
//////////////////////////////////////////////////////////////////////////
return array(
/******************************************************
* URL to your WebMUM installation.
*/
'base_url' => 'http://localhost/webmum',
/******************************************************
* MySQL database connection settings
*/
'mysql' => array(
'host' => 'localhost',
'user' => 'vmail',
'password' => 'vmail',
'database' => 'vmail',
),
/******************************************************
* Database schema mapping
*/
'schema' => array(
// Table names
'tables' => array(
// Example:
// 'table-keyword' => 'actual-table-name',
'users' => 'users',
'domains' => 'domains',
'aliases' => 'aliases',
),
'attributes' => array(
// Example:
// 'table-keyword' => array(
// 'attribute-keyword' => 'actual-attribute-name',
// ...
// ),
// Users table columns
'users' => array(
'id' => 'id',
'username' => 'username',
'domain' => 'domain',
'password' => 'password',
'mailbox_limit' => 'mailbox_limit', // (Optional see 'options.enable_mailbox_limits')
'max_user_redirects' => 'max_user_redirects', // (Optional see 'options.enable_user_redirects')
),
// Domains table columns
'domains' => array(
'id' => 'id',
'domain' => 'domain',
),
// Aliases table columns
'aliases' => array(
'id' => 'id',
'source' => 'source',
'destination' => 'destination',
'multi_source' => 'multi_source', // (Optional see 'options.enable_multi_source_redirects')
'is_created_by_user' => 'is_created_by_user', // (Optional see 'options.enable_user_redirects')
),
),
),
/******************************************************
* General options
*/
'options' => array(
/**
* Enable mailbox limits. (Default false == off)
*
* Needs a new db attribute in users table with INT(10).
* (see 'schema.attributes.users.mailbox_limit')
*/
'enable_mailbox_limits' => false,
/**
* Enable validating that the source addresses are ending with domain from domains. (Default true == on)
*/
'enable_validate_aliases_source_domain' => true,
/**
* Enable multi source redirects. (Default false == off)
*
* Needs a new db attribute in aliases table with VARCHAR(32).
* (see 'schema.attributes.aliases.multi_source')
*/
'enable_multi_source_redirects' => false,
/**
* Enable limited admin domain access. (Default false == off)
*
* Limitations can be configured under 'admin_domain_limits'.
*/
'enable_admin_domain_limits' => false,
/**
* Enable users can create own redirects. (Default false == off)
*
* Needs two new db attributes in users table with INT(10) and aliases table with INT(1) + DEFAULT 0
* (see 'schema.attributes.users.max_user_redirects' and 'schema.attributes.aliases.is_created_by_user')
*
* A maximum number of redirects per user can be configured.
*/
'enable_user_redirects' => false,
/**
* Enable logging for failed login attempts. (Default false == off)
*
* You can monitor the logfile with fail2ban and ban attackers' IP-addresses.
* Path to logfile can be configured under 'log_path'.
*/
'enable_logging' => false,
),
/******************************************************
* Admin e-mail addresses
*
* Users with these e-mail addresses will have admin access,
* you can limit their access with the 'options.enable_admin_domain_limits' feature
*/
'admins' => array(
'admin@domain.tld',
),
/******************************************************
* Limited admin domain access (only used if 'options.enable_admin_domain_limits' is true)
*
* Unlisted admins have access to every domain, the admin is limited to listed domains only!
* Unlisted domains are not accessible by that admin.
* Note that listed admins cannot create new domains!
*/
'admin_domain_limits' => array(
// Example:
// 'low_rank_admin@domain.tld' => array('his-domain.tld', 'shared-domain.tld'),
),
/******************************************************
* Password
*/
'password' => array(
// Algorithm used for password encryption
'hash_algorithm' => 'SHA-512', // Supported algorithms: SHA-512, SHA-256, BLOWFISH
// Minimum length for passwords
'min_length' => 8,
),
/******************************************************
* Log file path (only used if 'options.enable_logging' is true)
*
* Make sure that PHP has permission to create the log directory and webmum.log (write permissions for php user)
*/
'log_path' => '/var/www/webmum/log/',
/******************************************************
* Frontend options
*/
'frontend_options' => array(
// Separator for email lists
'email_separator_text' => ', ', // possible values: ', ' (default), '; ', PHP_EOL (newline)
'email_separator_form' => ',', // possible values: ',' (default), ';', PHP_EOL (newline)
),
);

View file

@ -5,16 +5,34 @@ body {
background-color: white;
}
/*
* Main Layout
*/
hr {
border: none;
border-bottom: 1px solid #ccc;
margin: 20px 0;
}
hr.invisible {
border-color: transparent;
}
.text-fail {
color: #d90000;
}
.text-warning {
color: #ADA900;
}
.text-success {
color: #39AD00;
}
#header {
position: relative;
height: 50px;
width: 100%;
background-color: rgba(15, 15, 15, 1);
background: linear-gradient(rgba(63, 63, 63, 1), rgba(15, 15, 15, 1));
background: rgba(15, 15, 15, 1) linear-gradient(rgba(63, 63, 63, 1), rgba(15, 15, 15, 1));
color: white;
line-height: 50px;
box-sizing: border-box;
@ -70,6 +88,13 @@ body {
color: rgba(62, 59, 59, 1);
}
#content .sub-header {
font-weight: normal;
font-size: .9em;
color: #999;
padding: 5px 0 0 5px;
}
#content a {
color: blue;
text-decoration: none;
@ -84,6 +109,10 @@ body {
margin: 25px 0;
}
#content .form hr {
margin: 5px 0 15px;
}
#content .form .input-group, #content .form .buttons {
padding-bottom: 10px;
}
@ -99,6 +128,18 @@ body {
color: #999;
}
#content .form .input-group > .input-group {
padding-left: 25px;
}
#content .form .input-group > .input-group:first-of-type {
padding-top: 10px;
}
#content .form .input-group > .input-group > label {
font-size: 12px;
}
#content .form .input {
}
@ -120,8 +161,31 @@ body {
}
#content .form .input input[type="number"] {
padding-left: 15px;
padding-right: 0;
min-width: 190px;
min-width: 70px;
width: 70px;
}
#content .form .input input[type="checkbox"],
#content .form .input input[type="radio"] {
border: none;
box-shadow: none;
min-width: inherit;
vertical-align: middle;
margin: 6px 0 8px 5px;
cursor: pointer;
}
#content .form .input input[type="checkbox"]+label,
#content .form .input input[type="radio"]+label {
padding-left: 3px;
margin-right: 15px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#content .form .input textarea {
@ -205,6 +269,11 @@ body {
text-align: center;
color: rgba(57, 57, 57, 1);
text-decoration: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#content a.button {
@ -244,6 +313,14 @@ body {
color: #fff;
}
#content .button.button-disabled {
background: #f1f1f1;
border-color: #f1f1f1;
box-shadow: none;
cursor: not-allowed;
color: #bbb;
}
#content .table {
margin: 25px 0;
@ -251,6 +328,10 @@ body {
border: none;
}
#content .table-compact {
margin: 5px 0;
}
#content .table thead th {
line-height: 38px;
padding: 2px 15px 0;
@ -269,8 +350,8 @@ body {
}
#content .table tbody td {
line-height: 33px;
padding: 0 10px;
line-height: 21px;
padding: 9px 10px;
border: 1px solid rgba(179, 176, 176, 1);
}
@ -278,6 +359,10 @@ body {
background-color: rgba(234, 234, 234, 1);
}
#content .table tbody > tr.warning {
background-color: #fcf897;
}
#content .table a {
color: rgb(148, 148, 255);
}
@ -285,47 +370,64 @@ body {
color: blue;
}
/*
* Footer
*/
#content .notifications {
}
#content .notification {
height: auto;
width: 100%;
margin: 15px 0;
text-align: center;
border: 1px solid;
border-radius: 3px;
padding: 15px 10px;
box-sizing: border-box;
}
#content .notification.notification-fail {
background-color: #fcacac;
border-color: red;
}
#content .notification.notification-warning {
background-color: #fcf897;
border-color: #ffe600;
}
#content .notification.notification-success {
background-color: rgba(182, 255, 183, 1);
border-color: green;
}
#footer {
position: relative;
height: 20px;
width: 100%;
background-color: white;
padding: 20px;
padding: 15px 20px;
box-sizing: border-box;
color: grey;
}
#footer ul {
list-style: none;
}
#footer li {
display: inline-block;
}
#footer li:not(:last-child):after {
content: '|';
color: #444444;
padding: 0 6px;
}
#footer a {
text-decoration: none;
color: grey;
color: #888888;
}
/*
* Notifications
*/
#content .notification {
height: auto;
width: 100%;
margin: 15px 0;
text-align: center;
border-style: solid;
border-width: 1px;
border-radius: 3px;
padding: 15px 10px;
box-sizing: border-box;
}
#content .notification.notification-fail {
background-color: #fcacac;
border-color: red;
}
#content .notification.notification-success {
background-color: rgba(182, 255, 183, 1);
border-color: green;
#footer a:hover {
color: #666666;
}

1
include/php/.htaccess Normal file
View file

@ -0,0 +1 @@
Deny from all

View file

@ -1,38 +0,0 @@
<?php
/*
* Checks, if the current user has the permission, which is required for an action.
* $role_req = required role [string]
*
* Returns:
* true: User has role $role_req
* false: User doesn't have role $role_req
*
* Possible roles: user, admin
*/
function user_has_permission($role_req){
global $user;
if($user->isLoggedIn() === true){
// User is logged in. Check permissions
// To be done. Load user role from database or better: save in SESSION
if($role_req === "user"){
if($user->getRole() == "user" || $user->getRole() == "admin"){
return true;
}
else{
return false;
}
}
else if($role_req === "admin"){
if($user->getRole() == "admin"){
return true;
}
}
}
else{
// User is not logged in => public user => no permissions
return false;
}
}
?>

View file

@ -0,0 +1,263 @@
<?php
class Auth
{
const SESSION_IDENTIFIER = 'uid';
/**
* @var User|null
*/
private static $loggedInUser = null;
/**
* Init Authentication
*/
public static function init()
{
static::loginUserViaSession();
}
/**
* Check whether the user is logged in or not.
*
* @return bool
*/
public static function isLoggedIn()
{
return !is_null(static::$loggedInUser);
}
/**
* Get the currently logged in user.
*
* @return null|User
*/
public static function getUser()
{
return static::$loggedInUser;
}
/**
* @param AbstractModel $user
*/
private static function loginUserByModel($user)
{
static::$loggedInUser = $user;
}
/**
* Checks session for logged in user, validates the login and finally logs him in.
*/
private static function loginUserViaSession()
{
global $_SESSION;
if(isset($_SESSION[static::SESSION_IDENTIFIER])
&& !empty($_SESSION[static::SESSION_IDENTIFIER])
){
$userId = $_SESSION[static::SESSION_IDENTIFIER];
/** @var User $user */
$user = User::find($userId);
// check if user still exists in database
if(!is_null($user)){
static::loginUserByModel($user);
}
}
}
/**
* Login user with provided credentials and save login in session
*
* @param string $email
* @param string $password
*
* @return bool
*/
public static function login($email, $password)
{
$email = strtolower($email);
$emailInParts = explode("@", $email);
if(count($emailInParts) !== 2){
return false;
}
$username = $emailInParts[0];
$domain = $emailInParts[1];
/** @var User $user */
$user = User::findWhereFirst(
array(
array(User::attr('username'), $username),
array(User::attr('domain'), $domain),
)
);
// Check if user exists
if(!is_null($user)){
if(static::checkPasswordByHash($password, $user->getPasswordHash())){
static::loginUserByModel($user);
$_SESSION[static::SESSION_IDENTIFIER] = $user->getId();
return true;
}
}
return false;
}
/**
* @return void
*/
public static function logout()
{
unset($_SESSION[static::SESSION_IDENTIFIER]);
static::$loggedInUser = null;
if(session_status() === PHP_SESSION_ACTIVE){
session_destroy();
}
}
/**
* Check if current user has a certain role, but User::ROLE_ADMIN will have access to all
*
* @param string $requiredRole
*
* @return bool
*/
public static function hasPermission($requiredRole)
{
if(static::isLoggedIn()){
$user = static::getUser();
return $user->getRole() === $requiredRole
|| $user->getRole() === User::ROLE_ADMIN;
}
return false;
}
/**
* Checks the new password entered by user on certain criteria, and throws an exception if its invalid.
*
* @param string $password
* @param string $passwordRepeated
*
* @throws AuthException Codes explained below
* 2: One password field is empty
* 3: Passwords aren't equal
* 4: Passwort is too snort
*/
public static function validateNewPassword($password, $passwordRepeated)
{
// Check if one passwort input is empty
if(empty($password)){
throw new AuthException("First password field was'nt filled out.", 2);
}
if(empty($passwordRepeated)){
throw new AuthException("Repeat password field was'nt filled out.", 2);
}
// Check if password are equal
if($password !== $passwordRepeated){
throw new AuthException("The repeated password must be equal to the first one.", 3);
}
// Check if password length is okay
if(Config::has('password.min_length')
&& strlen($password) < Config::get('password.min_length')
){
throw new AuthException("Passwords must be at least ".Config::get('password.min_length')." characters long.", 4);
}
}
/**
* @param string $password
* @param string $hash
*
* @return bool
*/
public static function checkPasswordByHash($password, $hash)
{
return crypt($password, $hash) === $hash;
}
/**
* @return string
*/
private static function getPasswordSchemaPrefix()
{
$map = array(
'SHA-256' => '$5$rounds=5000$',
'BLOWFISH' => '$2a$09$',
'SHA-512' => '$6$rounds=5000$',
);
$key = Config::get('password.hash_algorithm', 'SHA-512');
if(!isset($map[$key])){
$key = 'SHA-512';
}
return $map[$key];
}
/**
* @param string $password
*
* @return string
*/
public static function generatePasswordHash($password)
{
if(function_exists('mt_rand')){
mt_srand(time());
$num = mt_rand(1, 100000);
}
else{
srand(time());
$num = rand(1, 100000);
}
$salt = base64_encode($num);
$schemaPrefix = static::getPasswordSchemaPrefix();
$hash = crypt($password, $schemaPrefix.$salt.'$');
return $hash;
}
/**
* @param string $userId
* @param $password
*/
public static function changeUserPassword($userId, $password)
{
$passwordHash = static::generatePasswordHash($password);
/** @var User $user */
$user = User::find($userId);
if(!is_null($user)){
$user->setPasswordHash($passwordHash);
$user->save();
}
}
}

View file

@ -0,0 +1,6 @@
<?php
class AuthException extends Exception
{
}

View file

@ -0,0 +1,117 @@
<?php
class Config
{
/**
* @var array
*/
protected static $config = array();
/**
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* @codeCoverageIgnore
*/
private function __clone()
{
}
/**
* @param array $configArray
*/
public static function init($configArray)
{
static::set(null, $configArray);
}
/**
* Set a config value using "dot" notation.
*
* @param string $key
* @param mixed $value
* @return array
*/
public static function set($key, $value)
{
if(is_null($key)) return static::$config = $value;
$keys = explode('.', $key);
$array =& static::$config;
while(count($keys) > 1){
$key = array_shift($keys);
if(!isset($array[$key]) || !is_array($array[$key])){
$array[$key] = array();
}
$array =& $array[$key];
}
$array[array_shift($keys)] = $value;
return $array;
}
/**
* Get a config value using "dot" notation.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public static function get($key, $default = null)
{
if(is_null($key)) return static::$config;
if(isset(static::$config[$key])) return static::$config[$key];
$pointer = static::$config;
foreach(explode('.', $key) as $segment){
if(!is_array($pointer) || !array_key_exists($segment, $pointer)){
return $default;
}
$pointer = $pointer[$segment];
}
return $pointer;
}
/**
* Check if a config value exists using "dot" notation.
*
* @param string $key
* @return bool
*/
public static function has($key)
{
if(empty(static::$config) || is_null($key)) return false;
if(array_key_exists($key, static::$config)) return true;
$pointer = static::$config;
foreach(explode('.', $key) as $segment){
if(!is_array($pointer) || !array_key_exists($segment, $pointer)){
return false;
}
$pointer = $pointer[$segment];
}
return true;
}
}

View file

@ -0,0 +1,524 @@
<?php
class Database
{
/**
* @var Database
*/
protected static $instance = null;
/**
* @var mysqli
*/
protected $db;
/**
* @var string
*/
protected $config;
/**
* @var string
*/
protected $lastQuery;
/**
* @param string $host
* @param string $user
* @param string $password
* @param string $database
*
* @throws Exception
*/
protected function __construct($host, $user, $password, $database)
{
if(!static::isInitialized()){
$this->config = $database;
try{
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$this->db = new mysqli($host, $user, $password, $database);
}
catch(mysqli_sql_exception $e){
throw new Exception('Unable to connect to the database.', 0, $e);
}
}
}
protected function __clone()
{
}
/**
* @return Database
*
* @throws Exception
*/
public static function getInstance()
{
if(!static::isInitialized()){
throw new Exception('Database must be initialized before using it (see Database::init).');
}
return static::$instance;
}
/**
* @param Database $instance
*
* @codeCoverageIgnore
*/
protected static function setInstance($instance)
{
static::$instance = $instance;
}
/**
* @param string $host
* @param string $user
* @param string $password
* @param string $database
*
* @throws InvalidArgumentException
* @throws Exception
*
* @codeCoverageIgnore
*/
public static function init($host, $user = null, $password = null, $database = null)
{
if(!static::isInitialized()){
if(is_array($host)){
$database = isset($host['database']) ? $host['database'] : $database;
$password = isset($host['password']) ? $host['password'] : $password;
$user = isset($host['user']) ? $host['user'] : $user;
$host = isset($host['host']) ? $host['host'] : $host;
}
if(is_null($host) || is_null($user) || is_null($password) || is_null($database)){
throw new InvalidArgumentException('Missing parameters for database initialization.');
}
static::setInstance(
new static($host, $user, $password, $database)
);
}
}
/**
* @return bool
*/
public static function isInitialized()
{
return !is_null(static::$instance);
}
/**
* Execute query
*
* @param string $query
*
* @return bool|mysqli_result
*
* @throws DatabaseException
*/
public function query($query)
{
$this->lastQuery = $query;
$result = $this->db->query($query);
if($this->db->errno !== 0){
$ex = new DatabaseException('There was an error running the query ['.$this->db->error.']');
if(!is_null($this->lastQuery)){
$ex->setQuery($this->lastQuery);
}
throw $ex;
}
return $result;
}
/**
* @return mixed
*/
public function getInsertId()
{
return $this->db->insert_id;
}
/**
* Escape string
*
* @param string $input
*
* @return string
*/
public function escape($input)
{
return $this->db->real_escape_string($input);
}
/**
* @param string $table
* @param array $conditions
* @param string $conditionConnector
* @param null $orderBy
* @param int $limit
*
* @return bool|mysqli_result
*
* @throws DatabaseException
*/
public function select($table, $conditions = array(), $conditionConnector = 'AND', $orderBy = null, $limit = 0)
{
return $this->query(
sprintf(
"SELECT * FROM `%s` %s%s%s",
$table,
static::helperWhere($conditions, $conditionConnector),
static::helperOrderBy($orderBy),
static::helperLimit($limit)
)
);
}
/**
* Insert into table
*
* @param string $table
* @param array $values
*
* @return mixed
*
* @throws DatabaseException
*/
public function insert($table, $values)
{
if(count($values) === 0){
return null;
}
$this->query(
sprintf(
"INSERT INTO `%s` (%s) VALUES %s",
$table,
static::helperAttributeList(array_keys($values)),
static::helperValueList(array_values($values))
)
);
return $this->getInsertId();
}
/**
* Update table
*
* @param string $table
* @param array $values
* @param array $conditions
* @param string $conditionConnector
*
* @throws DatabaseException
*/
public function update($table, $values, $conditions = array(), $conditionConnector = 'AND')
{
if(count($values) === 0){
return;
}
$sqlValues = array();
foreach($values as $attribute => $value){
$sqlValues[] = array($attribute, '=', $value);
}
$this->query(
sprintf(
"UPDATE `%s` SET %s %s",
$table,
static::helperConditionList($sqlValues, ','),
static::helperWhere($conditions, $conditionConnector)
)
);
}
/**
* Count in table
*
* @param string $table
* @param string $byAttribute
* @param array $conditions
* @param string $conditionConnector
*
* @return int
*
* @throws DatabaseException
*/
public function count($table, $byAttribute, $conditions = array(), $conditionConnector = 'AND')
{
$result = $this->query(
sprintf(
"SELECT COUNT(`%s`) FROM `%s` %s",
$byAttribute,
$table,
static::helperWhere($conditions, $conditionConnector)
)
);
return intval($result->fetch_array(MYSQLI_NUM)[0]);
}
/**
* @param string $table
* @param string $attribute
* @param mixed $value
*
* @throws DatabaseException
*/
public function delete($table, $attribute, $value)
{
$sql = sprintf(
"DELETE FROM `%s` %s",
$table,
static::helperWhere(array($attribute, $value))
);
$this->query($sql);
}
/**
* @param string $potentialKeyword
*
* @return bool
*/
protected static function isKeyword($potentialKeyword)
{
return in_array(
strtoupper($potentialKeyword),
array('AS', 'ASC', 'DESC')
);
}
/**
* @param array $attributes
*
* @return string
*/
public static function helperAttributeList($attributes)
{
$sqlAttributes = array();
foreach($attributes as $attribute){
if(is_string($attribute)){ // raw
$sqlAttributes[] = $attribute;
continue;
}
if(!is_array($attribute)){
$attribute = array($attribute);
}
$sqlPieces = array();
for($i = 0; $i < count($attribute); ++$i){
if(static::isKeyword($attribute[$i])){
$sqlPieces[] = sprintf("%s", $attribute[$i]);
}
elseif(isset($attribute[$i + 1]) && !static::isKeyword($attribute[$i + 1])){
$sqlPieces[] = sprintf("`%s`.`%s`", $attribute[$i], $attribute[++$i]);
}
else{
$sqlPieces[] = sprintf("`%s`", $attribute[$i]);
}
}
$sqlAttributes[] = implode(" ", $sqlPieces);
}
return sprintf(
"%s",
implode(', ', $sqlAttributes)
);
}
/**
* @param mixed $value
*
* @return string
*/
public static function helperValue($value)
{
if(is_null($value) || (is_string($value) && strtoupper($value) === 'NULL')){
return "NULL";
}
elseif(is_array($value)){
return static::helperValueList($value);
}
return sprintf(
"'%s'",
static::getInstance()->escape($value)
);
}
/**
* @param array $values
*
* @return string
*/
public static function helperValueList($values)
{
$sqlValues = array();
foreach($values as $val){
$sqlValues[] = static::helperValue($val);
}
return sprintf(
"(%s)",
implode(', ', $sqlValues)
);
}
/**
* @param array $conditions
* array('attr', '=', '3') => "`attr` = '3'"
* array(
* array('`attr` = '3') (raw SQL) => `attr` = '3'
* array('attr', 3) => `attr` = '3'
* array('attr', '=', '3') => `attr` = '3'
* array('attr', '<=', 3) => `attr` <= '3'
* array('attr', 'LIKE', '%asd') => `attr` LIKE '%asd'
* array('attr', 'IS', null) => `attr` IS NULL
* array('attr', 'IS NOT', null) => `attr` IS NOT NULL
* )
* @param string $conditionConnector AND, OR
*
* @return string
*/
public static function helperConditionList($conditions, $conditionConnector = 'AND')
{
// detect non nested array
if(count($conditions) > 0 && !is_array($conditions[0])){
$conditions = array($conditions);
}
$conditionConnector = strtoupper($conditionConnector);
if(in_array($conditionConnector, array('AND', 'OR'))){
$conditionConnector = " ".$conditionConnector;
}
$values = array();
foreach($conditions as $val){
switch(count($val)){
case 1:
// raw
$values[] = $val;
break;
case 2:
$v = static::helperValue($val[1]);
$values[] = sprintf("`%s` = %s", $val[0], $v);
break;
case 3:
$v = static::helperValue($val[2]);
$values[] = sprintf("`%s` %s %s", $val[0], strtoupper($val[1]), $v);
break;
}
}
return implode($conditionConnector." ", $values);
}
/**
* @param array $conditions
* @param string $conditionConnector AND, OR
*
* @return string
*/
public static function helperWhere($conditions, $conditionConnector = 'AND')
{
if(count($conditions) > 0){
return sprintf(
" WHERE %s",
static::helperConditionList($conditions, $conditionConnector)
);
}
return "";
}
/**
* @param array|null $orderBy Examples below:
* null => ""
* array() => ""
* array('attr1' => 'asc', 'attr2' => 'desc') => " ORDER BY `attr1` ASC, `attr2` DESC"
* array('attr1') => " ORDER BY `attr1` ASC"
*
* @return string
*/
public static function helperOrderBy($orderBy = null)
{
if(is_null($orderBy) || count($orderBy) === 0){
return "";
}
$values = array();
foreach($orderBy as $key => $val){
if(is_int($key)){
$values[] = array($val);
}
else{
$values[] = array($key, strtoupper($val));
}
}
return sprintf(
" ORDER BY %s",
static::helperAttributeList($values)
);
}
/**
* @param int|array $limit
* 0 => ""
* 3 => " LIMIT 3"
* array(3, 4) => " LIMIT 3,4"
*
* @return string
*/
public static function helperLimit($limit = 0)
{
if(is_array($limit) && count($limit) == 2){
$limit = $limit[0].",".$limit[1];
}
if(is_string($limit) || (is_int($limit) && $limit > 0)){
return sprintf(
" LIMIT %s",
$limit
);
}
return "";
}
}

View file

@ -0,0 +1,31 @@
<?php
class DatabaseException extends Exception
{
/** @var string */
protected $query;
/**
* Set the executed SQL query
*
* @param string $query
*
* @return $this
*/
public function setQuery($query)
{
$this->query = $query;
return $this;
}
/**
* Get the executed SQL query
*
* @return string
*/
public function getQuery()
{
return $this->query;
}
}

View file

@ -0,0 +1,146 @@
<?php
class Message
{
const TYPE_FAIL = 'fail';
const TYPE_ERROR = 'fail';
const TYPE_WARNING = 'warning';
const TYPE_SUCCESS = 'success';
/**
* @var Message
*/
protected static $instance;
/**
* Holds all messages
*
* @var array
*/
protected $messages = array();
/**
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* @codeCoverageIgnore
*/
private function __clone()
{
}
/**
* @return Message
*
* @codeCoverageIgnore
*/
public static function getInstance()
{
if(is_null(static::$instance)){
static::$instance = new static();
}
return static::$instance;
}
/**
* Add a new message
*
* @param string $type Supported types: success, fail, info
* @param string $text
*/
public function add($type, $text)
{
if(!in_array($type, array(static::TYPE_FAIL, static::TYPE_ERROR, static::TYPE_WARNING, static::TYPE_SUCCESS))){
throw new InvalidArgumentException;
}
$this->messages[] = array(
'type' => $type,
'message' => $text,
);
}
/**
* Add a new success message
*
* @param string $text
*/
public function fail($text)
{
$this->add(static::TYPE_FAIL, $text);
}
/**
* Add a new success message
*
* @param string $text
*/
public function error($text)
{
$this->add(static::TYPE_ERROR, $text);
}
/**
* Add a new success message
*
* @param string $text
*/
public function warning($text)
{
$this->add(static::TYPE_WARNING, $text);
}
/**
* Add a new success message
*
* @param string $text
*/
public function success($text)
{
$this->add(static::TYPE_SUCCESS, $text);
}
/**
* Render all messages
*
* @param null|string $type null = render all
*
* @return string
*/
public function render($type = null)
{
$out = '';
if(count($this->messages) > 0){
$out .= '<div class="notifications">';
foreach($this->messages as $message){
if(is_null($type) || $type == $message['type']){
$out .= '<div class="notification notification-'.$message['type'].'">'.$message['message'].'</div>';
}
}
$out .= '</div>';
}
return $out;
}
}

View file

@ -0,0 +1,299 @@
<?php
class Router
{
const METHOD_GET = 'GET';
const METHOD_POST = 'POST';
/**
* @var array
*/
private static $routes = array();
/**
* @var array
*/
private static $errorPages = array(
404 => 'include/php/template/error/not-found.php',
403 => 'include/php/template/error/not-allowed.php'
);
/**
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* @codeCoverageIgnore
*/
private function __clone()
{
}
/**
* @param array $routes
*/
public static function init($routes)
{
static::$routes = $routes;
}
/**
* @param string $method
*
* @return bool
*/
protected static function isValidMethod($method)
{
return in_array(
$method,
array(
static::METHOD_GET,
static::METHOD_POST
)
);
}
/**
* @param string|array $methods
* @param string $pattern
* @param callable|array|string $routeConfig
* @param array $permission
*
* @throws Exception
*/
public static function addRoute($methods, $pattern, $routeConfig, $permission = null)
{
if(!is_array($methods)){
$methods = array($methods);
}
$config = array(
'pattern' => $pattern,
'config' => $routeConfig,
'permission' => $permission,
);
foreach($methods as $method){
$method = strtoupper($method);
if(!static::isValidMethod($method)){
throw new Exception('Unsupported HTTP method "'.$method.'".');
}
if(!isset(static::$routes[$method])){
static::$routes[$method] = array();
}
static::$routes[$method][] = $config;
}
}
/**
* @param string $pattern
* @param callable|string $routeConfig
* @param array $permission
*/
public static function addGet($pattern, $routeConfig, $permission = null)
{
static::addRoute(static::METHOD_GET, $pattern, $routeConfig, $permission);
}
/**
* @param string $pattern
* @param callable|string $routeConfig
* @param array $permission
*/
public static function addPost($pattern, $routeConfig, $permission = null)
{
static::addRoute(static::METHOD_POST, $pattern, $routeConfig, $permission);
}
/**
* @param string $pattern
* @param callable|string $routeConfig
* @param array $permission
*/
public static function addMixed($pattern, $routeConfig, $permission = null)
{
static::addRoute(array(static::METHOD_GET, static::METHOD_POST), $pattern, $routeConfig, $permission);
}
/**
* @param string $url
* @param string $method
*
* @return string
*
* @throws Exception
*/
public static function execute($url, $method = self::METHOD_GET)
{
$method = strtoupper($method);
if(!static::isValidMethod($method) && !isset(self::$routes[$method])){
throw new Exception('Unsupported HTTP method "'.$method.'".');
}
if(isset(self::$routes[$method])){
foreach(self::$routes[$method] as $route){
if(rtrim($route['pattern'], '/') === rtrim($url, '/')){
if(!is_null($route['permission'])){
if(!Auth::isLoggedIn() || !Auth::hasPermission($route['permission'])){
return static::loadAndBufferOutput(static::$errorPages[403]);
}
}
return static::resolveRouteConfig($route['config']);
}
}
}
return static::loadAndBufferOutput(static::$errorPages[404]);
}
/**
* @return string
*/
public static function executeCurrentRequest()
{
return static::execute(
static::getCurrentUrlPath(),
isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : static::METHOD_GET
);
}
/**
* @param int $errorNumber
*
* @return string|null
*
* @codeCoverageIgnore
*/
public static function displayError($errorNumber)
{
$errorPage = isset(static::$errorPages[$errorNumber])
? static::loadAndBufferOutput(static::$errorPages[$errorNumber])
: '';
echo Router::loadAndBufferOutput(
'include/php/template/layout.php',
array(
'content' => $errorPage,
)
);
exit;
}
/**
* @param bool $removeGetParameters
*
* @return string
*/
public static function getCurrentUrlPath($removeGetParameters = true)
{
$baseUrl = parse_url(Config::get('base_url'));
$basePath = isset($baseUrl['path']) ? rtrim($baseUrl['path'], '/') : '';
$url = $_SERVER['REQUEST_URI'];
if($removeGetParameters){
$url = preg_replace('/\?.*/', '', $url); // Trim GET Parameters
}
// Trim all leading slashes
$url = rtrim($url, '/');
if(!empty($basePath) && ($basePathPos = strpos($url, $basePath)) === 0){
$url = substr($url, strlen($basePath));
}
return $url;
}
/**
* @param array $config
*
* @return string
*/
public static function resolveRouteConfig($config)
{
if(is_string($config)){
if(file_exists($config)){
return static::loadAndBufferOutput($config);
}
}
elseif(is_callable($config) && $config instanceof Closure){
return $config();
}
return static::loadAndBufferOutput(static::$errorPages[404]);
}
/**
* @param string $file
* @param array $variables
*
* @return string
*/
public static function loadAndBufferOutput($file, $variables = array())
{
ob_start();
extract($variables);
require $file;
return ob_get_clean();
}
/**
* Generate full url
*
* @param string $url
*
* @return string
*/
public static function url($url = '')
{
return rtrim(
sprintf(
'%s/%s',
rtrim(Config::get('base_url'), '/'),
trim($url, '/')
),
'/'
);
}
/**
* Redirect user to an url
*
* @param string $url
*
* @codeCoverageIgnore
*/
public static function redirect($url)
{
header("Location: ".static::url($url));
exit;
}
}

View file

@ -1,129 +0,0 @@
<?php
class USER {
/*
* Class attributes
*/
private $uid;
private $email;
private $role;
private $loggedin = false;
/*
* Constructor
*
* Fills the user object up with anonymous data
*/
function __construct(){
global $admins;
// Start session
session_start();
session_regenerate_id();
if(isset($_SESSION['email']) && in_array($_SESSION['email'], $admins)){
$this->role = "admin";
}
else{
$this->role = "user";
}
if(isset($_SESSION['uid']) && $_SESSION['uid'] != ""){
// revive session ...
$this->uid = $_SESSION['uid'];
$this->loggedin = true;
}
}
/*
* Getter functions
*/
function getUID(){
return $this->uid;
}
function getRole(){
return $this->role;
}
function isLoggedIn(){
return $this->loggedin;
}
/*
* Login function. Checks login data and writes information to SESSION
*
* Returns:
* true: Login was successful
* false: Login was not successful
*/
function login($email, $password){
global $db;
// Prepare e-mail address
$email = $db->escape_string($email);
$email = strtolower($email);
$password = $db->escape_string($password);
$email_part = explode("@", $email);
$username = $email_part[0];
$domain = $email_part[1];
// Check e-mail address
$sql = "SELECT `".DBC_USERS_ID."`, `".DBC_USERS_PASSWORD."` FROM `".DBT_USERS."` WHERE `".DBC_USERS_USERNAME."` = '$username' AND `".DBC_USERS_DOMAIN."` = '$domain' LIMIT 1;";
if(!$result = $db->query($sql)){
dbError($db->error);
}
if($result->num_rows === 1){
$userdata = $result->fetch_array(MYSQLI_ASSOC);
$uid = $userdata[DBC_USERS_ID];
$password_hash = $userdata[DBC_USERS_PASSWORD];
// Check password
if (crypt($password, $password_hash) === $password_hash) {
// Password is valid, start a logged-in user session
$this->loggedin = true;
$_SESSION['uid'] = $uid;
$_SESSION['email'] = $email;
return true;
}
else {
// Password is invalid
return false;
}
}
else{
// User could not be found
return false;
}
}
/*
* Changes user password.
* Returns:
* true: Change success
* false: Error
*/
function change_password($newpass, $newpass_rep){
$pass_ok = check_new_pass($newpass, $newpass_rep);
if($pass_ok === true){
$pass_hash = gen_pass_hash($newpass);
write_pass_hash_to_db($pass_hash, $this->uid);
return true;
}
else{
return false;
}
}
}
?>

View file

@ -1,7 +0,0 @@
<?php
/*
* Free resources
*/
$db->close();
?>

View file

@ -1,35 +1,69 @@
<?php
// Include config
if(file_exists('config/config_override.inc.php')){
require_once 'config/config_override.inc.php';
}
else{
require_once 'config/config.inc.php';
}
/**
* Register automatic loading for dependency injection
*/
spl_autoload_register(function($class){
if(file_exists('include/php/models/'.$class.'.php')){
include_once 'include/php/models/'.$class.'.php';
}
elseif(file_exists('include/php/classes/'.$class.'.php')){
include_once 'include/php/classes/'.$class.'.php';
}
});
/**
* @param string $errorMessage
* Start session as the very first thing
*/
function dbError($errorMessage){
die('There was an error running the query ['.$errorMessage.']');
}
session_start();
session_regenerate_id();
// Establish database connection
$db = new mysqli(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);
if($db->connect_errno > 0){
die('Unable to connect to database [' . $db->connect_error . ']');
}
/* Import classes */
require_once 'include/php/classes/user.class.php';
$user = new USER();
/**
* Load some global accessible functions
*/
require_once 'include/php/global.inc.php';
require_once 'include/php/checkpermissions.inc.php';
?>
/**
* Setting up
*/
if(file_exists('config/config.php') && !isset($_SESSION['installer'])){
/**
* Loading config
*/
$configValues = require_once 'config/config.php';
if(!is_array($configValues)){
throw new Exception('Config must return an array of config values.');
}
Config::init($configValues);
/**
* Establish database connection
*/
Database::init(Config::get('mysql'));
/**
* Initialize Authentication (Login User if in session)
*/
Auth::init();
/**
* Setup routes
*/
require_once 'include/php/routes.inc.php';
}
else{
/**
* Switching to install mode
*/
define('INSTALLER_ENABLED', true);
}

View file

@ -1,165 +1,35 @@
<?php
/*
* Message manager
* Types of notifications:
* success
* fail
* info
*/
$MESSAGES = array();
function add_message($type, $message)
{
global $MESSAGES;
$newmessage = array();
$newmessage['type'] = $type;
$newmessage['message'] = $message;
$MESSAGES[] = $newmessage;
}
function output_messages()
{
global $MESSAGES;
if(count($MESSAGES) > 0) {
echo '<div class="messages">';
foreach($MESSAGES as $message){
echo '<div class="notification notification-'.$message['type'].'">'.$message['message'].'</div>';
}
echo '</div>';
}
}
/*
* Function checks password input for new password
* Return codes:
* true: password is okay
* 2: One password field is empty
* 3: Passwords are not equal
* 4: Passwort is too snort
*/
function check_new_pass($pass1, $pass2)
{
global $PASS_ERR;
global $PASS_ERR_MSG;
// Check if one passwort input is empty
if($pass1 !== "" && $pass2 !== ""){
// Check if password are equal
if($pass1 === $pass2){
// Check if password length is okay
if(strlen($pass1) >= MIN_PASS_LENGTH){
// Password is okay.
return true;
}
else{
// Password is not long enough
$PASS_ERR = 4;
$PASS_ERR_MSG = "Password is not long enough. Please enter a password which has ".MIN_PASS_LENGTH." characters or more.";
return $PASS_ERR;
}
}
else{
// Passwords are not equal
$PASS_ERR = 3;
$PASS_ERR_MSG = "Passwords are not equal.";
return $PASS_ERR;
}
}
else{
// One password is empty.
$PASS_ERR = 2;
$PASS_ERR_MSG = "Not all password fields were filled out";
return $PASS_ERR;
}
}
function get_hash()
{
switch(PASS_HASH_SCHEMA){
case "SHA-512":
return '$6$rounds=5000$';
break;
case "SHA-256":
return '$5$rounds=5000$';
break;
case "BLOWFISH":
return '$2a$09$';
break;
}
}
function gen_pass_hash($pass)
{
$salt = base64_encode(rand(1, 1000000) + microtime());
$hash_schema = get_hash();
$pass_hash = crypt($pass, $hash_schema.$salt.'$');
return $pass_hash;
}
function write_pass_hash_to_db($pass_hash, $uid)
{
global $db;
$uid = $db->escape_string($uid);
$pass_hash = $db->escape_string($pass_hash);
$db->query("UPDATE `".DBT_USERS."` SET `".DBC_USERS_PASSWORD."` = '$pass_hash' WHERE `".DBC_USERS_ID."` = '$uid';");
}
/*
/**
* Add message to logfile
*
* @param string $text
*
* @throws Exception
*/
function writeLog($text)
{
if(defined('WRITE_LOG') && defined('WRITE_LOG_PATH')){
$logdestination = realpath(WRITE_LOG_PATH).DIRECTORY_SEPARATOR."webmum.log";
if(is_writable(WRITE_LOG_PATH)){
$logfile = fopen($logdestination, "a") or die("Unable to create or open logfile \"".$logdestination."\" in root directory!");
fwrite($logfile, date('M d H:i:s').": ".$text."\n");
fclose($logfile);
if(Config::get('options.enable_logging', false) && Config::has('log_path')){
$logDestination = realpath(Config::get('log_path')).DIRECTORY_SEPARATOR."webmum.log";
if(is_writable(Config::get('log_path'))){
if($logfile = fopen($logDestination, "a")){
fwrite($logfile, date('M d H:i:s').": ".$text."\n");
fclose($logfile);
}
else{
throw new Exception('Unable to create or open logfile "'.$logDestination.'" in root directory!');
}
}
else{
die("Directory \"".WRITE_LOG_PATH."\" is not writable");
throw new Exception('Directory "'.Config::get('log_path').'" isn\'t writable');
}
}
}
/**
* @param string $url
* @return string
*/
function url($url)
{
$base = FRONTEND_BASE_PATH;
if (substr($base, -1) === '/') {
$base = substr($base, 0, -1);
}
if (strlen($url) > 0 && $url[0] === '/') {
$url = substr($url, 1);
}
return $base.'/'.$url;
}
/**
* @param string $url
*/
function redirect($url)
{
header("Location: ".url($url));
exit;
}
/**
* Split comma, semicolon or newline separated list of emails to string
*
@ -169,26 +39,33 @@ function redirect($url)
*/
function stringToEmails($input)
{
$separators = array(',', ';', "\r\n", "\r", "\n", '|', ':');
$list = explode(
'|',
str_replace(
array(' ', ',', ';', "\r\n", "\r", "\n", '|', ':'),
'|',
$input
)
);
$list = explode('|', str_replace($separators, '|', $input));
foreach($list as $i => &$email){
if(empty($email)){
unset($list[$i]);
}
else{
$email = trim($email);
}
}
return array_values(
array_map(
'strtolower',
array_unique(
$emails = array_values(
array_unique(
array_map(
'formatEmail',
$list
)
)
);
asort($emails);
return $emails;
}
/**
@ -201,6 +78,10 @@ function stringToEmails($input)
*/
function emailsToString($list, $glue = ',')
{
if(is_string($list)){
return $list;
}
return implode($glue, $list);
}
@ -208,6 +89,7 @@ function emailsToString($list, $glue = ',')
* Format single email address
*
* @param string $input
*
* @return string
*/
function formatEmail($input)
@ -220,6 +102,7 @@ function formatEmail($input)
*
* @param string|array $input
* @param string $glue
*
* @return string
*/
function formatEmails($input, $glue)
@ -231,5 +114,53 @@ function formatEmails($input, $glue)
return emailsToString($input, $glue);
}
/**
* Format email addresses for text output (not in an input field)
*
* @param string|array $input
* @return string
*/
function formatEmailsText($input)
{
return formatEmails(
$input,
str_replace(PHP_EOL, '<br>', Config::get('frontend_options.email_separator_text', ', '))
);
}
?>
/**
* Format email addresses for form output (in an input field)
*
* @param string|array $input
* @return string
*/
function formatEmailsForm($input)
{
return strip_tags(
formatEmails(
$input,
Config::get('frontend_options.email_separator_form', ',')
)
);
}
/**
* @param string $textPattern
* @param int|mixed $value
* @return string
*/
function textValue($textPattern, $value)
{
$text = str_replace(
array('_', ':val:', ':value:'),
$value,
$textPattern
);
if(is_numeric($value) && $value > 1){
$text .= 's';
}
return $text;
}

View file

@ -0,0 +1,457 @@
<?php
abstract class AbstractModel
{
/**
* Db table for find methods
*
* @var string
*/
public static $table;
/**
* Db id attribute for find methods
*
* @var string
*/
public static $idAttribute;
/**
* Mapping model attributes and database attributes for saving
*
* @var array
*/
protected static $attributeDbAttributeMapping = null;
/**
* Initialize Model
*/
protected static function initModel()
{
}
/**
* Get mapped db attribute
*
* @param string $name
* @return string
*/
public static function attr($name)
{
static::initModel();
if(isset(static::$attributeDbAttributeMapping[$name])){
return static::$attributeDbAttributeMapping[$name];
}
return false;
}
/**
* Format or do other things before saving
*
* @param array $data
*
* @return array
*/
protected function preSave($data)
{
return $data;
}
/**
* Hold all data from a model
*
* @var mixed
*/
protected $data = array();
/**
* Constructor.
*
* @param array $data
*/
protected function __construct($data)
{
static::initModel();
if(isset($data[static::$idAttribute])){
$id = is_numeric($data[static::$idAttribute]) && strpos($data[static::$idAttribute], ',') === false
? intval($data[static::$idAttribute])
: $data[static::$idAttribute];
$this->setId($id);
}
}
/**
* Create a model from data
*
* @param array $data
*
* @return static|null The Model
*/
public static function create($data)
{
if(count($data) > 0){
return new static($data);
}
return null;
}
/**
* Create a model collection from data
*
* @param array $multiData
*
* @return ModelCollection|static[]
*/
public static function createMultiple($multiData = array())
{
$collection = new ModelCollection();
foreach($multiData as $data){
$model = static::create($data);
if(!is_null($model)){
if(is_null($model->getId())){
$collection->add($model);
}
else{
$collection->add($model, $model->getId());
}
}
}
return $collection;
}
/**
* @see create
*
* @param array $data
*
* @return AbstractModel|null
*/
public static function createAndSave($data)
{
$model = static::create($data);
if(!is_null($model)){
$model->save();
return $model;
}
return null;
}
/**
* @see createMultiple
*
* @param array $multiData
*
* @return ModelCollection|static[]
*/
public static function createMultipleAndSave($multiData = array())
{
$collection = new ModelCollection();
foreach($multiData as $data){
$model = static::createAndSave($data);
if(!is_null($model)){
$collection->add($model);
}
}
return $collection;
}
/**
* Create a model from mysqli result
*
* @param mysqli_result $result
*
* @return static|null
*/
public static function createFromDbResult($result)
{
if($result->num_rows === 0){
return null;
}
return static::create($result->fetch_assoc());
}
/**
* Create a model collection from mysqli result
*
* @param mysqli_result $result
*
* @return ModelCollection|static[]
*/
public static function createMultipleFromDbResult($result)
{
$rows = array();
while($row = $result->fetch_assoc()){
$rows[] = $row;
}
return static::createMultiple($rows);
}
/**
* @param string $attribute
* @param mixed $value
*/
public function setAttribute($attribute, $value)
{
$this->data[$attribute] = $value;
}
/**
* @param string $attribute
*
* @return mixed|null
*/
public function getAttribute($attribute)
{
if(isset($this->data[$attribute])){
$value = $this->data[$attribute];
if(is_array($value)){
return array_map('strip_tags', $value);
}
elseif(is_string($value)){
return strip_tags($value);
}
return $value;
}
return null;
}
/**
* @return mixed
*/
public function getId()
{
return $this->getAttribute('id');
}
/**
* @param mixed $value
*/
protected function setId($value)
{
$this->setAttribute('id', $value);
}
/**
* Find all models by raw sql
*
* @param $sql
* @param null|string $useSpecificModel
*
* @return ModelCollection|static[]
*/
public static function findAllRaw($sql, $useSpecificModel = null)
{
$result = Database::getInstance()->query($sql);
if(is_null($useSpecificModel)){
return static::createMultipleFromDbResult($result);
}
elseif(class_exists($useSpecificModel)){
return call_user_func_array(array($useSpecificModel, 'createMultipleFromDbResult'), array($result));
}
return new ModelCollection();
}
/**
* Find a model by raw sql
*
* @param $sql
* @param null|string $useSpecificModel
*
* @return AbstractModel
*/
public static function findRaw($sql, $useSpecificModel = null)
{
$result = Database::getInstance()->query($sql);
if(is_null($useSpecificModel)){
return static::createFromDbResult($result);
}
elseif(class_exists($useSpecificModel)){
return call_user_func_array(array($useSpecificModel, 'createFromDbResult'), array($result));
}
return null;
}
/**
* Find models by a condition
*
* @param array $conditions see helperConditionArray
* @param string $conditionConnector see helperConditionArray
* @param array|null $orderBy
* @param int $limit see helperLimit
*
* @return ModelCollection|static[]|AbstractModel|null
*/
public static function findWhere($conditions = array(), $conditionConnector = 'AND', $orderBy = null, $limit = 0)
{
static::initModel();
$result = Database::getInstance()->select(static::$table, $conditions, $conditionConnector, $orderBy, $limit);
if($limit === 1){
return static::createFromDbResult($result);
}
return static::createMultipleFromDbResult($result);
}
/**
* Find all models
*
* @param array|null $orderBy see helperOrderBy
*
* @return ModelCollection|static[]
*/
public static function findAll($orderBy = null)
{
return static::findWhere(array(), 'AND', $orderBy);
}
/**
* Find first model matching a condition
*
* @param array $conditions see helperConditionArray
* @param string $conditionConnector see helperConditionArray
* @param array|null $orderBy
*
* @return AbstractModel|null
*/
public static function findWhereFirst($conditions = array(), $conditionConnector = 'AND', $orderBy = null)
{
return static::findWhere($conditions, $conditionConnector, $orderBy, 1);
}
/**
* Find a model by id
*
* @param mixed $id
*
* @return AbstractModel|null
*/
public static function find($id)
{
static::initModel();
return static::findWhereFirst(array(static::$idAttribute, $id));
}
/**
* Save model data to database
*/
public function save()
{
$data = $this->preSave($this->data);
$values = array();
foreach(static::$attributeDbAttributeMapping as $attribute => $sqlAttribute){
if($sqlAttribute === static::$idAttribute){
continue;
}
$values[$sqlAttribute] = $data[$attribute];
}
if(is_null($this->getId())){
$insertId = Database::getInstance()->insert(static::$table, $values);
$this->setId(intval($insertId));
}
else{
Database::getInstance()->update(static::$table, $values, array(static::$idAttribute, $this->getId()));
}
}
/**
* Delete model from database
*
* @return bool
*/
public function delete()
{
if(!is_null($this->getId())){
Database::getInstance()->delete(static::$table, static::$idAttribute, $this->getId());
return true;
}
return false;
}
/**
* Count models by a condition
*
* @param array $conditions see helperConditionArray
* @param string $conditionConnector see helperConditionArray
*
* @return int
*/
public static function countWhere($conditions = array(), $conditionConnector = 'AND')
{
static::initModel();
return Database::getInstance()->count(static::$table, static::$idAttribute, $conditions, $conditionConnector);
}
/**
* Count all models
*
* @return int
*/
public static function count()
{
return static::countWhere();
}
}

View file

@ -0,0 +1,6 @@
<?php
abstract class AbstractMultiRedirect extends AbstractRedirect
{
}

View file

@ -0,0 +1,398 @@
<?php
abstract class AbstractRedirect extends AbstractModel
{
use DomainLimitTrait;
/**
* Db table for find methods
*
* @var string
*/
public static $table;
/**
* Db id attribute for find methods
*
* @var string
*/
public static $idAttribute;
/**
* Mapping model attributes and database attributes for saving
*
* @var array
*/
protected static $attributeDbAttributeMapping = null;
/**
* @var ModelCollection
*/
protected $conflictingUsers = null;
/**
* @inheritdoc
*/
protected static function initModel()
{
if(is_null(static::$attributeDbAttributeMapping)){
static::$table = Config::get('schema.tables.aliases', 'aliases');
static::$idAttribute = Config::get('schema.attributes.aliases.id', 'id');
static::$attributeDbAttributeMapping = array(
'id' => Config::get('schema.attributes.aliases.id', 'id'),
'source' => Config::get('schema.attributes.aliases.source', 'source'),
'destination' => Config::get('schema.attributes.aliases.destination', 'destination'),
'multi_hash' => Config::get('schema.attributes.aliases.multi_source', 'multi_source'),
);
if(Config::get('options.enable_user_redirects', false)){
static::$attributeDbAttributeMapping['is_created_by_user'] = Config::get('schema.attributes.aliases.is_created_by_user', 'is_created_by_user');
}
}
}
/**
* @inheritdoc
*/
protected function preSave($data)
{
$data = parent::preSave($data);
$data['source'] = emailsToString($data['source']);
$data['destination'] = emailsToString($data['destination']);
if(Config::get('options.enable_user_redirects', false)){
$data['is_created_by_user'] = $data['is_created_by_user'] ? 1 : 0;
}
return $data;
}
/**
* @inheritdoc
*/
protected function __construct($data)
{
parent::__construct($data);
$source = stringToEmails($data[static::attr('source')]);
$destination = stringToEmails($data[static::attr('destination')]);
if(get_called_class() === 'Alias' || get_called_class() === 'Redirect'){
$source = $source[0];
}
if(get_called_class() === 'Alias' || get_called_class() === 'MultiAlias'){
$destination = $destination[0];
}
$this->setSource($source);
$this->setDestination($destination);
if(Config::get('options.enable_multi_source_redirects', false)){
$this->setMultiHash($data[static::attr('multi_hash')]);
}
if(Config::get('options.enable_user_redirects', false)){
$this->setIsCreatedByUser($data[static::attr('is_created_by_user')]);
}
}
/**
* @inheritdoc
*/
public static function create($data)
{
if(get_called_class() !== 'AbstractRedirect'){
return parent::create($data);
}
$hasMultipleSources = array_key_exists(static::attr('source'), $data)
&& strpos($data[static::attr('source')], ',') !== false;
$hasMultipleDestinations = array_key_exists(static::attr('destination'), $data)
&& strpos($data[static::attr('destination')], ',') !== false;
if(Config::get('options.enable_multi_source_redirects', false) && $hasMultipleSources
){
if($hasMultipleDestinations){
return MultiRedirect::create($data);
}
else{
return MultiAlias::create($data);
}
}
else{
if($hasMultipleDestinations){
return Redirect::create($data);
}
else{
return Alias::create($data);
}
}
}
/**
* @return array|string
*/
public function getSource()
{
return $this->getAttribute('source');
}
/**
* @param string|array $value
*/
public function setSource($value)
{
if(is_array($value)){
$this->setAttribute('source', array_map('strtolower', $value));
}
else{
$this->setAttribute('source', strtolower($value));
}
}
/**
* @return array|string
*/
public function getDestination()
{
return $this->getAttribute('destination');
}
/**
* @param string|array $value
*/
public function setDestination($value)
{
if(is_array($value)){
$this->setAttribute('destination', array_map('strtolower', $value));
}
else{
$this->setAttribute('destination', strtolower($value));
}
}
/**
* @return string
*/
public function getMultiHash()
{
return $this->getAttribute('multi_hash');
}
/**
* @param string $value
*/
public function setMultiHash($value)
{
$this->setAttribute('multi_hash', $value);
}
/**
* @return bool
*/
public function isCreatedByUser()
{
return $this->getAttribute('is_created_by_user');
}
/**n
* @param bool $value
*/
public function setIsCreatedByUser($value)
{
$this->setAttribute('is_created_by_user', $value ? true : false);
}
/**
* @return array
*/
protected function getDomain()
{
$sources = $this->getSource();
if(is_string($sources)){
$sources = array($sources);
}
$domains = array();
foreach($sources as $source){
$emailParts = explode('@', $source);
if(count($emailParts) === 2){
$domains[] = $emailParts[1];
}
}
return array_unique($domains);
}
/**
* @return ModelCollection
*/
public function getConflictingUsers()
{
if(is_null($this->conflictingUsers)){
$sources = $this->getSource();
if(is_string($sources)){
$sources = array($sources);
}
$this->conflictingUsers = new ModelCollection();
foreach($sources as $source){
$user = User::findByEmail($source);
if(!is_null($user)){
$this->conflictingUsers->add($user);
}
}
}
return $this->conflictingUsers;
}
/**
* @param string $template
*
* @return array|string
*/
public function getConflictingMarkedSource($template = "<u>%email%</u>")
{
$conflictingUsers = $this->getConflictingUsers();
$sources = $this->getSource();
if(is_string($sources)){
$sources = array($sources);
}
foreach($conflictingUsers as $user){
if(($key = array_search($user->getEmail(), $sources)) !== false){
$sources[$key] = str_replace('%email%', $sources[$key], $template);
}
}
return $sources;
}
/**
* @inheritdoc
*/
public static function findAll($orderBy = null)
{
if(is_null($orderBy)){
$orderBy = array(static::attr('source'));
}
return parent::findAll($orderBy);
}
/**
* @return string
*/
private static function generateRedirectBaseQuery()
{
if(Config::get('options.enable_multi_source_redirects', false)){
return "SELECT r.* FROM (
SELECT
GROUP_CONCAT(g.`".static::$idAttribute."` ORDER BY g.`".static::$idAttribute."` SEPARATOR ',') AS `".static::$idAttribute."`,
GROUP_CONCAT(g.`".static::attr('source')."` SEPARATOR ',') AS `".static::attr('source')."`,
g.`".static::attr('destination')."`,
g.`".static::attr('multi_hash')."`
".(Config::get('options.enable_user_redirects', false) ? ", g.`".static::attr('is_created_by_user')."`" : "")."
FROM `".static::$table."` AS g
WHERE g.`".static::attr('multi_hash')."` IS NOT NULL
GROUP BY g.`".static::attr('multi_hash')."`
UNION
SELECT
s.`".static::$idAttribute."`,
s.`".static::attr('source')."`,
s.`".static::attr('destination')."`,
s.`".static::attr('multi_hash')."`
".(Config::get('options.enable_user_redirects', false) ? ", s.`".static::attr('is_created_by_user')."`" : "")."
FROM `".static::$table."` AS s
WHERE s.`".static::attr('multi_hash')."` IS NULL
) AS r";
}
else{
return "SELECT * FROM `".static::$table."`";
}
}
public static function findMultiAll($orderBy = null)
{
static::initModel();
if(is_null($orderBy)){
$orderBy = array(static::attr('source'));
}
$sql = static::generateRedirectBaseQuery()
.Database::helperOrderBy($orderBy);
return static::findAllRaw($sql);
}
public static function findMultiWhere($conditions = array(), $conditionConnector = 'AND', $orderBy = null, $limit = 0)
{
$sql = static::generateRedirectBaseQuery()
.Database::helperWhere($conditions, $conditionConnector)
.Database::helperOrderBy($orderBy)
.Database::helperLimit($limit);
if($limit === 1){
return static::findRaw($sql);
}
return static::findAllRaw($sql);
}
public static function findMultiWhereFirst($conditions = array(), $conditionConnector = 'AND', $orderBy = null)
{
return static::findMultiWhere($conditions, $conditionConnector, $orderBy, 1);
}
public static function findMulti($id)
{
static::initModel();
return static::findMultiWhereFirst(array(static::$idAttribute, $id));
}
/**
* @param array|User|null $limitedBy
*
* @return ModelCollection|static[]
*/
public static function getMultiByLimitedDomains($limitedBy = null)
{
return static::filterModelCollectionByLimitedDomains(static::findMultiAll(), $limitedBy);
}
}

View file

@ -0,0 +1,10 @@
<?php
/**
* @method string getSource()
* @method string getDestination()
*/
class Alias extends Redirect
{
}

View file

@ -0,0 +1,103 @@
<?php
class Domain extends AbstractModel
{
use DomainLimitTrait;
/**
* Db table for find methods
*
* @var string
*/
public static $table;
/**
* Db id attribute for find methods
*
* @var string
*/
public static $idAttribute;
/**
* Mapping model attributes and database attributes for saving
*
* @var array
*/
protected static $attributeDbAttributeMapping = null;
/**
* @inheritdoc
*/
protected static function initModel()
{
if(is_null(static::$attributeDbAttributeMapping)){
static::$table = Config::get('schema.tables.domains', 'domains');
static::$idAttribute = Config::get('schema.attributes.domains.id', 'id');
static::$attributeDbAttributeMapping = array(
'id' => Config::get('schema.attributes.domains.id', 'id'),
'domain' => Config::get('schema.attributes.domains.domain', 'domain'),
);
}
}
/**
* @inheritdoc
*/
protected function __construct($data)
{
parent::__construct($data);
$this->setDomain($data[static::attr('domain')]);
}
/**
* @return string
*/
public function getDomain()
{
return $this->getAttribute('domain');
}
/**
* @param string $value
*/
public function setDomain($value)
{
$this->setAttribute('domain', strtolower($value));
}
/**
* @return int
*/
public function countUsers()
{
return User::countWhere(
array(User::attr('domain'), $this->getDomain())
);
}
/**
* @return int
*/
public function countRedirects()
{
return AbstractRedirect::countWhere(
array(
array(AbstractRedirect::attr('source'), 'LIKE', "%@{$this->getDomain()}"),
array(AbstractRedirect::attr('destination'), 'LIKE', "%@{$this->getDomain()}")
),
'OR'
);
}
}

View file

@ -0,0 +1,69 @@
<?php
trait DomainLimitTrait
{
/**
* @param array|User|null $limitedBy
*
* @return bool
*/
public function isInLimitedDomains($limitedBy = null)
{
if(!Config::get('options.enable_admin_domain_limits', false)) {
return true;
}
if(is_null($limitedBy)){
return static::isInLimitedDomains(Auth::getUser());
}
elseif($limitedBy instanceof User) {
/** @var User $limitedBy */
return !$limitedBy->isDomainLimited() || static::isInLimitedDomains($limitedBy->getDomainLimits());
}
if(!is_array($limitedBy)){
throw new InvalidArgumentException;
}
/** @var string|array|string[] $domain */
$domain = $this->getDomain();
if(is_string($domain)) {
return in_array($domain, $limitedBy);
}
foreach($domain as $d){
if(!in_array($d, $limitedBy)) {
return false;
}
}
return true;
}
/**
* @param ModelCollection|static[] $collection
* @param array|User|null $limitedBy
*
* @return ModelCollection|static[]
*/
protected static function filterModelCollectionByLimitedDomains($collection, $limitedBy = null)
{
return $collection->searchAll(function($model) use ($limitedBy){
/** @var static $model */
//var_dump($model->isInLimitedDomains($limitedBy), $model->getDomain());
return $model->isInLimitedDomains($limitedBy);
});
}
/**
* @param array|User|null $limitedBy
*
* @return ModelCollection|static[]
*/
public static function getByLimitedDomains($limitedBy = null)
{
return static::filterModelCollectionByLimitedDomains(static::findAll(), $limitedBy);
}
}

View file

@ -0,0 +1,278 @@
<?php
class ModelCollection implements Iterator, ArrayAccess, Countable
{
/**
* @var array|AbstractModel[]
*/
private $models = array();
/**
* Constructor.
*
* @param array|AbstractModel[] $array
*/
public function __construct($array = array())
{
if($this->isNumericArray($array)){
foreach($array as $model){
$this->add($model);
}
}
else{
foreach($array as $key => $model){
$this->add($model, $key);
}
}
}
/**
* @param array $array
*
* @return bool
*/
protected function isNumericArray($array)
{
return array_keys($array) === range(0, count($array) - 1)
&& count(array_filter($array, 'is_string')) === 0;
}
/**
* Adds a model to the collection,
* but won't replace if it exists with that key
*
* @param AbstractModel $model
* @param mixed|null $key
*/
public function add($model, $key = null)
{
if(is_null($model) || !($model instanceof AbstractModel)){
return;
}
if(is_null($key)){
$this->models[] = $model;
}
elseif(!$this->has($key)){
$this->models[$key] = $model;
}
}
/**
* Replace a model with given key
*
* @param AbstractModel $model
* @param mixed $key
*/
public function replace($model, $key)
{
if(is_null($model) || !($model instanceof AbstractModel)){
return;
}
$model[$key] = $model;
}
/**
* Delete a model by key
*
* @param mixed $key
*/
public function delete($key)
{
if($this->has($key)){
unset($this->models[$key]);
}
}
/**
* Check if collection has a model by key
*
* @param mixed $key
*
* @return bool
*/
public function has($key)
{
return isset($this->models[$key]);
}
/**
* Get a model from the collection by key
*
* @param mixed $key
*
* @return AbstractModel|null
*/
public function get($key)
{
if($this->has($key)){
return $this->models[$key];
}
return null;
}
/**
* Search a model in collection with a condition
*
* @param callable $callable Gives back if the search matches
*
* @return AbstractModel|null
*/
public function search($callable)
{
if(is_callable($callable)){
foreach($this->models as $model){
if($callable($model)){
return $model;
}
}
}
return null;
}
/**
* Search all models in collection with a condition
*
* @param callable $callable Gives back if the search matches
*
* @return static
*/
public function searchAll($callable)
{
$collection = new static;
if(is_callable($callable)){
foreach($this->models as $model){
if($callable($model)){
$collection->add($model);
}
}
}
return $collection;
}
/**
* Convert models to an array of strings
*
* @param callable $callable Gives back a string for a model
*
* @return array|string[]
*/
public function toStringArray($callable)
{
$strings = array();
if(is_callable($callable)){
foreach($this->models as $model){
$strings[] = $callable($model);
}
}
return $strings;
}
/**
* @inheritdoc
*/
public function current()
{
return current($this->models);
}
/**
* @inheritdoc
*/
public function next()
{
return next($this->models);
}
/**
* @inheritdoc
*/
public function key()
{
return key($this->models);
}
/**
* @inheritdoc
*/
public function valid()
{
return $this->current() !== false;
}
/**
* @inheritdoc
*/
public function rewind()
{
reset($this->models);
}
/**
* @inheritdoc
*/
public function offsetExists($offset)
{
return $this->has($offset);
}
/**
* @inheritdoc
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* @inheritdoc
*/
public function offsetSet($offset, $value)
{
$this->add($value, $offset);
}
/**
* @inheritdoc
*/
public function offsetUnset($offset)
{
$this->delete($offset);
}
/**
* @inheritdoc
*/
public function count()
{
return count($this->models);
}
}

View file

@ -0,0 +1,10 @@
<?php
/**
* @method array getSource()
* @method string getDestination()
*/
class MultiAlias extends AbstractMultiRedirect
{
}

View file

@ -0,0 +1,10 @@
<?php
/**
* @method array getSource()
* @method array getDestination()
*/
class MultiRedirect extends AbstractMultiRedirect
{
}

View file

@ -0,0 +1,10 @@
<?php
/**
* @method string getSource()
* @method array getDestination()
*/
class Redirect extends AbstractRedirect
{
}

467
include/php/models/User.php Normal file
View file

@ -0,0 +1,467 @@
<?php
class User extends AbstractModel
{
use DomainLimitTrait;
/**
* Db table for find methods
*
* @var string
*/
public static $table;
/**
* Db id attribute for find methods
*
* @var string
*/
public static $idAttribute;
/**
* Mapping model attributes and database attributes for saving
*
* @var array
*/
protected static $attributeDbAttributeMapping = null;
const ROLE_USER = 'user';
const ROLE_ADMIN = 'admin';
/**
* @var AbstractRedirect
*/
protected $conflictingRedirect = null;
/**
* @var ModelCollection|AbstractRedirect[]
*/
protected $redirects = null;
/**
* @inheritdoc
*/
protected static function initModel()
{
if(is_null(static::$attributeDbAttributeMapping)){
static::$table = Config::get('schema.tables.users', 'users');
static::$idAttribute = Config::get('schema.attributes.users.id', 'id');
static::$attributeDbAttributeMapping = array(
'id' => Config::get('schema.attributes.users.id', 'id'),
'username' => Config::get('schema.attributes.users.username', 'username'),
'domain' => Config::get('schema.attributes.users.domain', 'domain'),
'password_hash' => Config::get('schema.attributes.users.password', 'password'),
);
if(Config::get('options.enable_mailbox_limits', false)){
static::$attributeDbAttributeMapping['mailbox_limit'] = Config::get('schema.attributes.users.mailbox_limit');
}
if(Config::get('options.enable_user_redirects', false)){
static::$attributeDbAttributeMapping['max_user_redirects'] = Config::get('schema.attributes.users.max_user_redirects');
}
}
}
/**
* @inheritdoc
*/
protected function __construct($data)
{
parent::__construct($data);
$this->setUsername($data[static::attr('username')]);
$this->setDomain($data[static::attr('domain')]);
$this->setPasswordHash($data[static::attr('password_hash')]);
if(Config::get('options.enable_mailbox_limits', false)){
$this->setMailboxLimit($data[static::attr('mailbox_limit')]);
}
if(Config::get('options.enable_user_redirects', false)){
$this->setMaxUserRedirects($data[static::attr('max_user_redirects')]);
}
$this->setAttribute('role', static::getRoleByEmail($this->getEmail()));
}
/**
* @return string
*/
public function getUsername()
{
return $this->getAttribute('username');
}
/**
* @param string $value
*/
public function setUsername($value)
{
$this->setAttribute('username', strtolower($value));
}
/**
* @return string
*/
public function getDomain()
{
return $this->getAttribute('domain');
}
/**
* @param string $value
*/
public function setDomain($value)
{
$this->setAttribute('domain', strtolower($value));
}
/**
* @return string
*/
public function getEmail()
{
return $this->getUsername().'@'.$this->getDomain();
}
/**
* @return string
*/
public function getPasswordHash()
{
return $this->getAttribute('password_hash');
}
/**
* @param string $value
*/
public function setPasswordHash($value)
{
$this->setAttribute('password_hash', $value);
}
/**
* @return int
*/
public function getMailboxLimit()
{
return $this->getAttribute('mailbox_limit');
}
/**
* @param int $value
*/
public function setMailboxLimit($value)
{
$this->setAttribute('mailbox_limit', intval($value));
}
/**
* @return int
*/
public function getMaxUserRedirects()
{
return $this->getAttribute('max_user_redirects');
}
/**
* @param int $value
*/
public function setMaxUserRedirects($value)
{
$this->setAttribute('max_user_redirects', intval($value));
}
/**
* @param string $attr
* @param mixed $default
*
* @return mixed
*
* @throws Exception
*/
protected static function getAttributeDefaultValue($attr, $default)
{
static::initModel();
$sql = "SELECT DEFAULT(".static::attr($attr).") FROM `".static::$table."` LIMIT 1";
try {
$result = Database::getInstance()->query($sql);
if($result->num_rows === 1){
$row = $result->fetch_array();
return $row[0];
}
}
catch(Exception $e) {
if (strpos($e->getMessage(), 'doesn\'t have a default') !== false) {
throw new Exception('Database table "'.static::$table.'" is missing a default value for attribute "'.static::attr($attr).'".');
}
return $default;
}
return $default;
}
/**
* Get mailbox limit default via database default value
*
* @return int
*/
public static function getMailboxLimitDefault()
{
if(Config::get('options.enable_mailbox_limits', false)){
return intval(static::getAttributeDefaultValue('mailbox_limit', 0));
}
return 0;
}
/**
* Get max user redirects default via database default value
*
* @return int
*/
public static function getMaxUserRedirectsDefault()
{
if(Config::get('options.enable_user_redirects', false)){
return intval(static::getAttributeDefaultValue('max_user_redirects', 0));
}
return 0;
}
/**
* @return string
*/
public function getRole()
{
return $this->getAttribute('role');
}
/**
* @param string $email
*
* @return string
*/
private static function getRoleByEmail($email)
{
if(in_array($email, Config::get('admins', array()))){
return static::ROLE_ADMIN;
}
return static::ROLE_USER;
}
/**
* Is user limited by domain limits?
*
* @return bool
*/
public function isDomainLimited()
{
$adminDomainLimits = Config::get('admin_domain_limits', array());
return Config::get('options.enable_admin_domain_limits', false)
&& is_array($adminDomainLimits) && isset($adminDomainLimits[$this->getEmail()]);
}
/**
* Get domain limits, returns an empty array if user has no limits or ADMIN_DOMAIN_LIMITS_ENABLED is disabled
*
* @return array
*/
public function getDomainLimits()
{
if($this->isDomainLimited()){
$adminDomainLimits = Config::get('admin_domain_limits', array());
if(!is_array($adminDomainLimits[$this->getEmail()])){
throw new InvalidArgumentException('Config value of admin domain limits for email "'.$this->getEmail().'" needs to be of type array.');
}
return $adminDomainLimits[$this->getEmail()];
}
return array();
}
/**
* @return bool
*/
public function isAllowedToCreateUserRedirects()
{
return $this->getMaxUserRedirects() >= 0;
}
/**
* @return bool
*/
public function canCreateUserRedirects()
{
if(!$this->isAllowedToCreateUserRedirects()
|| (
$this->getMaxUserRedirects() > 0
&& $this->getSelfCreatedRedirects()->count() >= $this->getMaxUserRedirects()
)
){
return false;
}
return true;
}
/**
* @return AbstractRedirect
*/
public function getConflictingRedirect()
{
if(is_null($this->conflictingRedirect)){
$this->conflictingRedirect = AbstractRedirect::findWhereFirst(
array(AbstractRedirect::attr('source'), $this->getEmail())
);
}
return $this->conflictingRedirect;
}
/**
* @return ModelCollection|AbstractRedirect[]
*/
public function getRedirects()
{
if(is_null($this->redirects)){
$this->redirects = AbstractRedirect::findMultiWhere(
array(AbstractRedirect::attr('destination'), 'LIKE', '%'.$this->getEmail().'%')
);
}
return $this->redirects;
}
/**
* @return ModelCollection|AbstractRedirect[]
*/
public function getAnonymizedRedirects()
{
$redirects = $this->getRedirects();
foreach($redirects as $redirect){
$emails = $redirect->getDestination();
if(is_array($emails) && count($emails) > 1){
$redirect->setDestination(array($this->getEmail(), '&hellip;'));
}
}
return $redirects;
}
/**
* @return ModelCollection|AbstractRedirect[]
*/
public function getSelfCreatedRedirects()
{
$redirects = $this->getRedirects();
return $redirects->searchAll(
function($redirect) {
/** @var AbstractRedirect $redirect */
return $redirect->isCreatedByUser();
}
);
}
/**
* Change this users password, throws Exception if password is invalid.
*
* @param string $password
* @param string $passwordRepeated
*
* @throws AuthException
*/
public function changePassword($password, $passwordRepeated)
{
Auth::validateNewPassword($password, $passwordRepeated);
$passwordHash = Auth::generatePasswordHash($password);
$this->setPasswordHash($passwordHash);
$this->save();
}
/**
* @inheritdoc
*/
public static function findAll($orderBy = null)
{
if(is_null($orderBy)){
$orderBy = array(static::attr('domain'), static::attr('username'));
}
return parent::findAll($orderBy);
}
/**
* @param string $email
*
* @return User|null
*/
public static function findByEmail($email)
{
$emailInParts = explode("@", $email);
if(count($emailInParts) !== 2){
return null;
}
$username = $emailInParts[0];
$domain = $emailInParts[1];
return static::findWhereFirst(
array(
array(static::attr('username'), $username),
array(static::attr('domain'), $domain)
)
);
}
}

View file

@ -1,5 +0,0 @@
<h1>This page does not exist.</h1>
<p>
Sorry, the page you requested could not be found.
</p>

View file

@ -1,29 +1,33 @@
<?php
<?php
if(Auth::getUser()->isDomainLimited()){
Router::displayError(403);
}
if(isset($_POST['domain'])){
$domain = $db->escape_string($_POST['domain']);
$domain = strtolower($domain);
if($domain !== ""){
// Check if domain exists in database
$domain_exists = $db->query("SELECT `".DBC_DOMAINS_DOMAIN."` FROM `".DBT_DOMAINS."` WHERE `".DBC_DOMAINS_DOMAIN."` = '$domain';");
if($domain_exists->num_rows == 0){
$sql = "INSERT INTO `".DBT_DOMAINS."` (`".DBC_DOMAINS_DOMAIN."`) VALUES ('$domain');";
if(!$result = $db->query($sql)){
dbError($db->error);
}
else{
// Created domain successfull, redirect to overview
redirect("admin/listdomains/?created=1");
}
$inputDomain = $_POST['domain'];
if(!empty($inputDomain)){
$existingDomain = Domain::findWhere(array(Domain::attr('domain'), $inputDomain));
if(!is_null($existingDomain)){
Domain::createAndSave(
array(
Domain::attr('domain') => $inputDomain,
)
);
// Created domain successfull, redirect to overview
Router::redirect("admin/listdomains/?created=1");
}
else{
add_message("fail", "Domain already exists in database.");
Message::getInstance()->fail("Domain already exists in database.");
}
}
else{
add_message("fail", "Empty domain could not be created.");
Message::getInstance()->fail("Empty domain couldn't be created.");
}
}
@ -31,10 +35,10 @@ if(isset($_POST['domain'])){
<h1>Create new domain</h1>
<?php output_messages(); ?>
<?php echo Message::getInstance()->render(); ?>
<div class="buttons">
<a class="button" href="<?php echo url('admin/listdomains'); ?>">&#10092; Back to domain list</a>
<a class="button" href="<?php echo Router::url('admin/listdomains'); ?>">&#10092; Back to domain list</a>
</div>
<form class="form" action="" method="post" autocomplete="off">

View file

@ -1,85 +1,85 @@
<?php
if(Auth::getUser()->isDomainLimited()){
Router::displayError(403);
}
if(!isset($_GET['id'])){
// Domain id not set, redirect to overview
redirect("admin/listdomains");
Router::redirect("admin/listdomains");
}
$id = $db->escape_string($_GET['id']);
$id = $_GET['id'];
//Load user data from DB
$sql = "SELECT `".DBC_DOMAINS_DOMAIN."` FROM `".DBT_DOMAINS."` WHERE `".DBC_DOMAINS_ID."` = '$id' LIMIT 1;";
/** @var Domain $domain */
$domain = Domain::find($id);
if(!$result = $db->query($sql)){
dbError($db->error);
if(is_null($domain)){
// Domain doesn't exist, redirect to overview
Router::redirect("admin/listdomains");
}
if($result->num_rows !== 1){
// Domain does not exist, redirect to overview
redirect("admin/listdomains");
if(!$domain->isInLimitedDomains()){
Router::redirect("admin/listdomains/?missing-permission=1");
}
$row = $result->fetch_assoc();
$domain = $row[DBC_DOMAINS_DOMAIN];
// Delete domain
if(isset($_POST['confirm'])){
$confirm = $_POST['confirm'];
if($confirm === "yes"){
$admin_domains = array();
foreach($admins as $admin) {
// Check if admin domain is affected
$isAdminDomain = false;
foreach(Config::get('admins', array()) as $admin){
$parts = explode("@", $admin);
$admin_domains[] = $parts[1];
if(count($parts) === 2 && $parts[2] === $domain->getDomain()){
$isAdminDomain = true;
break;
}
}
// Check if admin domain is affected
if(!in_array($domain, $admin_domains)){
$sql = "DELETE FROM `".DBT_DOMAINS."` WHERE `".DBC_DOMAINS_ID."` = '$id'";
if(!$isAdminDomain){
if(!$result = $db->query($sql)){
dbError($db->error);
}
else{
$sql = "DELETE FROM `".DBT_USERS."` WHERE `".DBC_USERS_DOMAIN."` = '$domain'";
$users = User::findWhere(array(User::attr('domain'), $domain->getDomain()));
if(!$result = $db->query($sql)){
dbError($db->error);
}
else{
// Delete domain successfull, redirect to overview
redirect("admin/listdomains/?deleted=1");
}
/** @var User $user */
foreach($users as $user){
$user->delete();
}
$domain->delete();
// Delete domain successfull, redirect to overview
Router::redirect("admin/listdomains/?deleted=1");
}
else{
// Cannot delete domain with admin emails, redirect to overview
redirect("admin/listdomains/?adm_del=1");
Router::redirect("admin/listdomains/?adm_del=1");
}
}
else{
// Choose to not delete domain, redirect to overview
redirect("admin/listdomains");
Router::redirect("admin/listdomains");
}
}
?>
<h1>Delete domain "<?php echo $domain ?>"?</h1>
<h1>Delete domain "<?php echo $domain->getDomain() ?>"?</h1>
<div class="buttons">
<a class="button" href="<?php echo url('admin/listdomains'); ?>">&#10092; Back to domain list</a>
<a class="button" href="<?php echo Router::url('admin/listdomains'); ?>">&#10092; Back to domain list</a>
</div>
<form class="form" action="" method="post">
<form class="form" action="" method="post" autocomplete="off">
<div class="input-group">
<label>All mailboxes matching the domain will be deleted from the user database!</label>
<div class="input-info">Mailbox directories in the filesystem won't be affected.</div>
</div>
<div class="input-group">
<label>Do you realy want to delete this domain?</label>
<label for="confirm">Do you realy want to delete this domain?</label>
<div class="input">
<select name="confirm" autofocus required>
<option value="no">No!</option>

View file

@ -2,98 +2,74 @@
if(!isset($_GET['id'])){
// Redirect id not set, redirect to overview
redirect("admin/listredirects");
Router::redirect("admin/listredirects");
}
$id = $db->escape_string($_GET['id']);
$id = $_GET['id'];
if(defined('DBC_ALIASES_MULTI_SOURCE')){
$sql = "SELECT r.* FROM (
SELECT
group_concat(g.`".DBC_ALIASES_ID."` ORDER BY g.`".DBC_ALIASES_ID."` SEPARATOR ',') AS `".DBC_ALIASES_ID."`,
group_concat(g.`".DBC_ALIASES_SOURCE."` SEPARATOR ',') AS `".DBC_ALIASES_SOURCE."`,
g.`".DBC_ALIASES_DESTINATION."`,
g.`".DBC_ALIASES_MULTI_SOURCE."`
FROM `".DBT_ALIASES."` AS g
WHERE g.`".DBC_ALIASES_MULTI_SOURCE."` IS NOT NULL
GROUP BY g.`".DBC_ALIASES_MULTI_SOURCE."`
UNION
SELECT
s.`".DBC_ALIASES_ID."`,
s.`".DBC_ALIASES_SOURCE."`,
s.`".DBC_ALIASES_DESTINATION."`,
s.`".DBC_ALIASES_MULTI_SOURCE."`
FROM `".DBT_ALIASES."` AS s
WHERE s.`".DBC_ALIASES_MULTI_SOURCE."` IS NULL
) AS r
WHERE `".DBC_ALIASES_ID."` = '$id' LIMIT 1;";
}
else{
$sql = "SELECT `".DBC_ALIASES_ID."`, `".DBC_ALIASES_SOURCE."`, `".DBC_ALIASES_DESTINATION."` FROM `".DBT_ALIASES."` WHERE `".DBC_ALIASES_ID."` = '$id' LIMIT 1;";
/** @var AbstractRedirect $redirect */
$redirect = AbstractRedirect::findMulti($id);
if(is_null($redirect)){
// Redirect doesn't exist, redirect to overview
Router::redirect("admin/listredirects");
}
if(!$result = $db->query($sql)){
dbError($db->error);
if(!$redirect->isInLimitedDomains()){
Router::redirect("admin/listredirects/?missing-permission=1");
}
if($result->num_rows !== 1){
// Redirect does not exist, redirect to overview
redirect("admin/listredirects");
}
$redirect = $result->fetch_assoc();
if(isset($_POST['confirm'])){
$confirm = $_POST['confirm'];
if($confirm === "yes"){
$key = DBC_ALIASES_ID;
if(defined('DBC_ALIASES_MULTI_SOURCE') && !empty($redirect[DBC_ALIASES_MULTI_SOURCE])){
$key = DBC_ALIASES_MULTI_SOURCE;
}
$value = $redirect[$key];
if ($redirect instanceof AbstractMultiRedirect){
$sql = "DELETE FROM `".DBT_ALIASES."` WHERE `$key` = '$value'";
// Get single source rows of multi source redirect/alias instead
$hash = $redirect->getMultiHash();
$singleRedirects = AbstractRedirect::findWhere(array(AbstractRedirect::attr('multi_hash'), $hash));
if(!$result = $db->query($sql)){
dbError($db->error);
/** @var AbstractRedirect $redirectToDelete */
foreach($singleRedirects as $redirectToDelete){
$redirectToDelete->delete();
}
}
else{
// Delete redirect successfull, redirect to overview
redirect("admin/listredirects/?deleted=1");
else {
$redirect->delete();
}
// Delete redirect successfull, redirect to overview
Router::redirect("admin/listredirects/?deleted=1");
}
else{
// Choose to not delete redirect, redirect to overview
redirect("admin/listredirects");
Router::redirect("admin/listredirects");
}
}
else{
$source = $redirect[DBC_ALIASES_SOURCE];
$destination = $redirect[DBC_ALIASES_DESTINATION];
?>
<h1>Delete redirection?</h1>
<div class="buttons">
<a class="button" href="<?php echo url('admin/listredirects'); ?>">&#10092; Back to redirect list</a>
<a class="button" href="<?php echo Router::url('admin/listredirects'); ?>">&#10092; Back to redirect list</a>
</div>
<form class="form" action="" method="post">
<form class="form" action="" method="post" autocomplete="off">
<div class="input-group">
<label>Source</label>
<div class="input-info"><?php echo strip_tags(formatEmails($source, FRONTEND_EMAIL_SEPARATOR_TEXT)); ?></div>
<div class="input-info"><?php echo formatEmailsText($redirect->getSource()); ?></div>
</div>
<div class="input-group">
<label>Destination</label>
<div class="input-info"><?php echo strip_tags(formatEmails($destination, FRONTEND_EMAIL_SEPARATOR_TEXT)); ?></div>
<div class="input-info"><?php echo formatEmailsText($redirect->getDestination()); ?></div>
</div>
<div class="input-group">
<label>Do you realy want to delete this redirect?</label>
<label for="confirm">Do you realy want to delete this redirect?</label>
<div class="input">
<select name="confirm" autofocus required>
<option value="no">No!</option>

View file

@ -1,70 +1,146 @@
<?php
<?php
$id = $db->escape_string($_GET['id']);
//Load user data from DB
$sql = "SELECT `".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."` FROM `".DBT_USERS."` WHERE `".DBC_USERS_ID."` = '$id' LIMIT 1;";
if(!$result = $db->query($sql)){
dbError($db->error);
if(!isset($_GET['id'])){
// Redirect id not set, redirect to overview
Router::redirect('admin/listredirects');
}
$row = $result->fetch_assoc();
$id = $_GET['id'];
$username = $row[DBC_USERS_USERNAME];
$domain = $row[DBC_USERS_DOMAIN];
/** @var User $user */
$user = User::find($id);
$mailAddress = $username."@".$domain;
if(is_null($user)){
// User doesn't exist, redirect to overview
Router::redirect('admin/listusers');
}
if(!$user->isInLimitedDomains()){
Router::redirect('admin/listusers/?missing-permission=1');
}
// Delete user
if(isset($_POST['confirm'])){
$confirm = $_POST['confirm'];
if($confirm === "yes"){
if($confirm === 'yes'){
// Check if admin is affected
if (!in_array($mailAddress, $admins)) {
$sql = "DELETE FROM `".DBT_USERS."` WHERE `".DBC_USERS_ID."` = '$id'";
if(!$result = $db->query($sql)){
dbError($db->error);
}
else{
// Delete user successfull, redirect to overview
redirect("admin/listusers/?deleted=1");
if(!in_array($user->getEmail(), Config::get('admins', array()))){
// Delete redirects of this user
if(isset($_POST['delete_redirects']) && $_POST['delete_redirects'] === 'yes'
&& isset($_POST['selected_redirects']) && is_array($_POST['selected_redirects'])
){
$redirectMultiIds = $_POST['selected_redirects'];
foreach($redirectMultiIds as $redirectMultiId){
$redirectIds = explode(',', $redirectMultiId);
foreach($redirectIds as $redirectId){
// Note: No Multi* selected, so there is only Alias & Redirect
$redirects = AbstractRedirect::findWhere(
array(
array(AbstractRedirect::attr('id'), $redirectId),
array(AbstractRedirect::attr('destination'), 'LIKE', '%'.$user->getEmail().'%')
)
);
/** @var AbstractRedirect $redirect */
foreach($redirects as $redirect){
if($redirect instanceof Alias) {
$redirect->delete();
}
elseif($redirect instanceof Redirect) {
$redirect->setDestination(
array_diff(
$redirect->getDestination(),
array($user->getEmail())
)
);
$redirect->save();
}
}
}
}
}
$user->delete();
// Delete user successful, redirect to overview
Router::redirect('admin/listusers/?deleted=1');
}
else{
// Admin tried to delete himself, redirect to overview
redirect("admin/listusers/?adm_del=1");
Router::redirect('admin/listusers/?adm_del=1');
}
}
else{
// Choose to not delete user, redirect to overview
redirect("admin/listusers");
Router::redirect('admin/listusers');
}
}
$redirects = $user->getAnonymizedRedirects();
?>
<h1>Delete user "<?php echo strip_tags($mailAddress) ?>"?</h1>
<h1>Delete user "<?php echo $user->getEmail() ?>"?</h1>
<div class="buttons">
<a class="button" href="<?php echo url('admin/listusers'); ?>">&#10092; Back to user list</a>
<a class="button" href="<?php echo Router::url('admin/listusers'); ?>">&#10092; Back to user list</a>
</div>
<form class="form" action="" method="post">
<form class="form" action="" method="post" autocomplete="off">
<div class="input-group">
<label>The user's mailbox will be deleted from the database only!</label>
<div class="input-info">The mailbox in the filesystem won't be affected.</div>
</div>
<div class="input-group">
<label>Do you realy want to delete this user?</label>
<label>Redirects to this user:</label>
<?php if($redirects->count() > 0): ?>
<div class="input-info">Do you also want to delete the following redirects to this user?</div>
<table class="table table-compact">
<thead>
<tr>
<th></th>
<th>Source</th>
<th>Destination</th>
<tr>
</thead>
<tbody>
<?php foreach($redirects as $redirect): /** @var AbstractRedirect $redirect */ ?>
<tr>
<td><input type="checkbox" name="selected_redirects[]" value="<?php echo $redirect->getId(); ?>" checked></td>
<td><?php echo formatEmailsText($redirect->getSource()); ?></td>
<td><?php echo formatEmailsText($redirect->getDestination()); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="input">
<select name="confirm" autofocus required>
<option value="no">No!</option>
<option value="yes">Yes!</option>
</select>
<label>
<select name="delete_redirects" required>
<option value="no">Don't delete the redirects.</option>
<option value="yes">Yes, delete the selected redirects!</option>
</select>
</label>
</div>
<?php else: ?>
<div class="input-info">There are currently no redirects to this user.</div>
<?php endif; ?>
</div>
<div class="input-group">
<label for="confirm">Do you realy want to delete this user?</label>
<div class="input">
<label>
<select name="confirm" autofocus required>
<option value="no">No!</option>
<option value="yes">Yes!</option>
</select>
</label>
</div>
</div>

View file

@ -4,213 +4,210 @@ $id = null;
$redirect = null;
if(isset($_GET['id'])){
$id = $db->escape_string($_GET['id']);
$id = $_GET['id'];
if(defined('DBC_ALIASES_MULTI_SOURCE')){
$sql = "SELECT r.* FROM (
SELECT
group_concat(g.`".DBC_ALIASES_ID."` ORDER BY g.`".DBC_ALIASES_ID."` SEPARATOR ',') AS `".DBC_ALIASES_ID."`,
group_concat(g.`".DBC_ALIASES_SOURCE."` SEPARATOR ',') AS `".DBC_ALIASES_SOURCE."`,
g.`".DBC_ALIASES_DESTINATION."`,
g.`".DBC_ALIASES_MULTI_SOURCE."`
FROM `".DBT_ALIASES."` AS g
WHERE g.`".DBC_ALIASES_MULTI_SOURCE."` IS NOT NULL
GROUP BY g.`".DBC_ALIASES_MULTI_SOURCE."`
UNION
SELECT
s.`".DBC_ALIASES_ID."`,
s.`".DBC_ALIASES_SOURCE."`,
s.`".DBC_ALIASES_DESTINATION."`,
s.`".DBC_ALIASES_MULTI_SOURCE."`
FROM `".DBT_ALIASES."` AS s
WHERE s.`".DBC_ALIASES_MULTI_SOURCE."` IS NULL
) AS r
WHERE `".DBC_ALIASES_ID."` = '$id' LIMIT 1;";
}
else{
$sql = "SELECT `".DBC_ALIASES_ID."`, `".DBC_ALIASES_SOURCE."`, `".DBC_ALIASES_DESTINATION."` FROM `".DBT_ALIASES."` WHERE `".DBC_ALIASES_ID."` = '$id' LIMIT 1;";
/** @var AbstractRedirect $redirect */
$redirect = AbstractRedirect::findMulti($id);
if(is_null($redirect)){
// Redirect doesn't exist, redirect to overview
Router::redirect("admin/listredirects");
}
if(!$result = $db->query($sql)){
dbError($db->error);
if(!$redirect->isInLimitedDomains()){
Router::redirect("admin/listredirects/?missing-permission=1");
}
if($result->num_rows !== 1){
// Redirect does not exist, redirect to overview
redirect("admin/listredirects");
}
$redirect = $result->fetch_assoc();
$sources = stringToEmails($redirect[DBC_ALIASES_SOURCE]);
$destinations = stringToEmails($redirect[DBC_ALIASES_DESTINATION]);
}
if(isset($_POST['savemode'])){
$savemode = $_POST['savemode'];
$sources = stringToEmails($_POST['source']);
$destinations = stringToEmails($_POST['destination']);
$inputSources = stringToEmails($_POST['source']);
$inputDestinations = stringToEmails($_POST['destination']);
// validate emails
$emailErrors = array();
// basic email validation is not working 100% correct though
foreach(array_merge($sources, $destinations) as $email){
if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
$emailErrors[$email] = "Address \"$email\" is not a valid email address.";
// basic email validation isn't working 100% correct though
foreach(array_merge($inputSources, $inputDestinations) as $email){
if(strpos($email, '@') === false){
$emailErrors[$email] = "Address \"{$email}\" isn't a valid email address.";
}
}
if(defined('VALIDATE_ALIASES_SOURCE_ENABLED')){
$sql = "SELECT GROUP_CONCAT(`".DBC_DOMAINS_DOMAIN."` SEPARATOR ',') as `".DBC_DOMAINS_DOMAIN."` FROM `".DBT_DOMAINS."`";
if(!$resultDomains = $db->query($sql)){
dbError($db->error);
}
$domainRow = $resultDomains->fetch_assoc();
$domains = explode(',', $domainRow[DBC_DOMAINS_DOMAIN]);
// validate source emails are on domains
if(Config::get('options.enable_validate_aliases_source_domain', true)){
$domains = Domain::getByLimitedDomains();
// validate source emails are on domains
foreach($sources as $email){
foreach($inputSources as $email){
if(isset($emailErrors[$email])){
continue;
}
$splited = explode('@', $email);
if(count($splited) !== 2 || !in_array($splited[1], $domains)){
$emailErrors[$email] = "Domain of source address \"$email\" not in domains.";
$emailParts = explode('@', $email);
$searchResult = $domains->search(
function($domain) use ($emailParts){
/** @var Domain $domain */
return $domain->getDomain() === $emailParts[1];
}
);
if(is_null($searchResult)){
$emailErrors[$email] = "Domain of source address \"{$email}\" not in your domains.";
}
}
}
// validate no redirect loops
foreach(array_intersect($inputSources, $inputDestinations) as $email){
$emailErrors[$email] = "Address \"{$email}\" cannot be in source and destination in same redirect.";
}
if(count($emailErrors) > 0){
add_message("fail", implode("<br>", $emailErrors));
Message::getInstance()->fail(implode("<br>", $emailErrors));
}
else{
if(count($emailErrors) === 0 && $savemode === "edit" && !is_null($redirect)){
if(count($sources) > 0 && count($destinations) > 0){
$destination = $db->escape_string(emailsToString($destinations));
$source = $db->escape_string(emailsToString($sources));
if(count($inputSources) > 0 && count($inputDestinations) > 0){
$key = DBC_ALIASES_ID;
if(defined('DBC_ALIASES_MULTI_SOURCE') && !empty($redirect[DBC_ALIASES_MULTI_SOURCE])){
$key = DBC_ALIASES_MULTI_SOURCE;
if(Config::get('options.enable_multi_source_redirects', false) && $redirect instanceof AbstractMultiRedirect){
$existingRedirectsToEdit = AbstractRedirect::findWhere(
array(AbstractRedirect::attr('multi_hash'), $redirect->getMultiHash())
);
}
$value = $redirect[$key];
$sql = "SELECT `".DBC_ALIASES_ID."`, `".DBC_ALIASES_SOURCE."` FROM `".DBT_ALIASES."` WHERE `$key` = '$value'";
if(!$resultExisting = $db->query($sql)){
dbError($db->error);
else{
$existingRedirectsToEdit = AbstractRedirect::findWhere(
array(AbstractRedirect::attr('id'), $redirect->getId())
);
}
$sourceIdMap = array();
while($existingRedirect = $resultExisting->fetch_assoc()){
$sourceIdMap[$existingRedirect[DBC_ALIASES_SOURCE]] = $existingRedirect[DBC_ALIASES_ID];
}
// multi source handling
$hash = (count($sources) === 1) ? "NULL" : "'".md5($source)."'";
foreach($sources as $sourceAddress){
$sourceAddress = $db->escape_string(formatEmail($sourceAddress));
if(isset($sourceIdMap[$sourceAddress])){
// edit existing source
$id = $sourceIdMap[$sourceAddress];
$additionalSql = defined('DBC_ALIASES_MULTI_SOURCE') ? ", `".DBC_ALIASES_MULTI_SOURCE."` = $hash " : "";
$sql = "UPDATE `".DBT_ALIASES."` SET `".DBC_ALIASES_SOURCE."` = '$sourceAddress', `".DBC_ALIASES_DESTINATION."` = '$destination' $additionalSql WHERE `".DBC_ALIASES_ID."` = '$id';";
if(!$result = $db->query($sql)){
dbError($db->error);
}
unset($sourceIdMap[$sourceAddress]); // mark updated
$emailsToCheck = $inputSources;
foreach($existingRedirectsToEdit as $r){
$key = array_search($r->getSource(), $emailsToCheck);
if($key !== false){
unset($emailsToCheck[$key]);
}
else{
// add new source
$additionalSql = defined('DBC_ALIASES_MULTI_SOURCE') ? ", `".DBC_ALIASES_MULTI_SOURCE."`" : "";
$additionalSqlValue = defined('DBC_ALIASES_MULTI_SOURCE') ? ", $hash" : "";
$sql = "INSERT INTO `".DBT_ALIASES."` (`".DBC_ALIASES_SOURCE."`, `".DBC_ALIASES_DESTINATION."` $additionalSql) VALUES ('$sourceAddress', '$destination' $additionalSqlValue);";
}
if(!$result = $db->query($sql)){
dbError($db->error);
if(count($emailsToCheck) > 0){
$existingRedirectsOther = AbstractRedirect::findWhere(
array(
array(AbstractRedirect::attr('source'), 'IN', $emailsToCheck)
)
);
}
else{
$existingRedirectsOther = null;
}
if(!is_null($existingRedirectsOther) && $existingRedirectsOther->count() > 0){
$errorMessages = array();
/** @var AbstractRedirect $existingRedirect */
foreach($existingRedirectsOther as $id => $existingRedirect){
if(!$existingRedirectsToEdit->has($id)){
$errorMessages[] = "Source address \"{$existingRedirect->getSource()}\" is already redirected to some destination.";
}
}
Message::getInstance()->fail(implode("<br>", $errorMessages));
}
else{
// multi source handling
$hash = (count($inputSources) === 1) ? null : md5(emailsToString($inputSources));
// delete none updated redirect
foreach($sourceIdMap as $source => $id){
$sql = "DELETE FROM `".DBT_ALIASES."` WHERE `".DBC_ALIASES_ID."` = '$id';";
foreach($inputSources as $sourceAddress){
$sourceAddress = formatEmail($sourceAddress);
if(!$result = $db->query($sql)){
dbError($db->error);
/** @var AbstractRedirect $thisRedirect */
$thisRedirect = $existingRedirectsToEdit->search(
function($model) use ($sourceAddress){
/** @var AbstractRedirect $model */
return $model->getSource() === $sourceAddress;
}
);
if(!is_null($thisRedirect)){
// edit existing source
$thisRedirect->setSource($sourceAddress);
$thisRedirect->setDestination($inputDestinations);
$thisRedirect->setMultiHash($hash);
// Don't set 'isCreatedByUser' here, it will overwrite redirects created by user
$thisRedirect->save();
$existingRedirectsToEdit->delete($thisRedirect->getId()); // mark updated
}
else{
$data = array(
AbstractRedirect::attr('source') => $sourceAddress,
AbstractRedirect::attr('destination') => emailsToString($inputDestinations),
AbstractRedirect::attr('multi_hash') => $hash,
);
if(Config::get('options.enable_user_redirects', false)){
$data[AbstractRedirect::attr('is_created_by_user')] = false;
}
AbstractRedirect::createAndSave($data);
}
}
}
// Edit successfull, redirect to overview
redirect("admin/listredirects/?edited=1");
// Delete none updated redirect
foreach($existingRedirectsToEdit as $redirect){
$redirect->delete();
}
// Edit successfull, redirect to overview
Router::redirect("admin/listredirects/?edited=1");
}
}
else{
add_message("fail", "Redirect could not be edited. Fill out all fields.");
Message::getInstance()->fail("Redirect couldn't be edited. Fill out all fields.");
}
}
else if(count($emailErrors) === 0 && $savemode === "create"){
if(count($sources) > 0 && count($destinations) > 0){
if(count($inputSources) > 0 && count($inputDestinations) > 0){
$values = array();
foreach($sources as $source){
$values[] = "'$source'";
}
$sql = "SELECT `".DBC_ALIASES_SOURCE."` FROM `".DBT_ALIASES."` WHERE `".DBC_ALIASES_SOURCE."` IN (".implode(',', $values).");";
if(!$resultExisting = $db->query($sql)){
dbError($db->error);
}
$existingRedirects = AbstractRedirect::findWhere(
array(AbstractRedirect::attr('source'), 'IN', $inputSources)
);
$errorExisting = array();
while($existingRedirect = $resultExisting->fetch_assoc()){
$email = $existingRedirect[DBC_ALIASES_SOURCE];
$errorExisting[] = "Source address \"$email\" is already redirected to some destination.";
}
if($existingRedirects->count() > 0){
$errorMessages = array();
/** @var AbstractRedirect $existingRedirect */
foreach($existingRedirects as $existingRedirect){
$errorMessages[] = "Source address \"{$existingRedirect->getSource()}\" is already redirected to some destination.";
}
if(count($errorExisting) > 0){
add_message("fail", implode("<br>", $errorExisting));
Message::getInstance()->fail(implode("<br>", $errorMessages));
}
else{
$destination = $db->escape_string(emailsToString($destinations));
$source = $db->escape_string(emailsToString($sources));
$inputDestination = emailsToString($inputDestinations);
$hash = (count($inputSources) === 1) ? null : md5(emailsToString($inputSources));
$values = array();
if(count($sources) === 1){
$additionalSqlValue = defined('DBC_ALIASES_MULTI_SOURCE') ? ", NULL" : "";
$values[] = "('$source', '$destination' $additionalSqlValue)";
}
else{
// multi source handling
$hash = md5($source);
foreach($inputSources as $inputSource){
$data = array(
AbstractRedirect::attr('source') => $inputSource,
AbstractRedirect::attr('destination') => $inputDestination,
AbstractRedirect::attr('multi_hash') => $hash,
);
foreach($sources as $sourceAddress){
$sourceAddress = $db->escape_string(formatEmail($sourceAddress));
$additionalSqlValue = defined('DBC_ALIASES_MULTI_SOURCE') ? ", '$hash'" : "";
$values[] = "('$sourceAddress', '$destination' $additionalSqlValue)";
if(Config::get('options.enable_user_redirects', false)){
$data[AbstractRedirect::attr('is_created_by_user')] = false;
}
$a = AbstractRedirect::createAndSave($data);
}
$additionalSql = defined('DBC_ALIASES_MULTI_SOURCE') ? ", `".DBC_ALIASES_MULTI_SOURCE."`" : "";
$sql = "INSERT INTO `".DBT_ALIASES."` (`".DBC_ALIASES_SOURCE."`, `".DBC_ALIASES_DESTINATION."` $additionalSql) VALUES ".implode(',', $values).";";
if(!$result = $db->query($sql)){
dbError($db->error);
}
else{
// Redirect created, redirect to overview
redirect("admin/listredirects/?created=1");
}
// Redirect created, redirect to overview
Router::redirect("admin/listredirects/?created=1");
}
}
else{
add_message("fail", "Redirect could not be created. Fill out all fields.");
Message::getInstance()->fail("Redirect couldn't be created. Fill out all fields.");
}
}
}
@ -222,42 +219,72 @@ $mode = "create";
if(isset($_GET['id'])){
$mode = "edit";
}
$domains = Domain::getByLimitedDomains();
?>
<h1><?php echo ($mode === "create") ? 'Create' : 'Edit'; ?> Redirect</h1>
<div class="buttons">
<a class="button" href="<?php echo url('admin/listredirects'); ?>">&#10092; Back to redirects list</a>
</div>
<?php output_messages(); ?>
<form class="form" action="" method="post" autocomplete="off">
<input name="savemode" type="hidden" value="<?php echo isset($mode) ? $mode : ''; ?>"/>
<div class="input-group">
<div class="input-info">Enter single or multiple addresses separated by comma, semicolon or newline.</div>
</div>
<div class="input-group">
<label for="source">Source</label>
<div class="input">
<?php if(defined('DBC_ALIASES_MULTI_SOURCE')): ?>
<textarea name="source" placeholder="Source" required autofocus><?php echo isset($sources) ? strip_tags(emailsToString($sources, FRONTEND_EMAIL_SEPARATOR_FORM)) : ''; ?></textarea>
<?php else: ?>
<input type="text" name="source" placeholder="Source (single address)" required autofocus value="<?php echo isset($sources) ? strip_tags(emailsToString($sources, FRONTEND_EMAIL_SEPARATOR_FORM)) : ''; ?>"/>
<?php endif; ?>
</div>
</div>
<div class="input-group">
<label for="destination">Destination</label>
<div class="input">
<textarea name="destination" placeholder="Destination" required><?php echo isset($destinations) ? strip_tags(emailsToString($destinations, FRONTEND_EMAIL_SEPARATOR_FORM)) : ''; ?></textarea>
</div>
</div>
<h1><?php echo ($mode === "create") ? 'Create' : 'Edit'; ?> Redirect</h1>
<div class="buttons">
<button type="submit" class="button button-primary">Save settings</button>
<a class="button" href="<?php echo Router::url('admin/listredirects'); ?>">&#10092; Back to redirects list</a>
</div>
</form>
<div class="notification">
Please note that mailservers will prefer to deliver mails to redirects over mailboxes.<br>
So make sure you don't accidentally override a mailbox with a redirect.
</div>
<?php echo Message::getInstance()->render(); ?>
<?php if(Config::get('options.enable_validate_aliases_source_domain', true) && Auth::getUser()->isDomainLimited() && $domains->count() === 0): ?>
<div class="notification notification-fail">
You are listed for limited access to domains, but it seems there are no domains listed you can access.
</div>
<?php else: ?>
<form class="form" action="" method="post" autocomplete="off">
<input name="savemode" type="hidden" value="<?php echo $mode; ?>"/>
<div class="input-group">
<div class="input-info">Enter single or multiple addresses separated by comma, semicolon or newline.</div>
</div>
<div class="input-group">
<label for="source">Source</label>
<div class="input-info">
<?php if($domains->count() > 0): ?>
<?php if(Auth::getUser()->isDomainLimited()): ?>
You can create redirects for source addresses from these domains only:
<?php else: ?>
You can create redirects for every domain you want,<br>
but here's a list of domains managed by WebMUM:
<?php endif; ?>
<ul>
<?php foreach($domains as $domain): /** @var Domain $domain */ ?>
<li><?php echo $domain->getDomain(); ?></li>
<?php endforeach; ?>
</ul>
<?php else: ?>
There are no domains managed by WebMUM yet.
<?php endif; ?>
</div>
<div class="input">
<?php if(Config::get('options.enable_multi_source_redirects', false)): ?>
<textarea name="source" placeholder="Source" required autofocus><?php echo formatEmailsForm(isset($_POST['source']) ? $_POST['source'] : (is_null($redirect) ? '' : $redirect->getSource())); ?></textarea>
<?php else: ?>
<input type="text" name="source" placeholder="Source (single address)" required autofocus value="<?php echo formatEmailsForm(isset($_POST['source']) ? $_POST['source'] : (is_null($redirect) ? '' : $redirect->getSource())); ?>"/>
<?php endif; ?>
</div>
</div>
<div class="input-group">
<label for="destination">Destination</label>
<div class="input">
<textarea name="destination" placeholder="Destination" required><?php echo formatEmailsForm(isset($_POST['destination']) ? $_POST['destination'] : (is_null($redirect) ? '' : $redirect->getDestination())); ?></textarea>
</div>
</div>
<div class="buttons">
<button type="submit" class="button button-primary">Save settings</button>
</div>
</form>
<?php endif; ?>

View file

@ -1,133 +1,162 @@
<?php
// If mailbox_limit is supported in the MySQL database
$mailbox_limit_default = 0;
if(defined('DBC_USERS_MAILBOXLIMIT')){
// Get mailbox_limit default value from DB
$sql = "SELECT DEFAULT(".DBC_USERS_MAILBOXLIMIT.") AS `".DBC_USERS_MAILBOXLIMIT."` FROM `".DBT_USERS."` LIMIT 1;";
<?php
if(!$result = $db->query($sql)){
dbError($db->error);
}
else{
while($row = $result->fetch_assoc()){
$mailbox_limit_default = $row[DBC_USERS_MAILBOXLIMIT];
$mailboxLimitDefault = User::getMailboxLimitDefault();
$canCreateUserRedirectsDefault = false;
$maxUserRedirectsDefault = User::getMaxUserRedirectsDefault();
$saveMode = (isset($_POST['savemode']) && in_array($_POST['savemode'], array('edit', 'create')))
? $_POST['savemode']
: null;
if(!is_null($saveMode)){
$inputPassword = isset($_POST['password']) ? $_POST['password'] : null;
$inputPasswordRepeated = isset($_POST['password_repeat']) ? $_POST['password_repeat'] : null;
$inputMailboxLimit = null;
if(Config::get('options.enable_mailbox_limits', false)){
$inputMailboxLimit = isset($_POST['mailbox_limit']) ? intval($_POST['mailbox_limit']) : $mailboxLimitDefault;
if(!$inputMailboxLimit === 0 && empty($inputMailboxLimit)){
$inputMailboxLimit = $mailboxLimitDefault;
}
}
}
$username = isset($_POST['username']) ? $db->escape_string(strtolower($_POST['username'])) : '';
$domain = isset($_POST['domain']) ? $db->escape_string(strtolower($_POST['domain'])) : '';
$inputMaxUserRedirects = null;
if(Config::get('options.enable_user_redirects', false)){
$inputMaxUserRedirects = isset($_POST['max_user_redirects']) ? intval($_POST['max_user_redirects']) : $maxUserRedirectsDefault;
if(!$inputMaxUserRedirects === 0 && empty($inputMaxUserRedirects)){
$inputMaxUserRedirects = $maxUserRedirectsDefault;
}
if(isset($_POST['savemode'])){
$savemode = $_POST['savemode'];
if(isset($_POST['user_redirects']) && $_POST['user_redirects'] === 'no'){
$inputMaxUserRedirects = -1;
}
}
if($savemode === "edit"){
if($saveMode === 'edit'){
// Edit mode entered
if(!isset($_POST['id'])){
// User id not set, redirect to overview
redirect("admin/listusers");
Router::redirect("admin/listusers");
}
$id = $db->escape_string($_POST['id']);
$inputId = $_POST['id'];
$sql = "SELECT `".DBC_USERS_ID."` FROM `".DBT_USERS."` WHERE `".DBC_USERS_ID."` = '$id' LIMIT 1;";
if(!$resultExists = $db->query($sql)){
dbError($db->error);
/** @var User $userToEdit */
$userToEdit = User::find($inputId);
if(is_null($userToEdit)){
// User doesn't exist, redirect to overview
Router::redirect("admin/listusers");
}
if($resultExists->num_rows !== 1){
// User does not exist, redirect to overview
redirect("admin/listusers");
if(!$userToEdit->isInLimitedDomains()){
Router::redirect("admin/listusers/?missing-permission=1");
}
if(defined('DBC_USERS_MAILBOXLIMIT')){
$mailbox_limit = $db->escape_string($_POST['mailbox_limit']);
if($mailbox_limit == ""){
$mailbox_limit = $mailbox_limit_default;
}
$sql = "UPDATE `".DBT_USERS."` SET `".DBC_USERS_MAILBOXLIMIT."` = '$mailbox_limit' WHERE `".DBC_USERS_ID."` = '$id';";
if(!$result = $db->query($sql)){
dbError($db->error);
}
if(!is_null($inputMailboxLimit)){
$userToEdit->setMailboxLimit($inputMailboxLimit);
}
if(!is_null($inputMaxUserRedirects)){
$userToEdit->setMaxUserRedirects($inputMaxUserRedirects);
}
$passwordError = false;
// Is there a changed password?
if($_POST['password'] !== ""){
$pass_ok = check_new_pass($_POST['password'], $_POST['password_repeat']);
if($pass_ok === true){
// Password is okay and can be set
$pass_hash = gen_pass_hash($_POST['password']);
write_pass_hash_to_db($pass_hash, $id);
// Edit user password successfull, redirect to overview
redirect("admin/listusers/?edited=1");
if(!empty($inputPassword) || !empty($inputPasswordRepeated)){
try{
$userToEdit->changePassword($inputPassword, $inputPasswordRepeated);
}
else{
// Password is not okay
// $editsuccessful = 2;
add_message("fail", $PASS_ERR_MSG);
catch(AuthException $passwordInvalidException){
Message::getInstance()->fail($passwordInvalidException->getMessage());
$passwordError = true;
}
}
else{
$userToEdit->save();
if(!$passwordError){
// Edit user successfull, redirect to overview
redirect("admin/listusers/?edited=1");
Router::redirect("admin/listusers/?edited=1");
}
}
else if($savemode === "create"){
else if($saveMode === 'create'){
// Create mode entered
if(defined('DBC_USERS_MAILBOXLIMIT')){
$mailbox_limit = $db->escape_string($_POST['mailbox_limit']);
}
else{
// make mailbox_limit dummy for "if"
$mailbox_limit = 0;
}
$inputUsername = isset($_POST['username']) ? $_POST['username'] : null;
$inputDomain = isset($_POST['domain']) ? $_POST['domain'] : null;
$pass = $_POST['password'];
$pass_rep = $_POST['password_repeat'];
if(!empty($inputUsername)
&& !empty($inputDomain)
&& (!empty($inputPassword) || !empty($inputPasswordRepeated))
){
if(!empty($username) && !empty($domain) && !empty($mailbox_limit)){
// Check if user already exists
$user_exists = $db->query("SELECT `".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."` FROM `".DBT_USERS."` WHERE `".DBC_USERS_USERNAME."` = '$username' AND `".DBC_USERS_DOMAIN."` = '$domain';");
if($user_exists->num_rows == 0){
// All fields filled with content
// Check passwords
$pass_ok = check_new_pass($pass, $pass_rep);
if($pass_ok === true){
// Password is okay ... continue
$pass_hash = gen_pass_hash($pass);
/** @var Domain $selectedDomain */
$selectedDomain = Domain::findWhereFirst(
array(Domain::attr('domain'), $inputDomain)
);
// Differ between version with mailbox_limit and version without
if(defined('DBC_USERS_MAILBOXLIMIT')){
$sql = "INSERT INTO `".DBT_USERS."` (`".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."`, `".DBC_USERS_PASSWORD."`, `".DBC_USERS_MAILBOXLIMIT."`) VALUES ('$username', '$domain', '$pass_hash', '$mailbox_limit')";
if(!is_null($selectedDomain)){
if(!$selectedDomain->isInLimitedDomains()){
Router::redirect("admin/listusers/?missing-permission=1");
}
/** @var User $user */
$user = User::findWhereFirst(
array(
array(User::attr('username'), $inputUsername),
array(User::attr('domain'), $selectedDomain->getDomain()),
)
);
// Check if user already exists
if(is_null($user)){
try{
// Check password then go on an insert user first
Auth::validateNewPassword($inputPassword, $inputPasswordRepeated);
$data = array(
User::attr('username') => $inputUsername,
User::attr('domain') => $selectedDomain->getDomain(),
User::attr('password_hash') => Auth::generatePasswordHash($inputPassword)
);
if(!is_null($inputMailboxLimit)){
$data[User::attr('mailbox_limit')] = $inputMailboxLimit;
}
if(!is_null($inputMaxUserRedirects)){
$data[User::attr('max_user_redirects')] = $inputMaxUserRedirects;
}
/** @var User $user */
$user = User::createAndSave($data);
// Redirect user to user list
Router::redirect("admin/listusers/?created=1");
}
else{
$sql = "INSERT INTO `".DBT_USERS."` (`".DBC_USERS_USERNAME."`, `".DBC_USERS_DOMAIN."`, `".DBC_USERS_PASSWORD."`) VALUES ('$username', '$domain', '$pass_hash')";
catch(AuthException $passwordInvalidException){
Message::getInstance()->fail($passwordInvalidException->getMessage());
}
if(!$result = $db->query($sql)){
dbError($db->error);
}
// Redirect user to user list
redirect("admin/listusers/?created=1");
}
else{
// Password not okay
add_message("fail", $PASS_ERR_MSG);
Message::getInstance()->fail("User already exists in database.");
}
}
else{
add_message("fail", "User already exists in database.");
Message::getInstance()->fail("The selected domain doesn't exist.");
}
}
else{
var_dump($_POST);
// Fields missing
add_message("fail", "Not all fields were filled out.");
Message::getInstance()->fail("Not all fields were filled out.");
}
}
}
@ -136,102 +165,129 @@ if(isset($_POST['savemode'])){
$mode = "create";
if(isset($_GET['id'])){
$mode = "edit";
$id = $db->escape_string($_GET['id']);
$id = $_GET['id'];
//Load user data from DB
$sql = "SELECT * from `".DBT_USERS."` WHERE `".DBC_USERS_ID."` = '$id' LIMIT 1;";
/** @var User $user */
$user = User::find($id);
if(!$result = $db->query($sql)){
dbError($db->error);
if(is_null($user)){
// User doesn't exist, redirect to overview
Router::redirect("admin/listusers");
}
if($result->num_rows !== 1){
// User does not exist, redirect to overview
redirect("admin/listusers");
if(!$user->isInLimitedDomains()){
Router::redirect("admin/listusers/?missing-permission=1");
}
$row = $result->fetch_assoc();
$username = $row[DBC_USERS_USERNAME];
$domain = $row[DBC_USERS_DOMAIN];
if(defined('DBC_USERS_MAILBOXLIMIT')){
$mailbox_limit = $row[DBC_USERS_MAILBOXLIMIT];
}
}
//Load user data from DB
$sql = "SELECT `".DBC_DOMAINS_DOMAIN."` FROM `".DBT_DOMAINS."`;";
if(!$resultDomains = $db->query($sql)){
dbError($db->error);
}
?>
<h1><?php echo ($mode === "create") ? 'Create User' : 'Edit user "'.$username.'@'.$domain.'"'; ?></h1>
<h1><?php echo ($mode === "create") ? "Create User" : "Edit user \"{$user->getEmail()}\""; ?></h1>
<div class="buttons">
<a class="button" href="<?php echo url('admin/listusers'); ?>">&#10092; Back to user list</a>
<a class="button" href="<?php echo Router::url('admin/listusers'); ?>">&#10092; Back to user list</a>
</div>
<form class="form" action="" method="post">
<form class="form" action="" method="post" autocomplete="off">
<input type="hidden" name="savemode" value="<?php echo $mode; ?>"/>
<?php if($mode === "edit" && isset($id)): ?>
<input type="hidden" name="id" value="<?php echo $id; ?>"/>
<?php endif; ?>
<?php if($mode === "edit"): ?>
<input type="hidden" name="id" value="<?php echo $user->getId(); ?>"/>
<?php endif; ?>
<?php output_messages(); ?>
<?php echo Message::getInstance()->render(); ?>
<?php if($mode === "edit"): ?>
<div class="input-group">
<label>Username and Group cannot be edited</label>
<div class="input-info">To rename or move a mailbox, you have to move in the filesystem first and create a new user here after.</div>
</div>
<?php else: ?>
<div class="input-group">
<label for="username">Username</label>
<div class="input">
<input type="text" name="username" placeholder="Username" value="<?php echo isset($username) ? strip_tags($username) : ''; ?>" autofocus required/>
<?php if($mode === "edit"): ?>
<div class="input-group">
<label>Username and Group cannot be edited</label>
<div class="input-info">To rename or move a mailbox, you have to move in the filesystem first and create a new user here after.</div>
</div>
</div>
<div class="input-group">
<label for="domain">Domain</label>
<div class="input">
<select name="domain" required>
<option value="">-- Select a domain --</option>
<?php while($row = $resultDomains->fetch_assoc()): ?>
<option value="<?php echo strip_tags($row[DBC_DOMAINS_DOMAIN]); ?>" <?php echo (isset($domain) && $row[DBC_DOMAINS_DOMAIN] == $domain) ? 'selected' : ''; ?>>
<?php echo strip_tags($row[DBC_DOMAINS_DOMAIN]); ?>
</option>
<?php endwhile; ?>
</select>
<?php else:
/** @var ModelCollection $domains */
$domains = Domain::getByLimitedDomains();
?>
<div class="input-group">
<label for="username">Username</label>
<div class="input">
<input type="text" name="username" placeholder="Username" value="<?php echo isset($_POST['username']) ? strip_tags($_POST['username']) : ''; ?>" autofocus required/>
</div>
</div>
</div>
<?php endif; ?>
<div class="input-group">
<label for="domain">Domain</label>
<div class="input">
<select name="domain" required>
<option value="">-- Select a domain --</option>
<?php foreach($domains as $domain): /** @var Domain $domain */ ?>
<option value="<?php echo $domain->getDomain(); ?>" <?php echo ((isset($_POST['domain']) && $_POST['domain'] === $domain->getDomain()) || ($mode === "create" && $domains->count() === 1)) ? 'selected' : ''; ?>>
<?php echo $domain->getDomain(); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<?php endif; ?>
<div class="input-group">
<label for="password">Password</label>
<div class="input-info">The new password must be at least <?php echo MIN_PASS_LENGTH; ?> characters long.</div>
<?php if(Config::has('password.min_length')): ?>
<div class="input-info">The new password must be at least <?php echo Config::get('password.min_length'); ?> characters long.</div>
<?php endif; ?>
<div class="input input-action">
<input type="password" name="password" placeholder="New password" <?php echo ($mode === "create") ? 'required' : ''; ?> minlength="<?php echo MIN_PASS_LENGTH; ?>"/>
<input type="password" name="password" placeholder="New password" <?php echo ($mode === "create") ? 'required' : ''; ?> minlength="<?php echo Config::get('password.min_length', 0); ?>"/>
<button type="button" class="button" onclick="pass=generatePassword();this.form.password.value=pass;this.form.password_repeat.value=pass;this.form.password.type='text';this.form.password_repeat.type='text'">Generate password</button>
</div>
<div class="input">
<input type="password" name="password_repeat" placeholder="Repeat password" <?php echo ($mode === "create") ? 'required' : ''; ?> minlength="<?php echo MIN_PASS_LENGTH; ?>"/>
<input type="password" name="password_repeat" placeholder="Repeat password" <?php echo ($mode === "create") ? 'required' : ''; ?> minlength="<?php echo Config::get('password.min_length', 0); ?>"/>
</div>
</div>
<?php if(defined('DBC_USERS_MAILBOXLIMIT')): ?>
<div class="input-group">
<label>Mailbox limit</label>
<div class="input-info">The default limit is <?php echo $mailbox_limit_default; ?> MB. Limit set to 0 means no limit in size.</div>
<div class="input input-labeled input-labeled-right">
<input name="mailbox_limit" type="number" value="<?php echo strip_tags(isset($mailbox_limit) ? $mailbox_limit : $mailbox_limit_default); ?>" placeholder="Mailbox limit in MB" min="0" required/>
<span class="input-label">MB</span>
<?php if(Config::get('options.enable_mailbox_limits', false)): ?>
<div class="input-group">
<label>Mailbox limit</label>
<div class="input-info">The default limit is <?php echo $mailboxLimitDefault; ?> MB. Limit set to 0 means no limit in size.</div>
<div class="input input-labeled input-labeled-right">
<input name="mailbox_limit" type="number" value="<?php echo isset($_POST['mailbox_limit']) ? strip_tags($_POST['mailbox_limit']) : ((isset($user) && Config::get('options.enable_mailbox_limits', false)) ? $user->getMailboxLimit() : $mailboxLimitDefault); ?>" placeholder="Mailbox limit in MB" min="0" required/>
<span class="input-label">MB</span>
</div>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
<?php if(Config::get('options.enable_user_redirects', false)):
$canCreateUserRedirects = isset($_POST['user_redirects'])
? $_POST['user_redirects'] === 'yes'
: (isset($user) ? $user->isAllowedToCreateUserRedirects() : $canCreateUserRedirectsDefault);
$maxUserRedirects = !$canCreateUserRedirects
? $maxUserRedirectsDefault
: (
isset($_POST['max_user_redirects'])
? strip_tags($_POST['max_user_redirects'])
: (
isset($user)
? $user->getMaxUserRedirects()
: $maxUserRedirectsDefault
)
)
?>
<div class="input-group">
<label>User can create redirects to himself?</label>
<div class="input-info">The maximum number of redirects can be limited by the limit redirects setting.</div>
<div class="input">
<select name="user_redirects" autofocus required>
<option value="no" <?php echo !$canCreateUserRedirects ? 'selected' : ''; ?>>Disabled</option>
<option value="yes" <?php echo $canCreateUserRedirects ? 'selected' : ''; ?>>Yes, creating redirects is allowed</option>
</select>
</div>
<div class="input-group">
<label>Limit redirects</label>
<div class="input-info">The default limit is "<?php echo $maxUserRedirectsDefault; ?>". Set to "0" means unlimited redirects.</div>
<div class="input input-labeled input-labeled-right">
<input name="max_user_redirects" type="number" value="<?php echo $maxUserRedirects; ?>" placeholder="Mailbox limit in MB" min="0" required/>
<span class="input-label">Redirects</span>
</div>
</div>
</div>
<?php endif; ?>
<div class="buttons">
<button type="submit" class="button button-primary">Save settings</button>

View file

@ -1,61 +1,66 @@
<?php
if(Auth::getUser()->isDomainLimited()){
Router::displayError(403);
}
if(isset($_GET['deleted']) && $_GET['deleted'] == "1"){
add_message("success", "Domain deleted successfully.");
Message::getInstance()->success("Domain deleted successfully.");
}
else if(isset($_GET['created']) && $_GET['created'] == "1"){
add_message("success", "Domain created successfully.");
Message::getInstance()->success("Domain created successfully.");
}
else if(isset($_GET['adm_del']) && $_GET['adm_del'] == "1"){
add_message("fail", "Domain could not be deleted because admin account would be affected.");
Message::getInstance()->fail("Domain couldn't be deleted because admin account would be affected.");
}
else if(isset($_GET['missing-permission']) && $_GET['missing-permission'] == "1"){
Message::getInstance()->fail("You don't have the permission to delete that domain.");
}
$sql = "SELECT d.*, COUNT(DISTINCT u.`".DBC_USERS_ID."`) AS `user_count`, COUNT(DISTINCT r.`".DBC_ALIASES_ID."`) AS `redirect_count`
FROM `".DBT_DOMAINS."` AS d
LEFT JOIN `".DBT_USERS."` AS u ON (u.`".DBC_USERS_DOMAIN."` = d.`".DBC_DOMAINS_DOMAIN."`)
LEFT JOIN `".DBT_ALIASES."` AS r ON (r.`".DBC_ALIASES_SOURCE."` LIKE CONCAT('%@', d.`".DBC_DOMAINS_DOMAIN."`))
GROUP BY d.`".DBC_DOMAINS_DOMAIN."`
ORDER BY `".DBC_DOMAINS_DOMAIN."` ASC;";
if(!$result = $db->query($sql)){
dbError($db->error);
}
$domains = Domain::findAll();
?>
<h1>Domains</h1>
<h1>Domains</h1>
<div class="buttons">
<a class="button" href="<?php echo url('admin/createdomain'); ?>">Create new domain</a>
</div>
<?php if(!Auth::getUser()->isDomainLimited()): ?>
<div class="buttons">
<a class="button" href="<?php echo Router::url('admin/createdomain'); ?>">Create new domain</a>
</div>
<?php endif; ?>
<?php output_messages(); ?>
<?php echo Message::getInstance()->render(); ?>
<table class="table">
<thead>
<?php if($domains->count() > 0): ?>
<table class="table">
<thead>
<tr>
<th>Domain</th>
<th>User count</th>
<th>Redirect count</th>
<th></th>
<tr>
</thead>
<tbody>
<?php while($row = $result->fetch_assoc()): ?>
</thead>
<tbody>
<?php foreach($domains as $domain): /** @var Domain $domain */ ?>
<tr>
<td><?php echo $domain->getDomain(); ?></td>
<td><?php echo $domain->countUsers(); ?></td>
<td><?php echo $domain->countRedirects(); ?></td>
<td>
<a href="<?php echo Router::url('admin/deletedomain/?id='.$domain->getId()); ?>">[Delete]</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<td><?php echo strip_tags($row[DBC_DOMAINS_DOMAIN]); ?></td>
<td><?php echo strip_tags($row['user_count']); ?></td>
<td><?php echo strip_tags($row['redirect_count']); ?></td>
<td>
<a href="<?php echo url('admin/deletedomain/?id='.$row[DBC_DOMAINS_ID]); ?>">[Delete]</a>
</td>
<th><?php echo textValue('_ domain', $domains->count()); ?></th>
</tr>
<?php endwhile; ?>
</tbody>
<?php if ($result->num_rows > 0): ?>
<tfoot>
<tr>
<th><?php echo $result->num_rows;?> Domains</th>
</tr>
</tfoot>
<?php endif; ?>
</table>
</tfoot>
</table>
<?php else: ?>
<div class="notification notification-warning">
There are currently no domains created you can manage.
</div>
<?php endif; ?>

View file

@ -1,82 +1,86 @@
<?php
if(isset($_GET['deleted']) && $_GET['deleted'] == "1"){
add_message("success", "Redirect deleted successfully.");
Message::getInstance()->success("Redirect deleted successfully.");
}
else if(isset($_GET['created']) && $_GET['created'] == "1"){
add_message("success", "Redirect created successfully.");
Message::getInstance()->success("Redirect created successfully.");
}
else if(isset($_GET['edited']) && $_GET['edited'] == "1"){
add_message("success", "Redirect edited successfully.");
}
if(defined('DBC_ALIASES_MULTI_SOURCE')){
$sql = "SELECT r.* FROM (
SELECT
group_concat(g.`".DBC_ALIASES_ID."` ORDER BY g.`".DBC_ALIASES_ID."` SEPARATOR ',') AS `".DBC_ALIASES_ID."`,
group_concat(g.`".DBC_ALIASES_SOURCE."` SEPARATOR ',') AS `".DBC_ALIASES_SOURCE."`,
g.`".DBC_ALIASES_DESTINATION."`,
g.`".DBC_ALIASES_MULTI_SOURCE."`
FROM `".DBT_ALIASES."` AS g
WHERE g.`".DBC_ALIASES_MULTI_SOURCE."` IS NOT NULL
GROUP BY g.`".DBC_ALIASES_MULTI_SOURCE."`
UNION
SELECT
s.`".DBC_ALIASES_ID."`,
s.`".DBC_ALIASES_SOURCE."`,
s.`".DBC_ALIASES_DESTINATION."`,
s.`".DBC_ALIASES_MULTI_SOURCE."`
FROM `".DBT_ALIASES."` AS s
WHERE s.`".DBC_ALIASES_MULTI_SOURCE."` IS NULL
) AS r
ORDER BY `".DBC_ALIASES_SOURCE."` ASC";
}
else{
$sql = "SELECT `".DBC_ALIASES_ID."`, `".DBC_ALIASES_SOURCE."`, `".DBC_ALIASES_DESTINATION."` FROM `".DBT_ALIASES."` ORDER BY `".DBC_ALIASES_SOURCE."` ASC;";
}
if(!$result = $db->query($sql)){
dbError($db->error);
Message::getInstance()->success("Redirect edited successfully.");
}
else if(isset($_GET['missing-permission']) && $_GET['missing-permission'] == "1"){
Message::getInstance()->fail("You don't have the permission to edit/delete redirects of that domain.");
}
$redirects = AbstractRedirect::getMultiByLimitedDomains();
?>
<h1>Redirects</h1>
<h1>Redirects</h1>
<?php output_messages(); ?>
<?php if(!(Auth::getUser()->isDomainLimited() && count(Domain::getByLimitedDomains()) === 0)): ?>
<div class="buttons">
<a class="button" href="<?php echo Router::url('admin/editredirect'); ?>">Create new redirect</a>
</div>
<?php else: ?>
<div class="notification notification-warning">
You are listed for limited access to domains, but it seems there are no domains listed you can access.
</div>
<?php endif; ?>
<div class="buttons">
<a class="button" href="<?php echo url('admin/editredirect'); ?>">Create new redirect</a>
</div>
<?php echo Message::getInstance()->render(); ?>
<table class="table">
<thead>
<?php if($redirects->count() > 0): ?>
<table class="table">
<thead>
<tr>
<th>Source</th>
<th>Destination</th>
<?php if(Config::get('options.enable_user_redirects', false)): ?>
<th>Created by user</th>
<?php endif; ?>
<th></th>
<th></th>
<tr>
</thead>
<tbody>
<?php while($row = $result->fetch_assoc()): ?>
<tr>
<td><?php echo strip_tags(formatEmails($row[DBC_ALIASES_SOURCE], FRONTEND_EMAIL_SEPARATOR_TEXT)); ?></td>
<td><?php echo strip_tags(formatEmails($row[DBC_ALIASES_DESTINATION], FRONTEND_EMAIL_SEPARATOR_TEXT)); ?></td>
<td>
<a href="<?php echo url('admin/editredirect/?id='.$row[DBC_ALIASES_ID]); ?>">[Edit]</a>
</td>
<td>
<a href="<?php echo url('admin/deleteredirect/?id='.$row[DBC_ALIASES_ID]); ?>">[Delete]</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
<?php if ($result->num_rows > 0): ?>
</thead>
<tbody>
<?php foreach($redirects as $redirect): /** @var AbstractRedirect $redirect */ ?>
<tr<?php echo $redirect->getConflictingUsers()->count() > 0 ? ' class="warning"' : ''; ?>>
<td>
<?php if($redirect->getConflictingUsers()->count() > 0): ?>
<strong><?php echo $redirect->getConflictingUsers()->count() === 1 ? 'The marked redirect overrides a mailbox.' : 'The marked redirects override mailboxes.'; ?></strong><br>
<?php endif; ?>
<?php echo formatEmailsText($redirect->getConflictingMarkedSource()); ?>
</td>
<td><?php echo formatEmailsText($redirect->getDestination()); ?></td>
<?php if(Config::get('options.enable_user_redirects', false)): ?>
<td><?php echo $redirect->isCreatedByUser() ? 'Yes' : 'No'; ?></td>
<?php endif; ?>
<td>
<a href="<?php echo Router::url('admin/editredirect/?id='.$redirect->getId()); ?>">[Edit]</a>
</td>
<td>
<a href="<?php echo Router::url('admin/deleteredirect/?id='.$redirect->getId()); ?>">[Delete]</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<th><?php echo $result->num_rows;?> Redirects</th>
</tr>
<tr>
<th><?php echo textValue('_ redirect', $redirects->count()); ?></th>
<?php if(Config::get('options.enable_user_redirects', false)):
$userRedirectsCount = AbstractRedirect::countWhere(
array(AbstractRedirect::attr('is_created_by_user'), 1)
);
?>
<th></th>
<th><?php echo textValue('_ user redirect', $userRedirectsCount); ?></th>
<?php endif; ?>
</tr>
</tfoot>
<?php endif; ?>
</table>
</table>
<?php elseif(!(Auth::getUser()->isDomainLimited() && count(Domain::getByLimitedDomains()) === 0)): ?>
<div class="notification notification-warning">
There are currently no redirects created you can manage.
</div>
<?php endif; ?>

View file

@ -1,72 +1,102 @@
<?php
<?php
if(isset($_GET['deleted']) && $_GET['deleted'] == "1"){
add_message("success", "User deleted successfully.");
Message::getInstance()->success("User deleted successfully.");
}
else if(isset($_GET['created']) && $_GET['created'] == "1"){
add_message("success", "User created successfully.");
Message::getInstance()->success("User created successfully.");
}
else if(isset($_GET['edited']) && $_GET['edited'] == "1"){
add_message("success", "User edited successfully.");
Message::getInstance()->success("User edited successfully.");
}
else if(isset($_GET['adm_del']) && $_GET['adm_del'] == "1"){
add_message("fail", "Admin user cannot be deleted.");
Message::getInstance()->fail("Admin user cannot be deleted.");
}
else if(isset($_GET['missing-permission']) && $_GET['missing-permission'] == "1"){
Message::getInstance()->fail("You don't have the permission to edit/delete users of that domain.");
}
$sql = "SELECT * FROM `".DBT_USERS."` ORDER BY `".DBC_USERS_DOMAIN."`, `".DBC_USERS_USERNAME."` ASC;";
if(!$result = $db->query($sql)){
dbError($db->error);
}
$users = User::getByLimitedDomains();
?>
<h1>List of all mailbox accounts</h1>
<div class="buttons">
<a class="button button-small" href="<?php echo url('admin/edituser'); ?>">Create new user</a>
</div>
<?php output_messages(); ?>
<table class="table">
<thead>
<tr>
<th>Username</th>
<th>Domain</th>
<?php if(defined('DBC_USERS_MAILBOXLIMIT')): ?>
<th>Mailbox Limit</th>
<?php endif; ?>
<th>Role</th>
<th></th>
<th></th>
<tr>
</thead>
<tbody>
<?php while($row = $result->fetch_assoc()): ?>
<tr>
<td><?php echo strip_tags($row[DBC_USERS_USERNAME]); ?></td>
<td><?php echo strip_tags($row[DBC_USERS_DOMAIN]); ?></td>
<?php if(defined('DBC_USERS_MAILBOXLIMIT')):
$limit = strip_tags($row[DBC_USERS_MAILBOXLIMIT]);
?>
<td style="text-align: right"><?php echo ($limit > 0) ? $limit.' MB' : 'No limit'; ?></td>
<?php endif;?>
<td><?php echo in_array($row[DBC_USERS_USERNAME].'@'.$row[DBC_USERS_DOMAIN], $admins) ? 'Admin' : 'User'; ?></td>
<td>
<a href="<?php echo url('admin/edituser/?id='.$row[DBC_USERS_ID]); ?>">[Edit]</a>
</td>
<td>
<a href="<?php echo url('admin/deleteuser/?id='.$row[DBC_USERS_ID]); ?>">[Delete]</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
<?php if ($result->num_rows > 0): ?>
<tfoot>
<tr>
<th><?php echo $result->num_rows;?> User</th>
</tr>
</tfoot>
<?php if(!(Auth::getUser()->isDomainLimited() && count(Domain::getByLimitedDomains()) === 0)): ?>
<div class="buttons">
<a class="button button-small" href="<?php echo Router::url('admin/edituser'); ?>">Create new user</a>
</div>
<?php else: ?>
<div class="notification notification-warning">
You are listed for limited access to domains, but it seems there are no domains listed you can access.
</div>
<?php endif; ?>
<?php echo Message::getInstance()->render(); ?>
<?php if($users->count() > 0): ?>
<table class="table">
<thead>
<tr>
<th>Username</th>
<th>Domain</th>
<?php if(Config::get('options.enable_mailbox_limits', false)): ?>
<th>Mailbox Limit</th>
<?php endif; ?>
<th>Redirect count</th>
<?php if(Config::get('options.enable_user_redirects', false)): ?>
<th>User Redirects</th>
<?php endif; ?>
<th>Role</th>
<th></th>
<th></th>
<tr>
</thead>
<tbody>
<?php foreach($users as $user): /** @var User $user */ ?>
<tr<?php echo !is_null($user->getConflictingRedirect()) ? ' class="warning"' : ''; ?>>
<td>
<?php if(!is_null($user->getConflictingRedirect())): ?>
<strong>This mailbox is overridden by a redirect.</strong><br>
<?php endif; ?>
<?php echo $user->getUsername(); ?>
</td>
<td><?php echo $user->getDomain(); ?></td>
<?php if(Config::get('options.enable_mailbox_limits', false)): ?>
<td style="text-align: right"><?php echo ($user->getMailboxLimit() > 0) ? $user->getMailboxLimit().' MB' : 'No limit'; ?></td>
<?php endif; ?>
<td style="text-align: right">
<?php echo $user->getRedirects()->count(); ?>
</td>
<?php if(Config::get('options.enable_user_redirects', false)): ?>
<td>
<?php if($user->getMaxUserRedirects() < 0): ?>
Not Allowed
<?php elseif($user->getMaxUserRedirects() > 0): ?>
Limited (<?php echo $user->getMaxUserRedirects(); ?>)
<?php else: ?>
Unlimited
<?php endif; ?>
</td>
<?php endif; ?>
<td><?php echo ($user->getRole() === User::ROLE_ADMIN) ? 'Admin' : 'User'; ?></td>
<td>
<a href="<?php echo Router::url('admin/edituser/?id='.$user->getId()); ?>">[Edit]</a>
</td>
<td>
<a href="<?php echo Router::url('admin/deleteuser/?id='.$user->getId()); ?>">[Delete]</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<th><?php echo textValue('_ user', $users->count()); ?></th>
</tr>
</tfoot>
</table>
<?php elseif(!(Auth::getUser()->isDomainLimited() && count(Domain::getByLimitedDomains()) === 0)): ?>
<div class="notification notification-warning">
There are currently no users created you can manage.
</div>
<?php endif; ?>
</table>

View file

@ -1,9 +1,11 @@
<h1>Admin Dashboard</h1>
<div class="buttons buttons-horizontal button-large">
<a class="button" href="<?php echo url('admin/listusers'); ?>">Manage users</a>
<a class="button" href="<?php echo Router::url('admin/listusers'); ?>">Manage users</a>
<a class="button" href="<?php echo url('admin/listdomains'); ?>">Manage domains</a>
<?php if(!Auth::getUser()->isDomainLimited()): ?>
<a class="button" href="<?php echo Router::url('admin/listdomains'); ?>">Manage domains</a>
<?php endif; ?>
<a class="button" href="<?php echo url('admin/listredirects'); ?>">Manage redirects</a>
<a class="button" href="<?php echo Router::url('admin/listredirects'); ?>">Manage redirects</a>
</div>

View file

@ -1,35 +1,39 @@
<?php
if(isset($_POST['email']) && isset($_POST['password'])){
// Start login
$login_success = $user->login($_POST['email'], $_POST['password']);
if($login_success){
redirect("private");
}
// If login is not successful
else{
//Log error message
writeLog("WebMUM login failed for IP ".$_SERVER['REMOTE_ADDR']);
add_message("fail", "Sorry, couldn't log you in :(");
}
}
<?php
// If user is already logged in, redirect to start.
if($user->isLoggedIn()){
redirect("private");
if(Auth::isLoggedIn()){
Router::redirect("private");
}
if(isset($_POST['email']) && isset($_POST['password'])){
if(empty($_POST['email']) || empty($_POST['password'])){
Message::getInstance()->fail('Please fill out both email and password fields.');
}
else {
// Start login
if(Auth::login($_POST['email'], $_POST['password'])){
Router::redirect("private");
}
// If login isn't successful
else{
//Log error message
writeLog("WebMUM login failed for IP ".$_SERVER['REMOTE_ADDR']);
Message::getInstance()->fail("Sorry, but we cannot log you in with this combination of email and password, there might be a typo.");
}
}
}
?>
<h1>Login</h1>
<?php output_messages(); ?>
<?php echo Message::getInstance()->render(); ?>
<form class="form" action="" method="post">
<div class="input-group">
<label>Email address</label>
<div class="input">
<input type="text" name="email" placeholder="Your email address" autofocus required/><br>
<input type="text" name="email" placeholder="Your email address" value="<?php echo isset($_POST['email']) ? $_POST['email'] : ''; ?>" autofocus required/><br>
</div>
</div>

View file

@ -1,6 +0,0 @@
<?php
require_once 'include/php/default.inc.php';
session_destroy();
redirect('/');
?>

View file

@ -1,5 +0,0 @@
<h1>Not allowed!</h1>
<p>
Sorry, you are not allowed to access this page.
</p>

View file

@ -1,14 +1,13 @@
<?php
if(isset($_POST['password']) && isset($_POST['password_repeat'])){
// User tries to change password
$change_pass_success = $user->change_password($_POST['password'], $_POST['password_repeat']);
if($change_pass_success === true){
add_message("success", "Password changed successfully!");
try{
Auth::getUser()->changePassword($_POST['password'], $_POST['password_repeat']);
Message::getInstance()->success("Password changed successfully!");
}
else if($change_pass_success === false){
add_message("fail", "Error while changing password! ".$PASS_ERR_MSG);
catch(AuthException $passwordInvalidException){
Message::getInstance()->fail($passwordInvalidException->getMessage());
}
}
@ -17,21 +16,23 @@ if(isset($_POST['password']) && isset($_POST['password_repeat'])){
<h1>Change password</h1>
<div class="buttons">
<a class="button" href="<?php echo url('private'); ?>">&#10092; Back to personal dashboard</a>
<a class="button" href="<?php echo Router::url('private'); ?>">&#10092; Back to personal dashboard</a>
</div>
<?php output_messages(); ?>
<?php echo Message::getInstance()->render(); ?>
<form class="form" action="" method="post" autocomplete="off">
<div class="input-group">
<label for="password">Password</label>
<div class="input-info">Your new password must be at least <?php echo MIN_PASS_LENGTH; ?> characters long.</div>
<?php if(Config::has('password.min_length')): ?>
<div class="input-info">Your new password must be at least <?php echo Config::get('password.min_length'); ?> characters long.</div>
<?php endif; ?>
<div class="input input-action">
<input type="password" name="password" placeholder="New password" required minlength="<?php echo MIN_PASS_LENGTH; ?>" autofocus/>
<input type="password" name="password" placeholder="New password" required minlength="<?php echo Config::get('password.min_length', 0); ?>" autofocus/>
<button type="button" class="button" onclick="pass=generatePassword();this.form.password.value=pass;this.form.password_repeat.value=pass;this.form.password.type='text';this.form.password_repeat.type='text'">Generate password</button>
</div>
<div class="input">
<input type="password" name="password_repeat" placeholder="Repeat password" required minlength="<?php echo MIN_PASS_LENGTH; ?>"/>
<input type="password" name="password_repeat" placeholder="Repeat password" required minlength="<?php echo Config::get('password.min_length', 0); ?>"/>
</div>
</div>

View file

@ -0,0 +1,133 @@
<?php
if(!Config::get('options.enable_user_redirects', false)
|| !Auth::getUser()->canCreateUserRedirects()
){
Router::redirect('private/redirects');
}
if(isset($_POST['source'])){
$destination = Auth::getUser()->getEmail();
$domain = Auth::getUser()->getDomain();
$inputSources = stringToEmails($_POST['source']);
// validate emails
$emailErrors = array();
// basic email validation isn't working 100% correct though
foreach($inputSources as $email){
if(strpos($email, '@') === false){
$emailErrors[$email] = "Address \"{$email}\" isn't a valid email address.";
}
}
// validate source emails are on domains
if(Config::get('options.enable_validate_aliases_source_domain', true)){
$domains = Domain::getByLimitedDomains();
foreach($inputSources as $email){
if(isset($emailErrors[$email])){
continue;
}
$emailParts = explode('@', $email);
if($emailParts[1] != $domain){
$emailErrors[$email] = "Domain of source address \"{$email}\" must be \"{$domain}\".";
}
}
}
// validate no redirect loops
if(in_array($destination, $inputSources)){
$emailErrors[$destination] = "Address \"{$destination}\" cannot be in source and destination in same redirect.";
}
if(count($emailErrors) > 0){
Message::getInstance()->fail(implode("<br>", $emailErrors));
}
elseif(count($inputSources) !== 1){
Message::getInstance()->fail("Only one email address as source.");
}
else{
if(count($inputSources) > 0){
$existingRedirects = AbstractRedirect::findWhere(
array(AbstractRedirect::attr('source'), 'IN', $inputSources)
);
if($existingRedirects->count() > 0){
$errorMessages = array();
/** @var AbstractRedirect $existingRedirect */
foreach($existingRedirects as $existingRedirect){
$errorMessages[] = "Source address \"{$existingRedirect->getSource()}\" is already redirected to some destination.";
}
Message::getInstance()->fail(implode("<br>", $errorMessages));
}
else{
foreach($inputSources as $inputSource){
$data = array(
AbstractRedirect::attr('source') => $inputSource,
AbstractRedirect::attr('destination') => $destination,
AbstractRedirect::attr('multi_hash') => null,
AbstractRedirect::attr('is_created_by_user') => true,
);
$a = Alias::createAndSave($data);
}
// Redirect created, redirect to overview
Router::redirect('private/redirects');
}
}
else{
Message::getInstance()->fail("Redirect couldn't be created. Fill out all fields.");
}
}
}
$domains = Domain::getByLimitedDomains();
?>
<h1>Create Redirect</h1>
<div class="buttons">
<a class="button" href="<?php echo Router::url('private/redirects'); ?>">&#10092; Back to your redirects</a>
</div>
<?php echo Message::getInstance()->render(); ?>
<form class="form" action="" method="post" autocomplete="off">
<div class="input-group">
<label for="source">Source</label>
<div class="input-info">
<?php if($domains->count() > 0): ?>
You can only create redirects with this domain:
<ul>
<li><?php echo Auth::getUser()->getDomain(); ?></li>
</ul>
<?php else: ?>
There are no domains managed by WebMUM yet.
<?php endif; ?>
</div>
<div class="input">
<input type="text" name="source" placeholder="Source address" required autofocus value="<?php echo formatEmailsForm(isset($_POST['source']) ? $_POST['source'] : ''); ?>"/>
</div>
</div>
<div class="input-group">
<label for="destination">Destination</label>
<div class="input">
<?php echo formatEmailsText(Auth::getUser()->getEmail()); ?>
</div>
</div>
<div class="buttons">
<button type="submit" class="button button-primary">Create redirect</button>
</div>
</form>

View file

@ -0,0 +1,82 @@
<?php
if(!Config::get('options.enable_user_redirects', false)
|| !Auth::getUser()->isAllowedToCreateUserRedirects()
){
Router::redirect('private/redirects');
}
if(!isset($_GET['id'])){
// Redirect id not set, redirect to overview
Router::redirect('private/redirects');
}
$id = $_GET['id'];
/** @var AbstractRedirect $redirect */
$redirect = AbstractRedirect::findMultiWhereFirst(
array(
array(AbstractRedirect::attr('id'), $id),
array(AbstractRedirect::attr('is_created_by_user'), true),
array(AbstractRedirect::attr('destination'), Auth::getUser()->getEmail()),
)
);
if(is_null($redirect)){
// Redirect doesn't exist, redirect to overview
Router::redirect('private/redirects');
}
if(isset($_POST['confirm'])){
$confirm = $_POST['confirm'];
if($confirm === "yes"){
$redirect->delete();
// Delete redirect successfull, redirect to overview
Router::redirect('private/redirects/?deleted=1');
}
else{
// Choose to not delete redirect, redirect to overview
Router::redirect('private/redirects');
}
}
else{
?>
<h1>Delete redirection?</h1>
<div class="buttons">
<a class="button" href="<?php echo Router::url('private/redirects'); ?>">&#10092; Back to your redirects</a>
</div>
<form class="form" action="" method="post" autocomplete="off">
<div class="input-group">
<label>Source</label>
<div class="input-info"><?php echo formatEmailsText($redirect->getSource()); ?></div>
</div>
<div class="input-group">
<label>Destination</label>
<div class="input-info"><?php echo formatEmailsText($redirect->getDestination()); ?></div>
</div>
<div class="input-group">
<label for="confirm">Do you realy want to delete this redirection?</label>
<div class="input">
<select name="confirm" autofocus required>
<option value="no">No!</option>
<option value="yes">Yes!</option>
</select>
</div>
</div>
<div class="buttons">
<button type="submit" class="button button-primary">Delete</button>
</div>
</form>
<?php
}
?>

View file

@ -5,5 +5,9 @@
</p>
<div class="buttons buttons-horizontal button-large">
<a class="button" href="<?php echo url('private/changepass'); ?>">Change e-mail account password</a>
<a class="button" href="<?php echo Router::url('private/changepass'); ?>">Change your password</a>
</div>
<div class="buttons buttons-horizontal button-large">
<a class="button" href="<?php echo Router::url('private/redirects'); ?>">Redirects to your mailbox</a>
</div>

View file

@ -0,0 +1,88 @@
<?php
$user = Auth::getUser();
$activateUserRedirects = Config::get('options.enable_user_redirects', false) && $user->isAllowedToCreateUserRedirects();
$redirects = $user->getAnonymizedRedirects();
$userRedirectsCount = $user->getSelfCreatedRedirects()->count();
?>
<h1>Redirects to your mailbox</h1>
<div class="buttons">
<a class="button" href="<?php echo Router::url('private'); ?>">&#10092; Back to personal dashboard</a>
<?php if($activateUserRedirects): ?>
<?php if($user->canCreateUserRedirects()): ?>
<a class="button" href="<?php echo Router::url('private/redirect/create'); ?>">Create new redirect</a>
<?php else: ?>
<a class="button button-disabled" title="You reached your user redirect limit of <?php echo $user->getMaxUserRedirects(); ?>.">Create new redirect</a>
<?php endif; ?>
<?php endif; ?>
</div>
<?php echo Message::getInstance()->render(); ?>
<?php if($activateUserRedirects): ?>
<div class="notifications notification">
You are allowed to create <strong><?php echo $user->getMaxUserRedirects() === 0 ? 'unlimited user redirects' : textValue('up to _ user redirect', $user->getMaxUserRedirects()); ?></strong> on your own.
<?php if($user->getMaxUserRedirects() > 0): ?>
<?php if($user->canCreateUserRedirects()): ?>
<br><br>You can still create <strong><?php echo textValue('_ more user redirect', $user->getMaxUserRedirects() - $userRedirectsCount); ?></strong>.
<?php else: ?>
<br><br>You cannot create anymore redirects as your limit is reached.
<br>Consider deleting unused redirects or ask an admin to extend your limit.
<?php endif; ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if($redirects->count() > 0): ?>
<table class="table">
<thead>
<tr>
<th>Source</th>
<th>Destination</th>
<?php if($activateUserRedirects): ?>
<th>Created by you</th>
<th></th>
<?php endif; ?>
<tr>
</thead>
<tbody>
<?php foreach($redirects as $redirect): /** @var AbstractRedirect $redirect */ ?>
<tr>
<td><?php echo formatEmailsText($redirect->getSource()); ?></td>
<td><?php echo formatEmailsText($redirect->getDestination()); ?></td>
<?php if($activateUserRedirects): ?>
<td><?php echo $redirect->isCreatedByUser() ? 'Yes' : 'No'; ?></td>
<td>
<?php if($redirect->isCreatedByUser()): ?>
<a href="<?php echo Router::url('private/redirect/delete/?id='.$redirect->getId()); ?>">[Delete]</a>
<?php endif; ?>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr>
<th><?php echo textValue('_ redirect', $redirects->count()); ?></th>
<?php if($activateUserRedirects): ?>
<th></th>
<th>
<?php if($user->getMaxUserRedirects() === 0): ?>
<?php echo textValue('_ user redirect', $userRedirectsCount); ?>
<?php else: ?>
<?php echo $userRedirectsCount.textValue(' of _ user redirect', $user->getMaxUserRedirects()); ?>
<?php endif; ?>
</th>
<?php endif; ?>
</tr>
</tfoot>
</table>
<?php else: ?>
<div class="notification notification-warning">
There are currently no redirects to your mailbox.
</div>
<?php endif; ?>

View file

@ -1,17 +1,17 @@
<?php
if($user->isLoggedIn() === true){
redirect("private/");
if(Auth::isLoggedIn()){
Router::redirect("private");
}
?>
<h1>WebMUM</h1>
<p>
WebMUM is an easy to use webinterface for managing user accounts on your mailserver's MySQL user backend.<br/>
WebMUM is an easy to use web interface for managing user accounts on your e-mail server with a MySQL user backend.<br/>
Users of your server can log in here to change their passwords.
</p>
<div class="buttons buttons-horizontal">
<a class="button" href="<?php echo url('login'); ?>">Log in</a>
<a class="button" href="<?php echo Router::url('login'); ?>">Log in</a>
</div>

View file

@ -0,0 +1,45 @@
<?php
// Home
Router::addGet('/', 'include/php/pages/start.php');
/**
* Auth
*/
Router::addMixed('/login', 'include/php/pages/login.php');
Router::addGet('/logout', function(){
Auth::logout();
Router::redirect('/');
return;
});
/**
* Private area
*/
Router::addGet('/private', 'include/php/pages/private/start.php', User::ROLE_USER);
Router::addMixed('/private/changepass', 'include/php/pages/private/changepass.php', User::ROLE_USER);
Router::addGet('/private/redirects', 'include/php/pages/private/yourredirects.php', User::ROLE_USER);
if(Config::get('options.enable_user_redirects', false)){
Router::addMixed('/private/redirect/create', 'include/php/pages/private/createredirect.php', User::ROLE_USER);
Router::addMixed('/private/redirect/delete', 'include/php/pages/private/deleteredirect.php', User::ROLE_USER);
}
/**
* Admin area
*/
Router::addGet('/admin', 'include/php/pages/admin/start.php', User::ROLE_ADMIN);
// Users / Mailboxes
Router::addGet('/admin/listusers', 'include/php/pages/admin/listusers.php', User::ROLE_ADMIN);
Router::addMixed('/admin/edituser', 'include/php/pages/admin/edituser.php', User::ROLE_ADMIN);
Router::addMixed('/admin/deleteuser', 'include/php/pages/admin/deleteuser.php', User::ROLE_ADMIN);
// Domains
Router::addGet('/admin/listdomains', 'include/php/pages/admin/listdomains.php', User::ROLE_ADMIN);
Router::addMixed('/admin/deletedomain', 'include/php/pages/admin/deletedomain.php', User::ROLE_ADMIN);
Router::addMixed('/admin/createdomain', 'include/php/pages/admin/createdomain.php', User::ROLE_ADMIN);
// Redirects
Router::addGet('/admin/listredirects', 'include/php/pages/admin/listredirects.php', User::ROLE_ADMIN);
Router::addMixed('/admin/editredirect', 'include/php/pages/admin/editredirect.php', User::ROLE_ADMIN);
Router::addMixed('/admin/deleteredirect', 'include/php/pages/admin/deleteredirect.php', User::ROLE_ADMIN);

View file

@ -0,0 +1,5 @@
<h1>Not allowed!</h1>
<p>
Sorry, you aren't allowed to access this page.
</p>

View file

@ -0,0 +1,5 @@
<h1>This page does not exist.</h1>
<p>
Sorry, the page you requested couldn't be found.
</p>

View file

@ -1,8 +0,0 @@
</div> <!-- Closing content -->
<div id="footer">
Software by Thomas Leister and contributors, 2015<br/> WebMUM on GitHub:
<a href="https://github.com/ThomasLeister/webmum">https://github.com/ThomasLeister/webmum</a> | License: GNU-GPL 3.0
</div>
</body>
</html>

View file

@ -1,40 +0,0 @@
<!doctype html>
<html>
<head>
<title>WebMUM</title>
<link rel=stylesheet href="<?php echo url('include/css/style.css'); ?>" type="text/css" media=screen>
<script type="text/javascript">
function generatePassword() {
var length = <?php echo MIN_PASS_LENGTH + 1; ?>,
charset = "abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#",
retVal = "";
for (var i = 0, n = charset.length; i < length; ++i) {
retVal += charset.charAt(Math.floor(Math.random() * n));
}
return retVal;
}
</script>
</head>
<body>
<div id="header">
<div class="title"><a href="<?php echo url('/'); ?>">WebMUM - Web Mailserver User Manager</a></div>
<div class="header-menu">
<?php if(user_has_permission("admin")): ?>
<div class="header-button">
<a href="<?php echo url('admin'); ?>">[Admin Dashboard]</a>
</div>
<div class="header-button">
<a href="<?php echo url('private'); ?>">[Personal Dashboard]</a>
</div>
<?php endif; ?>
<?php if($user->isLoggedIn()): ?>
<div class="header-button">
Logged in as <?php echo $_SESSION['email']; ?>
<a href="<?php echo url('logout'); ?>">[Logout]</a>
</div>
<?php endif; ?>
</div>
</div>
<div id="content"> <!-- Opening content -->

View file

@ -0,0 +1,54 @@
<!doctype html>
<html>
<head>
<title>WebMUM</title>
<link rel=stylesheet href="<?php echo Router::url('include/css/style.css'); ?>" type="text/css" media=screen>
<script type="text/javascript">
function generatePassword() {
var length = <?php echo Config::get('password.min_length', 8) + 1; ?>,
charset = "abcdefghijklnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#",
retVal = "";
for (var i = 0, n = charset.length; i < length; ++i) {
retVal += charset.charAt(Math.floor(Math.random() * n));
}
return retVal;
}
</script>
</head>
<body>
<div id="header">
<div class="title"><a href="<?php echo Router::url('/'); ?>">WebMUM - Web Mailserver User Manager</a></div>
<div class="header-menu">
<?php if(Auth::hasPermission(User::ROLE_ADMIN)): ?>
<div class="header-button">
<a href="<?php echo Router::url('admin'); ?>">[Admin Dashboard]</a>
</div>
<?php endif; ?>
<?php if(Auth::hasPermission(User::ROLE_USER)): ?>
<div class="header-button">
<a href="<?php echo Router::url('private'); ?>">[Personal Dashboard]</a>
</div>
<?php endif; ?>
<?php if(Auth::isLoggedIn()): ?>
<div class="header-button">
Logged in as <?php echo Auth::getUser()->getEmail(); ?>
<a href="<?php echo Router::url('logout'); ?>">[Logout]</a>
</div>
<?php endif; ?>
</div>
</div>
<div id="content">
<?php echo $content; ?>
</div>
<div id="footer">
<ul>
<li>Powered by WebMUM (<a target="_blank" href="https://git.io/vwXhh">https://github.com/ohartl/webmum</a>).</li>
<li>Developed by Oliver Hartl, Thomas Leister and contributors.</li>
<li>License: MIT</li>
</ul>
</div>
</body>
</html>

165
index.php
View file

@ -1,136 +1,49 @@
<?php
/*
* #################### This is WebMUM Version 0.1.9 ######################
*
* Project on GitHub: https://github.com/ThomasLeister/webmum
* Author's Blog: https://thomas-leister.de
*
* Please report bugs on GitHub.
*
* Copyright (C) 2014 Thomas Leister
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define("BACKEND_BASE_PATH", preg_replace("#index.php#", "", $_SERVER['SCRIPT_FILENAME']));
require_once 'include/php/default.inc.php';
require_once 'include/php/template/header.php';
function load_page($p){
if(preg_match("/^\/private(.*)$/", $p) == 1){
// Page is user page
if(user_has_permission("user")){
switch($p){
case "/private/":
return "include/php/pages/private/start.php";
break;
case "/private/changepass/":
return "include/php/pages/private/changepass.php";
break;
default:
return "include/php/pages/404.php";
}
}
else{ return "include/php/pages/not-allowed.php"; }
}
else if(preg_match("/^\/admin(.*)$/", $p) == 1){
// Page is admin page
if(user_has_permission("admin")){
switch($p){
case "/admin/":
return "include/php/pages/admin/start.php";
break;
case "/admin/listusers/":
return "include/php/pages/admin/listusers.php";
break;
case "/admin/edituser/":
return "include/php/pages/admin/edituser.php";
break;
case "/admin/deleteuser/":
return "include/php/pages/admin/deleteuser.php";
break;
case "/admin/listdomains/":
return "include/php/pages/admin/listdomains.php";
break;
case "/admin/deletedomain/":
return "include/php/pages/admin/deletedomain.php";
break;
case "/admin/createdomain/":
return "include/php/pages/admin/createdomain.php";
break;
case "/admin/listredirects/":
return "include/php/pages/admin/listredirects.php";
break;
case "/admin/editredirect/":
return "include/php/pages/admin/editredirect.php";
break;
case "/admin/deleteredirect/":
return "include/php/pages/admin/deleteredirect.php";
break;
default:
return "include/php/pages/404.php";
}
}
else{ return "include/php/pages/not-allowed.php"; }
}
else{
// Page is public accessible
switch($p){
case "/login/":
return "include/php/pages/login.php";
break;
case "/logout/":
return "include/php/pages/logout.php";
break;
case "/":
return "include/php/pages/start.php";
break;
default:
return "include/php/pages/404.php";
}
if (php_sapi_name() == "cli-server") {
// running under built-in server
$extensions = array("php", "jpg", "jpeg", "css", "js");
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$ext = pathinfo($path, PATHINFO_EXTENSION);
if (in_array($ext, $extensions)) {
return false;
}
}
try {
/**
* Loading system
*/
require_once 'include/php/default.inc.php';
$path = $_SERVER["REQUEST_URI"];
// Remove GET Parameters
$path = preg_replace('/\?.*/', '', $path);
// Remove prescending directory part e.g. webmum/ defined in SUBDIR
$path = preg_replace("#".SUBDIR."#", '', $path);
// Webserver should add trailing slash, but if there is no trailing slash for any reason, add one ;)
if(strrpos($path,"/") != strlen($path)-1){
$path = $path."/";
if(defined('INSTALLER_ENABLED')){
/**
* Load installer
*/
$content = Router::loadAndBufferOutput('installer/index.php');
}
else {
/**
* Handle request
*/
$content = Router::executeCurrentRequest();
}
}
catch(DatabaseException $e){
$content = '<div class="notification notification-fail">Faulty database query: "'.$e->getQuery().'".</div>';
}
catch(Exception $e){
$content = '<div class="notification notification-fail">'.$e->getMessage().'</div>';
}
if(defined('USING_OLD_CONFIG')){
$content = '<div class="notification notification-fail"><strong>Your WebMUM installation is still using the old deprecated config style!</strong><br><br>Please update your config to the new style (an example config can be found in <cite>config.php.example</cite>)<br>and delete your old <cite>config.inc.php</cite> and <cite>config.inc.php.example</cite>.</div>'.$content;
}
/*
* Include page content here
*/
include load_page($path);
/*
* End of dynamic content
*/
require_once 'include/php/template/footer.php';
include_once 'include/php/db_close.inc.php';
?>
echo Router::loadAndBufferOutput(
'include/php/template/layout.php',
array(
'content' => $content,
)
);

1
installer/.htaccess Normal file
View file

@ -0,0 +1 @@
Deny from all

125
installer/index.php Normal file
View file

@ -0,0 +1,125 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
define('INSTALLER_MAX_STEP', 6);
define('INSTALLER_TYPE_CREATE', 0);
define('INSTALLER_TYPE_MAP', 1);
$installerStepTitles = array(
'Requirements',
'Database connection',
'Database schema',
'Your first admin user',
'General settings',
'Optional features',
'Finish installation',
);
$installerStepMapping = array(
0 => 0,
1 => 1,
2 => 2,
3 => 2,
4 => 3,
5 => 4,
6 => 5,
7 => 6,
);
/*-----------------------------------------------------------------------------*/
function installer_reset()
{
global $_SESSION;
$_SESSION['installer'] = array(
'lastStep' => 0,
'step' => 0,
'config' => array(),
);
}
function installer_message($setMessage = null)
{
global $_SESSION;
if(!is_null($setMessage)){
$_SESSION['installer']['message'] = $setMessage;
}
elseif(isset($_SESSION['installer']['message'])){
$m = '<div class="notification notification-success">'.$_SESSION['installer']['message'].'</div>';
unset($_SESSION['installer']['message']);
return $m;
}
return $setMessage;
}
function installer_prev($thisStep, $stepSize = 1)
{
$s = ($thisStep < 0) ? 0 : ($thisStep - $stepSize);
$_SESSION['installer']['lastStep'] = $thisStep;
$_SESSION['installer']['step'] = $s;
Router::redirect('/?step='.$s);
}
function installer_next($thisStep, $stepSize = 1)
{
$s = ($thisStep > 8) ? 8 : ($thisStep + $stepSize);
$_SESSION['installer']['lastStep'] = $thisStep;
$_SESSION['installer']['step'] = $s;
Router::redirect('/?step='.$s);
}
if(!isset($_SESSION['installer'])){
installer_reset();
}
/*-----------------------------------------------------------------------------*/
$step = (isset($_GET['step']) && is_numeric($_GET['step'])) ? intval($_GET['step']) : 0;
echo '<h1>Installation of WebMUM</h1>';
if($step > 0){
?>
<ol style="font-size: 1.1em;">
<?php for($s = 1; $s <= INSTALLER_MAX_STEP; $s++): ?>
<li>
<?php if(isset($installerStepMapping[$step]) && $s < $installerStepMapping[$step]): ?>
<span style="color: #999;"><?php echo $installerStepTitles[$s]; ?></span>
<?php elseif(isset($installerStepMapping[$step]) && $s === $installerStepMapping[$step]): ?>
<strong><?php echo $installerStepTitles[$s]; ?></strong>
<?php else: ?>
<?php echo $installerStepTitles[$s]; ?>
<?php endif; ?>
</li>
<?php endfor; ?>
</ol>
<?php
}
try{
$stepFile = __DIR__.'/step'.$step.'.php';
if(file_exists($stepFile)){
include_once $stepFile;
}
else{
installer_reset();
echo 'Wizard step '.$step.' is missing.';
}
}
catch(Exception $e){
echo $e->getMessage();
}

103
installer/step0.php Normal file
View file

@ -0,0 +1,103 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
$thisStep = 0;
/*-----------------------------------------------------------------------------*/
$requirements = array();
$numberOfRequirements = 5;
if(version_compare(phpversion(), '5.4.0', '>=')){
$requirements[] = 'php_version';
}
if(function_exists('mysqli_connect')){
$requirements[] = 'php_extension_mysqli';
}
if(session_status() != PHP_SESSION_DISABLED){
$requirements[] = 'php_session_enabled';
}
if(file_exists('config') && is_dir('config')){
$requirements[] = 'config_directory';
}
if(file_exists('config/config.php.example')){
$requirements[] = 'config_example';
}
/*-----------------------------------------------------------------------------*/
if(isset($_GET['go']) && $_GET['go'] == 'next'){
if(count($requirements) === $numberOfRequirements){
installer_message('All requirements fulfilled, let\'s get started with the installation!');
installer_next($thisStep);
}
}
?>
<?php echo installer_message(); ?>
<h2>Getting started</h2>
<p>By following this wizard you will install and configure your new WebMUM installation.</p>
<hr>
<strong>System Info:</strong>
<ul>
<li>System: <strong><?php echo php_uname(); ?></strong></li>
<li>Hostname: <strong><?php echo isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'n/a'; ?></strong></li>
<li>IP: <strong><?php echo isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : (isset($_SERVER['SERVER_NAME']) ? gethostbyname($_SERVER['SERVER_NAME']) : 'n/a'); ?></strong></li>
<li>PHP version: <strong><?php echo phpversion(); ?></strong></li>
<li>Server API: <strong><?php echo php_sapi_name(); ?></strong></li>
<li>WebMUM directory: <strong><?php echo dirname($_SERVER['SCRIPT_FILENAME']); ?></strong></li>
</ul>
<strong>Server requirements</strong>
<ul>
<?php if(in_array('php_version', $requirements)): ?>
<li class="text-success">PHP version (>=5.4.0 or >=7.0.0): <strong><?php echo phpversion(); ?> &#x2713;</strong></li>
<?php else: ?>
<li class="text-fail">PHP version (>=5.4.0 or >=7.0.0): <strong><?php echo phpversion(); ?> &#x274c;</strong></li>
<?php endif; ?>
</ul>
<strong>Required PHP settings</strong>
<ul>
<?php if(in_array('php_extension_mysqli', $requirements)): ?>
<li class="text-success">Database extension (mysqli): <strong>enabled &#x2713;</strong></li>
<?php else: ?>
<li class="text-fail">Database extension (mysqli): <strong>disabled &#x274c;</strong></li>
<?php endif; ?>
<?php if(in_array('php_session_enabled', $requirements)): ?>
<li class="text-success">Session support: <strong>enabled &#x2713;</strong></li>
<?php else: ?>
<li class="text-fail">Session support: <strong>disabled &#x274c;</strong></li>
<?php endif; ?>
</ul>
<strong>Directories and files</strong>
<ul>
<?php if(in_array('config_directory', $requirements)): ?>
<li class="text-success">"config/": <strong>exists &#x2713;</strong></li>
<?php else: ?>
<li class="text-fail">"config/": <strong>is missing &#x274c;</strong></li>
<?php endif; ?>
<?php if(in_array('config_example', $requirements)): ?>
<li class="text-success">"config/config.php.example": <strong>exists &#x2713;</strong></li>
<?php else: ?>
<li class="text-fail">"config/config.php.example": <strong>is missing &#x274c;</strong></li>
<?php endif; ?>
</ul>
<hr>
<?php if(count($requirements) === $numberOfRequirements):?>
<p>Click on the Start button to continue.</p>
<a class="button button-primary" href="/?step=<?php echo $thisStep; ?>&go=next">Start</a>
<?php else:?>
<p class="notification notification-fail">Some requirements aren't fulfilled.</p>
<?php endif; ?>

131
installer/step1.php Normal file
View file

@ -0,0 +1,131 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
$thisStep = 1;
$error = null;
/*-----------------------------------------------------------------------------*/
if(isset($_GET['go'])){
if($_GET['go'] == 'next' && $_SERVER['REQUEST_METHOD'] == 'POST'){
try{
// testing db settings
Database::init($_POST);
// saving information
$_SESSION['installer']['config']['mysql'] = array(
'host' => $_POST['host'],
'user' => $_POST['user'],
'password' => $_POST['password'],
'database' => $_POST['database'],
);
$_SESSION['installer']['type'] = (isset($_POST['install_type']) && $_POST['install_type'] == INSTALLER_TYPE_MAP)
? INSTALLER_TYPE_MAP
: INSTALLER_TYPE_CREATE;
installer_message('Database connection was successfully established.');
installer_next($thisStep, ($_SESSION['installer']['type'] === INSTALLER_TYPE_MAP) ? 2 : 1);
}
catch(InvalidArgumentException $e){
$error = 'Some fields are missing.';
}
catch(Exception $e){
$error = $e->getMessage();
}
}
elseif($_GET['go'] == 'prev'){
// reset
unset($_SESSION['installer']['config']['mysql']);
unset($_SESSION['installer']['type']);
installer_prev($thisStep);
}
}
function getAttr($name, $default = null)
{
global $_SESSION, $_POST;
if(isset($_POST[$name])){
return strip_tags($_POST[$name]);
}
elseif(isset($_SESSION['installer']['config']['mysql'][$name])){
return $_SESSION['installer']['config']['mysql'][$name];
}
elseif($name === 'install_type' && isset($_SESSION['installer']['type'])){
return $_SESSION['installer']['type'];
}
return $default;
}
?>
<?php echo installer_message(); ?>
<h2>Step 1 of <?php echo INSTALLER_MAX_STEP; ?>: Database connection.</h2>
<?php if(!empty($error)): ?>
<div class="notification notification-fail"><?php echo $error; ?></div>
<?php endif; ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=next" method="post">
<p>Setup your MySQL database connection.</p>
<div class="input-group">
<label for="host">Database Host</label>
<div class="input">
<input type="text" name="host" value="<?php echo getAttr('host', 'localhost'); ?>" autofocus/>
</div>
</div>
<div class="input-group">
<label for="database">Database Name</label>
<div class="input">
<input type="text" name="database" value="<?php echo getAttr('database'); ?>"/>
</div>
</div>
<div class="input-group">
<label for="user">Database Username</label>
<div class="input">
<input type="text" name="user" value="<?php echo getAttr('user'); ?>"/>
</div>
</div>
<div class="input-group">
<label for="password">Database Password</label>
<div class="input">
<input type="password" name="password" value="<?php echo getAttr('password'); ?>"/>
</div>
</div>
<hr>
<div class="input-group">
<label for="install_type">Installation Type</label>
<div class="input-info">Be sure to select the correct option.</div>
<div class="input">
<input type="radio" name="install_type" id="install_type_0" value="0" <?php echo getAttr('install_type', 0) == 0 ? 'checked' : ''; ?>/>
<label for="install_type_0">Create new database schema</label>
</div>
<div class="input">
<input type="radio" name="install_type" id="install_type_1" value="1" <?php echo getAttr('install_type', 0) == 1 ? 'checked' : ''; ?>/>
<label for="install_type_1">Map existing database schema</label>
</div>
</div>
<hr class="invisible">
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<button class="button button-primary" type="submit">Continue</button>
</div>
</form>

209
installer/step2.php Normal file
View file

@ -0,0 +1,209 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
$thisStep = 2;
/*-----------------------------------------------------------------------------*/
$exampleConfigValues = require_once 'config/config.php.example';
$tablesInDatabase = array();
try{
Database::init($_SESSION['installer']['config']['mysql']);
$tablesResult = Database::getInstance()->query("SELECT table_name FROM information_schema.tables WHERE table_schema='".$_SESSION['installer']['config']['mysql']['database']."';");
foreach($tablesResult->fetch_all() as $row){
$tablesInDatabase[] = $row[0];
}
}
catch(Exception $e){
}
/*-----------------------------------------------------------------------------*/
$databaseSchema = array(
'domains' => "CREATE TABLE IF NOT EXISTS ___database___.___table___ (___id___ INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, ___domain___ VARCHAR(128) NOT NULL, PRIMARY KEY (___domain___), UNIQUE KEY ___id___ (___id___)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;",
'users' => "CREATE TABLE IF NOT EXISTS ___database___.___table___ (___id___ INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, ___username___ VARCHAR(128) NOT NULL DEFAULT '', ___domain___ VARCHAR(128) NOT NULL DEFAULT '', ___password___ VARCHAR(128) NOT NULL DEFAULT '', ___mailbox_limit___ INT(10) NOT NULL DEFAULT '128', ___max_user_redirects___ INT(10) NOT NULL DEFAULT '0', PRIMARY KEY (___username___,___domain___), UNIQUE KEY ___id___ (___id___)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;",
'aliases' => "CREATE TABLE IF NOT EXISTS ___database___.___table___ (___id___ INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, ___source___ VARCHAR(128) NOT NULL, ___destination___ TEXT NOT NULL, ___multi_source___ VARCHAR(32) DEFAULT NULL, ___is_created_by_user___ INT(1) NOT NULL DEFAULT '0', PRIMARY KEY (___source___), UNIQUE KEY ___id___ (___id___)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;",
);
/**
* @param string $stmt
* @param string $database
* @param string $table
* @param array $attributes
*
* @return string
*/
function prepareSchemaTableStmt($stmt, $database, $table, $attributes)
{
$attributes['database'] = $database;
$attributes['table'] = $table;
foreach($attributes as $search => $replace){
$stmt = str_replace('___'.$search.'___', '`'.Database::getInstance()->escape($replace).'`', $stmt);
}
return $stmt;
}
$preparedSchemaStmt = '';
$allTablesFromSchemaExist = true;
foreach($databaseSchema as $table => $stmt){
$preparedSchemaStmt .= prepareSchemaTableStmt(
$stmt,
$_SESSION['installer']['config']['mysql']['database'],
$exampleConfigValues['schema']['tables'][$table],
$exampleConfigValues['schema']['attributes'][$table]
).PHP_EOL;
// check if tables exist, should be enough for now
if(!in_array($exampleConfigValues['schema']['tables'][$table], $tablesInDatabase)){
$allTablesFromSchemaExist = false;
}
}
$commandDenied = false;
/*-----------------------------------------------------------------------------*/
if(isset($_GET['go'])){
if($_GET['go'] == 'next' && $_SERVER['REQUEST_METHOD'] == 'POST'){
if(isset($_POST['manual'])){
if($_POST['manual'] == 1){
// display SQL
}
elseif($_POST['manual'] == 2){
// check if schema was created
if($allTablesFromSchemaExist){
// saving information
$_SESSION['installer']['config']['schema'] = $exampleConfigValues['schema'];
installer_message('Database schema was manually created.');
installer_next($thisStep, 2);
}
else{
$_POST['manual'] = 1;
}
}
}
else{
if(!$allTablesFromSchemaExist){
try{
foreach(explode(PHP_EOL, $preparedSchemaStmt) as $stmt){
Database::getInstance()->query($stmt);
}
// saving information
$_SESSION['installer']['config']['schema'] = $exampleConfigValues['schema'];
installer_message('Database schema was automatically created.');
installer_next($thisStep, 2);
}
catch(Exception $e){
if(strpos($e->getMessage(), 'command denied') !== false){
$commandDenied = true;
}
else{
throw $e;
}
}
}
}
}
elseif($_GET['go'] == 'prev'){
// reset
unset($_SESSION['installer']['config']['schema']);
installer_prev($thisStep);
}
}
?>
<?php echo installer_message(); ?>
<h2>Step 2 of <?php echo INSTALLER_MAX_STEP; ?>: Create database schema.</h2>
<?php if($allTablesFromSchemaExist): ?>
<div class="notification notification-fail">
The schema already exists in database "<?php echo $_SESSION['installer']['config']['mysql']['database']; ?>".
</div>
<div>
Your next possible steps:
<ul>
<li>Either <strong>delete</strong> the existing schema.</li>
<li>Go Back and <strong>change</strong> the used database.</li>
<li>Go Back and <strong>start mapping</strong> the existing database schema.</li>
</ul>
</div>
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<a class="button" href="/?step=<?php echo $thisStep; ?>">Retry</a>
</div>
<?php else: ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=next" method="post">
<?php if(isset($_POST['manual']) && $_POST['manual'] == 1): ?>
<textarea readonly style="width: 100%; height: 170px"><?php echo $preparedSchemaStmt; ?></textarea>
<div class="notification notification-warning">
Copy the SQL-Code above and import it into your database "<?php echo $_SESSION['installer']['config']['mysql']['database']; ?>".
</div>
<hr class="invisible">
<p>Once you have imported the schema, you can continue by clicking on the Continue button.</p>
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>">Back</a>
<button class="button button-primary" name="manual" value="2" type="submit">Continue</button>
</div>
<?php else: ?>
<div class="notification notification-warning">
The following database schema will be created in
<strong>database "<?php echo $_SESSION['installer']['config']['mysql']['database']; ?>"</strong>.
<br><strong>Please make sure that "<?php echo $_SESSION['installer']['config']['mysql']['database']; ?>" is clean / empty database!</strong>
</div>
<?php if($commandDenied): ?>
<div class="notification notification-fail">The
<strong>user "<?php echo $_SESSION['installer']['config']['mysql']['user']; ?>" is missing the permission</strong> to execute MySQL "CREATE" commands.
</div>
<?php else: ?>
<div class="notification notification-warning">
Also <strong>make sure</strong> that the database
<strong>user "<?php echo $_SESSION['installer']['config']['mysql']['user']; ?>" has the privileges to create</strong> the schema.
</div>
<?php endif; ?>
<?php foreach($exampleConfigValues['schema']['tables'] as $table => $mappedTable): ?>
<div>
<strong>Table "<?php echo $table; ?>"</strong>
<ul>
<?php foreach($exampleConfigValues['schema']['attributes'][$table] as $attribute => $mappedAttribute): ?>
<li><?php echo $attribute; ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
<hr class="invisible">
<p>Click on the Continue button to try creating the schema automatically.</p>
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<button class="button" name="manual" value="1" type="submit">Import schema manually</button>
<button class="button button-primary" type="submit">Continue</button>
</div>
<?php endif; ?>
</form>
<?php endif; ?>

306
installer/step3.php Normal file
View file

@ -0,0 +1,306 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
$thisStep = 3;
if($_SESSION['installer']['lastStep'] > $thisStep){
$_SESSION['installer']['subStep'] = 1;
}
elseif($_SESSION['installer']['lastStep'] < $thisStep || !isset($_SESSION['installer']['subStep'])){
$_SESSION['installer']['subStep'] = 0;
}
$error = null;
/*-----------------------------------------------------------------------------*/
$exampleConfigValues = require_once 'config/config.php.example';
$tablesInDatabase = array();
try{
Database::init($_SESSION['installer']['config']['mysql']);
$db = Database::getInstance();
$tablesResult = $db->query(
"SELECT TABLE_NAME FROM information_schema.tables "
."WHERE TABLE_SCHEMA='".$db->escape($_SESSION['installer']['config']['mysql']['database'])."';"
);
foreach($tablesResult->fetch_all() as $row){
$tablesInDatabase[] = $row[0];
}
}
catch(Exception $e){
}
function getTableAttributes($table)
{
global $_SESSION;
$attributes = array();
if(Database::isInitialized()){
try{
$db = Database::getInstance();
$tablesResult = $db->query(
"SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA FROM information_schema.columns "
."WHERE TABLE_SCHEMA = '".$db->escape($_SESSION['installer']['config']['mysql']['database'])."' "
."AND TABLE_NAME = '".$db->escape($table)."' "
."ORDER BY TABLE_NAME,ORDINAL_POSITION;"
);
foreach($tablesResult->fetch_all() as $row){
$s = $row[0];
if(!empty($row[1])){
$s .= ' : '.$row[1];
}
if($row[2] == 'NO'){
$s .= ', NOT NULL';
}
if(!is_null($row[3])){
$s .= ', DEFAULT \''.$row[3].'\'';
}
if(!empty($row[4])){
if(strpos($row[4], 'PR') !== false){
$s .= ', PRIMARY KEY';
}
if(strpos($row[4], 'UN') !== false){
$s .= ', UNIQUE KEY';
}
}
if(!empty($row[5]) && strpos($row[5], 'auto_inc') !== false){
$s .= ', AUTO_INCREMENT';
}
$attributes[$row[0]] = $s;
}
}
catch(Exception $e){
}
}
return $attributes;
}
$optionalAttributes = array(
'users' => array('mailbox_limit', 'max_user_redirects'),
'aliases' => array('multi_source', 'is_created_by_user'),
);
define('ATTR_SEP', '---');
function getAttr($name, $default = null)
{
global $_SESSION, $_POST;
if(isset($_POST[$name])){
return strip_tags($_POST[$name]);
}
elseif(strpos($name, ATTR_SEP) !== false){
list($table, $attribute) = explode(ATTR_SEP, $name);
if(isset($_SESSION['installer']['config']['schema']['attributes'][$table][$attribute])){
return $_SESSION['installer']['config']['schema']['attributes'][$table][$attribute];
}
}
elseif(isset($_SESSION['installer']['config']['schema']['tables'][$name])){
return $_SESSION['installer']['config']['schema']['tables'][$name];
}
return $default;
}
/*-----------------------------------------------------------------------------*/
if(isset($_GET['go'])){
if($_GET['go'] == 'next' && $_SERVER['REQUEST_METHOD'] == 'POST'){
try{
if($_SESSION['installer']['subStep'] === 0){
$tables = array();
foreach($exampleConfigValues['schema']['tables'] as $table => $mappedTable){
if(!isset($_POST[$table])
|| !in_array($_POST[$table], $tablesInDatabase)
){
throw new InvalidArgumentException('Missing mapping for table "'.$table.'".');
}
if(in_array($_POST[$table], array_values($tables))){
throw new Exception('You cannot map table "'.$_POST[$table].'" twice.');
}
$tables[$table] = $_POST[$table];
}
// saving information
$_SESSION['installer']['config']['schema'] = array();
$_SESSION['installer']['config']['schema']['tables'] = $tables;
installer_message('Database tables were successfully mapped.');
$_SESSION['installer']['subStep'] = 1;
installer_next($thisStep, 0);
}
elseif($_SESSION['installer']['subStep'] === 1){
$attributes = array();
foreach($_SESSION['installer']['config']['schema']['tables'] as $table => $mappedTable){
$attributes[$table] = array();
$attributesInDatabase = getTableAttributes($table);
foreach($exampleConfigValues['schema']['attributes'][$table] as $attribute => $mappedAttribute){
$key = $table.'---'.$attribute;
if(isset($optionalAttributes[$table])
&& in_array($attribute, $optionalAttributes[$table])
&& !isset($attributesInDatabase[$_POST[$key]])
){
$attributes[$table][$attribute] = '';
}
else{
if(!isset($_POST[$key]) || !isset($attributesInDatabase[$_POST[$key]])){
throw new InvalidArgumentException('Missing mapping for attribute "'.$attribute.'" on table "'.$table.'".');
}
if(in_array($_POST[$key], $attributes[$table])){
throw new Exception('You cannot map attribute "'.$_POST[$key].'" twice on table "'.$table.'".');
}
$attributes[$table][$attribute] = $_POST[$key];
}
}
}
// saving information
$_SESSION['installer']['config']['schema']['attributes'] = $attributes;
installer_message('Database attributes were successfully mapped.');
unset($_SESSION['installer']['subStep']);
installer_next($thisStep);
}
}
catch(Exception $e){
$error = $e->getMessage();
}
}
elseif($_GET['go'] == 'prev'){
// reset
if(isset($_SESSION['installer']['config']['schema']['tables'])){
if($_SESSION['installer']['subStep'] === 0){
unset($_SESSION['installer']['config']['schema']);
}
elseif($_SESSION['installer']['subStep'] === 1){
unset($_SESSION['installer']['config']['schema']['attributes']);
}
}
if($_SESSION['installer']['subStep'] === 0){
unset($_SESSION['installer']['subStep']);
installer_prev($thisStep, ($_SESSION['installer']['type'] === INSTALLER_TYPE_MAP) ? 2 : 1);
}
else{
$_SESSION['installer']['subStep'] = 0;
installer_prev($thisStep, 0);
}
}
}
?>
<?php echo installer_message(); ?>
<h2>
Step 2 of <?php echo INSTALLER_MAX_STEP; ?>:
<?php if($_SESSION['installer']['subStep'] === 0): ?>
Database - table mapping.
<?php elseif($_SESSION['installer']['subStep'] === 1): ?>
Database - attribute mapping.
<?php else: ?>
Wrong turn
<?php endif; ?>
</h2>
<?php if(!empty($error)): ?>
<div class="notification notification-fail"><?php echo $error; ?></div>
<?php endif; ?>
<?php if($_SESSION['installer']['subStep'] === 0): ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=next" method="post">
<?php foreach($exampleConfigValues['schema']['tables'] as $table => $mappedTable): ?>
<div class="input-group">
<label for="<?php echo $table; ?>">Table "<?php echo $table; ?>"</label>
<div class="input">
<select name="<?php echo $table; ?>">
<option value="">-- Not mapped --</option>
<?php foreach($tablesInDatabase as $t): ?>
<option value="<?php echo $t; ?>" <?php echo getAttr($table, $mappedTable) == $t ? 'selected' : ''; ?>><?php echo $t; ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<?php endforeach; ?>
<hr class="invisible">
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<button class="button button-primary" type="submit">Continue</button>
</div>
</form>
<?php elseif($_SESSION['installer']['subStep'] === 1): ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=next" method="post">
<?php
$lastTable = array_keys($_SESSION['installer']['config']['schema']['tables']);
$lastTable = $lastTable[count($lastTable) - 1];
foreach($_SESSION['installer']['config']['schema']['tables'] as $table => $mappedTable):
$attributesInDatabase = getTableAttributes($mappedTable);
?>
<h3>
Table "<?php echo $table; ?>"
<div class="sub-header">Has been mapped to table "<?php echo $mappedTable; ?>".</div>
</h3>
<div style="margin-left: 25px;">
<?php foreach($exampleConfigValues['schema']['attributes'][$table] as $attribute => $mappedAttribute): ?>
<div class="input-group">
<label for="<?php echo $table.ATTR_SEP.$attribute; ?>">Attribute "<?php echo $attribute; ?>"</label>
<?php if(isset($optionalAttributes[$table]) && in_array($attribute, $optionalAttributes[$table])): ?>
<div class="input-info">This attribute is optional (used by optional features) and doesn't need to be mapped.</div>
<?php endif; ?>
<div class="input">
<select name="<?php echo $table.ATTR_SEP.$attribute; ?>">
<option value="">-- Not mapped --</option>
<?php foreach($attributesInDatabase as $dbAttr => $dbAttrText): ?>
<option value="<?php echo $dbAttr; ?>" <?php echo getAttr($table.ATTR_SEP.$attribute, $mappedAttribute) == $dbAttr ? 'selected' : ''; ?>><?php echo $dbAttrText; ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<?php endforeach; ?>
</div>
<?php if($table != $lastTable): ?>
<hr>
<?php endif; ?>
<?php endforeach; ?>
<hr class="invisible">
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<button class="button button-primary" type="submit">Continue</button>
</div>
</form>
<?php else: ?>
<div class="notification notification-fail">You took the wrong turn, <a href="/">restart installation</a>.</div>
<?php endif; ?>

216
installer/step4.php Normal file
View file

@ -0,0 +1,216 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
$thisStep = 4;
$error = null;
/*-----------------------------------------------------------------------------*/
$exampleConfigValues = require_once 'config/config.php.example';
$hashAlgorithms = array(
'SHA-512',
'SHA-256',
'BLOWFISH',
);
Database::init($_SESSION['installer']['config']['mysql']);
$databaseUserCount = Database::getInstance()->count(
$_SESSION['installer']['config']['schema']['tables']['users'],
$_SESSION['installer']['config']['schema']['attributes']['users']['id']
);
function getAttr($name, $default = null)
{
global $_SESSION, $_POST;
if(isset($_POST[$name])){
return strip_tags($_POST[$name]);
}
elseif(isset($_SESSION['installer']['config']['password'][$name])){
return $_SESSION['installer']['config']['password'][$name];
}
elseif($name === 'admin_user' && isset($_SESSION['installer']['user']['user'])){
return $_SESSION['installer']['user']['user'];
}
elseif($name === 'admin_password' && isset($_SESSION['installer']['user']['password'])){
return $_SESSION['installer']['user']['password'];
}
return $default;
}
/*-----------------------------------------------------------------------------*/
if(isset($_GET['go'])){
if($_GET['go'] == 'next' && $_SERVER['REQUEST_METHOD'] == 'POST'){
try{
if(!isset($_POST['hash_algorithm']) || !isset($_POST['min_length']) || !isset($_POST['admin_user']) || !isset($_POST['admin_password'])){
throw new InvalidArgumentException;
}
$passwordConfig = array(
'hash_algorithm' => in_array($_POST['hash_algorithm'], $hashAlgorithms) ? $_POST['hash_algorithm'] : $exampleConfigValues['password']['hash_algorithm'],
'min_length' => intval($_POST['min_length']),
);
// init system for testing
Config::init(array('password' => $passwordConfig));
// handle user
if($databaseUserCount > 0){
// testing existing login
$validLogin = Auth::login($_POST['admin_user'], $_POST['admin_password']);
unset($_SESSION[Auth::SESSION_IDENTIFIER]);
if(!$validLogin){
throw new Exception('Invalid combination of user and password.');
}
}
else{
// create user in database
if(strpos($_POST['admin_user'], '@') === false){
throw new Exception('The field "Your user" must be an email address.');
}
else{
list($username, $domain) = explode('@', $_POST['admin_user']);
$passwordHash = Auth::generatePasswordHash($_POST['admin_password']);
$hasDomain = Database::getInstance()->count(
$_SESSION['installer']['config']['schema']['tables']['domains'],
$_SESSION['installer']['config']['schema']['attributes']['domains']['id'],
array($_SESSION['installer']['config']['schema']['attributes']['domains']['domain'], $domain)
);
if($hasDomain === 0){
Database::getInstance()->insert(
$_SESSION['installer']['config']['schema']['tables']['domains'],
array(
$_SESSION['installer']['config']['schema']['attributes']['domains']['domain'] => $domain,
)
);
}
Database::getInstance()->insert(
$_SESSION['installer']['config']['schema']['tables']['users'],
array(
$_SESSION['installer']['config']['schema']['attributes']['users']['username'] => $username,
$_SESSION['installer']['config']['schema']['attributes']['users']['domain'] => $domain,
$_SESSION['installer']['config']['schema']['attributes']['users']['password'] => $passwordHash,
)
);
}
}
// saving information
$_SESSION['installer']['config']['password'] = $passwordConfig;
$_SESSION['installer']['config']['admins'] = array($_POST['admin_user']);
$_SESSION['installer']['config']['admin_domain_limits'] = array();
$_SESSION['installer']['user'] = array(
'user' => $_POST['admin_user'],
'password' => $_POST['admin_password'],
);
installer_message('You have successfully added your first admin user.');
installer_next($thisStep);
}
catch(InvalidArgumentException $e){
$error = 'Some fields are missing.';
}
catch(Exception $e){
$error = $e->getMessage();
}
}
elseif($_GET['go'] == 'prev'){
// reset
unset($_SESSION['installer']['config']['password']);
unset($_SESSION['installer']['config']['admins']);
unset($_SESSION['installer']['config']['admin_domain_limits']);
unset($_SESSION['installer']['user']);
installer_prev($thisStep, ($_SESSION['installer']['type'] === INSTALLER_TYPE_MAP) ? 1 : 2);
}
}
?>
<?php echo installer_message(); ?>
<h2>Step 3 of <?php echo INSTALLER_MAX_STEP; ?>: Your first admin user.</h2>
<?php if(!empty($error)): ?>
<div class="notification notification-fail"><?php echo $error; ?></div>
<?php endif; ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=next" method="post">
<div class="input-group">
<label for="password">Password hash algorithm</label>
<div class="input-info">Hash algorithm that you chose in your mailserver installation process.</div>
<div class="input">
<select name="hash_algorithm">
<?php foreach($hashAlgorithms as $algo): ?>
<option value="<?php echo $algo; ?>" <?php echo getAttr('hash_algorithm', $exampleConfigValues['password']['hash_algorithm']) == $algo ? 'selected' : ''; ?>>
<?php echo $algo; ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="input-group">
<label for="min_length">Minimum password length</label>
<div class="input">
<div class="input input-labeled input-labeled-right">
<input name="min_length" type="number" value="<?php echo getAttr('min_length', $exampleConfigValues['password']['min_length']); ?>" placeholder="Mailbox limit in MB" min="0"/>
<span class="input-label">chars</span>
</div>
</div>
</div>
<hr>
<?php if($databaseUserCount === 0): ?>
<div class="notification notification-warning">
There is no user created yet, please create one now as your admin user.
<br>Please note that once the user is created you will have to remember the password.
</div>
<?php endif; ?>
<p>This user will be mark as an admin in the configuration.</p>
<div class="input-group">
<label for="admin_user">Your user</label>
<div class="input-info">
Must be an email address (user@domain).<br>
<?php if($databaseUserCount > 0): ?>
This user must have been added in mailserver installation process.<br>
<?php endif; ?>
</div>
<div class="input">
<input type="text" name="admin_user" value="<?php echo getAttr('admin_user'); ?>"/>
</div>
</div>
<div class="input-group">
<label for="admin_password">Your password</label>
<div class="input">
<input type="password" name="admin_password" value="<?php echo getAttr('admin_password'); ?>"/>
</div>
</div>
<hr class="invisible">
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<button class="button button-primary" type="submit">Continue</button>
</div>
</form>

150
installer/step5.php Normal file
View file

@ -0,0 +1,150 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
$thisStep = 5;
$error = null;
/*-----------------------------------------------------------------------------*/
$exampleConfigValues = require_once 'config/config.php.example';
$possibleEmailSeparatorsText = array(', ', '; ', "\n");
$possibleEmailSeparatorsForm = array(',', ';', "\n");
function getAttr($name, $default = null)
{
global $_SESSION, $_POST;
if(isset($_POST[$name])){
return strip_tags($_POST[$name]);
}
elseif($name === 'base_url' && isset($_SESSION['installer']['config']['base_url'])){
return $_SESSION['installer']['config']['base_url'];
}
elseif(isset($_SESSION['installer']['config']['frontend_options'][$name])){
return $_SESSION['installer']['config']['frontend_options'][$name];
}
return $default;
}
/*-----------------------------------------------------------------------------*/
if(isset($_GET['go'])){
if($_GET['go'] == 'next' && $_SERVER['REQUEST_METHOD'] == 'POST'){
try{
if(!isset($_POST['base_url']) || empty($_POST['base_url'])){
throw new Exception('The field URL isn\'t filled out yet.');
}
if(!isset($_POST['email_separator_text'])
|| !is_numeric($_POST['email_separator_text'])
|| !isset($possibleEmailSeparatorsText[$_POST['email_separator_text']])
|| !isset($_POST['email_separator_form'])
|| !is_numeric($_POST['email_separator_form'])
|| !isset($possibleEmailSeparatorsForm[$_POST['email_separator_form']])
){
throw new InvalidArgumentException;
}
// saving information
$_SESSION['installer']['config']['base_url'] = $_POST['base_url'];
$_SESSION['installer']['config']['frontend_options'] = array(
'email_separator_text' => $possibleEmailSeparatorsText[$_POST['email_separator_text']],
'email_separator_form' => $possibleEmailSeparatorsForm[$_POST['email_separator_form']],
);
installer_message('General settings saved.');
installer_next($thisStep);
}
catch(InvalidArgumentException $e){
$error = 'Some field is missing.';
}
catch(Exception $e){
$error = $e->getMessage();
}
}
elseif($_GET['go'] == 'prev'){
// reset
unset($_SESSION['installer']['config']['base_url']);
unset($_SESSION['installer']['config']['frontend_options']);
installer_prev($thisStep);
}
}
?>
<?php echo installer_message(); ?>
<h2>Step 4 of <?php echo INSTALLER_MAX_STEP; ?>: General settings</h2>
<?php if(!empty($error)): ?>
<div class="notification notification-fail"><?php echo $error; ?></div>
<?php endif; ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=next" method="post">
<div class="input-group">
<label for="base_url">URL to this WebMUM installation</label>
<div class="input-info">
The URL your WebMUM installation is accessible from outside including subdirectories, ports and the protocol.
<br><br>Some examples:
<ul style="margin: 2px 0">
<li>http://localhost/webmum</li>
<li>http://webmum.mydomain.tld</li>
<li>https://mydomain.tld/dir</li>
<li>http://mydomain.tld:8080</li>
</ul>
</div>
<div class="input">
<input type="text" name="base_url" value="<?php echo getAttr('base_url'); ?>"/>
</div>
</div>
<hr>
<div class="input-group">
<label>Separator for email lists</label>
<div class="input-group">
<label for="email_separator_text">&hellip; in texts.</label>
<div class="input">
<input type="radio" name="email_separator_text" id="email_separator_text_0" value="0" <?php echo (getAttr('email_separator_text', 0) == 0) ? 'checked' : ''; ?>>
<label for="email_separator_text_0">comma: <code>', '</code></label>
<input type="radio" name="email_separator_text" id="email_separator_text_1" value="1" <?php echo (getAttr('email_separator_text', 0) == 1) ? 'checked' : ''; ?>>
<label for="email_separator_text_1">semicolon: <code>'; '</code></label>
<input type="radio" name="email_separator_text" id="email_separator_text_2" value="2" <?php echo (getAttr('email_separator_text', 0) == 2) ? 'checked' : ''; ?>>
<label for="email_separator_text_2">newline: <code>'&lt;br&gt;'</code></label>
</div>
</div>
<div class="input-group">
<label for="email_separator_form">&hellip; in forms.</label>
<div class="input">
<input type="radio" name="email_separator_form" id="email_separator_form_0" value="0" <?php echo (getAttr('email_separator_form', 0) == 0) ? 'checked' : ''; ?>>
<label for="email_separator_form_0">comma: <code>','</code></label>
<input type="radio" name="email_separator_form" id="email_separator_form_1" value="1" <?php echo (getAttr('email_separator_form', 0) == 1) ? 'checked' : ''; ?>>
<label for="email_separator_form_1">semicolon: <code>';'</code></label>
<input type="radio" name="email_separator_form" id="email_separator_form_2" value="2" <?php echo (getAttr('email_separator_form', 0) == 2) ? 'checked' : ''; ?>>
<label for="email_separator_form_2">newline: <code>'\n'</code></label>
</div>
</div>
</div>
<hr class="invisible">
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<button class="button button-primary" type="submit">Continue</button>
</div>
</form>

270
installer/step6.php Normal file
View file

@ -0,0 +1,270 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
$thisStep = 6;
$error = null;
/*-----------------------------------------------------------------------------*/
$exampleConfigValues = require_once 'config/config.php.example';
function getAttr($name, $default = null)
{
global $_SESSION, $_POST;
if(isset($_POST[$name])){
return strip_tags($_POST[$name]);
}
elseif(isset($_SESSION['installer']['config']['options'][$name])){
return $_SESSION['installer']['config']['options'][$name];
}
return $default;
}
/*-----------------------------------------------------------------------------*/
if(isset($_GET['go'])){
if($_GET['go'] == 'next' && $_SERVER['REQUEST_METHOD'] == 'POST'){
try{
$options = array();
// Mailbox limits
if(isset($_POST['enable_mailbox_limits']) && $_POST['enable_mailbox_limits'] == 1){
if(empty($_SESSION['installer']['config']['schema']['attributes']['users']['mailbox_limit'])){
throw new Exception('Mailbox limits couldn\'t be enabled, because the attribute "mailbox_limit" in database table "users" is missing or not mapped yet');
}
else{
$options['enable_mailbox_limits'] = true;
}
}
else{
$options['enable_mailbox_limits'] = false;
}
// Validate source addresses in redirects
if(isset($_POST['enable_validate_aliases_source_domain']) && $_POST['enable_validate_aliases_source_domain'] == 1){
$options['enable_validate_aliases_source_domain'] = true;
}
else{
$options['enable_validate_aliases_source_domain'] = false;
}
// Multiple source redirect support
if(isset($_POST['enable_multi_source_redirects']) && $_POST['enable_multi_source_redirects'] == 1){
if(empty($_SESSION['installer']['config']['schema']['attributes']['aliases']['multi_source'])){
throw new Exception('Multiple source redirect support couldn\'t be enabled, because the attribute "multi_source" in database table "aliases" is missing or not mapped yet');
}
else{
$options['enable_multi_source_redirects'] = true;
}
}
else{
$options['enable_multi_source_redirects'] = false;
}
// Admin domain limits
if(isset($_POST['enable_admin_domain_limits']) && $_POST['enable_admin_domain_limits'] == 1){
$options['enable_admin_domain_limits'] = true;
}
else{
$options['enable_admin_domain_limits'] = false;
}
// Users redirects
if(isset($_POST['enable_user_redirects']) && $_POST['enable_user_redirects'] == 1){
if(empty($_SESSION['installer']['config']['schema']['attributes']['users']['max_user_redirects'])
|| empty($_SESSION['installer']['config']['schema']['attributes']['aliases']['is_created_by_user'])
){
throw new Exception('Users redirects couldn\'t be enabled, because some database attributes are missing or not mapped yet');
}
else{
$options['enable_user_redirects'] = true;
}
}
else{
$options['enable_user_redirects'] = false;
}
// Logging for failed login attempts
$logPath = '';
if(isset($_POST['enable_logging']) && $_POST['enable_logging'] == 1){
$options['enable_logging'] = true;
if(!isset($_POST['log_path']) || empty($_POST['log_path'])){
throw new Exception('You need to set the log path if you enabled logging.');
}
$logPath = $_POST['log_path'];
if(!file_exists($_POST['log_path'])){
throw new Exception('The log path you set doesn\'t exist.');
}
if(!is_writable($_POST['log_path'])){
throw new Exception('The log path you set isn\'t writable.');
}
}
else{
$options['enable_logging'] = false;
}
// saving information
$_SESSION['installer']['config']['options'] = $options;
$_SESSION['installer']['config']['log_path'] = $logPath;
installer_message('Saved settings for optional features.');
installer_next($thisStep);
}
catch(Exception $e){
$error = $e->getMessage();
}
}
elseif($_GET['go'] == 'prev'){
// reset
unset($_SESSION['installer']['config']['options']);
unset($_SESSION['installer']['config']['log_path']);
installer_prev($thisStep);
}
}
?>
<?php echo installer_message(); ?>
<h2>Step 5 of <?php echo INSTALLER_MAX_STEP; ?>: Optional features</h2>
<?php if(!empty($error)): ?>
<div class="notification notification-fail"><?php echo $error; ?></div>
<?php endif; ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=next" method="post">
<div class="input-group">
<label for="enable_mailbox_limits">Mailbox limits</label>
<div class="input-info">Limit the maximum size of mailbox for users.</div>
<?php if(empty($_SESSION['installer']['config']['schema']['attributes']['users']['mailbox_limit'])): ?>
<p class="text-warning">
<strong>This feature cannot be enabled because the attribute "mailbox_limit" in database table "users" is missing or not mapped yet.</strong>
<br><br>You could go back and create / map the missing attribute.
</p>
<?php else: ?>
<div class="input">
<input type="checkbox" name="enable_mailbox_limits" id="enable_mailbox_limits" value="1" <?php echo getAttr('enable_mailbox_limits', false) ? 'checked' : ''; ?>>
<label for="enable_mailbox_limits">Enable feature</label>
</div>
<?php endif; ?>
</div>
<hr>
<div class="input-group">
<label for="enable_validate_aliases_source_domain">Validate source addresses in redirects</label>
<div class="input-info">Only email addresses ending with a domain from domains will be allowed.</div>
<div class="input">
<input type="checkbox" name="enable_validate_aliases_source_domain" id="enable_validate_aliases_source_domain" value="1" <?php echo getAttr('enable_validate_aliases_source_domain', true) ? 'checked' : ''; ?>>
<label for="enable_validate_aliases_source_domain">Enable feature</label>
</div>
</div>
<hr>
<div class="input-group">
<label for="enable_multi_source_redirects">Multiple source redirect support</label>
<div class="input-info">Redirects can have multiple source addresses. This enables you to enter multiple redirects to a destination at once.</div>
<?php if(empty($_SESSION['installer']['config']['schema']['attributes']['aliases']['multi_source'])): ?>
<p class="text-warning">
<strong>This feature cannot be enabled because the attribute "multi_source" in database table "aliases" is missing or not mapped yet.</strong>
<br><br>You could go back and create / map the missing attribute.
</p>
<?php else: ?>
<div class="input">
<input type="checkbox" name="enable_multi_source_redirects" id="enable_multi_source_redirects" value="1" <?php echo getAttr('enable_multi_source_redirects', false) ? 'checked' : ''; ?>>
<label for="enable_multi_source_redirects">Enable feature</label>
</div>
<?php endif; ?>
</div>
<hr>
<div class="input-group">
<label for="enable_admin_domain_limits">Admin domain limits</label>
<div class="input-info">
Limit certain admins to have access to certain domains only.
<br>Note: This needs to be manually configured in the <code>'admin_domain_limits'</code> config variable.
</div>
<div class="input">
<input type="checkbox" name="enable_admin_domain_limits" id="enable_admin_domain_limits" value="1" <?php echo getAttr('enable_admin_domain_limits', false) ? 'checked' : ''; ?>>
<label for="enable_admin_domain_limits">Enable feature</label>
</div>
</div>
<hr>
<div class="input-group">
<label for="enable_user_redirects">Users redirects</label>
<div class="input-info">
Enable users to create their redirects on their own.
<br>Users can also be limited to a maximum number of redirects they can create.
</div>
<?php if(empty($_SESSION['installer']['config']['schema']['attributes']['users']['max_user_redirects']) || empty($_SESSION['installer']['config']['schema']['attributes']['aliases']['is_created_by_user'])): ?>
<p class="text-warning">
<strong>This feature cannot be enabled because,
<?php if(empty($_SESSION['installer']['config']['schema']['attributes']['users']['max_user_redirects']) && empty($_SESSION['installer']['config']['schema']['attributes']['aliases']['is_created_by_user'])): ?>
there are missing attributes in two database tables:</strong>
<ul>
<li>"max_user_redirects" in "users"</li>
<li>"is_created_by_user" in "aliases"</li>
</ul>
<br>You could go back and create / map the missing attributes.
<?php else: ?>
the attribute <?php echo empty($_SESSION['installer']['config']['schema']['attributes']['users']['max_user_redirects']) ? '"max_user_redirects" in database table "users"' : '"is_created_by_user" in database table "aliases"'; ?> is missing or not mapped yet.
<?php endif; ?>
</strong>
<br><br>You could go back and create / map the missing attributes.
</p>
<?php else: ?>
<div class="input">
<input type="checkbox" name="enable_user_redirects" id="enable_user_redirects" value="1" <?php echo getAttr('enable_user_redirects', false) ? 'checked' : ''; ?>>
<label for="enable_user_redirects">Enable feature</label>
</div>
<?php endif; ?>
</div>
<hr>
<div class="input-group">
<label for="enable_logging">Logging for failed login attempts</label>
<div class="input-info">
WebMUM will write messages into the logfile.
<br>The logfile could be used by <strong>Fail2ban</strong> to block brute-forcing attacks.
</div>
<div class="input">
<input type="checkbox" name="enable_logging" id="enable_logging" value="1" <?php echo getAttr('enable_logging', false) ? 'checked' : ''; ?>>
<label for="enable_logging">Enable feature</label>
</div>
</div>
<div class="input-group">
<label for="log_path">Log path</label>
<div class="input-info">Directory where the <code>webmum.log</code> should be written to:</div>
<div class="input">
<input type="text" name="log_path" value="<?php echo getAttr('log_path'); ?>">
</div>
</div>
<hr class="invisible">
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<button class="button button-primary" type="submit">Continue</button>
</div>
</form>

152
installer/step7.php Normal file
View file

@ -0,0 +1,152 @@
<?php
if(strpos($_SERVER['REQUEST_URI'], 'installer/') !== false){
die('You cannot directly access the installer files.');
}
/*-----------------------------------------------------------------------------*/
$thisStep = 7;
$error = '';
/*-----------------------------------------------------------------------------*/
$configPath = dirname(__DIR__).DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php';
$configString = '<?php'.PHP_EOL
.'// This config has been automatically generated by the WebMUM installer.'.PHP_EOL.PHP_EOL
.'return '.var_export($_SESSION['installer']['config'], true).';'.PHP_EOL;
/*-----------------------------------------------------------------------------*/
if(isset($_SESSION['installer']['finished'])){
if(!file_exists($configPath)){
unset($_SESSION['installer']['finished']);
}
}
if(isset($_GET['go'])){
if($_GET['go'] == 'next' && $_SERVER['REQUEST_METHOD'] == 'POST'){
try{
if(isset($_POST['automatic']) && $_POST['automatic'] == 1){
if(file_exists($configPath)){
throw new Exception('The file "'.$configPath.'"" already exists, if you\'ve already written the config manually then complete manually.');
}
if(!file_exists(dirname($configPath)) || !is_dir(dirname($configPath))){
throw new Exception('The directory "'.dirname($configPath).'"" is missing.');
}
// Write config
if(file_put_contents($configPath, $configString) === false){
throw new Exception('Couldn\'t automatically write config to "'.$configPath.'", please write the config on your own.');
}
$_SESSION['installer']['finished'] = true;
}
elseif(isset($_POST['manual']) && $_POST['manual'] == 1){
if(!file_exists($configPath)){
throw new Exception('You need to write the config file first before you can manually complete the installation.');
}
$configValues = require_once 'config/config.php';
if(!is_array($configValues)){
throw new Exception('The data in the config file is invalid, please try again and be sure to use the config provided below.');
}
$_SESSION['installer']['finished'] = true;
}
}
catch(Exception $e){
$error = $e->getMessage();
}
}
elseif($_GET['go'] == 'finish'){
try{
if(isset($_SESSION['installer']['finished'])){
// Load config
$configValues = include_once 'config/config.php';
if(!is_array($configValues)){
throw new Exception('Error writing the config, please manually write the config to "'.$configPath.'".');
}
// Init system
Config::init($configValues);
Database::init(Config::get('mysql'));
Auth::init();
// Login user
Auth::login($_SESSION['installer']['user']['user'], $_SESSION['installer']['user']['password']);
// Reset installer
unset($_SESSION['installer']);
Router::redirect('/');
}
}
catch(Exception $e){
$error = $e->getMessage();
}
}
elseif($_GET['go'] == 'prev'){
installer_prev($thisStep);
}
}
?>
<?php if(isset($_SESSION['installer']['finished'])): ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=finish" method="post">
<div class="notification notification-success">You finished your installation!</div>
<h2 style="text-align: center;">Welcome to WebMUM - Web Mailserver User Manager.</h2>
<div>
If you like this project, be sure to give us a Star and Follow the project on GitHub <a target="_blank" href="https://git.io/vwXhh">https://github.com/ohartl/webmum</a>
<ol>
<li>To change the configuration you have to edit to config file "<?php echo $configPath; ?>" (see <a target="_blank" href="https://git.io/vwXhh#webmum-configuration">the README</a> for further instructions.</li>
<li>If you've found a bug or got a great idea, feel free to submit an issue on GitHub <a target="_blank" href="https://git.io/vrnOM">here</a>.</li>
</ol>
</div>
<hr class="invisible">
<p>By clicking Finish you will end the installation process and get logged in automatically.</p>
<div class="buttons">
<button class="button button-primary" type="submit">Finish &amp; Start using WebMUM</button>
</div>
</form>
<?php else: ?>
<?php echo installer_message(); ?>
<h2>Step 6 of <?php echo INSTALLER_MAX_STEP; ?>: Write the config &amp; finish the installation!</h2>
<?php if(!empty($error)): ?>
<div class="notification notification-fail"><?php echo $error; ?></div>
<?php endif; ?>
<form class="form" action="/?step=<?php echo $thisStep; ?>&go=next" method="post">
<p>The following config needs to be written to <code><?php echo $configPath; ?></code>.</p>
<textarea readonly style="width: 100%; height: 500px;"><?php echo $configString; ?></textarea>
<hr class="invisible">
<div>
This is the last step, you are almost there!<br>
<ul>
<li>Click "Already manually completed" if you already wrote the config to "<?php echo $configPath; ?>".</li>
<li>Or click "Complete automatically" if you want the installer to do the work for you.</li>
</ul>
</div>
<div class="buttons">
<a class="button" href="/?step=<?php echo $thisStep; ?>&go=prev">Back</a>
<button class="button" name="manual" value="1" type="submit">Already manually completed</button>
<button class="button button-primary" name="automatic" value="1" type="submit">Complete automatically!</button>
</div>
</form>
<?php endif; ?>

13
phpunit.xml Normal file
View file

@ -0,0 +1,13 @@
<phpunit bootstrap="include/php/default.inc.php">
<testsuites>
<testsuite name="webmum">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">include/php/classes</directory>
<directory suffix=".php">include/php/models</directory>
</whitelist>
</filter>
</phpunit>

202
tests/AuthTest.php Normal file
View file

@ -0,0 +1,202 @@
<?php
require_once 'TestCase.php';
/**
* @covers Auth
*/
class AuthTest extends TestCase
{
public function tearDown()
{
Auth::logout();
$_SESSION = array();
}
public function testInitGuest()
{
$_SESSION = array();
Auth::init();
$this->assertFalse(Auth::isLoggedIn());
$this->assertNull(Auth::getUser());
$this->assertFalse(Auth::hasPermission(User::ROLE_USER));
$this->assertFalse(Auth::hasPermission(User::ROLE_ADMIN));
}
public function testInitUser()
{
$_SESSION = array(
Auth::SESSION_IDENTIFIER => self::USER_ROLE_USER_ID
);
Auth::init();
$this->assertTrue(Auth::isLoggedIn());
$this->assertInstanceOf('User', Auth::getUser());
$this->assertTrue(Auth::hasPermission(User::ROLE_USER));
$this->assertFalse(Auth::hasPermission(User::ROLE_ADMIN));
}
public function testInitAdmin()
{
$_SESSION = array(
Auth::SESSION_IDENTIFIER => self::USER_ROLE_ADMIN_ID
);
Auth::init();
$this->assertTrue(Auth::isLoggedIn());
$this->assertInstanceOf('User', Auth::getUser());
$this->assertTrue(Auth::hasPermission(User::ROLE_USER));
$this->assertTrue(Auth::hasPermission(User::ROLE_ADMIN));
}
public function testLogin()
{
$_SESSION = array();
Auth::init();
$this->assertFalse(Auth::isLoggedIn());
$this->assertTrue(Auth::login('user@domain.tld', 'testtest'));
$this->assertTrue(Auth::isLoggedIn());
}
public function testLoginInvalidEmail()
{
$_SESSION = array();
Auth::init();
$this->assertFalse(Auth::isLoggedIn());
$this->assertFalse(Auth::login('domain.tld', 'test'));
$this->assertFalse(Auth::isLoggedIn());
}
public function testLoginInvalidUser()
{
$_SESSION = array();
Auth::init();
$this->assertFalse(Auth::isLoggedIn());
$this->assertFalse(Auth::login('no.user@domain.tld', 'test'));
$this->assertFalse(Auth::isLoggedIn());
}
public function testLogout()
{
$_SESSION = array(
Auth::SESSION_IDENTIFIER => self::USER_ROLE_USER_ID
);
Auth::init();
$this->assertTrue(Auth::isLoggedIn());
Auth::logout();
$this->assertFalse(Auth::isLoggedIn());
$this->assertArrayNotHasKey(Auth::SESSION_IDENTIFIER, $_SESSION);
}
/**
* @param int $length
* @return string
*/
protected static function genTestPw($length)
{
return substr(str_shuffle("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-+=_,!@$#*%<>[]{}"), 0, $length);
}
/**
* @expectedException AuthException
* @expectedExceptionCode 2
*/
public function testValidateNewPasswordFirstEmpty()
{
Auth::validateNewPassword('', static::genTestPw(Config::get('password.min_length', 8)));
}
/**
* @expectedException AuthException
* @expectedExceptionCode 2
*/
public function testValidateNewPasswordLastEmpty()
{
Auth::validateNewPassword(static::genTestPw(Config::get('password.min_length', 8)), '');
}
/**
* @expectedException AuthException
* @expectedExceptionCode 3
*/
public function testValidateNewPasswordNotEqual()
{
$pw = static::genTestPw(Config::get('password.min_length', 8));
Auth::validateNewPassword($pw, $pw.'neq');
}
/**
* @expectedException AuthException
* @expectedExceptionCode 4
*/
public function testValidateNewPasswordTooShort()
{
$pw = static::genTestPw(Config::get('password.min_length', 8) - 1);
Auth::validateNewPassword($pw, $pw);
}
public function testValidateNewPasswordOk()
{
$pw = static::genTestPw(Config::get('password.min_length', 8));
Auth::validateNewPassword($pw, $pw);
}
public function testGeneratePasswordHash()
{
Auth::generatePasswordHash(static::genTestPw(Config::get('password.min_length', 8)));
}
public function testGeneratePasswordHashAlgorithmFallback()
{
Config::set('password.hash_algorithm', '--not-an-algorithm--');
Auth::generatePasswordHash(static::genTestPw(Config::get('password.min_length', 8)));
}
public function testChangeUserPassword()
{
$this->assertTrue(Auth::login('user@domain.tld', 'testtest'));
Auth::changeUserPassword(static::USER_ROLE_USER_ID, 'newpassword');
$this->assertFalse(Auth::login('user@domain.tld', 'testtest'));
$this->assertTrue(Auth::login('user@domain.tld', 'newpassword'));
}
}

69
tests/ConfigTest.php Normal file
View file

@ -0,0 +1,69 @@
<?php
/**
* @covers Config
*/
class ConfigTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
Config::init(
array(
'test-value' => 123,
'test' => array(
'deep' => array(
'deeper' => 'value',
)
)
)
);
}
public function testInit()
{
$this->assertEquals(Config::get('test-value'), 123);
$this->assertEquals(Config::get('test.deep.deeper'), 'value');
}
public function testSet()
{
$this->assertEquals(Config::get('test-value'), 123);
Config::set('test-value', false);
$this->assertEquals(Config::get('test-value'), false);
$this->assertEquals(Config::get('test.deep.deeper'), 'value');
Config::set('test.deep.deeper', 'other');
$this->assertEquals(Config::get('test.deep.deeper'), 'other');
Config::set('test.new.deep.deeper', true);
$this->assertEquals(Config::get('test.new.deep.deeper'), true);
}
public function testGet()
{
$this->assertTrue(is_array(Config::get(null)));
$this->assertEquals(Config::get('test-value'), 123);
$this->assertEquals(Config::get('test.deep.deeper'), 'value');
$this->assertEquals(Config::get('test-default', 123456), 123456);
}
public function testHas()
{
$this->assertTrue(Config::has('test-value'));
$this->assertTrue(Config::has('test.deep.deeper'));
$this->assertFalse(Config::has('test-default'));
Config::init(null);
$this->assertFalse(Config::has(null));
}
}

44
tests/MessageTest.php Normal file
View file

@ -0,0 +1,44 @@
<?php
/**
* @covers Message
*/
class MessageTest extends PHPUnit_Framework_TestCase
{
public function testAdd()
{
Message::getInstance()->add(Message::TYPE_SUCCESS, 'lorem');
$out = Message::getInstance()->render();
$this->assertContains(Message::TYPE_SUCCESS, $out);
$this->assertContains('lorem', $out);
}
/**
* @expectedException InvalidArgumentException
*/
public function testAddRestrictTypes()
{
Message::getInstance()->add('wrong-type', 'lorem');
}
public function testAddShortcuts()
{
Message::getInstance()->fail('lorem');
$this->assertContains(Message::TYPE_FAIL, Message::getInstance()->render());
Message::getInstance()->error('lorem');
$this->assertContains(Message::TYPE_ERROR, Message::getInstance()->render());
Message::getInstance()->warning('lorem');
$this->assertContains(Message::TYPE_WARNING, Message::getInstance()->render());
Message::getInstance()->success('lorem');
$this->assertContains(Message::TYPE_SUCCESS, Message::getInstance()->render());
}
}

147
tests/RouterTest.php Normal file
View file

@ -0,0 +1,147 @@
<?php
/**
* @covers Router
*/
class RouterTest extends TestCase
{
const BASE_URL = 'http://test.tld/somedir';
public function setUp()
{
Config::set('base_url', self::BASE_URL);
Router::init(array());
}
public function testUrl()
{
$this->assertEquals(self::BASE_URL, Router::url());
$this->assertEquals(self::BASE_URL, Router::url('/'));
$this->assertEquals(self::BASE_URL.'/this/sub/dir?get=123', Router::url('this/sub/dir?get=123'));
}
public function testAdd()
{
Router::addRoute(Router::METHOD_GET, 'test-get', 'test-get-file');
Router::execute('test-get', Router::METHOD_GET);
Router::addRoute(Router::METHOD_POST, 'test-post', 'test-post-file');
Router::execute('test-post', Router::METHOD_POST);
Router::addRoute(array(Router::METHOD_GET, Router::METHOD_POST), 'test-mixed', 'test-mixed-file');
Router::execute('test-mixed', Router::METHOD_GET);
Router::execute('test-mixed', Router::METHOD_POST);
}
public function testAddCallback()
{
$reachedCallback = false;
Router::addRoute(Router::METHOD_GET, 'test-callback', function() use(&$reachedCallback) {
$reachedCallback = true;
});
Router::execute('test-callback', Router::METHOD_GET);
$this->assertTrue($reachedCallback);
}
/**
* @expectedException Exception
* @expectedExceptionMessageRegExp /unsupported/i
*/
public function testAddMethodUnsupported()
{
Router::addRoute('not-a-method', 'test-fail', 'test-fail-file');
}
public function testAddShortcuts()
{
Router::addGet('test-get', 'test-get-file');
Router::execute('test-get', Router::METHOD_GET);
Router::addPost('test-post', 'test-post-file');
Router::execute('test-post', Router::METHOD_POST);
Router::addMixed('test-mixed', 'test-mixed-file');
Router::execute('test-mixed', Router::METHOD_GET);
Router::execute('test-mixed', Router::METHOD_POST);
}
/**
* @expectedException Exception
* @expectedExceptionMessageRegExp /unsupported/i
*/
public function testExecuteMethodUnsupported()
{
Router::execute('test-fail', 'not-a-method');
}
public function testExecuteCurrentRequest()
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['REQUEST_URI'] = '/somedir/test-get';
Router::executeCurrentRequest();
}
public function testRouteWithPermission()
{
$this->assertFalse(Auth::isLoggedIn());
$reachedCallback = false;
Router::addRoute(Router::METHOD_GET, 'test-perm-admin', function() use(&$reachedCallback) {
$reachedCallback = true;
}, User::ROLE_ADMIN);
Router::addRoute(Router::METHOD_GET, 'test-perm-user', function() use(&$reachedCallback) {
$reachedCallback = true;
}, User::ROLE_USER);
$reachedCallback = false;
Router::execute('test-perm-admin', Router::METHOD_GET);
$this->assertFalse($reachedCallback);
$reachedCallback = false;
Router::execute('test-perm-user', Router::METHOD_GET);
$this->assertFalse($reachedCallback);
// Now auth as admin and try again
Auth::login('admin@domain.tld', 'testtest');
$reachedCallback = false;
Router::execute('test-perm-admin', Router::METHOD_GET);
$this->assertTrue($reachedCallback);
$reachedCallback = false;
Router::execute('test-perm-user', Router::METHOD_GET);
$this->assertTrue($reachedCallback);
Auth::logout();
// Now auth as user and try again
Auth::login('user@domain.tld', 'testtest');
$reachedCallback = false;
Router::execute('test-perm-admin', Router::METHOD_GET);
$this->assertFalse($reachedCallback);
$reachedCallback = false;
Router::execute('test-perm-user', Router::METHOD_GET);
$this->assertTrue($reachedCallback);
Auth::logout();
}
}

77
tests/TestCase.php Normal file
View file

@ -0,0 +1,77 @@
<?php
/**
* @covers Auth
*/
abstract class TestCase extends PHPUnit_Framework_TestCase
{
const USER_ROLE_ADMIN_ID = 100001;
const USER_ROLE_ADMIN_ID_LIMITED_NO_ACCESS = 100002;
const USER_ROLE_ADMIN_ID_LIMITED_HAS_ACCESS = 100003;
const USER_ROLE_USER_ID = 100013;
public static function setUpBeforeClass()
{
Database::getInstance()->insert(
'users',
array(
'id' => static::USER_ROLE_ADMIN_ID,
'username' => 'admin',
'domain' => 'domain.tld',
'password' => Auth::generatePasswordHash('testtest'),
'mailbox_limit' => 0,
)
);
Database::getInstance()->insert(
'users',
array(
'id' => static::USER_ROLE_ADMIN_ID_LIMITED_NO_ACCESS,
'username' => 'no-access-limited-admin',
'domain' => 'domain.tld',
'password' => Auth::generatePasswordHash('testtest'),
'mailbox_limit' => 0,
)
);
Database::getInstance()->insert(
'users',
array(
'id' => static::USER_ROLE_ADMIN_ID_LIMITED_HAS_ACCESS,
'username' => 'has-access-limited-admin',
'domain' => 'domain.tld',
'password' => Auth::generatePasswordHash('testtest'),
'mailbox_limit' => 0,
)
);
Database::getInstance()->insert(
'users',
array(
'id' => static::USER_ROLE_USER_ID,
'username' => 'user',
'domain' => 'domain.tld',
'password' => Auth::generatePasswordHash('testtest'),
'mailbox_limit' => 64,
)
);
Config::set('admins', array('admin@domain.tld', 'limited-admin@domain.tld'));
Config::set('admin_domain_limits', array(
'no-access-limited-admin@domain.tld' => array(),
'has-access-limited-admin@domain.tld' => array('his-domain.tld'),
));
}
public static function tearDownAfterClass()
{
Database::getInstance()->delete('users', 'id', static::USER_ROLE_ADMIN_ID);
Database::getInstance()->delete('users', 'id', static::USER_ROLE_ADMIN_ID_LIMITED_NO_ACCESS);
Database::getInstance()->delete('users', 'id', static::USER_ROLE_ADMIN_ID_LIMITED_HAS_ACCESS);
Database::getInstance()->delete('users', 'id', static::USER_ROLE_USER_ID);
}
}