commit 0d2045ef1610a6276f57a5b6c3afe42b091c7800 Author: Mischa Peters Date: Sat Mar 24 07:27:00 2007 +0000 Initial Import in SourceForge git-svn-id: https://svn.code.sf.net/p/postfixadmin/code/tags/postfixadmin-1.5.4@1 a1433add-5e2c-0410-b055-b7f2511e0802 diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT new file mode 100644 index 00000000..416cb384 --- /dev/null +++ b/CHANGELOG.TXT @@ -0,0 +1,86 @@ +############################# +# Postfix Admin Release 1.x # +############################# +# +# 2003 (c) High5! +# Created by: Mischa Peters +# + +Version 1.5.4 -- 2003/06/16 +---------------------------- + - Added: Option for "Back to". + - Added: Option for Vacation module. + - Added: Table declaration for the use of Quota in the INSTALL.TXT. + This requires an additional local delivery agent. + Quotas are not supported by Postfix! + - Changed: The word "View" to "List". + + +Version 1.5.3 -- 2003/06/06 +---------------------------- + - Fixed: Even more minor bugs in regards to declaration of variables. + (Thanx Aquilante and Kyle_m) + + +Version 1.5.2 -- 2003/06/05 +---------------------------- + - Fixed: Minor bugs in regards to declaration of variables. + + +Version 1.5.1 -- 2003/06/04 +---------------------------- + - Added: Optional mailbox per domain directory structure. (Thanx Jim) + - Added: Option to completely control the stored aliases. (Thanx Alex) + - Change: config.inc.php is renamed to config.inc.php.sample. (Thanx Alex) + - Fixed: $PHP_SELF in config.inc.php and my_lib.php. (Thanx Jim) + + +Version 1.5.0 -- 2003/05/28 +---------------------------- + - Added: Support for "Back to Main Site" + - Added: config.inc.php as the main configuration file. + - Added: Drop down box for domain selection when adding a new admin. + - Added: Resend of test email to newly created mailbox. + - Added: Mailbox and Aliases count for domainview. + - Added: Change description of domain without deleting the complete + domain. + - Added: Change name of mailbox user without deleting the mailbox. + - Added: Expire headers for unnecessary reloads. (Thanx Alex) + - Removed: Completely removed the site_lib.php. + - Removed: my_lib.php from the admin directory. + - Removed: Symlink to index.php. + - Fix: Code clean up. + - Fix: Minor bugs and cosmetic fixes. + - Fix: Modified check_string() to check numbers and returns false if not + matched. (Thanx btaber) + - Fix: Correct session handling in login.php (Thanx Yen-Wei Liu) + - Fix: Correct deletion of RFC822 email addresses. (Thanx Yen-Wei Liu) + + +Version 1.4.0 -- 2003/04/07 +---------------------------- + - Added: When deleting a domain, all aliases and mailboxes for that domain + are also deleted from the database. + - Added: Add standard aliases for every domain that is created. + These aliases can point to the main "local" administrator. + The aliases are configured in the config.php in the admin directory. + - Change: The layout of my_lib.php and site_lib.php have been changed. + - Change: Modifying an alias is now done with TEXTAREA for more + flexibility. + - Fix: Minor bugs and cosmetic fixes. + + +Version 1.3.8a -- 2003/03/31 +---------------------------- + - Fix: After deletion of a domain it would not return to the correct page. + + +Version 1.3.8 -- 2003/03/25 +---------------------------- + - Added: Admin password change. No longer needed to delete and re-enter + the admin user for a specific domain. + + +Version 1.3.7 -- 2002/12/24 +---------------------------- + - Initial public release of Postfix Admin. diff --git a/INSTALL.TXT b/INSTALL.TXT new file mode 100644 index 00000000..7c5dd875 --- /dev/null +++ b/INSTALL.TXT @@ -0,0 +1,107 @@ +############################# +# Postfix Admin Release 1.x # +############################# +# +# 2003 (c) High5! +# Created by: Mischa Peters +# +# Detailed instructions on how to install / upgrade Postfix Admin can be +# found in UPGRADE.TXT +# +# Unpack Postfix Admin in the directory where you want it. For example: /usr/local/www//postfixadmin +# There is also an Admin Admin part, change directory to the "admin" directory +# and change the path to the .htpasswd file in the .htaccess file. +# +# Some other information that you might want to look at is in the config.php +# file. +# +# In order to be able to read & write from the database I have created a +# seperate user in MySQL. I do this because Postfix Admin needs to have more +# rights on the Postfix database. If you are worried abour the password for +# the database. I have Postfix Admin running as the WebServer owner:group, +# that way your postfix username and password are somewhat protected against +# local users. +# +# You can use this file to create the tables that are needed to use postfix +# with mysql. The bottom part is for Postfix Admin. +# +# You can do this from the command line with: +# +# mysql -u root [-p] < INSTALL.TXT + +# +# Postfix / MySQL +# +USE mysql +INSERT INTO user (Host, User, Password) VALUES ('localhost','postfix',password('postfix')); +INSERT INTO db (Host, Db, User, Select_priv) VALUES ('localhost','postfix','postfix','Y'); +CREATE DATABASE postfix; + +# +# Table structure for table alias +# +USE postfix; +CREATE TABLE alias ( + address varchar(255) NOT NULL default '', + goto text NOT NULL, + domain varchar(255) NOT NULL default '', + create_date datetime NOT NULL default '0000-00-00 00:00:00', + change_date datetime NOT NULL default '0000-00-00 00:00:00', + active tinyint(4) NOT NULL default '1', + PRIMARY KEY (address) +) TYPE=MyISAM COMMENT='Virtual Aliases - mysql_virtual_alias_maps'; + +# +# Table structure for table domain +# +USE postfix; +CREATE TABLE domain ( + domain varchar(255) NOT NULL default '', + description varchar(255) NOT NULL default '', + create_date datetime NOT NULL default '0000-00-00 00:00:00', + change_date datetime NOT NULL default '0000-00-00 00:00:00', + active tinyint(4) NOT NULL default '1', + PRIMARY KEY (domain) +) TYPE=MyISAM COMMENT='Virtual Domains - mysql_virtual_domains_maps'; + +# +# Table structure for table mailbox +# +USE postfix; +CREATE TABLE mailbox ( + username varchar(255) NOT NULL default '', + password varchar(255) NOT NULL default '', + name varchar(255) NOT NULL default '', + maildir varchar(255) NOT NULL default '', +# quota varchar(255) NOT NULL default '', + domain varchar(255) NOT NULL default '', + create_date datetime NOT NULL default '0000-00-00 00:00:00', + change_date datetime NOT NULL default '0000-00-00 00:00:00', + active tinyint(4) NOT NULL default '1', + PRIMARY KEY (username) +) TYPE=MyISAM COMMENT='Virtual Mailboxes - mysql_virtual_mailbox_maps'; + +# +# +# + +# +# Postfix Admin User & Table +# +USE mysql +INSERT INTO user (Host, User, Password) VALUES ('localhost','postfixadmin',password('postfixadmin')); +INSERT INTO db (Host, Db, User, Select_priv, Insert_priv, Update_priv, Delete_priv) VALUES ('localhost', 'postfix', 'postfixadmin', 'Y', 'Y', 'Y', 'Y'); + +# +# Table structure for table admin +# +USE postfix; +CREATE TABLE admin ( + username varchar(255) NOT NULL default '', + password varchar(255) NOT NULL default '', + domain varchar(255) NOT NULL default '', + create_date datetime NOT NULL default '0000-00-00 00:00:00', + change_date datetime NOT NULL default '0000-00-00 00:00:00', + active tinyint(4) NOT NULL default '1', + PRIMARY KEY (username) +) TYPE=MyISAM COMMENT='Virtual Admins - Store Virtual Domain Admins'; diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 00000000..71e00ff7 --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,50 @@ +License for Postfix Admin: + + The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is Postfix Admin. + + The Initial Developer of the Original Code is Mischa Peters . + Portions created by Mischa Peters are Copyright (c) 2002, 2003. + All Rights Reserved. + + Contributor(s): + + +This project includes work by Mischa Peters and others that is: + + Copyright (c) 2002,2003 Mischa Peters + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + Neither the names of the copyright holders nor the names of the XLW + Group and its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/UPGRADE.TXT b/UPGRADE.TXT new file mode 100644 index 00000000..5c352b6f --- /dev/null +++ b/UPGRADE.TXT @@ -0,0 +1,97 @@ +############################# +# Postfix Admin Release 1.x # +############################# +# +# 2003 (c) High5! +# Created by: Mischa Peters +# + +REQUIRED!! +---------- +There are 2 assumptions that I make with PostfixAdmin. +- You are using Postfix 2.0 or higher. +- You are using PHP 4.1 or higher. + + +Upgrade from 1.4.x and older +---------------------------- +Since some features have been added to this release which are partially +coming from config.inc.php it's wise to do a complete upgrade and modify your +settings in the new config.inc.php. + + +1. Backup old installation +--------------------------- +Make a backup of your current Postfix Admin directory. If you use "cp", be +sure to use the "-Rp" options. -R means recursive, and -p will save the +permissions in the directory. + +In this example, we assume that your httpd document directory is +/usr/local/www, that your Postfix Admin install is located at +/usr/local/www/postfixadmin, and that your new Postfix Admin version is 1.5.0. +Substitute version numbers and names as required. + + $ cd /usr/local/www + $ cp -Rp postfixadmin postfixadmin.old + + +2. Unarchive new Postfix Admin +------------------------------ +Make sure that you are in your /usr/local/postfixadmin/ directory and then unarchive the +Postfix Admin archive (whatever the filename is): + + $ tar -zxvf postfixadmin-1.5.0.tgz + + +3. Change permissions +---------------------- +Since the database password is stored in the config.inc.php it's a good idea +to have Postfix Admin set to the permission of the webserver. In this +example, we assume that user "www" and group "www" are the web server as is +often the case with Apache. + + $ cd /usr/local/www + $ chown -R www:www postfixadmin-1.5.0 + +This is also a good idea for the file permissions. + + $ cd /usr/local/www/postfixadmin-1.5.0 + $ chmod 640 *.php *.css + $ cd /usr/local/www/postfixadmin-1.5.0/admin + $ chmod 640 *.php *.css + +Additionally, if "chown user:group" doesn't work, you can use "chown user" +and "chgrp group" instead. See the man pages for these commands for more +information. + + +4. Create the MySQL Tables +-------------------------- +In INSTALL.TXT you can find the table structure that you need in order to +configure Postfix Admin and Postfix in general to work with Virtual Domains +and Users + + +5. Configure +------------ +Look at the file config.inc.php in the root of Postfix Admin, here you can +specify the username and possword of the Postfix Admin user as well as the +database name. + +In this file you can also find the text that is displayed as the title, +header and footer. You can change this as you see fit. +To change the background and text color please check the stylesheet.css + +In config.inc.php in the admin directory you can find an array of default +aliases that are created when a new domain is created. You can change these +aliases so that they reflect your setup. + +The default password for the admin part of Postfix Admin is admin/admin. +This is specified in the .htpasswd file in the admin directory. +Make sure that the location of the .htpasswd file matches your path. + + +6. Done +------- +This is all that is needed. Fire up your browser and go to the site that you +specified to host Postfix Admin. diff --git a/admin/.htaccess b/admin/.htaccess new file mode 100644 index 00000000..df745a3a --- /dev/null +++ b/admin/.htaccess @@ -0,0 +1,8 @@ +AuthUserFile /usr/local/postfixadmin/admin/.htpasswd +AuthGroupFile /dev/null +AuthName "Postfix Admin" +AuthType Basic + + +require valid-user + diff --git a/admin/.htpasswd b/admin/.htpasswd new file mode 100644 index 00000000..3b7d51c7 --- /dev/null +++ b/admin/.htpasswd @@ -0,0 +1 @@ +admin:$apr1$5awhn...$NvPhYnYme5lGzdXBd3/P// diff --git a/admin/adminview.php b/admin/adminview.php new file mode 100644 index 00000000..7b1a98ff --- /dev/null +++ b/admin/adminview.php @@ -0,0 +1,36 @@ + 0) { + print "\n"; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + while ($row = mysql_fetch_array ($result['result'])) { + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print "\n"; + } + print "
Domain NameAdmin AliasLast ModifiedActive 
" . $row['domain'] . "" . $row['username'] . "" . $row['change_date'] . "" . $row['active'] . "editdel
\n"; + print "

