├── .gitignore
├── docker
├── php
│ ├── timezone.ini
│ ├── xdebug.ini
│ └── Dockerfile
└── nginx
│ └── default.conf
├── vexim
├── sorttable.js
├── images
│ ├── check.gif
│ └── trashcan.gif
├── locale
│ ├── de
│ │ └── LC_MESSAGES
│ │ │ └── messages.mo
│ ├── es
│ │ └── LC_MESSAGES
│ │ │ └── messages.mo
│ ├── hu
│ │ └── LC_MESSAGES
│ │ │ └── messages.mo
│ ├── it
│ │ └── LC_MESSAGES
│ │ │ └── messages.mo
│ ├── ro
│ │ └── LC_MESSAGES
│ │ │ └── messages.mo
│ └── ru
│ │ └── LC_MESSAGES
│ │ └── messages.mo
├── config
│ ├── i18n.php
│ ├── httpheaders.php
│ ├── authuser.php
│ ├── Tests
│ │ └── functionsValidatePasswordTest.php
│ ├── authsite.php
│ ├── authpostmaster.php
│ └── variables.php.example
├── logout.php
├── adminfaildelete.php
├── admingroupaddsubmit.php
├── sitepasswordsubmit.php
├── admingroupchangesubmit.php
├── admingroupcontentaddsubmit.php
├── admingroupcontentdeletesubmit.php
├── userblocksubmit.php
├── admincatchallsubmit.php
├── adminfailchangesubmit.php
├── admingroupadd.php
├── sitepassword.php
├── adminlists.php
├── index.php
├── adminuserblocksubmit.php
├── adminfailaddsubmit.php
├── admincatchalladd.php
├── api
│ └── change-user-password.php
├── adminfailadd.php
├── adminfail.php
├── admin.php
├── admingroup.php
├── admincatchall.php
├── adminaliasdelete.php
├── login.php
├── adminfailchange.php
├── scripts.js
├── admingroupdelete.php
├── adminalias.php
├── adminaliasaddsubmit.php
├── adminaliaschangesubmit.php
├── style.css
├── userchangesubmit.php
├── adminaliasadd.php
├── adminuserdelete.php
├── sitechangesubmit.php
├── sitedelete.php
├── adminuser.php
├── siteaddsubmit.php
├── adminuseraddsubmit.php
├── site.php
├── admingroupchange.php
└── adminaliaschange.php
├── setup
└── migrations
│ ├── vexim_1.3_to_1.5_mysql.sql
│ ├── vexim_2.0.1_to_2.2_mysql.sql
│ ├── vexim_2.3_to_2.3.1_mysql.sql
│ ├── vexim_1.5_to_2.0.1_mysql.sql
│ └── vexim_2.2_to_2.3_mysql.sql
├── .gitattributes
├── docs
├── debian-conf.d
│ ├── transport
│ │ ├── 30_vexim_virtual_vacation_delivery
│ │ ├── 30_vexim_mailman
│ │ ├── 30_vexim_virtual_ditch_spam_transport
│ │ └── 30_vexim_virtual_delivery
│ ├── router
│ │ ├── 240_vexim_mailman
│ │ └── 250_vexim_virtual_domains
│ ├── auth
│ │ └── 30_vexim_authenticators
│ └── main
│ │ └── 00_vexim_listmacrosdefs
├── vexim-acl-check-helo.conf
├── vexim-group.txt
├── clients
│ ├── dovecot.txt
│ └── courierimap.txt
├── vexim-acl-check-mime.conf
├── vexim-acl-check-content.conf
├── vexim-acl-check-rcpt.conf
└── checklist.txt
├── LICENSE
├── docker-compose.yml
└── TODO
/.gitignore:
--------------------------------------------------------------------------------
1 | vexim/config/variables.php
2 |
--------------------------------------------------------------------------------
/docker/php/timezone.ini:
--------------------------------------------------------------------------------
1 | date.timezone = 'UTC'
2 |
--------------------------------------------------------------------------------
/vexim/sorttable.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/sorttable.js
--------------------------------------------------------------------------------
/vexim/images/check.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/images/check.gif
--------------------------------------------------------------------------------
/vexim/images/trashcan.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/images/trashcan.gif
--------------------------------------------------------------------------------
/vexim/locale/de/LC_MESSAGES/messages.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/locale/de/LC_MESSAGES/messages.mo
--------------------------------------------------------------------------------
/vexim/locale/es/LC_MESSAGES/messages.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/locale/es/LC_MESSAGES/messages.mo
--------------------------------------------------------------------------------
/vexim/locale/hu/LC_MESSAGES/messages.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/locale/hu/LC_MESSAGES/messages.mo
--------------------------------------------------------------------------------
/vexim/locale/it/LC_MESSAGES/messages.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/locale/it/LC_MESSAGES/messages.mo
--------------------------------------------------------------------------------
/vexim/locale/ro/LC_MESSAGES/messages.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/locale/ro/LC_MESSAGES/messages.mo
--------------------------------------------------------------------------------
/vexim/locale/ru/LC_MESSAGES/messages.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vexim/vexim2/HEAD/vexim/locale/ru/LC_MESSAGES/messages.mo
--------------------------------------------------------------------------------
/docker/php/xdebug.ini:
--------------------------------------------------------------------------------
1 | [xdebug]
2 | xdebug.client_host=host.docker.internal
3 | xdebug.start_with_request=yes
4 | xdebug.mode=debug
5 |
--------------------------------------------------------------------------------
/vexim/config/i18n.php:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/setup/migrations/vexim_1.3_to_1.5_mysql.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- MySQL script to upgrade Vexim database schema from Vexim 1.1, 1.2, 1.2.1 and 1.3 to Vexim 1.5
3 | --
4 |
5 | ALTER TABLE `users` ADD COLUMN `status` tinyint(1) NOT NULL DEFAULT '1' AFTER `admin`;
6 |
--------------------------------------------------------------------------------
/vexim/config/httpheaders.php:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/vexim/logout.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behaviour, in case users don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly declare text files we want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.php text
7 | *.conf text
8 | *.txt text
9 | *.sql text
10 | *.pl text
11 |
12 | # Denote all files that are truly binary and should not be modified.
13 | *.gif binary
14 |
--------------------------------------------------------------------------------
/docker/php/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:8.3-fpm-alpine
2 |
3 | COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
4 |
5 | RUN addgroup -S -g 90 vexim && \
6 | adduser -S -u 90 -G vexim -h /var/vmail vexim && \
7 | cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini && \
8 | install-php-extensions pdo pdo_mysql gettext xdebug
9 |
10 | WORKDIR /srv/app/
11 |
--------------------------------------------------------------------------------
/docker/nginx/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 |
4 | error_log /dev/stderr debug;
5 | access_log /dev/stdout;
6 |
7 | root /srv/app;
8 | index index.php;
9 | try_files $uri $uri/ /index.php?$query_string;
10 |
11 | location ~ \.php$ {
12 | fastcgi_pass php:9000;
13 | include fastcgi_params;
14 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/setup/migrations/vexim_2.0.1_to_2.2_mysql.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- MySQL script to upgrade Vexim database schema from Vexim 2.0.1 to Vexim 2.2
3 | --
4 |
5 | ALTER TABLE `users` ADD COLUMN `unseen` bool DEFAULT '0' AFTER `forward`;
6 |
7 | CREATE TABLE `groups` (
8 | `id` int(10) AUTO_INCREMENT,
9 | `domain_id` mediumint(8) UNSIGNED NOT NULL,
10 | `name` varchar(64) NOT NULL,
11 | `is_public` char(1) NOT NULL DEFAULT 'Y',
12 | `enabled` bool NOT NULL DEFAULT '1',
13 | PRIMARY KEY (`id`),
14 | UNIQUE KEY `group_name`(`domain_id`, `name`)
15 | );
16 | CREATE TABLE `group_contents` (
17 | `group_id` int(10) NOT NULL,
18 | `member_id` int(10) NOT NULL,
19 | PRIMARY KEY (`group_id`, `member_id`)
20 | );
21 |
--------------------------------------------------------------------------------
/docs/debian-conf.d/transport/30_vexim_virtual_vacation_delivery:
--------------------------------------------------------------------------------
1 |
2 | ### transport/30_vexim_virtual_vacation_delivery
3 | #######################################
4 |
5 | virtual_vacation_delivery:
6 | driver = autoreply
7 | from = "${local_part}@${domain}"
8 | to = ${sender_address}
9 | subject = "Autoreply from ${local_part}@${domain}"
10 | headers = "Content-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: quoted-printable"
11 | text = ${lookup mysql{select vacation from users,domains \
12 | where domain='${quote_mysql:$domain}' \
13 | and localpart='${quote_mysql:$local_part}' \
14 | and users.domain_id=domains.domain_id}}
15 |
--------------------------------------------------------------------------------
/vexim/adminfaildelete.php:
--------------------------------------------------------------------------------
1 | prepare($query);
11 | $success = $sth->execute(array(':user_id'=>$_GET['user_id'], ':domain_id'=>$_SESSION['domain_id']));
12 | if ($success) {
13 | header ("Location: adminfail.php?deleted={$_GET['localpart']}");
14 | } else {
15 | header ("Location: adminfail.php?faildeleted={$_GET['localpart']}");
16 | die;
17 | }
18 | ?>
19 |
20 |
--------------------------------------------------------------------------------
/docs/debian-conf.d/transport/30_vexim_mailman:
--------------------------------------------------------------------------------
1 |
2 | ### transport/30_vexim_mailman
3 | ##############################
4 |
5 | # This transport delivers messages to Mailman lists.
6 | # The if def:local_part_suffix section selects whether the suffix is used
7 | # as the mailman command, or whether there is no suffix and so post is
8 | # passed as a command.
9 | # The sg phrase strips the VERP information (if any) from the suffix,
10 |
11 | .ifdef VEXIM_HAVE_MAILMAN
12 | mailman_transport:
13 | driver = pipe
14 | command = MAILMAN_WRAP \
15 | '${if def:local_part_suffix \
16 | {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \
17 | {post}}' \
18 | $local_part
19 | current_directory = MAILMAN_HOME
20 | home_directory = MAILMAN_HOME
21 | user = MAILMAN_USER
22 | group = MAILMAN_GROUP
23 | .endif
24 |
--------------------------------------------------------------------------------
/docs/debian-conf.d/router/240_vexim_mailman:
--------------------------------------------------------------------------------
1 |
2 | ### router/240_vexim_mailman
3 | ############################
4 |
5 | # This router picks up all the addresses going to the Mailman lists. Initially
6 | # it selects only the domains that may have lists in them, then selects where
7 | # local_part matches a list name (ie you can see a list config file). The
8 | # suffixes pick up all the Mailman admin addresses
9 |
10 | .ifdef VEXIM_HAVE_MAILMAN
11 | mailman_router:
12 | driver = accept
13 | domains = +local_domains
14 | require_files = MAILMAN_LISTCHK
15 | local_part_suffix_optional
16 | local_part_suffix = -admin : \
17 | -bounces : -bounces+* : \
18 | -confirm : -confirm+* : \
19 | -join : -leave : \
20 | -owner : -request : \
21 | -subscribe : -unsubscribe
22 | transport = mailman_transport
23 | .endif
24 |
--------------------------------------------------------------------------------
/docs/vexim-acl-check-helo.conf:
--------------------------------------------------------------------------------
1 | # These ACL's allow filtering by the HELO argument.
2 | # The two ACL's overlap. You may want to comment out one of them.
3 |
4 | accept hosts = :
5 | accept hosts = +relay_from_hosts
6 |
7 | # DROP all messages with raw IP address as HELO (not allowed, it must be enclosed in [])
8 | drop
9 | condition = ${if isip{$sender_helo_name}}
10 | message = Access denied - Invalid HELO name (See RFC2821 4.1.3)
11 |
12 | # Drop all messages where the HELO is _our_ IP address
13 | drop condition = ${if eq{[$interface_address]}{$sender_helo_name}}
14 | message = $interface_address is _my_ address
15 |
16 | # DROP all messages with same hostname as *ours*
17 | drop message = "REJECTED - Bad HELO - Host impersonating [$sender_helo_name]"
18 | condition = ${if match{$sender_helo_name}{$primary_hostname}}
19 |
20 | # Accept all others
21 | accept
22 |
--------------------------------------------------------------------------------
/docs/debian-conf.d/transport/30_vexim_virtual_ditch_spam_transport:
--------------------------------------------------------------------------------
1 |
2 | ### transport/30_vexim_virtual_ditch_spam_transport
3 | #######################################
4 |
5 | virtual_ditch_spam_transport:
6 | driver = appendfile
7 | envelope_to_add
8 | return_path_add
9 | mode = 0600
10 | maildir_format = true
11 | create_directory = true
12 | user = ${lookup mysql{select users.uid from users,domains \
13 | where localpart = '${quote_mysql:$local_part}' \
14 | and domain = '${quote_mysql:$domain}' \
15 | and users.domain_id = domains.domain_id}}
16 | group = ${lookup mysql{select users.gid from users,domains \
17 | where localpart = '${quote_mysql:$local_part}' \
18 | and domain = '${quote_mysql:$domain}' \
19 | and users.domain_id = domains.domain_id}}
20 | maildir_use_size_file = false
21 |
--------------------------------------------------------------------------------
/docs/vexim-group.txt:
--------------------------------------------------------------------------------
1 | # Things to do :
2 |
3 | major
4 |
5 | - translation
6 | - the create SQL statements are missing for postgres in create_db.pl
7 |
8 | minor
9 |
10 | - find a way to garbage members (user or alias) of a group that has been deleted
11 | - improvement : could add spam processing and virus checking at the group level
12 | - improvement : it is not possible to add member that is not a alias nor a user
13 | if you want to put a group in a group the work around is to create
14 | an alias for the internal group
15 | if you want to add a member that is not a user or an alias in the
16 | domain the work around is to create an alias for this user
17 |
18 | trivial
19 |
20 | - when added the same user twice there is no clean error message
21 | - a confirmation message is missing when removing a member from a group
22 | - move information messages to config/header.php
23 |
--------------------------------------------------------------------------------
/setup/migrations/vexim_2.3_to_2.3.1_mysql.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- MySQL script to upgrade Vexim database schema from Vexim 2.3 to Vexim 2.3.1.
3 | -- The only change so far is that of character set from utf8mb3 to utf8mb4.
4 | --
5 |
6 | --
7 | -- Table: `domains`
8 | --
9 | ALTER TABLE `domains` ROW_FORMAT=DYNAMIC;
10 | ALTER TABLE `domains` CONVERT TO CHARACTER SET utf8mb4;
11 |
12 | --
13 | -- Table: `users`
14 | --
15 | ALTER TABLE `users` ROW_FORMAT=DYNAMIC;
16 | ALTER TABLE `users` CONVERT TO CHARACTER SET utf8mb4;
17 |
18 | --
19 | -- Table: `blocklists`
20 | --
21 | ALTER TABLE `blocklists` ROW_FORMAT=DYNAMIC;
22 | ALTER TABLE `blocklists` CONVERT TO CHARACTER SET utf8mb4;
23 |
24 | --
25 | -- Table: `domainalias`
26 | --
27 | ALTER TABLE `domainalias` ROW_FORMAT=DYNAMIC;
28 | ALTER TABLE `domainalias` CONVERT TO CHARACTER SET utf8mb4;
29 |
30 | --
31 | -- Table: `groups`
32 | --
33 | ALTER TABLE `groups` ROW_FORMAT=DYNAMIC;
34 | ALTER TABLE `groups` CONVERT TO CHARACTER SET utf8mb4;
35 |
36 | --
37 | -- Table: `group_contents`
38 | --
39 | ALTER TABLE `group_contents` ROW_FORMAT=DYNAMIC;
40 | ALTER TABLE `group_contents` CONVERT TO CHARACTER SET utf8mb4;
41 |
--------------------------------------------------------------------------------
/vexim/admingroupaddsubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
22 | $success = $sth->execute(array(':localpart'=>$_POST['localpart'], ':domain_id'=>$_SESSION['domain_id']));
23 |
24 | if ($success) {
25 | header ("Location: admingroup.php?group_added={$_POST['localpart']}");
26 | } else {
27 | header ("Location: admingroup.php?group_failadded={$_POST['localpart']}");
28 | }
29 | ?>
30 |
31 |
32 |
--------------------------------------------------------------------------------
/vexim/sitepasswordsubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
15 | $success = $sth->execute(array(':crypt'=>$cryptedpassword));
16 | if ($success) {
17 | $_SESSION['crypt'] = $cryptedpassword;
18 | header ("Location: site.php?sitepass=success");
19 | die;
20 | } else {
21 | header ("Location: site.php?sitepass=fail");
22 | die;
23 | }
24 | } else {
25 | header ("Location: site.php?badpass=siteadmin");
26 | }
27 | ?>
28 |
29 |
--------------------------------------------------------------------------------
/vexim/config/authuser.php:
--------------------------------------------------------------------------------
1 | prepare($query);
22 | $success = $sth->execute(array(':user_id'=>$_SESSION['user_id'], ':domain_id'=>$_SESSION['domain_id']));
23 | if(!$success || !$sth->rowCount()) {
24 | header ("Location: index.php?login=failed&2");
25 | die();
26 | }
27 | $row = $sth->fetch();
28 |
29 | # confirm the crypted password in the session matches the crypted password in the database for the user
30 | if ($row['crypt'] !== $_SESSION['crypt']) {
31 | header ("Location: index.php?login=failed&3");
32 | die();
33 | }
34 |
35 | ?>
36 |
--------------------------------------------------------------------------------
/vexim/config/Tests/functionsValidatePasswordTest.php:
--------------------------------------------------------------------------------
1 | assertFalse(validate_password("", ""));
14 | }
15 |
16 | public function testSendSamePasswords()
17 | {
18 | $this->assertTrue(validate_password("password", "password"));
19 | }
20 |
21 | public function testSendDifferentPasswords()
22 | {
23 | $this->assertFalse(validate_password("password", "pass"));
24 | }
25 |
26 | public function testSendNullValues()
27 | {
28 | $this->assertFalse(validate_password(null, null));
29 | }
30 |
31 | public function testSendBoolValues()
32 | {
33 | $this->assertFalse(validate_password(true, true));
34 | $this->assertFalse(validate_password(false, false));
35 | }
36 |
37 | public function testSendIntegerValues()
38 | {
39 | $this->assertFalse(validate_password(10, 10));
40 | }
41 |
42 | public function testSendZeroValues()
43 | {
44 | $this->assertFalse(validate_password(0, 0));
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/vexim/admingroupchangesubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
19 | $success = $sth->execute(array(':localpart'=>$_POST['localpart'], ':enabled'=>$_POST['enabled'],
20 | ':is_public'=>$_POST['is_public'], ':group_id'=>$_POST['group_id'], ':domain_id'=>$_SESSION['domain_id']));
21 | if ($success) {
22 | header ("Location: admingroupchange.php?group_id={$_POST['group_id']}&group_updated={$_POST['localpart']}");
23 | } else {
24 | header ("Location: admingroupchange.php?group_id={$_POST['group_id']}&group_failupdated={$_POST['localpart']}");
25 | }
26 | ?>
27 |
--------------------------------------------------------------------------------
/docs/debian-conf.d/transport/30_vexim_virtual_delivery:
--------------------------------------------------------------------------------
1 |
2 | ### transport/30_vexim_virtual_delivery
3 | #######################################
4 |
5 | virtual_delivery:
6 | driver = appendfile
7 | envelope_to_add
8 | return_path_add
9 | mode = 0600
10 | maildir_format = true
11 | create_directory = true
12 | directory = ${extract{smtp}{$address_data}}
13 | user = ${extract{uid}{$address_data}}
14 | group = ${extract{gid}{$address_data}}
15 | quota = ${extract{quota}{$address_data}{${value}M}}
16 | quota_is_inclusive = false
17 | #quota_size_regex = ,S=(\d+):
18 | quota_warn_threshold = 75%
19 | # enable if you want to create quota files
20 | maildir_use_size_file = false
21 | # create directory on first mail recipient
22 | #create_directory = true
23 | quota_warn_message = "To: $local_part@$domain\n\
24 | Subject: Mailbox quota warning\n\n\
25 | This message was automatically generated by the mail delivery software.\n\n\
26 | You are now using over 75% of your allocated mail storage quota.\n\n\
27 | If your mailbox fills completely, further incoming messages will be automatically\n\
28 | returned to their senders.\n\n\
29 | Please take note of this and remove unwanted mail from your mailbox.\n"
30 |
--------------------------------------------------------------------------------
/vexim/admingroupcontentaddsubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
9 | $sth->execute(array(':group_id'=>$_POST['group_id'], ':domain_id'=>$_SESSION['domain_id']));
10 | if (!$sth->rowCount()) {
11 | header ("Location: admingroupchange.php?group_id={$_POST['group_id']}&group_failupdated={$_POST['localpart']}");
12 | die();
13 | }
14 |
15 | # validate user_id and group_id
16 | if (!isset($_POST['usertoadd']) or !isset($_POST['group_id'])) {
17 | header("Location: admingroup.php?badname={$_POST['usertoadd']}");
18 | die;
19 | }
20 | $query = "INSERT INTO group_contents (group_id, member_id) VALUES (:group_id, :usertoadd)";
21 | $sth = $dbh->prepare($query);
22 | $success = $sth->execute(array(':group_id'=>$_POST['group_id'], ':usertoadd'=>$_POST['usertoadd']));
23 | if ($success) {
24 | header ("Location: admingroupchange.php?group_id={$_POST['group_id']}&group_updated={$_POST['localpart']}");
25 | } else {
26 | header ("Location: admingroupchange.php?group_id={$_POST['group_id']}&group_failupdated={$_POST['localpart']}");
27 | die;
28 | }
29 | ?>
30 |
--------------------------------------------------------------------------------
/vexim/admingroupcontentdeletesubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
9 | $sth->execute(array(':group_id'=>$_REQUEST['group_id'], ':domain_id'=>$_SESSION['domain_id']));
10 | if (!$sth->rowCount()) {
11 | header ("Location: admingroupchange.php?group_id={$_REQUEST['group_id']}&group_failupdated={$_REQUEST['localpart']}");
12 | die();
13 | }
14 |
15 | # validate user_id and group_id
16 | if (!isset($_REQUEST['member_id']) or !isset($_REQUEST['group_id'])) {
17 | header("Location: admingroup.php?badname={$_REQUEST_['member_id']}");
18 | die;
19 | }
20 | $query = "DELETE FROM group_contents
21 | WHERE group_id=:group_id
22 | AND member_id=:member_id";
23 | $sth = $dbh->prepare($query);
24 | $success = $sth->execute(array(':group_id'=>$_REQUEST['group_id'], ':member_id'=>$_REQUEST['member_id']));
25 | if ($success) {
26 | header ("Location: admingroupchange.php?group_id={$_REQUEST['group_id']}&group_updated={$_REQUEST['localpart']}");
27 | } else {
28 | header ("Location: admingroupchange.php?group_failupdated={$_REQUEST['localpart']}");
29 | }
30 | ?>
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Virtual Exim Copyright Notice
2 |
3 | Copyright 2003 Avleen Vig and Virtual Exim Development Team.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions
7 | are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | 2. Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in
13 | the documentation and/or other materials provided with the
14 | distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE VEXIM PROJECT ``AS IS'' AND ANY
17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE VEXIM PROJECT OR
20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/vexim/userblocksubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
10 | $success = $sth->execute(array(':block_id'=>$_GET['block_id']));
11 | if ($success) {
12 | header ("Location: userchange.php?updated");
13 | } else {
14 | header ("Location: userchange.php?failed");
15 | }
16 | }
17 |
18 | # Finally 'the rest' which is handled by the profile form
19 | if (preg_match("/^\s*$/",$_POST['blockval'])) { header("Location: userchange.php"); die; }
20 | $query = "INSERT INTO blocklists (domain_id, user_id, blockhdr, blockval, color) values (
21 | :domain_id, :user_id, :blockhdr, :blockval, :color)";
22 | $sth = $dbh->prepare($query);
23 | $success = $sth->execute(array(':domain_id'=>$_SESSION['domain_id'], ':user_id'=>$_SESSION['user_id'],
24 | ':blockhdr'=>$_POST['blockhdr'], ':blockval'=>$_POST['blockval'], ':color'=>$_POST['color']));
25 | if ($success) {
26 | header ("Location: userchange.php?updated");
27 | } else {
28 | header ("Location: userchange.php?failed");
29 | }
30 | ?>
31 |
32 |
--------------------------------------------------------------------------------
/vexim/admincatchallsubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
12 | $success = $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
13 | if(!$success) {
14 | header ("Location: adminalias.php?failupdated=Catchall");
15 | } else {
16 | $query = "INSERT INTO users (localpart, username, domain_id, smtp,
17 | pop, uid, gid, realname, type, enabled) SELECT '*',
18 | :domain, :domain_id, :smtp, :smtp, uid, gid, 'CatchAll', 'catch',
19 | '1' FROM domains WHERE domains.domain_id=:domain_id";
20 | $sth = $dbh->prepare($query);
21 | $success = $sth->execute(array(':domain'=>'*@'.$_SESSION['domain'], ':domain_id'=>$_SESSION['domain_id'], ':smtp'=>$_POST['smtp']));
22 |
23 | if ($success) {
24 | header ("Location: adminalias.php?updated=Catchall");
25 | } else {
26 | header ("Location: adminalias.php?failupdated=Catchall");
27 | }
28 | }
29 | ?>
30 |
31 |
--------------------------------------------------------------------------------
/vexim/adminfailchangesubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
24 | $success = $sth->execute(array(':localpart'=>$_POST['localpart'],
25 | ':username'=>$_POST['localpart'].'@'.$_SESSION['domain'],
26 | ':user_id'=>$_POST['user_id'], ':domain_id'=>$_SESSION['domain_id'],
27 | ':smtp'=>$_POST['smtp'], ':realname'=>$_POST['realname']
28 | ));
29 | if ($success) {
30 | header ("Location: adminfail.php?updated={$_POST['localpart']}");
31 | } else {
32 | header ("Location: adminfail.php?failupdated={$_POST['localpart']}");
33 | die;
34 | }
35 | ?>
36 |
37 |
--------------------------------------------------------------------------------
/vexim/admingroupadd.php:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | db:
5 | image: mysql:5.7
6 | restart: on-failure
7 | ports:
8 | - "${MYSQL_PORT:-3306}:3306"
9 | environment:
10 | MYSQL_ROOT_PASSWORD: ''
11 | MYSQL_DATABASE: 'vexim'
12 | MYSQL_USER: 'vexim'
13 | MYSQL_PASSWORD: 'CHANGE'
14 | MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
15 | volumes:
16 | - db_data:/var/lib/mysql:rw
17 | - ./setup/mysql.sql:/docker-entrypoint-initdb.d/mysql.sql:ro,cached
18 |
19 | php:
20 | build: docker/php
21 | restart: on-failure
22 | user: '${UID:-1000}'
23 | volumes:
24 | - mail_dir:/var/vmail:rw
25 | - ./docker/php/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini:ro,cached
26 | - ./docker/php/timezone.ini:/usr/local/etc/php/conf.d/timezone.ini:ro,cached
27 | - ./vexim:/srv/app:ro,cached
28 | - ./vexim/config/variables.php.example:/srv/app/config/variables.php:ro,cached
29 | extra_hosts:
30 | - host.docker.internal:host-gateway
31 | environment:
32 | PHP_IDE_CONFIG: '${PHP_IDE_CONFIG:-serverName=vexim}'
33 | depends_on:
34 | - db
35 | links:
36 | - db
37 |
38 | nginx:
39 | image: nginx:1-alpine-slim
40 | restart: on-failure
41 | ports:
42 | - "${WEB_PORT:-80}:80"
43 | volumes:
44 | - ./vexim:/srv/app:ro,cached
45 | - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro,cached
46 | depends_on:
47 | - php
48 | links:
49 | - php
50 |
51 | volumes:
52 | db_data:
53 | mail_dir:
54 |
--------------------------------------------------------------------------------
/vexim/sitepassword.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/vexim/adminlists.php:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
23 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/vexim/config/authsite.php:
--------------------------------------------------------------------------------
1 | prepare($query);
40 | $success = $sth->execute(array(':user_id'=>$_SESSION['user_id'], ':domain_id'=>$_SESSION['domain_id']));
41 | if(!$success || ($sth->rowCount()!=1)) {
42 | header ("Location: index.php?login=failed");
43 | die();
44 | }
45 |
46 | $row = $sth->fetch();
47 |
48 | # confirm the crypted password in the session matches the crypted password in the database for the siteadmin
49 | if ($row['crypt'] !== $_SESSION['crypt']) {
50 | header ("Location: index.php?login=failed");
51 | die();
52 | }
53 |
--------------------------------------------------------------------------------
/vexim/index.php:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
41 | " . _("Login failed") . "";
44 | }
45 | ?>
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/vexim/adminuserblocksubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
13 | $success = $sth->execute(array(':block_id'=>$_GET['block_id'],
14 | ':domain_id'=>$_SESSION['domain_id'], ':user_id'=>$_GET['user_id']));
15 | if ($success) {
16 | header ("Location: adminuser.php?updated={$_GET['localpart']}");
17 | die;
18 | } else {
19 | header ("Location: adminuser.php?failed={$_GET['localpart']}");
20 | die;
21 | }
22 | }
23 |
24 | # Finally 'the rest' which is handled by the profile form
25 | if (preg_match("/^\s*$/",$_POST['blockval'])) {
26 | header("Location: adminuser.php");
27 | die;
28 | }
29 | $query = "INSERT INTO blocklists
30 | (domain_id, user_id, blockhdr, blockval, color)
31 | VALUES (:domain_id, :user_id, :blockhdr, :blockval, :color)";
32 | $sth = $dbh->prepare($query);
33 | $success = $sth->execute(array(':domain_id'=>$_SESSION['domain_id'],
34 | ':user_id'=>$_POST['user_id'],
35 | ':blockhdr'=>$_POST['blockhdr'],
36 | ':blockval'=>$_POST['blockval'],
37 | ':color'=>$_POST['color']));
38 | if ($success) {
39 | header ("Location: adminuser.php?updated={$_POST['localpart']}");
40 | } else {
41 | header ("Location: adminuser.php?failed={$_POST['localpart']}");
42 | }
43 | ?>
44 |
45 |
--------------------------------------------------------------------------------
/vexim/adminfailaddsubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
38 | $success = $sth->execute(array(':localpart'=>$_POST['localpart'],
39 | ':username'=>$_POST['localpart'].'@'.$_SESSION['domain'],
40 | ':domain_id'=>$_SESSION['domain_id'],
41 | ':smtp' => $_POST['smtp'],
42 | ':realname' => $_POST['realname']
43 | ));
44 |
45 | if ($success) {
46 | header ("Location: adminfail.php?added={$_POST['localpart']}");
47 | } else {
48 | header ("Location: adminfail.php?failadded={$_POST['localpart']}");
49 | }
50 | ?>
51 |
52 |
--------------------------------------------------------------------------------
/docs/clients/dovecot.txt:
--------------------------------------------------------------------------------
1 | Configuring Dovecot to work with Virtual Exim
2 |
3 | This short manual is based on my experience setting up Dovecot 1.2.15
4 | to work with Vexim under Debian 6.0 (Squeeze). Any additions or
5 | improvements to this document are very welcome.
6 |
7 | Dovecot is an open source IMAP and POP3 email server for Linux/UNIX-like
8 | systems, written with security primarily in mind.
9 |
10 | Setting Dovecot up to work with Vexim is indeed rather trivial:
11 |
12 | 1. Edit /etc/dovecot/dovecot.conf. It is documented rather extensively,
13 | at least in Debian, so I won't go through everything. The things I had to
14 | change for Vexim auth to work were the following:
15 | * 'first_valid_uid' must be low enough for vexim user to be able to log in.
16 | For example:
17 |
18 | first_valid_uid = 100
19 |
20 | * comment out the 'passdb pam' block, and uncomment the 'passdb sql' block
21 | instead. My `passdb sql' looks like this:
22 |
23 | passdb sql {
24 | # Path for SQL configuration file
25 | args = /etc/dovecot/dovecot-sql.conf
26 | }
27 |
28 | * I have also uncommented the 'userdb prefetch' block:
29 |
30 | userdb prefetch {
31 | }
32 |
33 | 2. Edit /etc/dovecot/dovecot-sql.conf. Again, it is well documeted, so I
34 | won't go into detail. With comments stripped out, my dovecot-sql.conf
35 | looks like following:
36 |
37 | driver = mysql
38 | connect = host=/var/run/mysqld/mysqld.sock dbname=vexim user=vexim password=CHANGE
39 | default_pass_scheme = CRYPT #PLAIN
40 | password_query = \
41 | SELECT `username` AS `user`, `crypt` AS `password`, \
42 | `pop` AS `userdb_home`, `uid` AS `userdb_uid`, `gid` AS `userdb_gid` \
43 | FROM `users` WHERE `username` = '%u'
44 |
45 | Dovecot expects the select to return a set of columns with particular
46 | names, hence usage of the 'AS' keyword above.
47 |
48 | 3. Just restart Dovecot, and it should work.
49 |
--------------------------------------------------------------------------------
/docs/clients/courierimap.txt:
--------------------------------------------------------------------------------
1 | Installing Courier-IMAP 2.x on FreeBSD:
2 |
3 | * We are going to be performing configuration for both IMAP and POP3
4 | below. If you'd like to not run one of these two daemons, you can
5 | simply ignore the steps for its configuration file.
6 |
7 | cd /usr/ports/mail/courier-imap && make -DWITH_MYSQL install clean
8 | cd /usr/local/etc/courier-imap/
9 | cp imapd.dist imapd
10 | vi imapd
11 |
12 | Installing Courier-IMAP on Debian:
13 | apt-get install courier-imap courier-imap-ssl courier-pop courier-pop-ssl courier-authlib-mysql courier-authdaemon
14 |
15 |
16 | General Instructions:
17 | Now, replace the following lines:
18 | > Replace: AUTHMODULES="authdaemon"
19 | > with: AUTHMODULES="authmysql"
20 | AND:
21 | > Replace: AUTHMODULES_ORIG="authdaemon"
22 | > with: AUTHMODULES_ORIG=""
23 | Make the same changes to the pop3d.dist file, after copying it to
24 | pop3d.
25 |
26 | Now create a file in that directory called authmysqlrc, with the
27 | following contents:
28 | MYSQL_SERVER localhost
29 | MYSQL_USERNAME vexim
30 | MYSQL_PASSWORD CHANGE
31 | MYSQL_SOCKET /tmp/mysql.sock
32 | ** Debian: /var/run/mysqld/mysqld.sock
33 | MYSQL_PORT 3306
34 | MYSQL_OPT 0
35 | MYSQL_DATABASE vexim
36 | MYSQL_USER_TABLE users
37 | MYSQL_CRYPT_PWFIELD crypt
38 | MYSQL_UID_FIELD uid
39 | MYSQL_GID_FIELD gid
40 | MYSQL_LOGIN_FIELD username
41 | MYSQL_HOME_FIELD pop
42 | MYSQL_NAME_FIELD realname
43 |
44 | FreeBSD:
45 | Finally, perform these steps to move the startup scripts into
46 | place, and start the daemons:
47 | cd /usr/local/etc/rc.d
48 | mv imapd.sh.dist imapd.sh
49 | mv pop3d.sh.dist pop3d.sh
50 | /usr/local/etc/rc.d/imapd.sh start
51 | /usr/local/etc/rc.d/pop3d.sh start
52 |
53 | Debian:
54 | Restart the daemons
55 | for i in /etc/init.d/courier*; do $i restart; done
--------------------------------------------------------------------------------
/vexim/admincatchalladd.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/docs/vexim-acl-check-mime.conf:
--------------------------------------------------------------------------------
1 | # Decode MIME parts to disk. This will support virus scanners later.
2 | deny
3 | decode = default
4 | condition = ${if > {$mime_anomaly_level}{2} \
5 | {true}{false}}
6 | message = This message contains a MIME error ($mime_anomaly_text)
7 | log_message = DENY: MIME Error ($mime_anomaly_text)
8 |
9 | # File extension filtering.
10 | deny
11 | condition = ${if match \
12 | {${lc:$mime_filename}} \
13 | {\N(?i)\.(exe|com|ade|adep|adp|bas|bat|chm|cmd|cnf|com|cpl|crt|dll|hlp|hta|inf|ins|isp|js|jse|lnk|mad|maf|mag|mam|maq|mar|mas|matmav|maw|ocx|pcd|pif|reg|scf|scr|sct|vbe|vbs|wsc|wsf|wsh|url|xnk)$\N} \
14 | {1}{0}}
15 | message = Blacklisted file extension detected in "$mime_filename". If you legitimately need to send these files please use a compressed archive or a file exchange provider.
16 | log_message = DENY: Blacklisted extension ("$mime_filename")
17 |
18 | # Check zip files for suspicious mail extensions
19 | # from: http://www.gossamer-threads.com/lists/exim/users/98336#98336
20 | #
21 | # deny message = A .zip attachment contains a Windows-executable file - \
22 | # blocked because we are afraid of new viruses \
23 | # not recognized [yet] by antiviruses.
24 | # condition = ${if match{$mime_filename}{\N(?i)\.zip$\N}}
25 | # condition = ${if def:sender_host_address}
26 | # !authenticated = *
27 | # decode = default
28 | # log_message = forbidden binary in attachment: filename=$mime_filename, \
29 | # recipients=$recipients
30 | # condition = ${if match{${run{/usr/bin/unzip -l \
31 | # $mime_decoded_filename}}}\
32 | # {\N(?i)\.(exe|com|ade|adep|adp|bas|bat|chm|cmd|cnf|com|cpl|crt|dll|hlp|hta|inf|ins|isp|js|jse|lnk|mad|maf|mag|mam|maq|mar|mas|matmav|maw|ocx|pcd|pif|reg|scf|scr|sct|vbe|vbs|wsc|wsf|wsh|url|xnk)\n\N}}
33 |
34 | # Further checks can be implemented, especially if older email clients are used: https://github.com/Exim/exim/wiki/ExiscanBugBlocking
35 |
--------------------------------------------------------------------------------
/docs/vexim-acl-check-content.conf:
--------------------------------------------------------------------------------
1 | # Reject virus infested messages.
2 | #
3 | warn message = This message contains malware ($malware_name)
4 | malware = *
5 | log_message = This message contains malware ($malware_name)
6 |
7 | # Reject messages with an "X-Spam-Flag: YES" header.
8 | #
9 | deny message = Sender openly considers message as spam (X-Spam-Flag header with a positive value was found).
10 | condition = ${if bool{$h_x-spam-flag:}{true}{false}}
11 |
12 | # Reject messages containing "viagra" in all kinds of whitespace/case combinations
13 | # WARNING: this is an example !
14 | #
15 | # deny message = This message matches a blacklisted regular expression ($regex_match_string)
16 | # regex = [Vv] *[Ii] *[Aa] *[Gg] *[Rr] *[Aa]
17 |
18 | # Mails larger than 300 KB are accepted without spam-checking to save resources.
19 | # Message scanning time grows exponentially in the size of the message and goes timeout
20 | # after 2~5 minutes, while typical spam has only a few KB in size.
21 | #
22 | accept condition = ${if >={$message_size}{300k}{yes}{no}}
23 | remove_header = X-Spam-Flag : X-Spam-Score : X-Spam-Status : X-Spam-Report
24 |
25 | # Pass missing spam score and report data to routers and transports, but do not reject anything yet.
26 | # Also, if set, remove X-Spam-* headers here: we will add our own ones later.
27 | #
28 | warn spam = VEXIM_SA_USERNAME:true
29 | set acl_m_spam_score = $spam_score
30 | set acl_m_spam_bar = $spam_bar
31 | set acl_m_spam_report = $spam_report
32 | # $spam_score_int persists by itself
33 | remove_header = X-Spam-Flag : X-Spam-Score : X-Spam-Status : X-Spam-Report
34 |
35 | # Reject all mails with more than 15 spam points, REGARDLESS OF INDIVIDUAL USER SETTINGS.
36 | #
37 | # deny message = This message has been rejected due to spam.
38 | # condition = ${if >{$spam_score_int}{150}{true}{false}}
39 |
40 | accept hosts = 127.0.0.1:+relay_from_hosts
41 | accept authenticated = *
42 |
--------------------------------------------------------------------------------
/vexim/api/change-user-password.php:
--------------------------------------------------------------------------------
1 | prepare($query);
30 | $success = $sth->execute(array(':username'=>$_POST['user']));
31 | if(!$success) {
32 | http_response_code(500);
33 | if(ini_get('display_errors')) {
34 | print_r($sth->errorInfo());
35 | }
36 | die('internal server error');
37 | }
38 | if ($sth->rowCount()!=1) {
39 | http_response_code(403);
40 | die('invalid user or password');
41 | }
42 |
43 | $row = $sth->fetch();
44 | $cryptedpass = crypt_password($_POST['curpass'], $row['crypt']);
45 |
46 | if ($cryptedpass !== $row['crypt']) {
47 | http_response_code(403);
48 | die('invalid user or password');
49 | }
50 |
51 | if (!password_strengthcheck($_POST['newpass'])) {
52 | http_response_code(422);
53 | die("week password");
54 | }
55 |
56 | $cryptedpass = crypt_password($_POST['newpass']);
57 |
58 | $query = "UPDATE users SET crypt=:crypt WHERE user_id=:user_id";
59 | $sth = $dbh->prepare($query);
60 | $success = $sth->execute(array(':crypt'=>$cryptedpass, ':user_id'=>$row['user_id']));
61 | if (!$success) {
62 | http_response_code(500);
63 | if(ini_get('display_errors')) {
64 | print_r($sth->errorInfo());
65 | }
66 | die('internal server error');
67 | }
68 |
--------------------------------------------------------------------------------
/vexim/config/authpostmaster.php:
--------------------------------------------------------------------------------
1 | prepare($query);
30 | $success = $sth->execute(array(':user_id'=>$_SESSION['user_id'], ':domain_id'=>$domain_id));
31 | if(!$success || ($sth->rowCount()!=1)) {
32 | header ("Location: index.php?login=failed");
33 | die();
34 | }
35 | $row = $sth->fetch();
36 |
37 | # confirm the crypted password in the session matches the crypted password in the database for the user
38 | if ($row['crypt'] != $_SESSION['crypt']) {
39 | header ("Location: index.php?login=failed");
40 | die();
41 | }
42 |
43 | if (isset($siteadminManageDomains)
44 | && $siteadminManageDomains
45 | && isset($_SESSION['username'])
46 | && 'siteadmin' === $_SESSION['username']
47 | && isset($_GET['manage_domain_id'])
48 | )
49 | {
50 | $query = "SELECT domain FROM domains WHERE domain_id=:domain_id;";
51 | $sth = $dbh->prepare($query);
52 | $success = $sth->execute(array(':domain_id'=>$_GET['manage_domain_id']));
53 | if(!$success || ($sth->rowCount()!=1)) {
54 | header ("Location: index.php?login=failed");
55 | die();
56 | }
57 | $row = $sth->fetch();
58 | $_SESSION['domain_id'] = $_GET['manage_domain_id'];
59 | $_SESSION['domain'] = $row['domain'];
60 | }
61 |
--------------------------------------------------------------------------------
/docs/debian-conf.d/auth/30_vexim_authenticators:
--------------------------------------------------------------------------------
1 |
2 | ### 30_vexim_authenticators
3 | ###########################
4 |
5 | plain_virtual_exim:
6 | driver = plaintext
7 | public_name = PLAIN
8 | server_condition = ${if crypteq{$auth3}{${lookup mysql{ \
9 | SELECT crypt FROM users \
10 | WHERE username = '${quote_mysql:$auth2}' \
11 | AND enabled = 1 \
12 | }}}{1}{0}}
13 | server_set_id = $auth2
14 | server_advertise_condition = ${if or{\
15 | {!eq{$tls_cipher}{}}\
16 | {match_ip {$sender_host_address}{@[]}}\
17 | }\
18 | {*}{}}
19 |
20 | login_virtual_exim:
21 | driver = plaintext
22 | public_name = LOGIN
23 | server_prompts = "Username:: : Password::"
24 |
25 | server_condition = ${if crypteq{$auth2}{${lookup mysql{ \
26 | SELECT crypt FROM users \
27 | WHERE username = '${quote_mysql:$auth1}' \
28 | AND enabled = 1 \
29 | }}}{1}{0}}
30 | server_set_id = $auth1
31 | server_advertise_condition = ${if or{\
32 | {!eq{$tls_cipher}{}}\
33 | {match_ip {$sender_host_address}{@[]}}\
34 | }\
35 | {*}{}}
36 |
37 |
38 | # You can use the authenticator of your IMAP server (Dovecot or Courier) to authenticate
39 | # users in Exim. To do that, comment out the lines above and uncomment either the Dovecot
40 | # section below or the Courier section in 30_exim4-config_examples file.
41 |
42 | # Authenticate against Dovecot SASL
43 | # Based on: http://wiki2.dovecot.org/HowTo/EximAndDovecotSASL
44 | #
45 | # login_dovecot_sasl:
46 | # driver = dovecot
47 | # public_name = LOGIN
48 | # server_socket = /var/run/dovecot/auth-client
49 | # server_set_id = $auth1
50 | # server_advertise_condition = ${if or{\
51 | # {!eq{$tls_cipher}{}}\
52 | # {match_ip {$sender_host_address}{@[]}}\
53 | # }\
54 | # {*}{}}
55 | #
56 | # plain_dovecot_sasl:
57 | # driver = dovecot
58 | # public_name = PLAIN
59 | # server_socket = /var/run/dovecot/auth-client
60 | # server_set_id = $auth1
61 | # server_advertise_condition = ${if or{\
62 | # {!eq{$tls_cipher}{}}\
63 | # {match_ip {$sender_host_address}{@[]}}\
64 | # }\
65 | # {*}{}}
66 |
--------------------------------------------------------------------------------
/vexim/adminfailadd.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/vexim/adminfail.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/vexim/admin.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | ' . _('Manage mailing lists') . ' ';
52 | }
53 | ?>
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | prepare($query);
66 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
67 |
68 | if($sth->rowCount()) {
69 | print 'Domain data: ';
70 | while ($row = $sth->fetch()) {
71 | print '';
72 | print "{$row['alias']} is an alias of {$_SESSION['domain']}";
73 | print ' ';
74 | }
75 | }
76 | ?>
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/vexim/admingroup.php:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | prepare($query);
33 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
34 | while ($row = $sth->fetch()) {
35 | if($row['enabled'] === 0) print ''; else print ' ';
36 | ?>
37 |
38 |
39 |
41 |
42 |
43 |
44 |
46 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
56 |
58 |
59 |
60 |
61 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/vexim/admincatchall.php:
--------------------------------------------------------------------------------
1 | prepare($query);
9 | $sth->execute(array(':user_id'=>$_GET['user_id'], ':domain_id'=>$_SESSION['domain_id']));
10 | if ($sth->rowCount()) {
11 | $row = $sth->fetch();
12 | }
13 | ?>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/docs/vexim-acl-check-rcpt.conf:
--------------------------------------------------------------------------------
1 | # Use spfquery to perform a pair of SPF checks (for details, see
2 | # http://www.openspf.org/)
3 | # This check has been copied from a stock Debian config and has not been
4 | # tested, that's why it's commented out.
5 | #
6 | # This is quite costly in terms of DNS lookups (~6 lookups per mail). Do not
7 | # enable if that's an issue. Also note that if you enable this, you must
8 | # install "spf-tools-perl" which provides the spfquery command.
9 | # Missing spf-tools-perl will trigger the "Unexpected error in
10 | # SPF check" warning.
11 | # deny
12 | # message = [SPF] $sender_host_address is not allowed to send mail from \
13 | # ${if def:sender_address_domain {$sender_address_domain}{$sender_helo_name}}. \
14 | # Please see \
15 | # http://www.openspf.org/Why?scope=${if def:sender_address_domain \
16 | # {mfrom}{helo}};identity=${if def:sender_address_domain \
17 | # {$sender_address}{$sender_helo_name}};ip=$sender_host_address
18 | # log_message = SPF check failed.
19 | # !acl = acl_local_deny_exceptions
20 | # condition = ${run{/usr/bin/spfquery.mail-spf-perl --ip \
21 | # \"$sender_host_address\" --identity \
22 | # ${if def:sender_address_domain \
23 | # {--scope mfrom --identity \"$sender_address\"}\
24 | # {--scope helo --identity \"$sender_helo_name\"}}}\
25 | # {no}{${if eq {$runrc}{1}{yes}{no}}}}
26 | # defer
27 | # message = Temporary DNS error while checking SPF record. Try again later.
28 | # !acl = acl_local_deny_exceptions
29 | # condition = ${if eq {$runrc}{5}{yes}{no}}
30 | #
31 | # warn
32 | # condition = ${if <={$runrc}{6}{yes}{no}}
33 | # message = Received-SPF: ${if eq {$runrc}{0}{pass}\
34 | # {${if eq {$runrc}{2}{softfail}\
35 | # {${if eq {$runrc}{3}{neutral}\
36 | # {${if eq {$runrc}{4}{permerror}\
37 | # {${if eq {$runrc}{6}{none}{error}}}}}}}}}\
38 | # } client-ip=$sender_host_address; \
39 | # ${if def:sender_address_domain \
40 | # {envelope-from=${sender_address}; }{}}\
41 | # helo=$sender_helo_name
42 | #
43 | # warn
44 | # log_message = Unexpected error in SPF check.
45 | # condition = ${if >{$runrc}{6}{yes}{no}}
46 |
47 |
48 |
49 | # deny hosts = ! +relay_from_hosts
50 | # condition = ${if eq {${lookup mysql{select count(*) from domains \
51 | # where domain = '${quote_mysql:$domain}' \
52 | # and spamassassin='1'}}}{1} {yes}{no}}
53 | # !acl = spf_rcpt_acl
54 |
55 |
56 | .ifdef CHECK_RCPT_REVERSE_DNS
57 | warn message = Warning - Reverse DNS lookup failed for host $sender_host_address.
58 | !authenticated = *
59 | !verify = reverse_host_lookup
60 | .endif
61 |
62 |
63 | # Deny unless the sender address can be verified.
64 | # require verify = sender
65 |
66 |
67 | # To use a DNSBL the best would be to set up a caching dns server (e.g. unbound) because there is
68 | # often a rate limit that you will hit when using public DNS servers.
69 | deny message = DNSBL listed at $dnslist_domain\n$dnslist_text
70 | dnslists = zen.spamhaus.org
71 |
--------------------------------------------------------------------------------
/docs/debian-conf.d/main/00_vexim_listmacrosdefs:
--------------------------------------------------------------------------------
1 |
2 | ### main/00_vexim_listmacrosdefs
3 | #################################
4 |
5 | hide mysql_servers = localhost::(/var/run/mysqld/mysqld.sock)/vexim/vexim/CHANGE
6 |
7 | # domains
8 | VEXIM_VIRTUAL_DOMAINS = SELECT DISTINCT domain FROM domains WHERE type = 'local' AND enabled = '1' AND domain = '${quote_mysql:$domain}'
9 | VEXIM_RELAY_DOMAINS = SELECT DISTINCT domain FROM domains WHERE type = 'relay' AND domain = '${quote_mysql:$domain}'
10 | VEXIM_ALIAS_DOMAINS = SELECT DISTINCT alias FROM domainalias WHERE alias = '${quote_mysql:$domain}'
11 |
12 | # domains and relay networks
13 | MAIN_LOCAL_DOMAINS = MAIN_LOCAL_DOMAINS : ${lookup mysql{VEXIM_VIRTUAL_DOMAINS}} : ${lookup mysql{VEXIM_ALIAS_DOMAINS}}
14 | MAIN_RELAY_TO_DOMAINS = MAIN_RELAY_TO_DOMAINS : ${lookup mysql{VEXIM_RELAY_DOMAINS}}
15 |
16 | # primary hostname
17 | #MAIN_HARDCODE_PRIMARY_HOSTNAME=myhostname
18 |
19 | # add vexim system user
20 | #MAIN_TRUSTED_USERS = www-data
21 |
22 | # enable TLS
23 | #MAIN_TLS_ENABLE = true
24 |
25 | # enable av scanner
26 | #av_scanner = clamd:/var/run/clamav/clamd.ctl
27 |
28 | # use spamassassing
29 | #spamd_address = 127.0.0.1 783
30 |
31 | .ifdef MAIN_KEEP_ENVIRONMENT
32 | keep_environment = MAIN_KEEP_ENVIRONMENT
33 | .else
34 | keep_environment =
35 | .endif
36 | .ifdef MAIN_ADD_ENVIRONMENT
37 | add_environment = MAIN_ADD_ENVIRONMENT
38 | .endif
39 |
40 | # validation of sending mailserver
41 | #CHECK_RCPT_REVERSE_DNS = true
42 | #CHECK_RCPT_SPF = true
43 |
44 |
45 | # Mailman-related options. Uncomment VEXIM_HAVE_MAILMAN and adjust the macros
46 | # below if want to use Mailman with this installation.
47 | # NOTE: we only have integration with Mailman 2 ready at the moment.
48 |
49 | #VEXIM_HAVE_MAILMAN = yes
50 |
51 |
52 | # Home dir for your Mailman installation - aka Mailman's prefix
53 | # directory.
54 | # On a Red Hat/Fedora system using RPM, use "/var/mailman"
55 | # On Debian using the deb package use "/var/lib/mailman"
56 | # This is normally the same as ~mailman
57 |
58 | MAILMAN_HOME=/usr/local/mailman
59 |
60 |
61 | # User and group for Mailman, should match your --with-mail-gid
62 | # switch to Mailman's configure script.
63 | # Value is normally "mailman"
64 |
65 | MAILMAN_USER=mailman
66 | MAILMAN_GROUP=mailman
67 |
68 |
69 | # The path of the Mailman mail wrapper script
70 |
71 | MAILMAN_WRAP=MAILMAN_HOME/mail/mailman
72 |
73 |
74 | # The path of the list config file (used as a required file when
75 | # verifying list addresses)
76 |
77 | MAILMAN_LISTCHK = MAILMAN_HOME/lists/${lc::$local_part}/config.pck
78 |
79 |
80 | # If the local-part suffix is used, mails to user+whatevertext@example.org will be delivered to user@example.org.
81 | # Comment this line if you want to disable it. You can also use a different separator instead of `+`.
82 | VEXIM_LOCALPART_SUFFIX = +*
83 |
84 | CHECK_RCPT_LOCAL_ACL_FILE = /etc/exim4/vexim-acl-check-rcpt.conf
85 | CHECK_DATA_LOCAL_ACL_FILE = /etc/exim4/vexim-acl-check-content.conf
86 |
87 | # Exim will put the detailed spam report into an X-Spam-Report header by default.
88 | # This report is really huge by default, but its template can be tweaked to make
89 | # it look almost exactly like contents of the X-Spam-Status header, which
90 | # SpamAssassin adds when scanning messages externally, and which is a much more
91 | # compact version of the report. If you tweak your template this way, you may
92 | # as well want to change the header name here.
93 | VEXIM_SPAM_REPORT_HEADER_NAME = X-Spam-Report
94 |
--------------------------------------------------------------------------------
/vexim/adminaliasdelete.php:
--------------------------------------------------------------------------------
1 | prepare($query);
14 | $success = $sth->execute(array(':user_id'=>$_GET['user_id'], ':domain_id'=>$_SESSION['domain_id']));
15 | if ($success) {
16 | header ("Location: adminalias.php?deleted={$_GET['localpart']}");
17 | die;
18 | } else {
19 | header ("Location: adminalias.php?faildeleted={$_GET['localpart']}");
20 | die;
21 | }
22 | } else if ($_GET['confirm'] == 'cancel') {
23 | header ("Location: adminalias.php?faildeleted={$_GET['localpart']}");
24 | die;
25 | }
26 | }
27 | ?>
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
41 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/vexim/login.php:
--------------------------------------------------------------------------------
1 | prepare($query);
23 | $success = $sth->execute(array(':username'=>$_POST['username']));
24 | if(!$success) {
25 | print_r($sth->errorInfo());
26 | die();
27 | }
28 | if ($sth->rowCount()!=1) {
29 | header ('Location: index.php?login=failed');
30 | die();
31 | }
32 |
33 | $row = $sth->fetch();
34 | $cryptedpass = crypt_password($_POST['crypt'], $row['crypt']);
35 |
36 | // Some debugging prints. They help when you don't know why auth is failing.
37 | /*
38 | print $query. " \n";;
39 | print $row['username']. " \n";
40 | print $_POST['username'] . " \n";
41 | print "Posted crypt: " .$_POST['crypt'] . " \n";
42 | print $row['crypt'] . " \n";
43 | print $cryptscheme . " \n";
44 | print $cryptedpass . " \n";
45 | */
46 |
47 | # if they have the wrong password bail out
48 | if ($cryptedpass !== $row['crypt']) {
49 | header ('Location: index.php?login=failed');
50 | die();
51 | }
52 | if($row['username']!=='siteadmin') {
53 | if (($row['userenabled'] === '0')) {
54 | header ('Location: index.php?userdisabled');
55 | die();
56 | }
57 | if (($row['domainenabled'] === '0')) {
58 | header ('Location: index.php?domaindisabled');
59 | die();
60 | }
61 | }
62 |
63 | # populate session variables from what was retrieved from the database (NOT what they posted)
64 | $_SESSION['username'] = $row['username'];
65 | $_SESSION['localpart'] = $row['localpart'];
66 | $_SESSION['domain_id'] = $row['domain_id'];
67 | $_SESSION['domain'] = $row['domain'];
68 | $_SESSION['crypt'] = $row['crypt'];
69 | $_SESSION['user_id'] = $row['user_id'];
70 |
71 | if (isset($siteadminManageDomains)
72 | && $siteadminManageDomains
73 | && 'siteadmin' === $row['username']
74 | ) {
75 | $_SESSION['siteadmin_domain_id'] = $row['domain_id'];
76 | $_SESSION['siteadmin_domain'] = $row['domain'];
77 | }
78 |
79 | # redirect the user to the correct starting page
80 | if (($row['admin'] == '1') && ($row['type'] == 'site')) {
81 | header ('Location: site.php');
82 | die();
83 | }
84 |
85 | if ($row['admin'] == '1') {
86 | header ('Location: admin.php');
87 | die();
88 | }
89 |
90 | # must be a user, send them to edit their own details, if User-Login is permitted
91 | if($AllowUserLogin===1) {
92 | header ('Location: userchange.php');
93 | die();
94 | }
95 | header ('Location: index.php?login=disabled');
96 | ?>
97 |
98 |
--------------------------------------------------------------------------------
/vexim/adminfailchange.php:
--------------------------------------------------------------------------------
1 | prepare($query);
9 | $sth->execute(array(':user_id'=>$_GET['user_id'], ':domain_id'=>$_SESSION['domain_id']));
10 | if($sth->rowCount()) {
11 | $row = $sth->fetch();
12 | }
13 | ?>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
29 | rowCount()) {
32 | echo '
';
33 | echo "Invalid fail userid '" . htmlentities($_GET['user_id']) . "' for domain '" . htmlentities($_SESSION['domain']). "'";
34 | echo '
';
35 | }else{
36 | ?>
37 |
75 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/vexim/scripts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generate a new password, which may then be copied to the form
3 | * with suggestPasswordCopy().
4 | *
5 | * Function copied from phpMyAdmin
6 | *
7 | * @param string name of the field that should be filled
8 | *
9 | * @return string the generated password
10 | */
11 | function suggestPassword(fieldName) {
12 | // restrict the password to just letters and numbers to avoid problems:
13 | // "editors and viewers regard the password as multiple words and
14 | // things like double click no longer work"
15 | var pwchars = "23456789abcdefhjmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWYXZ";
16 | var passwordlength = 12; // do we want that to be dynamic? no, keep it simple :)
17 | var passwd = '';
18 |
19 | for ( i = 0; i < passwordlength; i++ ) {
20 | passwd += pwchars.charAt( Math.floor( Math.random() * pwchars.length ) )
21 | }
22 |
23 | if((fieldName != '') && (field = document.getElementById(fieldName))) {
24 | field.value = passwd;
25 | }
26 | return passwd;
27 | }
28 |
29 |
30 | /**
31 | * Copy the generated password (or anything in the field) to the form
32 | *
33 | * Function copied from phpMyAdmin
34 | *
35 | * @param string name of the field to copy the password from
36 | * @param string name of the field to copy the password to
37 | * @param string name of the field to copy the password to
38 | *
39 | * @return boolean always true
40 | */
41 | function copyPassword(sourceFieldName, target1FieldName, target2FieldName) {
42 | document.getElementById(target1FieldName).value = document.getElementById(sourceFieldName).value;
43 | document.getElementById(target2FieldName).value = document.getElementById(sourceFieldName).value;
44 | return true;
45 | }
46 |
47 |
48 | /**
49 | * Enables forward-destination field and selection box only if forwarding is enabled
50 | */
51 | function fwform() {
52 | document.getElementById('forward').disabled = !document.getElementById('on_forward').checked;
53 | if (document.getElementById('forwardmenu') != null) { //userchange.php has no box
54 | document.getElementById('forwardmenu').disabled = !document.getElementById('on_forward').checked;
55 | }
56 | return true;
57 | }
58 |
59 |
60 | /**
61 | * If item on forwarding destination is selected from a list of destinations
62 | * the forwarding text box is updated
63 | */
64 | function boxadd() {
65 | var exstring = document.getElementById('forward').value;
66 | var box = document.getElementById('forwardmenu');
67 | var selectitem = box.options[box.selectedIndex].value;
68 | if (!exstring.match(/\S/)) {
69 | document.getElementById('forward').value=selectitem;
70 | } else {
71 | document.getElementById('forward').value += "," + selectitem;
72 | }
73 | }
74 |
75 |
76 | /**
77 | * Add event listener
78 | */
79 | document.addEventListener('DOMContentLoaded', function () {
80 | if (document.getElementById('pwcopy') != null) {
81 | document.getElementById('pwcopy').addEventListener('click', function() { copyPassword('suggest', 'clear', 'vclear')});
82 | }
83 | if (document.getElementById('pwgenerate') != null) {
84 | document.getElementById('pwgenerate').addEventListener('click', function() { suggestPassword('suggest') });
85 | }
86 | if (document.getElementById('on_forward') != null) {
87 | document.getElementById('on_forward').addEventListener('change', function() { fwform() });
88 | }
89 | if (document.getElementById('forwardmenu') != null) {
90 | document.getElementById('forwardmenu').addEventListener('change', function() { boxadd() });
91 | }
92 |
93 | // settings at page load
94 | if (document.getElementById('forward') != null) {
95 | fwform();
96 | }
97 | });
98 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | 1. Site Controls
2 | a. Add/Modify/Delete domain [Done]
3 | b. Domain can add admin [Done]
4 | c. Procmail available switch [Done]
5 | d. SpamAssassin available [Done]
6 | e. Virus scanner available [Done]
7 | f. Mailing list availability [For later release]
8 | g. Quota can be set [Done]
9 | -> Need exim config [Done]
10 | h. Blacklists and Whitelists can be set
11 | -> [Blacklisting done]
12 | i. UID/GID [Done]
13 | -> Let siteadmin decide if these are domain admin tunable [Done]
14 | j. Domain tag line can be added for outbound mail [Unsure]
15 | k. Add/Modify/Delete domain admin [Done]
16 | l. Add/Modify/Delete relay_domains [Done]
17 | m. Password complexity requirements [For later release]
18 | o. Add the ability to choose any filesystem directory [Done]
19 | -> Check the permissions to make sure Exim can create it
20 | p. Add limits on how many accounts each domain can have [Done]
21 | q. Add a "junior site admin" with different abilities
22 |
23 | 2. Domain Controls (depends on site settings)
24 | a. Add/delete/modify users [Done]
25 | b. Add/delete/modify admins [Done]
26 | c. Add/delete/modify aliases [Done]
27 | d. Quota can be set [Done]
28 | e. SpamAssassin settings [Done]
29 | f. Virus Scanner [Done]
30 | g. Blacklists and Whitelists
31 | -> tables are ready, almost code required
32 | h. Mailing lists [For later release]
33 | i. UID/GID [Done]
34 | j. Domain tag line attached to all outgoing mail [Unsure]
35 | k. Allow any user or alias to act as domain admin [Done]
36 | l. Password complexity requirements [For later release]
37 | m. Allow domain admin to tweak any user settings [Done]
38 | n. Multiple levels of 'admin':
39 | -> Some who can one add users, some who can only change
40 | passwords, others who can only create aliases, etc
41 | Eg, when an admin tries to create an alias, assign the
42 | variable '$task=addalias', and query the db to see if the
43 | admin has the privilege to do this. This requires a
44 | 'flags' column in the db. [For later release]
45 |
46 | 3. User Controls (depends on site and domain settings)
47 | a. Change password [Done]
48 | b. SpamAssassin settings [Done]
49 | c. Virus scanner settings [Done]
50 | d. Personal block lists [Done]
51 | e. Add alias (?)
52 | f. Vacation response [Done]
53 | g. Forward incoming mail [Done]
54 | h. See current quota limit [Done]
55 | -> Later, need a way to see current mailbox size
56 | This is dependent on the maildirsize option getting fixed in Exim
57 | i. Code to intergrate whitelists to webpages
58 |
59 | 4. System Controls
60 | a. Choose from multiple databases: MySQL, PostgreSQL, LDAP [MySQL, PostgreSQL done]
61 | b. Shell scripts to perform all the same work the webpages do.
62 | Possibly in PHP or Perl. Probably PHP for simpler DB
63 | integration/package maintenance.
64 |
65 | 5. Setup
66 | a. Scripts used to configure vexim the first time.
67 | -> These scripts should be deleted (for security purposes)
68 | after configuration is completed.
69 | The scripts will create the basic database (with site-wide
70 | settings), various tables, and the variables.php file from
71 | the site admin's input. Would it be wise to require a
72 | user/password with enough privileges to create a database
73 | and grant privileges on it? I've seen this done with other
74 | programs. It certainly makes things easier, but some
75 | people might have a problem with entering the mysql god
76 | password.
77 | b. Reduce the overall number of PHP files in the package, by
78 | consolidating common functions.
79 | -> Various 'submit' pages can be consolidated to one or two,
80 | etc.
81 | c. Add Wash's docs on using tpop3d to CVS [Done]
82 | d. Make APOP work!
83 |
84 |
--------------------------------------------------------------------------------
/vexim/config/variables.php.example:
--------------------------------------------------------------------------------
1 | 'SET NAMES UTF8');
13 |
14 | try {
15 | $dbh = new PDO($dsn, $sqluser, $sqlpass, $dboptions);
16 | $dbh->setAttribute($dbh::ATTR_DEFAULT_FETCH_MODE, $dbh::FETCH_ASSOC);
17 | } catch (PDOException $e) {
18 | die($e->getMessage());
19 | }
20 |
21 | /* We use this IMAP server to check user quotas */
22 | $imapquotaserver = "{mail.CHANGE.com:143/imap/notls}";
23 | $imap_to_check_quota = "no";
24 |
25 | /* Setting this to 0 if only admins should be allowed to login */
26 | $AllowUserLogin = 1;
27 |
28 | /* Choose whether to break up domain and user lists alphabetically */
29 | $alphadomains = 1;
30 | $alphausers = 1;
31 |
32 | /* Set to either "sha512" or "bcrypt" (only on *BSD) for advanced
33 | pw-hash functions, "des" and "md5" (kept for compatibility
34 | to older setups), or "clear" for clear-text passwords.
35 | It is not recommended to use the "clear" option
36 | Alternatively, you can specify custom salt prefix here, e.g.
37 | SHA-512 with 10000 rounds -> $cryptscheme='$6$rounds=10000$'
38 | or bcrypt with complexity 2^12 -> $cryptscheme='$2a$12$' */
39 | $cryptscheme = 'sha512';
40 |
41 | /* Guess domain name from hostname and allow login based on
42 | local part only. It is off by default, set this value to 1
43 | in order to enable this function
44 | and set a string which will be cut off the left side of
45 | the hostname*/
46 | $domainguess = 0;
47 | $domainguess_lefttrim = "mail|vexim";
48 |
49 | /* Enable password strength check
50 | To disable this check set $passwordstrengthcheck = 0;
51 | */
52 | $passwordstrengthcheck = 1;
53 |
54 | /* Enable Content Security Policy (CSP) to prevent cross-site scripting
55 | and other code-injection attacks. */
56 | $CSPenabled = false;
57 |
58 | /* The UID's and GID's control the default UID and GID for new domains
59 | and if postmasters can define their own.
60 | THE UID AND GID MUST BE NUMERIC! */
61 | $uid = "90";
62 | $gid = "90";
63 | $postmasteruidgid = "yes";
64 |
65 | /* Allow the siteadmin user to manage domains? */
66 | $siteadminManageDomains = true;
67 |
68 | /* The location of your mailstore for new domains.
69 | Make sure the directory belongs to the configured $uid/$gid! */
70 | $mailroot = "/var/vmail/";
71 |
72 | /* Check if mail store specified above exists when creating a new domain.
73 | Generally, this shouldn't be disabled, but there may be special cases
74 | when our check doesn't work and should be disabled (e.g. the parent
75 | directory of mail store is inaccessible to your web server). */
76 | $testmailroot = true;
77 |
78 | /* path to Mailman */
79 | $mailmanroot = "http://www.EXAMPLE.com/mailman";
80 |
81 | /* sa_tag is the default value to offer when we create new domains for SpamAssassin tagging
82 | sa_refuse is the default value to offer when we create new domains for SpamAssassin dropping */
83 | $sa_tag = "2";
84 | $sa_refuse = "5";
85 |
86 | /* Welcome message, sent to new POP/IMAP accounts */
87 | @$welcome_message = "Welcome, {$_POST['realname']} !\n\n"
88 | . "Your new E-mail account is all ready for you.\n\n"
89 | . "Here are some settings you might find useful:\n\n"
90 | . "Username: {$_POST['localpart']}@{$_SESSION['domain']}\n"
91 | . "POP3 server: mail.{$_SESSION['domain']}\n"
92 | . "SMTP server: mail.{$_SESSION['domain']}\n";
93 |
94 | /* Welcome message, sent to new domains */
95 | @$welcome_newdomain = "Welcome, and thank you for registering your e-mail domain\n"
96 | . "{$_POST['domain']} with us.\n\nIf you have any questions, please\n"
97 | . "don't hesitate to ask your account representitive.\n";
98 | ?>
99 |
--------------------------------------------------------------------------------
/docs/checklist.txt:
--------------------------------------------------------------------------------
1 | Checklist to follow after updating code, to confirm functionality
2 | =================================================================
3 |
4 | [x] Logging in as Siteadmin
5 | Site management:
6 | [ ] Adding domains: local and relay
7 | [ ] With and without max accounts
8 | [ ] With and without quotas
9 | [ ] With and without maximum message size
10 | [ ] Different SpamAssassin scores
11 | [ ] With and without SpamAssassin
12 | [ ] With and without Anti-Virus
13 | [ ] With and without Piping mail option
14 | [ ] With and without 'Domain enabled'
15 | [ ] Changing domain admin password
16 | [ ] Changing UID/GID flags
17 | [ ] Changing Boolean flags has impact on admin/users
18 | [ ] Changing 'Enabled' works
19 | [ ] Deleting domains
20 | [ ] Adding domain alias
21 | [ ] Deleting domain alias
22 | [ ] Adding relay domain
23 | [ ] Deleting relay domain
24 |
25 | Siteadmin Management:
26 | [ ] Change siteadmin password
27 | [ ] Set siteadmin password to 'CHANGE', at next login you must change password.
28 |
29 | Account Additions:
30 | [ ] Adding aliases works
31 | [ ] Adding Catchall:
32 | [ ] Does the 'Add Catchall' option disappear if a Catchall exists?
33 | [ ] Adding POP/IMAP accounts:
34 | [ ] If the SpamAssassin/Anti-Virus/Piping options are disabled,
35 | do they still show up?
36 | [ ] Can you enable/disable the availible options?
37 | [ ] Can you change SpamAssassin scores?
38 | [ ] Can you change if spam mails are moved/deleted?
39 | [ ] Does adding :fail:'s work?
40 | [ ] If you have a maximum number of accounts:
41 | [ ] Is this displayed?
42 | [ ] Is the current number of accounts displayed?
43 | [ ] Does adding more than max accounts fail?
44 |
45 | Account Management:
46 | As Admin:
47 | [ ] Update Aliases:
48 | [ ] Can you update the name?
49 | [ ] Can you update the 'to' ?
50 | [ ] Can you update the final destination?
51 | [ ] Can you enable/disable Anti-Virus?
52 | [ ] Can you enable/disable SpamAssassin?
53 | [ ] Can you update SpamAssassin scores?
54 | [ ] Can you change if spam mails are forwarded/deleted?
55 | [ ] Update Catchall:
56 | [ ] Can you update the final destination?
57 | [ ] Update POP/IMAP accounts - name, password, UID, GID, procmail, admin
58 | [ ] Can you update the name?
59 | [ ] Can you update the password?
60 | [ ] Can you update the UID?
61 | [ ] Can you update the GID?
62 | [ ] Can you update the piping value?
63 | [ ] Can you update the admin flag?
64 | [ ] Can you update the SpamAssassin flag?
65 | [ ] Can you update the Anti-Virus flag?
66 | [ ] Can you update the SpamAssassin scores?
67 | [ ] Can you update the Spam-mail handling (move/delete)?
68 | [ ] Can you update the enabled flag?
69 | [ ] Can you add and remove header filters?
70 | [ ] Update :fail:'s
71 | [ ] Can you update the name?
72 | As User:
73 | [ ] Can you update your name?
74 | [ ] Can you change your password? (Try to re-login!)
75 | [ ] Can you enable/disable Anti-Virus?
76 | [ ] Can you enable/disable Spamassassin?
77 | [ ] Can you change SpamAssassin scores?
78 | [ ] Can you change the Spam-mail handling (move/delete)?
79 | [ ] Can you add/remove header filters?
80 |
81 | [ ] Logging in as Postmaster, a second Admin user, and a regular user
82 | [ ] Can you log in if the domain is disabled? Should work as postmaster, should not work as user
83 | [ ] Can you log in if the account is disabled? Should fail
84 |
85 | Account Deletions:
86 | [ ] Does deleting Aliases work?
87 | [ ] Does deleting Catchall work?
88 | [ ] Does deleteing POP/IMAP accounts work?
89 | [ ] Does deleting :fail:'s work?
90 |
91 | Testing Failures:
92 | [ ] Adding duplicate account
93 | [ ] Adding alias using existing account name
94 | [ ] Adding duplicate alias
95 | [ ] Email disabled accounts
96 | [ ] Going over quota
97 | [ ] Quota warnings
98 | [ ] Update mis-matched passwords as admin
99 | [ ] Update mis-matched passwords as user
100 | [ ] Creating POP/IMAP accounts with blank values
101 | [ ] Creating alias accounts with blank values
102 |
--------------------------------------------------------------------------------
/vexim/admingroupdelete.php:
--------------------------------------------------------------------------------
1 | prepare($query);
11 | $sth->execute(array(':group_id'=>$_GET['group_id'], ':domain_id'=>$_SESSION['domain_id']));
12 | if (!$sth->rowCount()) {
13 | header ("Location: admingroup.php?group_faildeleted={$_GET['localpart']}");
14 | die();
15 | }
16 |
17 | # delete group member first
18 | $query = "DELETE FROM group_contents WHERE group_id=:group_id";
19 | $sth = $dbh->prepare($query);
20 | $success = $sth->execute(array(':group_id'=>$_GET['group_id']));
21 | if ($success) {
22 | # delete group
23 | $query = "DELETE FROM groups WHERE id=:group_id AND domain_id=:domain_id";
24 | $sth = $dbh->prepare($query);
25 | $success = $sth->execute(array(':group_id'=>$_GET['group_id'], ':domain_id'=>$_SESSION['domain_id']));
26 | if ($success) {
27 | header ("Location: admingroup.php?group_deleted={$_GET['localpart']}");
28 | die;
29 | } else {
30 | header ("Location: admingroup.php?group_faildeleted={$_GET['localpart']}");
31 | die;
32 | }
33 | } else {
34 | header ("Location: admingroup.php?group_faildeleted={$_GET['localpart']}");
35 | die;
36 | }
37 | } else if ($_GET['confirm'] == 'cancel') {
38 | header ("Location: admingroup.php?group_faildeleted={$_GET['localpart']}");
39 | die;
40 | }
41 | }
42 | ?>
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
56 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/vexim/adminalias.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | rowCount()) {
42 | $row = $sth->fetch();
43 | print ''
44 | . ''
49 | . ' ';
53 | print ''
54 | . ''
57 | . $row['realname']
58 | . ' ';
59 | print '* ';
60 | print '' . $row['smtp'] . ' ';
61 | print '';
62 | print ' ';
63 | }
64 | $query = "SELECT user_id,localpart,smtp,realname,type,admin,enabled
65 | FROM users
66 | WHERE domain_id=:domain_id AND type='alias'
67 | ORDER BY localpart;";
68 | $sth = $dbh->prepare($query);
69 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
70 | if ($sth->rowCount()) {
71 | while ($row = $sth->fetch()) {
72 | if($row['enabled'] === 0) print ''; else print ' ';
73 | print ''
74 | . ' ';
82 | print '';
83 | print ''
86 | . $row['realname']
87 | . ' ';
88 | print '' . $row['localpart'] . ' ';
89 | print '' . $row['smtp'] . ' ';
90 | print '';
91 | if ($row['admin'] == "1") {
92 | print ' ';
95 | }
96 | print ' ';
97 | }
98 | }
99 | ?>
100 |
101 |
:
102 | '
105 | . _('It will catch and forward all email that does not get delivered to a specific mailbox.');
106 | ?>
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/vexim/adminaliasaddsubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
21 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
22 | $row = $sth->fetch();
23 | if ((isset($_POST['on_avscan'])) && ($row['avscan'] == 1)) {
24 | $_POST['on_avscan'] = 1;
25 | } else {
26 | $_POST['on_avscan'] = 0;
27 | }
28 | if ((isset($_POST['on_spamassassin'])) && ($row['spamassassin'] == 1)) {
29 | $_POST['on_spamassassin'] = 1;
30 | } else {
31 | $_POST['on_spamassassin'] = 0;
32 | }
33 | # If a password wasn't specified, create a randomised 128bit password
34 | if (($_POST['clear'] === "") && ($_POST['vclear'] === "")) {
35 | $junk = md5(rand().time().rand());
36 | $_POST['clear'] = $junk;
37 | $_POST['vclear'] = $junk;
38 | }
39 |
40 | # aliases must have a localpart defined
41 | if ($_POST['localpart']==''){
42 | header("Location: adminalias.php?badname={$_POST['localpart']}");
43 | die;
44 | }
45 |
46 | check_mail_address(
47 | $_POST['localpart'],$_SESSION['domain_id'],'adminalias.php'
48 | );
49 |
50 | # check_user_exists() will die if a user account already exists with the same localpart and domain id
51 | check_user_exists(
52 | $dbh,$_POST['localpart'],$_SESSION['domain_id'],'adminalias.php'
53 | );
54 |
55 | if(!isset($_POST['realname']) || $_POST['realname']==='') {
56 | $_POST['realname']=$_POST['localpart'];
57 | }
58 |
59 | if ((preg_match("/['@%!\/\|\" ']/",$_POST['localpart']))
60 | || preg_match("/^\s*$/",$_POST['realname'])) {
61 | header("Location: adminalias.php?badname={$_POST['localpart']}");
62 | die;
63 | }
64 | $forwardto=explode(",",$_POST['smtp']);
65 | for($i=0; $iprepare($query);
86 | $success = $sth->execute(array(
87 | ':localpart' => $_POST['localpart'],
88 | ':username' => $_POST['localpart'] . '@' . $_SESSION['domain'],
89 | ':domain_id' => $_SESSION['domain_id'],
90 | ':crypt' => crypt_password($_POST['clear']),
91 | ':smtp' => $aliasto,
92 | ':pop' => $aliasto,
93 | ':realname' => $_POST['realname'],
94 | ':admin' => $_POST['admin'],
95 | ':on_avscan' => $_POST['on_avscan'],
96 | ':on_spamassassin' => $_POST['on_spamassassin'],
97 | ':sa_tag'=>(isset($_POST['sa_tag']) ? $_POST['sa_tag'] : $sa_tag),
98 | ':sa_refuse'=>(isset($_POST['sa_refuse']) ? $_POST['sa_refuse'] : $sa_refuse),
99 | ':spam_drop'=>(isset($_POST['spam_drop']) ? $_POST['spam_drop'] : 0),
100 | ':enabled' => $_POST['enabled']
101 | ));
102 |
103 |
104 | if ($success) {
105 | header ("Location: adminalias.php?added={$_POST['localpart']}");
106 | } else {
107 | header ("Location: adminalias.php?failadded={$_POST['localpart']}");
108 | }
109 | } else {
110 | header ("Location: adminalias.php?badaliaspass={$_POST['localpart']}");
111 | }
112 | ?>
113 |
114 |
--------------------------------------------------------------------------------
/vexim/adminaliaschangesubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
12 | $sth->execute(array(':user_id'=>$_POST['user_id'], ':domain_id'=>$_SESSION['domain_id']));
13 | if (!$sth->rowCount()) {
14 | header ("Location: adminalias.php?failupdated={$_POST['localpart']}");
15 | die();
16 | }
17 |
18 | # Fix the boolean values
19 | if (isset($_POST['admin'])) {
20 | $_POST['admin'] = 1;
21 | } else {
22 | $_POST['admin'] = 0;
23 | }
24 | if (isset($_POST['enabled'])) {
25 | $_POST['enabled'] = 1;
26 | } else {
27 | $_POST['enabled'] = 0;
28 | }
29 | $query = "SELECT avscan,spamassassin from domains
30 | WHERE domain_id=:domain_id";
31 | $sth = $dbh->prepare($query);
32 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
33 | $row = $sth->fetch();
34 | if ((isset($_POST['on_avscan'])) && ($row['avscan'] == 1)) {
35 | $_POST['on_avscan'] = 1;
36 | } else {
37 | $_POST['on_avscan'] = 0;
38 | }
39 | if ((isset($_POST['on_spamassassin'])) && ($row['spamassassin'] == 1)) {
40 | $_POST['on_spamassassin'] = 1;
41 | } else {
42 | $_POST['on_spamassassin'] = 0;
43 | }
44 |
45 | # Update the password, if the password was given
46 | if(isset($_POST['password']) && $_POST['password']!=='' ){
47 | if (validate_password($_POST['password'], $_POST['vpassword'])) {
48 | if (!password_strengthcheck($_POST['password'])) {
49 | header ("Location: adminalias.php?weakpass={$_POST['localpart']}");
50 | die;
51 | }
52 | $cryptedpassword = crypt_password($_POST['password']);
53 | $query = "UPDATE users SET crypt=:crypt WHERE user_id=:user_id AND domain_id=:domain_id AND type='alias'";
54 | $sth = $dbh->prepare($query);
55 | $success = $sth->execute(array(':crypt'=>$cryptedpassword, ':user_id'=>$_POST['user_id'], ':domain_id'=>$_SESSION['domain_id']));
56 |
57 | if ($success) {
58 | if ($_POST['localpart'] == $_SESSION['localpart']) {
59 | $_SESSION['crypt'] = $cryptedpassword;
60 | }
61 | } else {
62 | header ('Location: adminalias.php?failedupdated=' . $_POST['localpart']);
63 | die();
64 | }
65 | } else {
66 | header ('Location: adminalias.php?badaliaspass');
67 | die();
68 | }
69 | }
70 |
71 | # update the actual alias in the users table
72 | $forwardto=explode(",",$_POST['target']);
73 | for($i=0; $iprepare($query);
89 | $success = $sth->execute(array(
90 | ':localpart'=>$_POST['localpart'],
91 | ':username'=>$_POST['localpart'].'@'.$_SESSION['domain'],
92 | ':smtp'=>$aliasto,
93 | ':pop'=>$aliasto,
94 | ':realname'=>$_POST['realname'],
95 | ':admin'=>$_POST['admin'],
96 | ':on_avscan'=>$_POST['on_avscan'],
97 | ':on_spamassassin'=>$_POST['on_spamassassin'],
98 | ':sa_tag'=>(isset($_POST['sa_tag']) ? $_POST['sa_tag'] : $sa_tag),
99 | ':sa_refuse'=>(isset($_POST['sa_refuse']) ? $_POST['sa_refuse'] : $sa_refuse),
100 | ':spam_drop'=>(isset($_POST['spam_drop']) ? $_POST['spam_drop'] : 0),
101 | ':enabled'=>$_POST['enabled'],
102 | ':user_id'=>$_POST['user_id'],
103 | ':domain_id'=>$_SESSION['domain_id']
104 | ));
105 | if ($success) {
106 | header ("Location: adminalias.php?updated={$_POST['localpart']}");
107 | } else {
108 | header ("Location: adminalias.php?failupdated={$_POST['localpart']}");
109 | }
110 | ?>
111 |
--------------------------------------------------------------------------------
/vexim/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | Layout and CSS tricks obtained from
3 | http://www.bluerobot.com/web/layouts/
4 | */
5 |
6 |
7 | body {
8 | font-size: 10pt;
9 | margin:0px;
10 | padding:0px;
11 | font-family:verdana, arial, helvetica, sans-serif;
12 | color:#333;
13 | background-color:white;
14 | }
15 | h1 {
16 | margin:0px 0px 15px 0px;
17 | padding:0px;
18 | font-size:28px;
19 | line-height:28px;
20 | font-weight:900;
21 | color:#ccc;
22 | }
23 | p {
24 | font:11px/20px verdana, arial, helvetica, sans-serif;
25 | margin:0px 0px 16px 0px;
26 | padding:0px;
27 | }
28 | #Content>p {margin:0px;}
29 | #Content>p+p {text-indent:30px;}
30 |
31 | a {
32 | color:#09c;
33 | font-size:11px;
34 | text-decoration:none;
35 | font-weight:600;
36 | font-family:verdana, arial, helvetica, sans-serif;
37 | }
38 |
39 | p.alpha {
40 | padding-bottom: 2em;
41 | }
42 |
43 | a.alpha {
44 | font-size: 110%;
45 | }
46 |
47 | table,tr,td,th {
48 | border: hidden;
49 | font-size: 10pt;
50 | text-align: left;
51 | }
52 |
53 | table.zebra tr:nth-child(even) {
54 | background-color: #f0f0f0;
55 | }
56 |
57 | td.check {
58 | text-align: center;
59 | }
60 |
61 | td.button {
62 | padding-top: 1em;
63 | text-align: center;
64 | }
65 |
66 | td.trash {
67 | padding-right: 3px;
68 | }
69 |
70 | td.padafter {
71 | padding-bottom:1em;
72 | }
73 |
74 | td.padbefore {
75 | padding-top:1em;
76 | }
77 |
78 | tr.disabled a {
79 | color:#666;
80 | }
81 |
82 | div.row {
83 | clear: both;
84 | padding-top: 10px;
85 | }
86 |
87 | div.button {
88 | clear: both;
89 | padding-top: 10px;
90 | text-align: center;
91 | }
92 |
93 | div.row span.label {
94 | float: left;
95 | width: 125px;
96 | text-align: right;
97 | }
98 |
99 | div.row span.formw {
100 | position: relative;
101 | width: 300px;
102 | text-align: left;
103 | }
104 |
105 | .textfield { border-color: #000000;
106 | border-width: 1px; }
107 |
108 | a:link {color:#09c;}
109 | a:visited {color:#07a;}
110 | a:hover {background-color:#eee;}
111 |
112 | #Header {
113 | font-size: 12pt;
114 | margin:50px 0px 10px 0px;
115 | padding:10px 0px 2px 20px;
116 | /* For IE5/Win's benefit height = [correct height] + [top padding] + [top and bottom border widths] */
117 | height:40px; /* 14px + 17px + 2px = 33px */
118 | border-style:solid;
119 | border-color:black;
120 | border-width:1px 0px; /* top and bottom borders: 1px; left and right borders: 0px */
121 | line-height:11px;
122 | background-color:#eee;
123 | voice-family: "\"}\"";
124 | voice-family:inherit;
125 | height:28px; /* the correct height */
126 | }
127 | body>#Header {height:28px;}
128 |
129 | #Header>a:link, #Header>a:visited {
130 | font-family:verdana, arial, helvetica, sans-serif;
131 | font-size: 12pt;
132 | color: #000;
133 | }
134 |
135 | #Header>a:hover {
136 | font-family:verdana, arial, helvetica, sans-serif;
137 | font-size: 12pt;
138 | color: #09c;
139 | }
140 |
141 | #SiteadminHome {
142 | float: right;
143 | margin-right: 20px;
144 | }
145 |
146 | #Content {
147 | margin: 0px 50px 50px 250px;
148 | padding:10px;
149 | overflow: auto;
150 | }
151 |
152 | #forms {
153 | margin: 15px 200px 50px 250px;
154 | width: 450px;
155 | border: 1px dashed #999;
156 | padding: 10px;
157 | overflow: visible;
158 | }
159 |
160 | form.login {
161 | margin-top:3em;
162 | }
163 |
164 | #Centered {
165 | position:absolute;
166 | left:50%;
167 | width:500px;
168 | margin-top:50px;
169 | margin-left:-266px;
170 | padding:15px;
171 | border:1px dashed #333;
172 | background-color:#eee;
173 | }
174 |
175 | #Menu {
176 | position:absolute;
177 | top:100px;
178 | left:20px;
179 | width:172px;
180 | padding:10px;
181 | background-color:#eee;
182 | border:1px dashed #999;
183 | line-height:17px;
184 | /* Again, the ugly brilliant hack. */
185 | voice-family: "\"}\"";
186 | voice-family:inherit;
187 | width:150px;
188 | }
189 | /* Again, "be nice to Opera 5". */
190 | body>#Menu {width:150px;}
191 |
192 | #Status {
193 | position: absolute;
194 | bottom: 2em;
195 | font-weight: bold;
196 | left: 10%;
197 | text-align: center;
198 | border: 1px dashed #999;
199 | padding-top: .5em;
200 | padding-bottom: .5em;
201 | padding-left: 2em;
202 | padding-right: 2em;
203 | /* Again, the ugly brilliant hack. */
204 | voice-family: "\"}\"";
205 | voice-family:inherit;
206 | width:500px;
207 | }
208 | /* Again, "be nice to Opera 5". */
209 | body>#Status {width:500px;}
210 |
211 | img.trash { border:0;
212 | width:10px;
213 | height:16px
214 | }
215 | img.check { border:0;
216 | width:13px;
217 | height:12px
218 | }
219 |
--------------------------------------------------------------------------------
/vexim/userchangesubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
28 | $sth->execute(array(':user_id'=>$_SESSION['user_id'], ':domain_id'=>$_SESSION['domain_id']));
29 | $account = $sth->fetch();
30 | $_POST['forward']=$account['forward'];
31 | }
32 |
33 | # Do some checking, to make sure the user is ALLOWED to make these changes
34 | $query = "SELECT avscan,spamassassin,maxmsgsize from domains WHERE domain_id=:domain_id";
35 | $sth = $dbh->prepare($query);
36 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
37 | $row = $sth->fetch();
38 | $_POST['on_avscan'] = (isset($_POST['on_avscan']) && $row['avscan'] == '1') ? 1 : 0;
39 | $_POST['on_spamassassin'] = (isset($_POST['on_spamassassin']) && $row['spamassassin'] == '1') ? 1 : 0;
40 | if (isset($_POST['maxmsgsize']) && $row['maxmsgsize']!=='0') {
41 | if ($_POST['maxmsgsize']<=0 || $_POST['maxmsgsize']>$row['maxmsgsize']) {
42 | $_POST['maxmsgsize']=$row['maxmsgsize'];
43 | }
44 | } else {
45 | $_POST['maxmsgsize']=0;
46 | }
47 |
48 | if (isset($_POST['realname']) && $_POST['realname']!=="") {
49 | $query = "UPDATE users SET realname=:realname
50 | WHERE user_id=:user_id";
51 | $sth = $dbh->prepare($query);
52 | $sth->execute(array(':realname'=>$_POST['realname'], ':user_id'=>$_SESSION['user_id']));
53 | }
54 |
55 | # Update the password, if the password was given
56 | if (isset($_POST['clear']) && $_POST['clear']!=='') {
57 | if (validate_password($_POST['clear'], $_POST['vclear'])) {
58 | if (!password_strengthcheck($_POST['clear'])) {
59 | header ("Location: userchange.php?weakpass");
60 | die;
61 | }
62 | $cryptedpassword = crypt_password($_POST['clear']);
63 | $query = "UPDATE users SET crypt=:crypt WHERE user_id=:user_id";
64 | $sth = $dbh->prepare($query);
65 | $success = $sth->execute(array(':crypt'=>$cryptedpassword, ':user_id'=>$_SESSION['user_id']));
66 | if ($success) {
67 | $_SESSION['crypt'] = $cryptedpassword;
68 | header ("Location: userchange.php?userupdated");
69 | die;
70 | }
71 | }
72 | header ("Location: userchange.php?badpass");
73 | die;
74 | }
75 |
76 | #If the realname was changed in the upper form, don't run the rest of the script
77 | if (isset($_POST['realname'])) {
78 | header ("Location: userchange.php?userupdated");
79 | die;
80 | }
81 |
82 | if (isset($_POST['vacation']) && is_string($_POST['vacation'])) {
83 | $vacation = trim($_POST['vacation']);
84 | $vacation = quoted_printable_encode($vacation);
85 | } else {
86 | $vacation = '';
87 | }
88 |
89 | # Finally 'the rest' which is handled by the profile form
90 | $query = "UPDATE users SET on_avscan=:on_avscan,
91 | on_spamassassin=:on_spamassassin, sa_tag=:sa_tag,
92 | sa_refuse=:sa_refuse, on_vacation=:on_vacation,
93 | vacation=:vacation, on_forward=:on_forward,
94 | forward=:forward, maxmsgsize=:maxmsgsize,
95 | unseen=:unseen, spam_drop=:spam_drop
96 | WHERE user_id=:user_id";
97 | $sth = $dbh->prepare($query);
98 | $success = $sth->execute(array(':on_avscan'=>$_POST['on_avscan'],
99 | ':on_spamassassin'=>$_POST['on_spamassassin'],
100 | ':sa_tag'=>(isset($_POST['sa_tag']) ? $_POST['sa_tag'] : $sa_tag),
101 | ':sa_refuse'=>(isset($_POST['sa_refuse']) ? $_POST['sa_refuse'] : $sa_refuse),
102 | ':on_vacation'=>$_POST['on_vacation'],
103 | ':vacation'=>$vacation,
104 | ':on_forward'=>$_POST['on_forward'], ':forward'=>$_POST['forward'],
105 | ':maxmsgsize'=>$_POST['maxmsgsize'], ':unseen'=>$_POST['unseen'],
106 | ':spam_drop'=>(isset($_POST['spam_drop']) ? $_POST['spam_drop'] : 0),
107 | ':user_id'=>$_SESSION['user_id']
108 | ));
109 | if ($success) {
110 | header ("Location: userchange.php?userupdated");
111 | die;
112 | }
113 | header ("Location: userchange.php?userfailed");
114 | die;
115 |
116 |
--------------------------------------------------------------------------------
/vexim/adminaliasadd.php:
--------------------------------------------------------------------------------
1 | prepare($query);
9 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
10 | if ($sth->rowCount()) { $row = $sth->fetch(); }
11 | ?>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/vexim/adminuserdelete.php:
--------------------------------------------------------------------------------
1 | prepare($query);
12 | $sth->execute(array(':user_id'=>$_GET['user_id'], ':domain_id'=>$_SESSION['domain_id']));
13 | if (!$sth->rowCount()) {
14 | header ("Location: adminuser.php?faildeleted={$_GET['localpart']}");
15 | die();
16 | }
17 | if(!isset($_GET['confirm'])) { $_GET['confirm'] = null; }
18 |
19 | if ($_GET['confirm'] == '1') {
20 | # prevent deleting the last admin
21 | $query = "SELECT COUNT(user_id) AS count FROM users
22 | WHERE admin=1 AND domain_id=:domain_id
23 | AND (type='local' OR type='piped')
24 | AND user_id!=:user_id";
25 | $sth = $dbh->prepare($query);
26 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id'], ':user_id'=>$_GET['user_id']));
27 | $row = $sth->fetch();
28 | if ($row['count'] == "0") {
29 | header ("Location: adminuser.php?lastadmin={$_GET['localpart']}");
30 | die;
31 | }
32 |
33 | $query = "DELETE FROM users
34 | WHERE user_id=:user_id
35 | AND domain_id=:domain_id
36 | AND (type='local' OR type='piped')";
37 | $sth = $dbh->prepare($query);
38 | $success = $sth->execute(array(':user_id'=>$_GET['user_id'], ':domain_id'=>$_SESSION['domain_id']));
39 | if ($success) {
40 | $query = "DELETE FROM group_contents WHERE member_id=:user_id";
41 | $sth = $dbh->prepare($query);
42 | $sth->execute(array(':user_id'=>$_GET['user_id']));
43 | header ("Location: adminuser.php?deleted={$_GET['localpart']}");
44 | } else {
45 | header ("Location: adminuser.php?faildeleted={$_GET['localpart']}");
46 | }
47 | die;
48 | } else if ($_GET['confirm'] == "cancel") {
49 | header ("Location: adminuser.php?faildeleted={$_GET['localpart']}");
50 | die;
51 | } else {
52 | $query = "SELECT COUNT(user_id) AS count FROM users
53 | WHERE admin=1 AND domain_id=:domain_id
54 | AND (type='local' OR type='piped')
55 | AND user_id!=:user_id";
56 | $sth = $dbh->prepare($query);
57 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id'], ':user_id'=>$_GET['user_id']));
58 | $row = $sth->fetch();
59 | if ($row['count'] == "0") {
60 | header ("Location: adminuser.php?lastadmin={$_GET['localpart']}");
61 | die;
62 | }
63 | $query = "SELECT localpart FROM users WHERE user_id=:user_id";
64 | $sth = $dbh->prepare($query);
65 | $sth->execute(array(':user_id'=>$_GET['user_id']));
66 | if ($sth->rowCount()) { $row = $sth->fetch(); }
67 | }
68 | ?>
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
82 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/vexim/sitechangesubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
29 | $success = $sth->execute(array(
30 | ':domain_id' => $_POST['allusers'],
31 | ':on_spamassassin' => $_POST['on_spamassassin']));
32 | }
33 |
34 | // Process Anti-Virus
35 | if (isset($_POST['on_avscan']) && $success && ($_POST['on_avscan'] == '1' || $_POST['on_avscan'] == '0')) {
36 | $query = "UPDATE users SET on_avscan=:on_avscan WHERE domain_id=:domain_id";
37 | $sth = $dbh->prepare($query);
38 | $success = $sth->execute(array(
39 | ':domain_id' => $_POST['allusers'],
40 | ':on_avscan' => $_POST['on_avscan']));
41 | }
42 | break;
43 |
44 | case 'adminpass':
45 | if (validate_password($_POST['clear'], $_POST['vclear'])) {
46 | if (!password_strengthcheck($_POST['clear'])) {
47 | header ("Location: site.php?weakpass={$_POST['domain']}");
48 | die;
49 | }
50 | $query = "UPDATE users SET crypt=:crypt WHERE localpart=:localpart AND domain_id=:domain_id";
51 | $sth = $dbh->prepare($query);
52 | $success = $sth->execute(array(
53 | ':crypt' => crypt_password($_POST['clear']),
54 | ':localpart' => $_POST['localpart'],
55 | ':domain_id' => $_POST['domain_id']));
56 | } else {
57 | header("Location: site.php?badpass={$_POST['domain']}");
58 | die;
59 | }
60 | break;
61 |
62 | default:
63 | if (isset($_POST['avscan'])) {
64 | $_POST['avscan'] = 1;
65 | } else {
66 | $_POST['avscan'] = 0;
67 | }
68 |
69 | if (isset($_POST['spamassassin'])) {
70 | $_POST['spamassassin'] = 1;
71 | } else {
72 | $_POST['spamassassin'] = 0;
73 | }
74 |
75 | if (isset($_POST['enabled'])) {
76 | $_POST['enabled'] = 1;
77 | } else {
78 | $_POST['enabled'] = 0;
79 | }
80 |
81 | if (isset($_POST['pipe'])) {
82 | $_POST['pipe'] = 1;
83 | } else {
84 | $_POST['pipe'] = 0;
85 | }
86 |
87 | if (!isset($_POST['max_accounts']) || $_POST['max_accounts'] == '') {
88 | $_POST['max_accounts'] = '0';
89 | }
90 |
91 | // User can specify either UID, or username, the former being preferred.
92 | // Using posix_getpwuid/posix_getgrgid even when we have an UID is so we
93 | // are sure the UID exists.
94 | if (isset ($_POST['uid'])) {
95 | $uid = $_POST['uid'];
96 | }
97 | if (isset ($_POST['gid'])) {
98 | $gid = $_POST['gid'];
99 | }
100 |
101 | if ($userinfo = @posix_getpwuid($uid)) {
102 | $uid = $userinfo['uid'];
103 | } elseif ($userinfo = @posix_getpwnam($uid)) {
104 | $uid = $userinfo['uid'];
105 | } else {
106 | header("Location: site.php?failuidguid={$_POST['domain']}");
107 | die;
108 | }
109 |
110 | if ($groupinfo = @posix_getgrgid($gid)) {
111 | $gid = $groupinfo['gid'];
112 | } elseif ($groupinfo = @posix_getgrnam($gid)) {
113 | $gid = $groupinfo['gid'];
114 | } else {
115 | header("Location: site.php?failuidguid={$_POST['domain']}");
116 | die;
117 | }
118 | $query = "UPDATE domains SET uid=:uid, gid=:gid, avscan=:avscan,
119 | maxmsgsize=:maxmsgsize, pipe=:pipe, max_accounts=:max_accounts,
120 | quotas=:quotas, sa_tag=:sa_tag, sa_refuse=:sa_refuse,
121 | spamassassin=:spamassassin, enabled=:enabled
122 | WHERE domain_id=:domain_id";
123 | $sth = $dbh->prepare($query);
124 | $success = $sth->execute(array(':uid' => $uid, ':gid' => $gid,
125 | ':avscan' => $_POST['avscan'], ':maxmsgsize' => $_POST['maxmsgsize'],
126 | ':pipe' => $_POST['pipe'], ':max_accounts' => $_POST['max_accounts'],
127 | ':quotas' => $_POST['quotas'],
128 | ':sa_tag' => ((isset($_POST['sa_tag'])) ? $_POST['sa_tag'] : $sa_tag),
129 | ':sa_refuse' => ((isset($_POST['sa_refuse'])) ? $_POST['sa_refuse'] : $sa_refuse),
130 | ':spamassassin' => $_POST['spamassassin'], ':enabled' => $_POST['enabled'],
131 | ':domain_id' => $_POST['domain_id'],
132 | ));
133 | }
134 |
135 | if ($success) {
136 | header("Location: site.php?updated={$_POST['domain']}");
137 | die;
138 | } else {
139 | // Just-in-case catchall
140 | header("Location: site.php?failupdated={$_POST['domain']}");
141 | }
142 |
--------------------------------------------------------------------------------
/vexim/sitedelete.php:
--------------------------------------------------------------------------------
1 | prepare($usrdelquery);
21 | $usrdelsuccess = $usrdelsth->execute(array(':domain_id'=>$_POST['domain_id']));
22 | // if we were successful, delete the domain's blocklists
23 | if ($usrdelsuccess) {
24 | $usrdelquery = "DELETE FROM blocklists WHERE domain_id=:domain_id";
25 | $usrdelsth = $dbh->prepare($usrdelquery);
26 | $usrdelsuccess = $usrdelsth->execute(array(':domain_id'=>$_POST['domain_id']));
27 | // if we were successful, delete the domain's aliases
28 | if($usrdelsuccess) {
29 | $aliasdelquery = "DELETE FROM domainalias WHERE domain_id=:domain_id";
30 | $aliasdelsth = $dbh->prepare($aliasdelquery);
31 | $aliasdelsuccess = $aliasdelsth->execute(array(':domain_id'=>$_POST['domain_id']));
32 | // if we were successful, delete the domain itself
33 | if ($aliasdelsuccess) {
34 | $domdelquery = "DELETE FROM domains WHERE domain_id=:domain_id";
35 | $domdelsth = $dbh->prepare($domdelquery);
36 | $domdelsuccess = $domdelsth->execute(array(':domain_id'=>$_POST['domain_id']));
37 | // If everything went well, redirect to a success page.
38 | if ($domdelsuccess) {
39 | header ("Location: site.php?deleted={$_POST['domain']}");
40 | die;
41 | }
42 | }
43 | }
44 | } else {
45 | header ("Location: site.php?faildeleted={$_POST['domain']}");
46 | die;
47 | }
48 | } else if (($_POST['confirm'] == "1") && ($_POST['type'] == "alias")) {
49 | $aliasdeletequery = "DELETE FROM domainalias WHERE alias=:domain";
50 | $sth = $dbh->prepare($aliasdeletequery);
51 | $success = $sth->execute(array(':domain'=>$_POST['domain']));
52 | if ($success) {
53 | header ("Location: site.php?deleted={$_POST['domain']}");
54 | die;
55 | } else {
56 | header ("Location: site.php?faildeleted={$_POST['domain']}");
57 | die;
58 | }
59 | } else if ($_POST['confirm'] == "cancel") {
60 | header ("Location: site.php?canceldelete={$_POST['domain']}");
61 | die;
62 | }
63 |
64 | if ($_GET['type'] != "alias") {
65 | $query = "SELECT COUNT(*) AS count, domain, domains.type FROM users,domains
66 | WHERE (domains.domain_id=:domain_id
67 | AND users.domain_id=domains.domain_id)
68 | GROUP BY domain,domains.type";
69 | $sth = $dbh->prepare($query);
70 | $sth->execute(array(':domain_id'=>$_GET['domain_id']));
71 | $row = $sth->fetch();
72 | }
73 | ?>
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
87 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/vexim/adminuser.php:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
49 |
50 |
53 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | $_SESSION['domain_id']);
81 | if ($alphausers AND $letter != '') {
82 | $query .= " AND lower(localpart) LIKE lower(:letter)";
83 | $queryParams[':letter'] = $letter.'%';
84 | } elseif ($_POST['searchfor'] != '') {
85 | $query .= ' AND ' . $dbh->quote($_POST['field']) . ' LIKE :searchfor';
86 | $queryParams[':searchfor'] = '%'.$_POST['searchfor'].'%';
87 | }
88 | $query .= ' ORDER BY realname, localpart';
89 | $sth = $dbh->prepare($query);
90 | $sth->execute($queryParams);
91 | while ($row = $sth->fetch()) {
92 | if($row['enabled'] === 0) print ''; else print ' ';
93 | print '';
98 | print ' ';
101 | print ''
107 | . $row['realname']
108 | . ' ';
109 | print ''
115 | . $row['localpart'] .'@'. htmlspecialchars($_SESSION['domain'])
116 | . ' ';
117 | print '';
118 | if ($row['admin'] == 1) {
119 | print ' ';
123 | }
124 | print " \n";
125 | }
126 | ?>
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/setup/migrations/vexim_1.5_to_2.0.1_mysql.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- MySQL script to upgrade Vexim database schema from Vexim 1.5 to Vexim 2.0.1
3 | --
4 |
5 | -- Create the `domains` table
6 | CREATE TABLE `domains` (
7 | `domain_id` mediumint(8) UNSIGNED AUTO_INCREMENT NOT NULL,
8 | `domain` varchar(64) NOT NULL DEFAULT '',
9 | `maildir` varchar(128) NOT NULL DEFAULT '',
10 | `uid` smallint(5) UNSIGNED NOT NULL DEFAULT '65534',
11 | `gid` smallint(5) UNSIGNED NOT NULL DEFAULT '65534',
12 | `max_accounts` int(10) UNSIGNED NOT NULL DEFAULT '0',
13 | `quotas` int(10) UNSIGNED NOT NULL DEFAULT '0',
14 | `type` varchar(5) DEFAULT NULL,
15 | `avscan` bool NOT NULL DEFAULT '0',
16 | `blocklists` bool NOT NULL DEFAULT '0',
17 | `complexpass` bool NOT NULL DEFAULT '0',
18 | `enabled` bool NOT NULL DEFAULT '1',
19 | `mailinglists` bool NOT NULL DEFAULT '0',
20 | `maxmsgsize` mediumint(8) UNSIGNED NOT NULL DEFAULT '0',
21 | `pipe` bool NOT NULL DEFAULT '0',
22 | `spamassassin` bool NOT NULL DEFAULT '0',
23 | `sa_tag` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
24 | `sa_refuse` smallint(5) UNSIGNED NOT NULL DEFAULT '0',
25 | PRIMARY KEY (`domain_id`),
26 | UNIQUE KEY `domain` (`domain`),
27 | KEY `domain_id` (`domain_id`),
28 | KEY `domains` (`domain`)
29 |
30 | );
31 | -- Insert the siteadmin domain
32 | INSERT INTO `domains` (`domain_id`, `domain`) VALUES ('1', 'admin');
33 | -- Insert other domains
34 | INSERT INTO `domains` (`domain`, `type`) SELECT DISTINCT `domain`, 'local' FROM `users` WHERE `domain`!='admin';
35 | -- Set domain maildirs
36 | UPDATE `domains` SET `maildir`=(
37 | SELECT SUBSTRING_INDEX(`pophome`, `local_part`, 1) FROM `users`
38 | WHERE `users`.`domain`=`domains`.`domain`
39 | AND SUBSTRING(`users`.`pophome`, 1, 1)='/'
40 | LIMIT 1
41 | ) WHERE `domain_id` != '1';
42 |
43 | -- Update the structure and contents of the `users` table
44 | ALTER TABLE `users` DROP INDEX `username`;
45 | ALTER TABLE `users` ADD COLUMN `user_id` int(10) UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY FIRST;
46 | ALTER TABLE `users` ADD COLUMN `domain_id` mediumint(8) UNSIGNED NOT NULL AFTER `user_id`;
47 | UPDATE `users` SET `domain_id`=(SELECT `domain_id` FROM `domains` WHERE `domains`.`domain`=`users`.`domain`);
48 | ALTER TABLE `users` CHANGE COLUMN `local_part` `localpart` varchar(192) NOT NULL DEFAULT '' AFTER `domain_id`;
49 | ALTER TABLE `users` MODIFY COLUMN `username` varchar(255) NOT NULL DEFAULT '' AFTER `localpart`;
50 | ALTER TABLE `users` CHANGE COLUMN `cpassword` `clear` varchar(255) DEFAULT NULL AFTER `username`;
51 | ALTER TABLE `users` CHANGE COLUMN `password` `crypt` varchar(48) DEFAULT NULL AFTER `clear`;
52 | -- Set default uid and gid values where these are not numeric
53 | UPDATE `users` SET `uid`='65535' WHERE `type`='admin' AND `admin`='site' AND `uid` NOT REGEXP '^[0-9]+$';
54 | UPDATE `users` SET `uid`='65534' WHERE `uid` NOT REGEXP '^[0-9]+$';
55 | ALTER TABLE `users` MODIFY COLUMN `uid` smallint(5) UNSIGNED NOT NULL DEFAULT '65534' AFTER `crypt`;
56 | UPDATE `users` SET `gid`='65535' WHERE `type`='admin' AND `admin`='site' AND `gid` NOT REGEXP '^[0-9]+$';
57 | UPDATE `users` SET `gid`='65534' WHERE `gid` NOT REGEXP '^[0-9]+$';
58 | ALTER TABLE `users` MODIFY COLUMN `gid` smallint(5) UNSIGNED NOT NULL DEFAULT '65534' AFTER `uid`;
59 | ALTER TABLE `users` CHANGE COLUMN `smtphome` `smtp` varchar(255) DEFAULT NULL AFTER `gid`;
60 | ALTER TABLE `users` CHANGE COLUMN `pophome` `pop` varchar(255) DEFAULT NULL AFTER `smtp`;
61 | -- Reset invalid type values to 'local'
62 | UPDATE `users` SET `type`='local' WHERE `type` NOT IN ('local','alias','catch','fail','piped','admin','site');
63 | ALTER TABLE `users` MODIFY COLUMN `type` enum('local','alias','catch','fail','piped','admin','site') NOT NULL DEFAULT 'local' AFTER `pop`;
64 | UPDATE `users` SET `type`='site',`localpart`=`username` WHERE `type`='admin' AND `admin`='site';
65 | UPDATE `users` SET `admin`='1' WHERE `admin` IS NOT NULL AND `admin`!='';
66 | UPDATE `users` SET `admin`='0' WHERE `admin`!='1' OR `admin` IS NULL;
67 | ALTER TABLE `users` MODIFY COLUMN `admin` bool NOT NULL DEFAULT '0' AFTER `type`;
68 | ALTER TABLE `users` ADD COLUMN `on_avscan` bool NOT NULL DEFAULT '0' AFTER `admin`;
69 | ALTER TABLE `users` ADD COLUMN `on_blocklist` bool NOT NULL DEFAULT '0' AFTER `on_avscan`;
70 | ALTER TABLE `users` ADD COLUMN `on_complexpass` bool NOT NULL DEFAULT '0' AFTER `on_blocklist`;
71 | ALTER TABLE `users` ADD COLUMN `on_forward` bool NOT NULL DEFAULT '0' AFTER `on_complexpass`;
72 | ALTER TABLE `users` ADD COLUMN `on_piped` bool NOT NULL DEFAULT '0' AFTER `on_forward`;
73 | UPDATE `users` SET `on_piped`='1' WHERE `smtp` LIKE '"|%';
74 | ALTER TABLE `users` ADD COLUMN `on_spamassassin` bool NOT NULL DEFAULT '0' AFTER `on_piped`;
75 | ALTER TABLE `users` ADD COLUMN `on_vacation` bool NOT NULL DEFAULT '0' AFTER `on_spamassassin`;
76 | ALTER TABLE `users` CHANGE COLUMN `status` `enabled` bool NOT NULL DEFAULT '1' AFTER `on_vacation`;
77 | ALTER TABLE `users` ADD COLUMN `flags` varchar(16) DEFAULT NULL AFTER `enabled`;
78 | ALTER TABLE `users` ADD COLUMN `forward` varchar(255) DEFAULT NULL AFTER `flags`;
79 | ALTER TABLE `users` ADD COLUMN `maxmsgsize` mediumint(8) UNSIGNED NOT NULL DEFAULT '0' AFTER `forward`;
80 | ALTER TABLE `users` ADD COLUMN `quota` int(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `maxmsgsize`;
81 | ALTER TABLE `users` MODIFY COLUMN `realname` varchar(255) DEFAULT NULL AFTER `quota`;
82 | ALTER TABLE `users` ADD COLUMN `sa_tag` smallint(5) UNSIGNED NOT NULL DEFAULT '0' AFTER `realname`;
83 | ALTER TABLE `users` ADD COLUMN `sa_refuse` smallint(5) UNSIGNED NOT NULL DEFAULT '0' AFTER `sa_tag`;
84 | ALTER TABLE `users` ADD COLUMN `tagline` varchar(255) DEFAULT NULL AFTER `sa_refuse`;
85 | ALTER TABLE `users` ADD COLUMN `vacation` varchar(255) DEFAULT NULL AFTER `tagline`;
86 | ALTER TABLE `users` DROP COLUMN `domain`;
87 | ALTER TABLE `users` ADD UNIQUE INDEX `username` (`localpart`, `domain_id`);
88 | ALTER TABLE `users` ADD INDEX `local` (`localpart`);
89 |
90 | -- Create the `blocklists` table
91 | CREATE TABLE `blocklists` (
92 | `block_id` int(10) UNSIGNED AUTO_INCREMENT NOT NULL,
93 | `domain_id` mediumint(8) UNSIGNED NOT NULL,
94 | `user_id` int(10) UNSIGNED DEFAULT NULL,
95 | `blockhdr` varchar(192) NOT NULL DEFAULT '',
96 | `blockval` varchar(192) NOT NULL DEFAULT '',
97 | `color` varchar(8) NOT NULL DEFAULT '',
98 | PRIMARY KEY (`block_id`)
99 | );
100 |
101 | -- Create the `domainalias` table
102 | CREATE TABLE `domainalias` (
103 | `domain_id` mediumint(8) UNSIGNED NOT NULL,
104 | `alias` varchar(64) DEFAULT NULL
105 | );
106 |
--------------------------------------------------------------------------------
/vexim/siteaddsubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
80 | $sth->execute(array(':domain_id'=>$_POST['aliasdest'], ':alias'=>$_POST['domain']));
81 | if ($sth->rowCount()!==1) {
82 | header ("Location: site.php?failaddeddomerr={$_POST['domain']}");
83 | die;
84 | } else {
85 | header ("Location: site.php?added={$_POST['domain']}" .
86 | "&type={$_POST['type']}");
87 | die;
88 | }
89 | } else { // local or relay
90 | if ($_POST['type'] === "local") {
91 | if (!validate_password($_POST['clear'], $_POST['vclear'])) {
92 | header ("Location: site.php?failaddedpassmismatch={$_POST['domain']}");
93 | die;
94 | }
95 | if (!password_strengthcheck($_POST['clear'])) {
96 | header ("Location: site.php?weakpass={$_POST['domain']}");
97 | die;
98 | }
99 | }
100 | $query = "INSERT INTO domains
101 | (domain, spamassassin, sa_tag, sa_refuse, avscan,
102 | max_accounts, quotas, maildir, pipe, enabled, uid, gid,
103 | type, maxmsgsize)
104 | VALUES (:domain, :spamassassin, :sa_tag, :sa_refuse,
105 | :avscan, :max_accounts, :quotas, :maildir, :pipe, :enabled,
106 | :uid, :gid, :type, :maxmsgsize)";
107 | $sth = $dbh->prepare($query);
108 | $success = $sth->execute(array(':domain'=>$_POST['domain'],
109 | ':spamassassin'=>$_POST['spamassassin'],
110 | ':sa_tag'=>((isset($_POST['sa_tag'])) ? $_POST['sa_tag'] : $sa_tag),
111 | ':sa_refuse'=>((isset($_POST['sa_refuse'])) ? $_POST['sa_refuse'] : $sa_refuse),
112 | ':avscan'=>$_POST['avscan'], ':max_accounts'=>$_POST['max_accounts'],
113 | ':quotas'=>((isset($_POST['quotas'])) ? $_POST['quotas'] : 0),
114 | ':maildir'=>((isset($_POST['maildir'])) ? $domainpath : ''),
115 | ':pipe'=>$_POST['pipe'], ':enabled'=>$_POST['enabled'],
116 | ':uid'=>$uid, ':gid'=>$gid, ':type'=>$_POST['type'],
117 | ':maxmsgsize'=>((isset($_POST['maxmsgsize'])) ? $_POST['maxmsgsize'] : 0)
118 | ));
119 | if ($success) {
120 | if ($_POST['type'] == "local") {
121 | $query = "INSERT INTO users
122 | (domain_id, localpart, username, crypt, uid, gid, smtp, pop, realname, type, admin)
123 | SELECT domain_id, :localpart, :username, :crypt, :uid, :gid, :smtp, :pop, 'Domain Admin', 'local', 1
124 | FROM domains
125 | WHERE domains.domain=:domain";
126 | $sth = $dbh->prepare($query);
127 | $success = $sth->execute(array(':localpart'=>$_POST['localpart'],
128 | ':username'=>$_POST['localpart'].'@'.$_POST['domain'],
129 | ':crypt'=>crypt_password($_POST['clear']),
130 | ':uid'=>$uid, ':gid'=>$gid, ':smtp'=>$smtphomepath,
131 | ':pop'=>$pophomepath,
132 | ':domain'=>$_POST['domain'],
133 | ));
134 | if (!$success) {
135 | header ("Location: site.php?failaddedusrerr={$_POST['domain']}");
136 | die;
137 | } else {
138 | header ("Location: site.php?added={$_POST['domain']}" .
139 | "&type={$_POST['type']}");
140 | mail("{$_POST['localpart']}@{$_POST['domain']}",
141 | vexim_encode_header(_("Welcome Domain Admin!")),
142 | "$welcome_newdomain",
143 | "From: {$_POST['localpart']}@{$_POST['domain']}\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 8bit\r\n");
144 | die;
145 | }
146 | } else {
147 | header ("Location: site.php?added={$_POST['domain']}" .
148 | "&type={$_POST['type']}");
149 | die;
150 | }
151 | }
152 | header ("Location: site.php?failaddeddomerr={$_POST['domain']}");
153 | }
154 | ?>
155 |
156 |
--------------------------------------------------------------------------------
/vexim/adminuseraddsubmit.php:
--------------------------------------------------------------------------------
1 | prepare($query);
15 | $success = $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
16 | if ($success) {
17 | $domrow = $sth->fetch();
18 | if (!$domrow['allowed']) {
19 | header ("Location: adminuser.php?maxaccounts=true");
20 | die();
21 | }
22 | }
23 |
24 | # Strip off leading and trailing spaces
25 | $_POST['localpart'] = preg_replace("/^\s+/","",$_POST['localpart']);
26 | $_POST['localpart'] = preg_replace("/\s+$/","",$_POST['localpart']);
27 |
28 | # get the settings for the domain
29 | $query = "SELECT avscan,spamassassin,pipe,uid,gid,quotas,maxmsgsize FROM domains
30 | WHERE domain_id=:domain_id";
31 | $sth = $dbh->prepare($query);
32 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
33 | if ($sth->rowCount()) {
34 | $row = $sth->fetch();
35 | }
36 |
37 | # Fix the boolean values
38 | if (isset($_POST['admin'])) {
39 | $_POST['admin'] = 1;
40 | } else {
41 | $_POST['admin'] = 0;
42 | }
43 | if (isset($_POST['enabled'])) {
44 | $_POST['enabled'] = 1;
45 | } else {
46 | $_POST['enabled'] = 0;
47 | }
48 | if ($postmasteruidgid == "yes"){
49 | if(!isset($_POST['uid'])) {
50 | $_POST['uid'] = $row['uid'];
51 | }
52 | if(!isset($_POST['gid'])) {
53 | $_POST['gid'] = $row['gid'];
54 | }
55 | }else{
56 | # customisation of the uid and gid is not permitted for postmasters, use the domain defaults
57 | $_POST['uid'] = $row['uid'];
58 | $_POST['gid'] = $row['gid'];
59 | }
60 | if(!isset($_POST['quota'])) {
61 | $_POST['quota'] = $row['quotas'];
62 | }
63 | if($row['quotas'] != "0") {
64 | if (($_POST['quota'] > $row['quotas']) || ($_POST['quota'] === "0")) {
65 | header ("Location: adminuser.php?quotahigh={$row['quotas']}");
66 | die;
67 | }
68 | }
69 | if($row['maxmsgsize'] !== "0") {
70 | if (($_POST['maxmsgsize'] > $row['maxmsgsize']) || ($_POST['maxmsgsize'] === "0")) {
71 | $_POST['maxmsgsize']=$row['maxmsgsize'];
72 | }
73 | }
74 |
75 | # Do some checking, to make sure the user is ALLOWED to make these changes
76 | if ((isset($_POST['on_piped'])) && ($row['pipe'] == 1)) {
77 | $_POST['on_piped'] = 1;
78 | } else {
79 | $_POST['on_piped'] = 0;
80 | }
81 | if ((isset($_POST['on_avscan'])) && ($row['avscan'] == 1)) {
82 | $_POST['on_avscan'] = 1;
83 | } else {
84 | $_POST['on_avscan'] = 0;
85 | }
86 | if ((isset($_POST['on_spamassassin'])) && ($row['spamassassin'] == 1)) {
87 | $_POST['on_spamassassin'] = 1;
88 | } else {
89 | $_POST['on_spamassassin'] = 0;
90 | }
91 |
92 | check_mail_address(
93 | $_POST['localpart'],$_SESSION['domain_id'],'adminuser.php'
94 | );
95 |
96 | check_user_exists(
97 | $dbh,$_POST['localpart'],$_SESSION['domain_id'],'adminuser.php'
98 | );
99 |
100 | if (preg_match("/^\s*$/",$_POST['realname'])) {
101 | $_POST['realname']=$_POST['localpart'];
102 | }
103 |
104 | if (preg_match("/['@%!\/\| ']/",$_POST['localpart'])
105 | || preg_match("/^\s*$/",$_POST['localpart'])) {
106 | header("Location: adminuser.php?badname={$_POST['localpart']}");
107 | die;
108 | }
109 |
110 | $query = "SELECT maildir FROM domains WHERE domain_id=:domain_id";
111 | $sth = $dbh->prepare($query);
112 | $sth->execute(array(':domain_id'=>$_SESSION['domain_id']));
113 | if ($sth->rowCount()) { $row = $sth->fetch(); }
114 | if (($_POST['on_piped'] == 1) && ($_POST['smtp'] != '')) {
115 | $smtphomepath = $_POST['smtp'];
116 | $pophomepath = "{$row['maildir']}/{$_POST['localpart']}";
117 | $_POST['type'] = 'piped';
118 | } else {
119 | $smtphomepath = "{$row['maildir']}/{$_POST['localpart']}/Maildir";
120 | $pophomepath = "{$row['maildir']}/{$_POST['localpart']}";
121 | $_POST['type'] = 'local';
122 | }
123 |
124 | if (validate_password($_POST['clear'], $_POST['vclear'])) {
125 | if (!password_strengthcheck($_POST['clear'])) {
126 | header ("Location: adminuser.php?weakpass={$_POST['localpart']}");
127 | die;
128 | }
129 | $query = "INSERT INTO users (localpart, username, domain_id, crypt,
130 | smtp, pop, uid, gid, realname, type, admin, on_avscan, on_piped,
131 | on_spamassassin, sa_tag, sa_refuse, spam_drop, maxmsgsize, enabled, quota)
132 | VALUES (:localpart, :username, :domain_id, :crypt, :smtp, :pop, :uid, :gid,
133 | :realname, :type, :admin, :on_avscan, :on_piped, :on_spamassassin,
134 | :sa_tag, :sa_refuse, :spam_drop, :maxmsgsize, :enabled, :quota)";
135 | $sth = $dbh->prepare($query);
136 | $success = $sth->execute(array(':localpart'=>$_POST['localpart'],
137 | ':localpart'=>$_POST['localpart'],
138 | ':username'=>$_POST['localpart'].'@'.$_SESSION['domain'],
139 | ':domain_id'=>$_SESSION['domain_id'],
140 | ':crypt'=>crypt_password($_POST['clear']),
141 | ':smtp'=>$smtphomepath,
142 | ':pop'=>$pophomepath,
143 | ':uid'=>$_POST['uid'],
144 | ':gid'=>$_POST['gid'],
145 | ':realname'=>$_POST['realname'],
146 | ':type'=>$_POST['type'],
147 | ':admin'=>$_POST['admin'],
148 | ':on_avscan'=>$_POST['on_avscan'],
149 | ':on_piped'=>$_POST['on_piped'],
150 | ':on_spamassassin'=>$_POST['on_spamassassin'],
151 | ':sa_tag'=>(isset($_POST['sa_tag']) ? $_POST['sa_tag'] : $sa_tag),
152 | ':sa_refuse'=>(isset($_POST['sa_refuse']) ? $_POST['sa_refuse'] : $sa_refuse),
153 | ':spam_drop'=>(isset($_POST['spam_drop']) ? $_POST['spam_drop'] : 0),
154 | ':maxmsgsize'=>$_POST['maxmsgsize'],
155 | ':enabled'=>$_POST['enabled'],
156 | ':quota'=>$_POST['quota'],
157 | ));
158 |
159 | if ($success) {
160 | header ("Location: adminuser.php?added={$_POST['localpart']}");
161 | mail("{$_POST['localpart']}@{$_SESSION['domain']}",
162 | vexim_encode_header(sprintf(_("Welcome %s!"), $_POST['realname'])),
163 | "$welcome_message",
164 | "From: {$_SESSION['localpart']}@{$_SESSION['domain']}\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 8bit\r\n");
165 | die;
166 | } else {
167 | header ("Location: adminuser.php?failadded={$_POST['localpart']}");
168 | die;
169 | }
170 | } else {
171 | header ("Location: adminuser.php?badpass={$_POST['localpart']}");
172 | die;
173 | }
174 | ?>
175 |
176 |
--------------------------------------------------------------------------------
/docs/debian-conf.d/router/250_vexim_virtual_domains:
--------------------------------------------------------------------------------
1 |
2 | ### router/250_vexim_virtual_domains
3 | #################################
4 |
5 | virtual_vacation:
6 | driver = accept
7 | domains = +local_domains
8 | condition = ${if and { {!match {$h_precedence:}{(?i)junk|bulk|list}} \
9 | {eq {${lookup mysql{select users.on_vacation from users,domains \
10 | where localpart = '${quote_mysql:$local_part}' \
11 | and domain = '${quote_mysql:$domain}' \
12 | and users.on_vacation = '1' \
13 | and users.domain_id=domains.domain_id}}}{1} }} {yes}{no} }
14 | no_verify
15 | no_expn
16 | unseen
17 | transport = virtual_vacation_delivery
18 |
19 | virtual_forward:
20 | driver = redirect
21 | domains = +local_domains
22 | check_ancestor
23 | unseen = ${if eq {${lookup mysql{select unseen from users,domains \
24 | where localpart = '${quote_mysql:$local_part}' \
25 | and domain = '${quote_mysql:$domain}' \
26 | and users.on_forward = '1' \
27 | and users.domain_id=domains.domain_id}}}{1} {yes}{no}}
28 | data = ${lookup mysql{select forward from users,domains \
29 | where localpart='${quote_mysql:$local_part}' \
30 | and domain='${quote_mysql:$domain}' \
31 | and users.domain_id=domains.domain_id \
32 | and on_forward = '1'}}
33 | # We explicitly make this condition NOT forward mailing list mail!
34 | condition = ${if and { {!match {$h_precedence:}{(?i)junk}} \
35 | {eq {${lookup mysql{select users.on_forward from users,domains \
36 | where localpart = '${quote_mysql:$local_part}' \
37 | and domain = '${quote_mysql:$domain}' \
38 | and users.on_forward = '1' \
39 | and users.domain_id=domains.domain_id}}}{1} }} {yes}{no} }
40 |
41 | virtual_domains:
42 | driver = redirect
43 | domains = +local_domains
44 | address_data = ${lookup mysql{\
45 | select smtp, users.sa_tag*10 AS sa_tag, users.on_spamassassin AND domains.spamassassin AS on_spamassassin, \
46 | users.uid AS uid, users.gid AS gid, quota \
47 | from users,domains \
48 | where localpart = '${quote_mysql:$local_part}' \
49 | and domain = '${quote_mysql:$domain}' \
50 | and domains.enabled = '1' \
51 | and users.enabled = '1' \
52 | and users.domain_id = domains.domain_id}{$value}fail}
53 | allow_fail
54 | data = ${extract{smtp}{$address_data}}
55 | headers_add = ${if and { \
56 | {match{$domain}{$original_domain}} \
57 | {match{$local_part}{$original_local_part}} \
58 | {>={$spam_score_int}{${extract{sa_tag}{$address_data}}}} \
59 | {eq{1}{${extract{on_spamassassin}{$address_data}}}} \
60 | } {X-Spam-Flag: YES\nX-Spam-Score: $acl_m_spam_score\nVEXIM_SPAM_REPORT_HEADER_NAME: $acl_m_spam_report}{} }
61 | # using local_part_suffixes enables possibility to use user-"something" localparts
62 | # which could cause you trouble if you're creating email-adresses with dashes in between.
63 | .ifdef VEXIM_LOCALPART_SUFFIX
64 | local_part_suffix = VEXIM_LOCALPART_SUFFIX
65 | local_part_suffix_optional
66 | .endif
67 | retry_use_local_part
68 | file_transport = virtual_delivery
69 | reply_transport = address_reply
70 | pipe_transport = address_pipe
71 |
72 | # A group is a list of users
73 | #
74 | # if a group is flaged public
75 | # then anyone on the internet can write to it
76 | # else only members can write to it
77 | #
78 | # If not public non member sender will receive a "550 Unknown user" message
79 | virtual_dom_groups:
80 | driver = redirect
81 | domains = +local_domains
82 | allow_fail
83 | senders = ${if eq{Y}{${lookup mysql{select g.is_public \
84 | from groups g, domains d \
85 | where d.enabled = '1' and d.domain = '${quote_mysql:$domain}' and \
86 | d.domain_id = g.domain_id and g.enabled = '1' and \
87 | g.name = '${quote_mysql:$local_part}'}}} \
88 | {$sender_address} \
89 | {${lookup mysql{select concat_ws('@', u.localpart, d.domain) \
90 | from domains d, groups g, group_contents c, users u \
91 | where d.enabled = '1' and d.domain = '${quote_mysql:$domain}' and \
92 | d.domain_id = g.domain_id and g.name = '${quote_mysql:$local_part}' and \
93 | g.enabled = '1' and \
94 | g.is_public = 'N' and c.member_id = u.user_id and \
95 | d.domain_id = u.domain_id and u.enabled = '1' \
96 | and u.username = '${quote_mysql:$sender_address}' limit 1}}}}
97 | data = ${lookup mysql{ \
98 | select concat_ws('@', u.localpart, d.domain) \
99 | from domains d, groups g, group_contents c, users u \
100 | where d.enabled = '1' and \
101 | d.domain = '${quote_mysql:$domain}' and \
102 | d.domain_id = g.domain_id and \
103 | g.enabled = '1' and \
104 | g.id = c.group_id and \
105 | c.member_id = u.user_id and \
106 | d.domain_id = u.domain_id and \
107 | u.enabled = '1' and \
108 | g.name = '${quote_mysql:$local_part}'} }
109 | # using local_part_suffixes enables possibility to use user-"something" localparts
110 | # which could cause you trouble if you're creating email-adresses with dashes in between.
111 | .ifdef VEXIM_LOCALPART_SUFFIX
112 | local_part_suffix = VEXIM_LOCALPART_SUFFIX
113 | local_part_suffix_optional
114 | .endif
115 | retry_use_local_part
116 | reply_transport = address_reply
117 | pipe_transport = address_pipe
118 |
119 | virtual_domains_catchall:
120 | driver = redirect
121 | domains = +local_domains
122 | allow_fail
123 | data = ${lookup mysql{select smtp from users,domains where localpart = '*' \
124 | and domain = '${quote_mysql:$domain}' \
125 | and users.domain_id = domains.domain_id}}
126 | retry_use_local_part
127 | file_transport = virtual_delivery
128 | reply_transport = address_reply
129 | pipe_transport = address_pipe_catchall
130 |
131 | virtual_domain_alias:
132 | driver = redirect
133 | domains = +local_domains
134 | allow_fail
135 | data = ${lookup mysql{select concat('${quote_mysql:$local_part}@', domain) \
136 | from domains,domainalias where domainalias.alias = '${quote_mysql:$domain}' \
137 | and domainalias.domain_id = domains.domain_id}}
138 | retry_use_local_part
139 |
--------------------------------------------------------------------------------
/vexim/site.php:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
35 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | prepare($query);
67 | $sth->execute($queryParams);
68 | while ($row = $sth->fetch()) {
69 | if($row['enabled'] === 0) print ''; else print ' ';
70 | ?>
71 |
72 |
75 |
77 |
78 |
79 |
80 |
83 |
84 |
87 |
88 |
91 |
92 |
95 |
96 |
99 | manage
100 |
103 |
104 |
107 |
108 |
109 |
110 | :
111 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | query($query);
127 | while ($row = $sth->fetch()) {
128 | ?>
129 |
130 |
131 |
134 |
136 |
137 |
138 |
139 |
140 |
143 |
144 |
145 |
146 |
147 | query($query);
151 | while ($row = $sth->fetch()) {
152 | ?>
153 |
154 |
155 |
158 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | ';
171 | if($AllowUserLogin){
172 | echo ''._('Standard user accounts are currently able to login and change their own personal details').'. ';
173 | }else{
174 | echo ''._('The system is currently configured to prevent standard users logging in to change their own personal details.').' ';
175 | }
176 | ?>
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/vexim/admingroupchange.php:
--------------------------------------------------------------------------------
1 |
6 | prepare($query);
9 | $sth->execute(array(':group_id'=>$_GET['group_id'], ':domain_id'=>$_SESSION['domain_id']));
10 | if($sth->rowCount()) {
11 | $row = $sth->fetch();
12 | $grouplocalpart = $row['name'];
13 | }
14 | ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
29 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/setup/migrations/vexim_2.2_to_2.3_mysql.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- MySQL script to upgrade Vexim database schema from Vexim 2.2RC1, 2.2 and 2.2.1 to Vexim 2.3
3 | --
4 | -- Uncomment the section at the bottom of this file if you used Arne Schirmacher's patch for Vexim 2.2RC1
5 | -- from https://www.schirmacher.de/display/INFO/improved+Vexim+frontend+and+bug+fixes
6 | --
7 | -- The following defines new salt prefix for password hashes.
8 | -- Before dropping the `clear` field, we will re-hash passwords for all users whose cleartext and encrypted
9 | -- passwords match using a secure hashing scheme (SHA-512 by default, supported by Linux, FreeBSD and Solaris).
10 | -- Below you may change this prefix if you prefer a different hashing scheme, e.g. '$2a$10$' for bcrypt
11 | -- (*BSD-only Blowfish hashing scheme):
12 | SET @NEW_PW_PREFIX='$6$';
13 | -- Comment out this definition if you do not want your password hashes updated. Be advised that doing so would
14 | -- leave these passwords less secure, and you will not be able to re-hash them later.
15 |
16 | --
17 | -- Table: `domains`
18 | --
19 | ALTER TABLE `domains` ENGINE=InnoDB;
20 | ALTER TABLE `domains` CONVERT TO CHARACTER SET utf8;
21 | ALTER TABLE `domains` MODIFY COLUMN `domain_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
22 | ALTER TABLE `domains` MODIFY COLUMN `domain` varchar(255) NOT NULL DEFAULT '';
23 | ALTER TABLE `domains` MODIFY COLUMN `maildir` varchar(4096) NOT NULL DEFAULT '';
24 | ALTER TABLE `domains` MODIFY COLUMN `uid` smallint(5) unsigned NOT NULL DEFAULT '65534';
25 | ALTER TABLE `domains` MODIFY COLUMN `gid` smallint(5) unsigned NOT NULL DEFAULT '65534';
26 | ALTER TABLE `domains` DROP COLUMN `complexpass`;
27 | ALTER TABLE `domains` DROP KEY `domain_id`;
28 | ALTER TABLE `domains` DROP KEY `domains`;
29 |
30 | --
31 | -- Table: `users`
32 | --
33 | ALTER TABLE `users` ENGINE=InnoDB;
34 | ALTER TABLE `users` CONVERT TO CHARACTER SET utf8;
35 | ALTER TABLE `users` MODIFY COLUMN `domain_id` int(10) unsigned NOT NULL;
36 | ALTER TABLE `users` MODIFY COLUMN `localpart` varchar(64) NOT NULL DEFAULT '';
37 | ALTER TABLE `users` MODIFY COLUMN `crypt` varchar(255) DEFAULT NULL;
38 | -- Re-hash passwords using the more secure scheme.
39 | UPDATE `users`
40 | SET `crypt` = COALESCE(ENCRYPT(`clear`, CONCAT(@NEW_PW_PREFIX, MD5(RAND()))), `crypt`)
41 | WHERE `crypt` = ENCRYPT(`clear`, `crypt`) AND @NEW_PW_PREFIX IS NOT NULL;
42 | -- Passwords are now re-hashed.
43 | SET @NEW_PW_PREFIX=NULL;
44 | ALTER TABLE `users` DROP COLUMN `clear`;
45 | ALTER TABLE `users` MODIFY COLUMN `uid` smallint(5) unsigned NOT NULL DEFAULT '65534';
46 | ALTER TABLE `users` MODIFY COLUMN `gid` smallint(5) unsigned NOT NULL DEFAULT '65534';
47 | ALTER TABLE `users` MODIFY COLUMN `smtp` varchar(4096) DEFAULT NULL;
48 | ALTER TABLE `users` MODIFY COLUMN `pop` varchar(4096) DEFAULT NULL;
49 | ALTER TABLE `users` DROP COLUMN `on_complexpass`;
50 | ALTER TABLE `users` ADD COLUMN `spam_drop` tinyint(1) NOT NULL DEFAULT '0' AFTER `on_vacation`;
51 | ALTER TABLE `users` MODIFY COLUMN `forward` varchar(4096) DEFAULT NULL;
52 | ALTER TABLE `users` MODIFY COLUMN `unseen` tinyint(1) NOT NULL DEFAULT '0';
53 | ALTER TABLE `users` MODIFY COLUMN `vacation` text DEFAULT NULL;
54 | ALTER TABLE `users` ADD KEY `fk_users_domain_id_idx` (`domain_id`);
55 | ALTER TABLE `users` ADD CONSTRAINT `fk_users_domain_id`
56 | FOREIGN KEY (`domain_id`)
57 | REFERENCES `domains` (`domain_id`)
58 | ON DELETE CASCADE
59 | ON UPDATE CASCADE;
60 |
61 | --
62 | -- Table: `blocklists`
63 | --
64 | ALTER TABLE `blocklists` ENGINE=InnoDB;
65 | ALTER TABLE `blocklists` CONVERT TO CHARACTER SET utf8;
66 | ALTER TABLE `blocklists` MODIFY COLUMN `domain_id` int(10) unsigned NOT NULL;
67 | ALTER TABLE `blocklists` MODIFY COLUMN `blockval` varchar(255) NOT NULL DEFAULT '';
68 | ALTER TABLE `blocklists` ADD KEY `fk_blocklists_domain_id_idx` (`domain_id`);
69 | ALTER TABLE `blocklists` ADD KEY `fk_blocklists_user_id_idx` (`user_id`);
70 | ALTER TABLE `blocklists` ADD CONSTRAINT `fk_blocklists_domain_id`
71 | FOREIGN KEY (`domain_id`)
72 | REFERENCES `domains` (`domain_id`)
73 | ON DELETE CASCADE
74 | ON UPDATE CASCADE;
75 | ALTER TABLE `blocklists` ADD CONSTRAINT `fk_blocklists_user_id`
76 | FOREIGN KEY (`user_id`)
77 | REFERENCES `users` (`user_id`)
78 | ON DELETE CASCADE
79 | ON UPDATE CASCADE;
80 |
81 | --
82 | -- Table: `domainalias`
83 | --
84 | ALTER TABLE `domainalias` ENGINE=InnoDB;
85 | ALTER TABLE `domainalias` CONVERT TO CHARACTER SET utf8;
86 | ALTER TABLE `domainalias` MODIFY COLUMN `domain_id` int(10) unsigned NOT NULL;
87 | ALTER TABLE `domainalias` MODIFY COLUMN `alias` varchar(255) NOT NULL;
88 | ALTER TABLE `domainalias` ADD PRIMARY KEY (`alias`);
89 | ALTER TABLE `domainalias` ADD KEY `fk_domainalias_domain_id_idx` (`domain_id`);
90 | ALTER TABLE `domainalias` ADD CONSTRAINT `fk_domainalias_domain_id`
91 | FOREIGN KEY (`domain_id`)
92 | REFERENCES `domains` (`domain_id`)
93 | ON DELETE CASCADE
94 | ON UPDATE CASCADE;
95 |
96 | --
97 | -- Table: `groups`
98 | --
99 | ALTER TABLE `groups` ENGINE=InnoDB;
100 | ALTER TABLE `groups` CONVERT TO CHARACTER SET utf8;
101 | ALTER TABLE `groups` MODIFY COLUMN `id` int(10) unsigned NOT NULL AUTO_INCREMENT;
102 | ALTER TABLE `groups` MODIFY COLUMN `domain_id` int(10) unsigned NOT NULL;
103 | ALTER TABLE `groups` ADD KEY `fk_groups_domain_id_idx` (`domain_id`);
104 | ALTER TABLE `groups` ADD CONSTRAINT `fk_groups_domain_id`
105 | FOREIGN KEY (`domain_id`)
106 | REFERENCES `domains` (`domain_id`)
107 | ON DELETE CASCADE
108 | ON UPDATE CASCADE;
109 |
110 | --
111 | -- Table: `group_contents`
112 | --
113 | ALTER TABLE `group_contents` ENGINE=InnoDB;
114 | ALTER TABLE `group_contents` CONVERT TO CHARACTER SET utf8;
115 | ALTER TABLE `group_contents` MODIFY COLUMN `group_id` int(10) unsigned NOT NULL;
116 | ALTER TABLE `group_contents` MODIFY COLUMN `member_id` int(10) unsigned NOT NULL;
117 | ALTER TABLE `group_contents` ADD KEY `fk_group_contents_group_id_idx` (`group_id`);
118 | ALTER TABLE `group_contents` ADD KEY `fk_group_contents_member_id_idx` (`member_id`);
119 | ALTER TABLE `group_contents` ADD CONSTRAINT `fk_group_contents_group_id`
120 | FOREIGN KEY (`group_id`)
121 | REFERENCES `groups` (`id`)
122 | ON DELETE CASCADE
123 | ON UPDATE CASCADE;
124 | ALTER TABLE `group_contents` ADD CONSTRAINT `fk_group_contents_member_id`
125 | FOREIGN KEY (`member_id`)
126 | REFERENCES `users` (`user_id`)
127 | ON DELETE CASCADE
128 | ON UPDATE CASCADE;
129 |
130 | --
131 | -- Uncomment the following section if you used Arne Schirmacher's patch for Vexim 2.2RC1
132 | --
133 |
134 | -- UPDATE `users` SET spam_drop=1 WHERE movedelete=2;
135 | -- ALTER TABLE `users` DROP COLUMN `movedelete`;
136 | -- ALTER TABLE `users` DROP COLUMN `on_rewritesubject`;
137 |
--------------------------------------------------------------------------------
/vexim/adminaliaschange.php:
--------------------------------------------------------------------------------
1 | prepare($query);
10 | $sth->execute(array(':user_id'=>$_GET['user_id'], ':domain_id'=>$_SESSION['domain_id']));
11 | if ($sth->rowCount()) {
12 | $row = $sth->fetch();
13 | }
14 | $domquery = "SELECT avscan,spamassassin,quotas,pipe FROM domains
15 | WHERE domain_id=:domain_id";
16 | $domsth = $dbh->prepare($domquery);
17 | $domsth->execute(array(':domain_id'=>$_SESSION['domain_id']));
18 | if ($domsth->rowCount()) {
19 | $domrow = $domsth->fetch();
20 | }
21 | ?>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
38 |
186 |
187 |
188 |
189 |
--------------------------------------------------------------------------------