\n"; + print "Found: " . $result['rows'] . "
\n"; +} else { + print "

\n"; + print "Nothing Found in the Admin Table!\n"; +} +print_footer(); +?> diff --git a/admin/delete.php b/admin/delete.php new file mode 100644 index 00000000..3e17124f --- /dev/null +++ b/admin/delete.php @@ -0,0 +1,49 @@ += 0) and ($r_alias >= 0) and ($r_mailbox >= 0)) { + header("Location: $url"); + } else { + print_header(); + print "


\n"; + print "Unable to delete all entries for complete domain deletion!

\n"; + print "Domain delete: $r_domain
\n"; + print "Admin delete: $r_admin
\n"; + print "Alias delete: $r_alias
\n"; + print "Mailbox delete: $r_mailbox
\n"; + print "

\n"; + print_footer(); + } +} else { + $result = db_delete ($table,$where,$delete); + if ($result == 1) { + header("Location: $url"); + } else { + print_header(); + print "


\n"; + print "Unable to delete entry $delete from the $table table!\n"; + print "

\n"; + print_footer(); + } +} + +function db_delete ($table,$where,$delete) { + $result = db_query("DELETE FROM $table WHERE $where='$delete'"); + if ($result['rows'] >= 1) { + return $result['rows']; + } else { + return true; + } +} +?> diff --git a/admin/domainview.php b/admin/domainview.php new file mode 100644 index 00000000..8afc9091 --- /dev/null +++ b/admin/domainview.php @@ -0,0 +1,42 @@ + 0) { + print "\n"; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + while ($row = mysql_fetch_array ($result['result'])) { + $row_domain = $row['domain']; + print ""; + print ""; + print ""; + $aliases = db_query("SELECT * FROM alias WHERE domain='$row_domain'"); + print ""; + $mailbox = db_query("SELECT * FROM mailbox WHERE domain='$row_domain'"); + print ""; + print ""; + print ""; + print ""; + print "\n"; + } + print "
DomainDescriptionAliasesMailboxesLast Modified  
" . $row['domain'] . "" . $row['description'] . "" . $aliases['rows'] . "" . $mailbox['rows'] . "" . $row['change_date'] . "editdel
\n"; + print "

\n"; + print "Found: " . $result['rows'] . "
\n"; +} else { + print "

\n"; + print "Nothing Found in the Domain Table!\n"; +} +print_footer(); +?> diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 00000000..dd2c192e --- /dev/null +++ b/admin/index.php @@ -0,0 +1,3 @@ + diff --git a/admin/modify.php b/admin/modify.php new file mode 100644 index 00000000..86934004 --- /dev/null +++ b/admin/modify.php @@ -0,0 +1,36 @@ +$domain to the domain table!", "YES", "ADMIN"); + } +} +print_header("admin"); +?> +Create a new domain. +

+

+ + + + +
Domain:">
Description:
+
+ diff --git a/admin/newadmin.php b/admin/newadmin.php new file mode 100644 index 00000000..6650baab --- /dev/null +++ b/admin/newadmin.php @@ -0,0 +1,58 @@ +Email is not a valid email address, please go back."); + + $result = db_query ("SELECT * FROM domain WHERE domain='$domain'"); + if ($result['rows'] != 1) print_error("The domain $domain is not present in the domain table!"); + + $result = db_query ("SELECT * FROM admin WHERE username='$username'"); + if ($result['rows'] == 1) print_error("This email address already exists, please choose a different one."); + + $result = db_query ("INSERT INTO admin (username,password,domain,create_date,change_date) VALUES('$username','$passwd','$domain',NOW(),NOW())"); + if ($result['rows'] == 1) { + print "$username has been added to the admin table!\n"; + print "

\n"; + } else { + print_error("Unable to add $username to the mailbox table!"); + } +} +?> +Create a new admin for a domain. +

+

+ + + + + + +
Email:
Passwd:
Domain: + +
+
+ diff --git a/admin/newdomain.php b/admin/newdomain.php new file mode 100644 index 00000000..f9335909 --- /dev/null +++ b/admin/newdomain.php @@ -0,0 +1,55 @@ +$domain has been added to the domain table!\n"; + print "

\n"; + } else { + print_error ("Unable to add: $domain to the domain table!"); + } + + if ($aliases == "on") { + $alias_keys = array_keys($default_aliases); + $alias_values = array_values($default_aliases); + for ($i = 0; $i < count($alias_keys); $i++) { + $address = $alias_keys[$i] . "@" . $domain; + $result = db_query ("INSERT INTO alias (address,goto,domain,create_date,change_date) VALUES('$address','$alias_values[$i]','$domain',NOW(),NOW())"); + if ($result['rows'] == 1) { + print "$address has been added to the alias table!
\n"; + } else { + print_error ("Unable to add: $address to the alias table!"); + } + } + print "

\n"; + } +} +?> +Create a new domain. +

+

+ + + + + +
Domain:
Description:
Add default mail aliases:
+
+ diff --git a/admin/passwd.php b/admin/passwd.php new file mode 100644 index 00000000..22cedebf --- /dev/null +++ b/admin/passwd.php @@ -0,0 +1,37 @@ +\n"; + print_footer(); + exit; + } else { + print_error("Unable to update your password!"); + } +} +?> +Change admin password. +

+

+ + + + +
Login:
New Password:
+
+ diff --git a/admin/sendmail.php b/admin/sendmail.php new file mode 100644 index 00000000..b82dcce0 --- /dev/null +++ b/admin/sendmail.php @@ -0,0 +1,41 @@ +\n"; +} +?> +Send test message to a new mailbox. +

+

+ + + +
From:
To: + +
+
+ diff --git a/admin/virtualview.php b/admin/virtualview.php new file mode 100644 index 00000000..14896f8e --- /dev/null +++ b/admin/virtualview.php @@ -0,0 +1,76 @@ +\n"; +} else { + $query = "SELECT * FROM alias $where ORDER BY domain, address"; +} +$result = db_query("$query"); +if ($result['rows'] > 0) { + print "
\n"; + print "\n"; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + while ($row = mysql_fetch_array ($result['result'])) { + print ""; + print ""; + print ""; + print ""; + print ""; + print "\n"; + } + print "
FromToLast ModifiedActive
" . $row['address'] . "" . ereg_replace (",", "
", $row['goto']) . "
" . $row['change_date'] . "" . $row['active'] . "
\n"; + print "
\n"; + print "Found: " . $result['rows'] . "\n"; + print "

\n"; +} else { + print "Nothing Found in the Alias Table!\n"; + print "

\n"; +} + +if (!empty($where)) { + $query = "SELECT * FROM mailbox WHERE domain='$where'"; +} else { + $query = "SELECT * FROM mailbox ORDER BY domain, username"; +} +$result = db_query("$query"); +if ($result['rows'] > 0) { + print "

\n"; + print "\n"; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + while ($row = mysql_fetch_array ($result['result'])) { + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print "\n"; + } + print "
EmailNameMailboxLast ModifiedActive
" . $row['username'] . "" . $row['name'] . "" . $row['maildir'] . "" . $row['change_date'] . "" . $row['active'] . "
\n"; + print "
\n"; + print "Found: " . $result['rows'] . "
\n"; +} else { + print "

\n"; + print "Nothing Found in the Mailbox Table!\n"; +} +print_footer(); +?> diff --git a/alias.php b/alias.php new file mode 100644 index 00000000..d2d5e992 --- /dev/null +++ b/alias.php @@ -0,0 +1,51 @@ +Alias is not a valid email address, please go back."); + + if (!check_email($goto)) print_error("The email address that you have supplied at To is not a valid email address, please go back."); + + if ($address_value == "none") $address = "@" . $sessid_domain; + + $result = db_query("SELECT * FROM alias WHERE address='$address'"); + if ($result['rows'] == 1) print_error("This email address already exists, please choose a different one."); + + $result = db_query("INSERT INTO alias (address,goto,domain,create_date,change_date) VALUES('$address','$goto','$sessid_domain',NOW(),NOW())"); + if ($result['rows'] == 1) { + print "

\n"; + print "$address -> $goto has been added to the alias table!\n"; + print "

\n"; + } else { + print_error("Unable to add: $address -> $goto to the alias table!"); + } +} +?> +Create a new alias for your domain. +

+

+ + + + +
Alias:@
To:Where the mail needs to be send to.
Use "edit" in the overview to add more
then one email address.
+
+If you want to add a catchall enter "none" in the alias field. + diff --git a/changename.php b/changename.php new file mode 100644 index 00000000..75f34981 --- /dev/null +++ b/changename.php @@ -0,0 +1,39 @@ + +Change password. +

+

+ + + + +
Login:
Name:">
+
+ diff --git a/config.inc.php.sample b/config.inc.php.sample new file mode 100644 index 00000000..639c3882 --- /dev/null +++ b/config.inc.php.sample @@ -0,0 +1,66 @@ + "abuse@example.com", + "hostmaster" => "hostmaster@example.com", + "postmaster" => "postmaster@example.com", + "webmaster" => "webmaster@example.com" +); + +// Back to main website information +// If you don't want to use this option set this to "NO". +$show_organization_info = "YES"; +$organization_name = "domain.com"; +$organization_link = "http://domain.com"; + +// Admin email address +$admin_email = "admin@domain.com"; + +// If you wish to keep each domain in its own subdirectory beneath the +// $virtual_mailbox_base directory, set this to "YES". +$use_subdir = ""; + +// When you want to offer the "vacation" daemon to your users, +// set this to "YES". +// You need to install the vacation module first! +$use_vacation = ""; +$vacation_text = "Users click here when you are going to be out of the office."; +$vacation_file = "vacation.php"; +$vacation_email = "vacation@domain.tld"; + +// If you want to view the aliases that are created for the mailboxes set +// this to "YES". +$alias_control = ""; + +// Specify the table where you have your quotas, leave empty if you don't +// enforce quotas. For example a 2MB mailbox quota: +// $quota_table = "quota"; +// $default_quota = "2000000"; +$quota_table = ""; +$default_quota = ""; + +// Show Postfix Admin information +$show_postfix_admin_info = "YES"; + +// Title used for all pages except login.php +$title = "Mail Admin"; + +// Header used for login.php +$welcome_header = ":: Welcome to Mail Admin ::"; + +// Title used for login.php +$welcome_title = ":: Welcome to Mail Admin ::"; +?> diff --git a/delete.php b/delete.php new file mode 100644 index 00000000..b606ba7e --- /dev/null +++ b/delete.php @@ -0,0 +1,51 @@ +$delete from the $table table!", "YES", "MENU"); + } + + $query = "SELECT * FROM mailbox WHERE username='$delete' AND domain='$sessid_domain'"; + $result = db_query ("$query"); + if ($result['rows'] == 1) { + + $query = "DELETE FROM mailbox WHERE username='$delete' AND domain='$sessid_domain'"; + $result = db_query ("$query"); + if ($result['rows'] == 1) { + header("Location: $url"); + } else { + print_error ("Unable to delete entry $delete from the $table table!", "YES", "MENU"); + } + } + header("Location: $url"); +} + +if ($table == "mailbox") { + $query = "DELETE FROM mailbox WHERE username='$delete' AND domain='$sessid_domain'"; + $result = db_query ("$query"); + if ($result['rows'] != 1) { + print_error ("Unable to delete entry $delete from the $table table!", "YES", "MENU"); + } + + $query = "DELETE FROM alias WHERE address='$delete' AND domain='$sessid_domain'"; + $result = db_query ("$query"); + if ($result['rows'] == 1) { + header("Location: $url"); + } else { + print_error ("Unable to delete entry $delete from the $table table!", "YES", "MENU"); + } +} +?> diff --git a/index.php b/index.php new file mode 100644 index 00000000..e59f7471 --- /dev/null +++ b/index.php @@ -0,0 +1,3 @@ + diff --git a/login.php b/login.php new file mode 100644 index 00000000..7891c248 --- /dev/null +++ b/login.php @@ -0,0 +1,88 @@ + $row[domain], + "username" => $row[username] + ); + } else { + print_header(); + print "

Mail Admin

\n"; + print "
\n"; + print_error ("Either the password that you supplied is incorrect, or you are not authorized to view this page.
Go back and try again.\n"); + } + } else { + print_header(); + print "

Mail Admin

\n"; + print "
\n"; + print_error ("The login that you supplied is not correct, please press BACK and try again."); + } + header("Location: main.php?" . session_name() . "=" . session_id()); +} +print_header("", $welcome_title, "YES"); +?> +
+ + + + + + + + +
+
+ + + + + + + +
+
+Mail admins login here to administrate your domain. +
+
+ +
+ +
+ +
+
+

+Users click here to change your email password. +

+$vacation_text\n"; +?> +

+
+
+ diff --git a/logout.php b/logout.php new file mode 100644 index 00000000..aa8963bd --- /dev/null +++ b/logout.php @@ -0,0 +1,18 @@ +Mail Admin\n"; +print "
\n"; +print "You are logged out\n"; +print "

\n"; +print "Login again\n"; +print "

\n"; +print_footer(); +?> diff --git a/mailbox.php b/mailbox.php new file mode 100644 index 00000000..e1a82147 --- /dev/null +++ b/mailbox.php @@ -0,0 +1,75 @@ +Email is not a valid email address, please go back."); + + if ($password != $password2) print_error("The passwords that you supplied don't match!"); + + if (!check_string($name)) print_error("The name that you have supplied at Name is not valid, please go back."); + + $result = db_query("SELECT * FROM alias WHERE address='$username'"); + if ($result['rows'] == 1) print_error("This email address already exists, please choose a different one."); + + $result = db_query("INSERT INTO alias (address,goto,domain,create_date,change_date) VALUES('$username','$username','$sessid_domain',NOW(),NOW())"); + if ($result['rows'] != 1) print_error("Unable to add: $username to the alias table!"); + + if ($use_subdir == "YES") $maildir = $sessid_domain . "/" . $maildir; + + if (!empty($quota_table)) { + $result = db_query("INSERT INTO mailbox (username,password,name,maildir,domain,create_date,change_date,$quota_table) VALUES('$username','$passwd','$name','$maildir','$sessid_domain',NOW(),NOW(),'$quota') "); + } else { + $result = db_query("INSERT INTO mailbox (username,password,name,maildir,domain,create_date,change_date) VALUES('$username','$passwd','$name','$maildir','$sessid_domain',NOW(),NOW())"); + } + + if ($result['rows'] == 1) { + $headers = "From: $sessid_username"; + $subject = "Welcome"; + $message = "Hi $name,\n\nWelcome to your new email account.\n\n"; + print "$username has been added to the mailbox table!\n"; + print "

\n"; + print "NOTE:\n"; + if (!mail($username, $subject, $message, $headers)) { + print "The user needs to first receive an email in order to use the account.
\n"; + } + print "User needs to login with the full email address, in this case: $username\n"; + print "

\n"; + } else { + print_error("Unable to add: $username to the mailbox table!"); + } +} +?> +Create a new local mailbox for your domain. +

+

+ + + + + +\n"; ?> + +
Email:@
Password:Password for POP/IMAP
Password (again): 
Name:Full name
Quota: 
+
+ diff --git a/main.php b/main.php new file mode 100644 index 00000000..993056ab --- /dev/null +++ b/main.php @@ -0,0 +1,80 @@ +\n"; + +if ($alias_control == "YES") { + $query = "SELECT alias.address,alias.goto,alias.change_date FROM alias WHERE alias.domain='$sessid_domain' ORDER BY alias.address"; +} else { + $query = "SELECT alias.address,alias.goto,alias.change_date FROM alias LEFT JOIN mailbox ON alias.address=mailbox.username WHERE alias.domain='$sessid_domain' AND mailbox.maildir IS NULL ORDER BY alias.address"; +} + +$result = db_query ("$query"); + +if ($result['rows'] > 0) { + print "
\n"; + print "\n"; + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + while ($row = mysql_fetch_array ($result['result'])) { + print ""; + print ""; + print ""; + print ""; + print ""; + print ""; + print "\n"; + } + print "
FromToLast Modified 
" . $row['address'] . "" . ereg_replace (",", "
", $row['goto']) . "
" . $row['change_date'] . "editdel
\n"; + print "
\n"; + print "

\n"; +} else { + print "Nothing Found in the Alias Table!\n"; + print "

\n"; +} + +$query = "SELECT * FROM mailbox WHERE domain='$sessid_domain' ORDER BY username"; + +$result = db_query ("$query"); + +if ($result['rows'] > 0) { + print "

\n"; + print "\n"; + print ""; + print ""; + print ""; + if (!empty($quota_table)) print ""; + print ""; + print ""; + print ""; + while ($row = mysql_fetch_array ($result['result'])) { + print ""; + print ""; + print ""; + if (!empty($quota_table)) print ""; + print ""; + print ""; + print ""; + print "\n"; + } + print "
EmailNameQuotaLast Modified 
" . $row['username'] . "" . $row['name'] . "" . $row[$quota_table] . "" . $row['change_date'] . "editdel
\n"; + print "
\n"; + print "

\n"; +} else { + print "Nothing Found in the Mailbox Table!\n"; + print "

\n"; +} +print_footer(); +?> diff --git a/modify.php b/modify.php new file mode 100644 index 00000000..9da03187 --- /dev/null +++ b/modify.php @@ -0,0 +1,63 @@ +To:.", "YES", "MENU"); + + $goto = preg_replace('/\r\n/', ',', $goto); + $goto = preg_replace('/\,*$/', '', $goto); + $array = preg_split('/,/', $goto); + for ($i = 0; $i < sizeof($array); $i++) { + if (in_array("$array[$i]", $default_aliases)) continue; + if (empty($array[$i])) continue; + if (!check_email($array[$i])) print_error("The email address $array[$i] is not a valid email address, please go back.", "YES", "MENU"); + } + $result = db_query("UPDATE alias SET goto='$goto', change_date=NOW() WHERE address='$modify' AND domain='$sessid_domain'"); + if ($result['rows'] == 1) { + header("Location: $url"); + } else { + print_error("Unable to update: $address -> $goto in the alias table!", "YES", "MENU"); + } +} + +$query = "SELECT * FROM alias WHERE address='$modify' AND domain='$sessid_domain'"; +$result = db_query("$query"); +if ($result['rows'] == 1) { + $row = mysql_fetch_array ($result['result']); +} else { + print_error("Unable to find the alias!","YES", "MENU"); +} +print_header("menu"); +?> +Change an alias for your domain. +

+

+ + + + + + +
Alias:
 
Enter your email aliases below. One per line!
To:
+
+ diff --git a/my_lib.php b/my_lib.php new file mode 100644 index 00000000..e5586f5d --- /dev/null +++ b/my_lib.php @@ -0,0 +1,348 @@ +]*?>.*?'si", + "'<[\/\!]*?[^<>]*?>'si", + "'\''i"); + + $replace = array ("", + "", + ""); + + $escaped = preg_replace ($search, $replace, $var); + return $escaped; +} + + + +// +// check_email +// Action: Checks if email is valid and returns TRUE if this is the case. +// Call: check_email(string email) +// +function check_email($email) { + return (preg_match('/^[-!#$%&\'*+\\.\/0-9=?A-Z^_{|}~]+' . '@' . '([-0-9A-Z]+\.)+' . '([0-9A-Z]){2,4}$/i', trim($email))); +} + + + +// +// md5crypt +// Action: Creates an MD5 passwd that is readable by FreeBSD daemons +// Call: md5crypt(string cleartextpasswd) +// + +$MAGIC = "$1$"; +$ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +function md5crypt($pw, $salt="", $magic="") { + global $MAGIC; + if ($magic == "") $magic = $MAGIC; + if ($salt == "") $salt = create_salt(); + $slist = explode("$", $salt); + if ($slist[0] == "1") $salt = $slist[1]; + $salt = substr($salt, 0, 8); + $ctx = $pw . $magic . $salt; + $final = hex2bin(md5($pw . $salt . $pw)); + for ($i=strlen($pw); $i>0; $i-=16) { + if ($i > 16) + $ctx .= substr($final,0,16); + else + $ctx .= substr($final,0,$i); + } + $i = strlen($pw); + while ($i > 0) { + if ($i & 1) $ctx .= chr(0); + else $ctx .= $pw[0]; + $i = $i >> 1; + } + $final = hex2bin(md5($ctx)); + for ($i=0;$i<1000;$i++) { + $ctx1 = ""; + if ($i & 1) $ctx1 .= $pw; + else $ctx1 .= substr($final,0,16); + if ($i % 3) $ctx1 .= $salt; + if ($i % 7) $ctx1 .= $pw; + if ($i & 1) $ctx1 .= substr($final,0,16); + else $ctx1 .= $pw; + $final = hex2bin(md5($ctx1)); + } + $passwd = ""; + $passwd .= to64( ( (ord($final[0]) << 16) | (ord($final[6]) << 8) | (ord($final[12])) ), 4); + $passwd .= to64( ( (ord($final[1]) << 16) | (ord($final[7]) << 8) | (ord($final[13])) ), 4); + $passwd .= to64( ( (ord($final[2]) << 16) | (ord($final[8]) << 8) | (ord($final[14])) ), 4); + $passwd .= to64( ( (ord($final[3]) << 16) | (ord($final[9]) << 8) | (ord($final[15])) ), 4); + $passwd .= to64( ( (ord($final[4]) << 16) | (ord($final[10]) << 8) | (ord($final[5])) ), 4); + $passwd .= to64( ord($final[11]), 2); + return "$magic$salt\$$passwd"; +} +function create_salt() { + srand((double)microtime()*1000000); + $salt = substr(md5(rand(0,9999999)), 0, 8); + return $salt; +} +function hex2bin($str) { + $len = strlen($str); + $nstr = ""; + for ($i=0;$i<$len;$i+=2) { + $num = sscanf(substr($str,$i,2), "%x"); + $nstr.=chr($num[0]); + } + return $nstr; +} +function to64($v, $n) { + global $ITOA64; + $ret = ""; + while (($n - 1) >= 0) { + $n--; + $ret .= $ITOA64[$v & 0x3f]; + $v = $v >> 6; + } + return $ret; +} + + + +// +// print_header +// Action: Prints out the default header for every page +// Call: print_header([string title]) +// +function print_header($menu = "", $title = "", $welcome = "NO") { + if (empty($title)) global $title; + global $welcome_header; + header("Expires: Sun, 16 Mar 2003 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + print "\n"; + print "\n"; + print "\n"; + print "\n"; + if (file_exists(realpath("./stylesheet.css"))) print "\n"; + if (file_exists(realpath("../stylesheet.css"))) print "\n"; + print "$title\n"; + print "\n"; + print "\n"; + print "
\n"; + if ($welcome == "YES") { + print "

$welcome_header

\n"; + print "

\n"; + } + if ($menu == "admin") print_admin_menu(); + if ($menu == "menu") print_menu(); +} + + + +// +// print_footer +// Action: Prints out the default footer for every page +// Call: print_footer() +// +function print_footer($hr = "YES") { + global $version; + global $show_organization_info; + global $organization_link; + global $organization_name; + global $show_postfix_admin_info; + if ($hr == "YES") print "


\n"; + print "

\n"; + if ($show_postfix_admin_info == "YES") print "$version
\n"; + if (($show_organization_info == "YES") AND !empty($organization_link)) print "

Back to $organization_name

\n"; + print "

\n"; + print "
\n"; + print "\n"; + print "\n"; +} + + + +// +// print_menu +// Action: Prints out the requirement menu bar +// Call: print_menu() +// +function print_menu() { + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "
 \n"; + print "Overview"; + print " \n"; + print "Add Alias"; + print " \n"; + print "Add Mailbox"; + print " \n"; + print "Send Email"; + print " \n"; + print "Passwd"; + print " \n"; + print "Logout"; + print " 
\n"; + print "
\n"; +} + + + +// +// print_admin_menu +// Action: Prints out the requirement admin menu bar +// Call: print_admin_menu() +// +function print_admin_menu() { + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "\n"; + print "
 \n"; + print "Admin List"; + print " \n"; + print "Domain List"; + print " \n"; + print "Virtual List"; + print " \n"; + print "Send Email"; + print " \n"; + print "New Admin"; + print " \n"; + print "New Domain"; + print " 
\n"; + print "
\n"; +} + + + +// +// print_error +// Action: Prints an error message and exits/dies +// Call: print_error(string error message); +// +function print_error($msg, $header = "NO", $menu = "", $hr = "NO") { + if ($header == "YES") print_header(); + if ($menu == "ADMIN") print_admin_menu(); + if ($menu == "MENU") print_menu(); + if ($hr == "YES") print "
\n"; + print "

\n"; + print "$msg\n"; + print "

\n"; + print_footer(); + exit; +} + + + +// +// db_connect +// Action: Makes a connection to the database if it doesn't exist +// Call: db_connect() +// +function db_connect() { + global $db_host; + global $db_name; + global $db_user; + global $db_pass; + $link = mysql_connect($db_host, $db_user, $db_pass) or print_error("Could not connect to database server: $db_host."); + $succes = mysql_select_db($db_name, $link) or print_error("Could not select database: $db_name."); + return $link; +} + + + +// +// db_query +// Action: Sends a query to the database and returns query result and number of rows +// Call: db_query(string query) +// +function db_query($query) { + $link = db_connect(); + $result = mysql_query($query, $link) or print_error("Could not query the table.
", "NO"); + // if $query was a select statement check the number of rows with mysql_num_rows(). + if (eregi("^select", $query)) { + $number_rows = mysql_num_rows($result); + // if $query was something else, UPDATE, DELETE or INSERT check the number of rows with + // mysql_affected_rows(). + } else { + $number_rows = mysql_affected_rows($link); + } + $return = array ( + "result" => $result, + "rows" => $number_rows + ); + return $return; +} +?> diff --git a/passwd.php b/passwd.php new file mode 100644 index 00000000..e3646227 --- /dev/null +++ b/passwd.php @@ -0,0 +1,58 @@ +\n"; + print "Login\n"; + print_footer(); + exit; + } else { + print_error("Unable to update your password!"); + } +} +?> +Change your password. +

+

+ + + + + + +
Login:
Current Password:
New Password:
New Password (again):
+
+ diff --git a/pwd.php b/pwd.php new file mode 100644 index 00000000..b6f7b873 --- /dev/null +++ b/pwd.php @@ -0,0 +1,44 @@ + +Change password. +

+

+ + + + + +
Login:
New Password:
New Password (again):
+
+ diff --git a/sendmail.php b/sendmail.php new file mode 100644 index 00000000..56adc14e --- /dev/null +++ b/sendmail.php @@ -0,0 +1,45 @@ +\n"; +} +?> +Send test message to a new mailbox. +

+

+ + + +
From:
To: + +
+
+ diff --git a/stylesheet.css b/stylesheet.css new file mode 100644 index 00000000..c1b6f3e8 --- /dev/null +++ b/stylesheet.css @@ -0,0 +1,184 @@ +body { + background: white; + color: black; + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 13px; + margin: 8px; + padding: 0px; + text-align: center; +} + +h1 { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 18px; + font-weight: bold; + margin-bottom: 0px; + margin-top: 20px; +} + +a { + color: blue; + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + text-decoration: none; +} + +a:visited { + color: blue; + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + text-decoration: none; +} + +hr { + line-heigt: 1px; + margin-top: 10px; + margin-bottom: 10px; + width: 640px; + text-align: center; +} + +hr.footer { + margin-top: 10px; + margin-bottom: 0px; + width: 640px; +} + +p { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 13px; + margin-top: 13px; + text-align: center; +} + +p.footer { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + margin-bottom: 0px; + margin-top: 5px; + text-align: center; +} + +p.error { + color: red; + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 13px; + text-align: center; +} + +table { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + width: 640px; + text-align: left; + margin-top: 0px; + margin-bottom: 0px; + padding-top: 0px; +} + +table.auto { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + width: auto; + text-align: left; + margin-top: 0px; + margin-bottom: 0px; + padding-top: 0px; +} + + +table.form { + font-size: 11px; + padding-left: 0px; + padding-right: 0px; + text-align: left; + width: auto; +} + +td { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + padding-left: 5px; + padding-right: 5px; +} + +tr.header { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-weight: bold; + padding-left: 11px; + padding-right: 11px; +} + +td.center { + text-align: center; +} + +td.header { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-weight: bold; + padding-left: 11px; + padding-right: 11px; + text-align: center; +} + +td.highlight { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + background: rgb(169,194,162); + padding-left: 5px; + padding-right: 5px; +} + +td.menu { + text-align: center; + background: #dfdfdf; + border-top: 1px solid #999; + border-right: 1px solid #999; + border-left: 1px solid #999; + border-bottom: 1px solid #999; + border-radius: 2px; + -moz-border-radius: 2px; + padding-bottom: 5px; + padding-top: 5px; + +} + +td.right { + text-align: center; + padding-left: 0px; + padding-right: 0px; + margin-left: 0px; + margin-right: 0px; +} + +input { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; + float: none; + clear: none; +} + +input.button { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + background: rgb(232,236,176) +} + +textarea { + font-family: Verdana; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 11px; +} diff --git a/vacation.php b/vacation.php new file mode 100644 index 00000000..36f48ceb --- /dev/null +++ b/vacation.php @@ -0,0 +1,78 @@ +$form_login does not exist!", "", "", "YES"); + + $result = db_query("SELECT password FROM mailbox WHERE username='$form_login'"); + if ($result['rows'] == 1) { + $row = mysql_fetch_array($result['result']); + $db_passwd = $row['password']; + $keys = preg_split('/\$/', $row['password']); + $checked_passwd = md5crypt($form_passwd, $keys[2]); + + $result = db_query("SELECT * FROM mailbox WHERE username='$form_login' AND password='$checked_passwd' AND active='1'"); + if ($result['rows'] != 1) print_error("The password that you have entered is not correct!", "", "", "YES"); + } + + $result = db_query("SELECT email FROM vacation WHERE email='$form_login'"); + if ($result['rows'] == 0 and !empty($_POST['cancel'])) print_error("Unable to cancel your \"Out of the Office\" message! (vacation)", "", "", "YES"); + if ($result['rows'] == 1 and empty($_POST['cancel'])) print_error("There is already an \"Out of the Office\" message present! (vacation)", "", "", "YES"); + + $result = db_query("SELECT goto FROM alias WHERE address='$form_login'"); + if ($result['rows'] == 1) { + $row = mysql_fetch_array($result['result']); + if (!empty($_POST['cancel'])) { + $db_goto = preg_replace("/,$vacation_email/", "", $row['goto']); + } else { + $db_goto = $row['goto'] . ",$vacation_email"; + } + } else { + if ($result['rows'] != 1) print_error("Unable collect your data! (alias)", "", "", "YES"); + } + + $result = db_query("UPDATE alias SET goto='$db_goto', change_date=NOW() WHERE address='$form_login'"); + if ($result['rows'] != 1) print_error("Unable to create your \"Out of the Office\" message! (alias)", "", "", "YES"); + + if (!empty($_POST['cancel'])) $result = db_query("DELETE FROM vacation WHERE email='$form_login'"); + if (!empty($_POST['submit'])) $result = db_query("INSERT INTO vacation (email,subject,body) VALUES('$form_login', '$form_subject', '$form_body')"); + if ($result['rows'] == 1) { + print "
\n"; + if (!empty($_POST['cancel'])) print "Your \"Out of the Office\" message is removed!\n"; + if (!empty($_POST['submit'])) print "Your \"Out of the Office\" message is active!\n"; + print_footer(); + exit; + } else { + print_error("Unable create your \"Out of the Office\" message! (vacation)", "", "", "YES"); + } +} +?> +Out of the Office. +

+

+ + + + + + +
Email:
Password:
Subject:
Body:
+ + +
+
+ diff --git a/vcp.php b/vcp.php new file mode 100644 index 00000000..c2835c6f --- /dev/null +++ b/vcp.php @@ -0,0 +1,56 @@ +\n"; + print "Your password has been updated!\n"; + print_footer(); + exit; + } else { + print_error("Unable to update your password!", "", "", "YES"); + } +} +?> +Change your mailbox password. +

+

+ + + + + + +
Email:
Current Password:
New Password:
New Password (again):
+
+