- 1 min read
I can't seem to find this on Beatport or iTunes to buy :(
I can't seem to find this on Beatport or iTunes to buy :(
Sometimes, I feel like people give Java a bad rap. It's a language that's in demand out on the field and after using it for my school classes, I have found that it is easy to program in, it performs very well and your code is portable/cross-platform. Sounds great, right? At first glance Java magically turns everything that's hard to do into something easy.
But then I'll use Java-based software and somehow it manages to consistently be extremely complex to setup/configure (ie, tomcat+webapps), to have horrible looking UIs (ie, LimeWire, FreeNode, OpenOffice) and often it consumes lots of resources needlessly (ie, OpenOffice, LimeWire). I mean seriously, you can get a PHP or Python-enabled webserver up and running in under 2 commands on RHEL and then you just need to create a single script file to start serving pages. The Java community really needs to spend some time working on developer/user experience in my opinion.
I've always cared a lot about user experience because I find that no matter how great a single piece or a collection of software is, it is the user experience that shapes your impression of that software. That said, I was cleaning up my (very, very messy) desktop and came across an old backup of a Fedora installation. There was a file on my desktop from July 2008 that I had completely forgotten about... I had collected my thoughts at the time on how Linux-based distributions could be improved to make the user experience better. It's really neat to see how many of these have been implemented in only 3 short years:
my apologies for the messy read, I tend to write my notes in Wiki format and I don't feel like copy/pasteing
* The Linux installation+boot process
* Installers must try to recognize an existing Linux installation's boot
configuration and add theirs to it, not overwrite the previous one.
* Be able to partition (read: resize) other filesystems intelligently and
efficiently.
* Provide installation profiles. Stop fighting over what packages or
configurations to use and realize that a server, an enterprise and the
typical user all have different sets of expectations and needs.
* GRUB should have an extendable plug-in system where distributions could
plug-in modules to have it suggest which partitions to boot from (ie
distribution auto-find)
* Graphical bootup: X in initramfs. Ubuntu does this already, it's an excellent
idea and gives the user a better overall experience.
* Standardizing the Desktop
* User accounts
* Unix names are confusing to users. Have the system map metanames to Unix
names so that people can login with e.g. "firewing" or "Stewart Adam"
* Allow the administrator to create user groups and define their privileges.
User accounts belong to one or more groups which defines what they can do.
* User control is easy and at the same time they can be given needed
privileges (software updates, mounting drives, etc) without having to
know the root password.
* Unified package management. Create a standard for both package managers and
packaging. This enables large, cross-distro compatible repositories that
benefit the users.* User experience
* Prompt the user for backups once a week. Include a don't show me again
option.
* Why can't we configure tapping on a per-user basis again? Right, xorg.conf.
* Developers
* Need to accept and handle user feedback. Although Linux is used by a lot of
developers, most of the users are non-developers users. It would make
sense to prioritize what they have to say.
* Kernel
* If a device is present but isn't supported, provide a signal so that the
desktop environment can present a dialog explaining the problem and showing
the user what they can do to help.
* More specifically, reporting the device IDs and collecting common log
files.
* Create "FooKits" for helping monitor and solve common problems. Power usage,
kernel oopses, SELinux, etc.
* Reload parts of the kernel without rebooting (just improve kmem)
* Documentation
* Don't leave users out in the cold. They shouldn't have to do a day of
research to get the OS installed or to perform simple tasks. Provide
tooltips and help buttons inside programs.
* Dumbing down doesn't solve much. The best type of documentation is easy
to understand but contains technical information at the same time.
* Other
* Interfaces need to be somewhat standardized and resemble each other in
nature. They overall goal is that programs should be intuitive -
Documentation should accompany a program, but the interface should be
intuitive enough that users shouldn't have to read it to get started.
* Something nice for the help menu layout:
- Documentation
- Check for Updates (this would use the standardized package manager)
- Report a bug
- Help translate this program
- About this program
* Synchronize user information (ie, UID/GIDs) between various distributions.
* There needs to be an easy communication channel between developers/
packagers and users so that they are encouraged to help out. Testing and
providing feedback and bug reporting and bug sorting/solving is not hard but
goes a long way in helping the developers troubleshoot problems.
This how-to will show you how to configure:
By default, the Apache web server runs as the 'apache' user. This is good because a successful attack on the web server will only cause limited damage to the system, as they do not have root access. However on a shared servers which hosts multiple websites, an attack can still be very dangerous because the 'apache' user is used by all of the websites. Thus, an attack on one website can potentially grant access to any other websites that are being hosted on the system!
Privilege separation is a technique that can be used to mitigate the risk of an attack against a shared hosting server. By allowing webpages to be served by different users, each host can be assigned its own user and so the damage from a site hack will be limited to just that account and not all of the accounts hosted on the system. We will be using the ITK Apache MPM to achieve this.
Unfortunately, the ITK MPM is not included in the stock httpd distribution. Fortunately, it is relatively easy to add it. Run this as your regular user to install the httpd source RPM and download the ITK patch sources:
yumdownloader --source httpd
rpm -i httpd*.src.rpm
rm httpd*.src.rpm
cd ~/rpmbuild/SOURCES
wget http://mpm-itk.sesse.net/apache2.2-mpm-itk-2.2.17-01/{02-rename-prefork-to-itk.patch,03-add-mpm-to-build-system.patch,04-correct-output-makefile-location.patch,05-add-copyright.patch,06-hook-just-after-merging-perdir-config.patch,07-base-functionality.patch,08-max-clients-per-vhost.patch,09-capabilities.patch,10-nice.patch,11-fix-htaccess-reads-for-persistent-connections.patch}
With that done, let's make a few quick modifications to the RPM spec file located at ~/rpmbuild/SPECS/httpd.spec
.
We will first need to add the ITK patches. Find the last patch line in the RPM spec file, for example, Patch202: httpd-2.2.3-deflate2215.patch
, then add after it:
# ITK MPM
Patch802: 02-rename-prefork-to-itk.patch
Patch803: 03-add-mpm-to-build-system.patch
Patch804: 04-correct-output-makefile-location.patch
Patch805: 05-add-copyright.patch
Patch806: 06-hook-just-after-merging-perdir-config.patch
Patch807: 07-base-functionality.patch
Patch808: 08-max-clients-per-vhost.patch
Patch809: 09-capabilities.patch
Patch810: 10-nice.patch
Now in the %pre
section - after the Red Hat patches are applied - add the following lines:
# ITK MPM
mkdir server/mpm/experimental/itk/
cp -d --preserve=all server/mpm/prefork/* server/mpm/experimental/itk/
mv server/mpm/experimental/itk/prefork.c server/mpm/experimental/itk/itk.c
%patch802 -p1 -b .mpm02
%patch803 -p1 -b .mpm03
%patch804 -p1 -b .mpm04
%patch805 -p1 -b .mpm05
%patch806 -p1 -b .mpm06
%patch807 -p1 -b .mpm07
%patch808 -p1 -b .mpm08
%patch809 -p1 -b .mpm09
%patch810 -p1 -b .mpm10
This will copy the (fully Red Hat-patched) prefork MPM code into a new folder and then apply the ITK patches.
In the %build
section of the spec file, you will see these lines:
# For the other MPMs, just build httpd and no optional modules
mpmbuild worker --enable-modules=none
mpmbuild event --enable-modules=none
Following the same format, add a line for the ITK MPM:
mpmbuild itk --enable-modules=none
Similarly, we find in the %install
section:
install -m 755 worker/httpd $RPM_BUILD_ROOT%{_sbindir}/httpd.worker
install -m 755 event/httpd $RPM_BUILD_ROOT%{_sbindir}/httpd.event
Add a line for the ITK MPM:
install -m 755 itk/httpd $RPM_BUILD_ROOT%{_sbindir}/httpd.itk
Last of all, in the %check
section there is another section were we need to add in the ITK MPM:
# Verify that the same modules were built into the httpd binaries
./prefork/httpd -l | grep -v prefork > prefork.mods
for mpm in worker; do
Change the middle line to read:
for mpm in worker event itk
We are now ready to rebuild our ITK-enabled httpd package.
cd ~/rpmbuild/SPECS
rpmbuild -ba httpd.spec
Install the dependencies listed and then re-run the rpmbuild again if necessary. After the build has finished, install your new RPMs:
rpm -Uhv ../RPMS/[arch]/{httpd,mod_ssl}-2*.rpm
Remember to replace [arch] in the second to last command with the appropriate value (most probably i686 for 32-bit machines or x86_64 for 64-bit machines). The last step is to set the httpd worker to the ITK binary by editing /etc/sysconfig/httpd
and adding after the line #HTTPD=/usr/sbin/httpd.worker
:
HTTPD=/usr/sbin/httpd.itk
Let's install PHP, AWStats and a few other useful add-ons:
yum install mod_security php php-{suhosin,mysql,mcrypt,mhash,gd} awstats
Keep in mind that by default, mod_security's rules are very restrictive and you will almost certainly need to tweak them. Be sure to test thoroughly before enabling mod_security on your live server. You can modify the mod_security settings by editing the rule files in /etc/httpd/modsecurity.d
.
You will find in a stock installation, no indexes are permitted at all and that visiting localhost displays the standard CentOS test page. This can be changed by editing /etc/httpd/conf.d/welcome.conf
and comment out the entire LocationMatch
section:
#
# Options -Indexes
# ErrorDocument 403 /error/noindex.html
#
Next, let's add a small configuration file with some custom settings. Below we will try and prevent information leaks by restricting access to backup files (files ending with a "~"), .sql and .inc plaintext files which could potentially reveal critical information about how a site functions. We will also add a one-line include directive so that all files in the virtual hosts configuration directory get included as well.
mkdir /etc/httpd/vhosts.d
cat << EOF > /etc/httpd/conf.d/z_custom.conf
# named with a z_ prefix to ensure this is parsed last.# Restrict access to sensitive file extensions that shouldn't be read via a
# browser: *~ for temp/backup files, *.sql, *.inc as it isn't registered as
# a php file.
Order allow,deny
Deny from all
# Include our VirtualHost configurations
Include vhosts.d/*.conf
EOF
The following will initialize a template VirtualHost configuration that can be used by a script to automate the installation of new VirtualHosts:
cat << EOF > /etc/httpd/vhosts.d/sample.conf
#
# ServerName \$WEBDOMAIN
# ServerAdmin webmaster@\$DOMAIN
# ServerAlias \$DOMAIN_ALIASES
# DocumentRoot /home/\$USER/web/public_html
# ErrorLog /home/\$USER/web/logs/error_log
# CustomLog /home/\$USER/web/logs/access_log combined
# php_admin_flag log_errors on
# php_admin_value error_log /home/\$USER/web/php_error_log
#
# php_admin_value session.save_path "/var/lib/php/session/\$USER"
# AssignUserId \$USER \$USER
#
#
# Options FollowSymLinks
# AllowOverride FileInfo AuthConfig Limit Indexes Options
#
#
EOF
Similarly, we will setup a awstats template that can create configuration via a script:
cat << EOF > /etc/awstats/awstats.model_custom.conf
LogFile="/home/\$USER/web/logs/access_log"
LogType=W
LogFormat=1
LogSeparator=" "SiteDomain="\$WEBDOMAIN"
HostAliases="\$DOMAIN_ALIASES"
DNSLookup=2
DirData="/var/lib/awstats"
DirCgi="/awstats"
DirIcons="/awstatsicons"
AllowToUpdateStatsFromBrowser=0
AllowFullYearView=3
EnableLockForUpdate=1
DNSStaticCacheFile="dnscache.txt"
DNSLastUpdateCacheFile="dnscachelastupdate.txt"
SkipDNSLookupFor=""
AllowAccessFromWebToAuthenticatedUsersOnly=1
AllowAccessFromWebToFollowingAuthenticatedUsers="\$USER"
AllowAccessFromWebToFollowingIPAddresses=""
CreateDirDataIfNotExists=0
BuildHistoryFormat=text
BuildReportFormat=html
SaveDatabaseFilesWithPermissionsForEveryone=0
PurgeLogFile=0
ArchiveLogRecords=0
KeepBackupOfHistoricFiles=0
DefaultFile="index.html"
SkipHosts="127.0.0.1"
SkipUserAgents=""
SkipFiles=""
SkipReferrersBlackList=""
OnlyHosts=""
OnlyUserAgents=""
OnlyUsers=""
OnlyFiles=""
NotPageList="css js class gif jpg jpeg png bmp ico rss xml swf"
ValidHTTPCodes="200 304"
ValidSMTPCodes="1 250"
AuthenticatedUsersNotCaseSensitive=0
URLNotCaseSensitive=0
URLWithAnchor=0
URLQuerySeparators="?;"
URLWithQuery=0
URLWithQueryWithOnlyFollowingParameters=""
URLWithQueryWithoutFollowingParameters=""
URLReferrerWithQuery=0
WarningMessages=1
ErrorMessages=""
DebugMessages=0
NbOfLinesForCorruptedLog=50
WrapperScript="/awstats"
DecodeUA=0
MiscTrackerUrl="/js/awstats_misc_tracker.js"
LevelForBrowsersDetection=2
LevelForOSDetection=2
LevelForRefererAnalyze=2
LevelForRobotsDetection=2
LevelForSearchEnginesDetection=2
LevelForKeywordsDetection=2
LevelForFileTypesDetection=2
LevelForWormsDetection=0
UseFramesWhenCGI=1
DetailedReportsOnNewWindows=1
Expires=3600
MaxRowsInHTMLOutput=1000
Lang="auto"
DirLang="./lang"
ShowMenu=1
ShowSummary=UVPHB
ShowMonthStats=UVPHB
ShowDaysOfMonthStats=VPHB
ShowDaysOfWeekStats=PHB
ShowHoursStats=PHB
ShowDomainsStats=PHB
ShowHostsStats=PHBL
ShowAuthenticatedUsers=0
ShowRobotsStats=HBL
ShowWormsStats=0
ShowEMailSenders=0
ShowEMailReceivers=0
ShowSessionsStats=1
ShowPagesStats=PBEX
ShowFileTypesStats=HB
ShowFileSizesStats=0
ShowOSStats=1
ShowBrowsersStats=1
ShowScreenSizeStats=0
ShowOriginStats=PH
ShowKeyphrasesStats=1
ShowKeywordsStats=1
ShowMiscStats=a
ShowHTTPErrorsStats=1
ShowSMTPErrorsStats=0
ShowClusterStats=0
AddDataArrayMonthStats=1
AddDataArrayShowDaysOfMonthStats=1
AddDataArrayShowDaysOfWeekStats=1
AddDataArrayShowHoursStats=1
IncludeInternalLinksInOriginSection=0
MaxNbOfDomain = 10
MinHitDomain = 1
MaxNbOfHostsShown = 10
MinHitHost = 1
MaxNbOfLoginShown = 10
MinHitLogin = 1
MaxNbOfRobotShown = 10
MinHitRobot = 1
MaxNbOfPageShown = 10
MinHitFile = 1
MaxNbOfOsShown = 10
MinHitOs = 1
MaxNbOfBrowsersShown = 10
MinHitBrowser = 1
MaxNbOfScreenSizesShown = 5
MinHitScreenSize = 1
MaxNbOfWindowSizesShown = 5
MinHitWindowSize = 1
MaxNbOfRefererShown = 10
MinHitRefer = 1
MaxNbOfKeyphrasesShown = 10
MinHitKeyphrase = 1
MaxNbOfKeywordsShown = 10
MinHitKeyword = 1
MaxNbOfEMailsShown = 20
MinHitEMail = 1
FirstDayOfWeek=1
ShowFlagLinks=""
ShowLinksOnUrl=1
UseHTTPSLinkForUrl=""
MaxLengthOfShownURL=64
HTMLHeadSection=""
HTMLEndSection=""
Logo="awstats_logo6.png"
LogoLink="http://awstats.sourceforge.net"
BarWidth = 260
BarHeight = 90
StyleSheet=""
color_Background="FFFFFF" # Background color for main page (Default = "FFFFFF")
color_TableBGTitle="CCCCDD" # Background color for table title (Default = "CCCCDD")
color_TableTitle="000000" # Table title font color (Default = "000000")
color_TableBG="CCCCDD" # Background color for table (Default = "CCCCDD")
color_TableRowTitle="FFFFFF" # Table row title font color (Default = "FFFFFF")
color_TableBGRowTitle="ECECEC" # Background color for row title (Default = "ECECEC")
color_TableBorder="ECECEC" # Table border color (Default = "ECECEC")
color_text="000000" # Color of text (Default = "000000")
color_textpercent="606060" # Color of text for percent values (Default = "606060")
color_titletext="000000" # Color of text title within colored Title Rows (Default = "000000")
color_weekend="EAEAEA" # Color for week-end days (Default = "EAEAEA")
color_link="0011BB" # Color of HTML links (Default = "0011BB")
color_hover="605040" # Color of HTML on-mouseover links (Default = "605040")
color_u="FFAA66" # Background color for number of unique visitors (Default = "FFAA66")
color_v="F4F090" # Background color for number of visites (Default = "F4F090")
color_p="4477DD" # Background color for number of pages (Default = "4477DD")
color_h="66DDEE" # Background color for number of hits (Default = "66DDEE")
color_k="2EA495" # Background color for number of bytes (Default = "2EA495")
color_s="8888DD" # Background color for number of search (Default = "8888DD")
color_e="CEC2E8" # Background color for number of entry pages (Default = "CEC2E8")
color_x="C1B2E2" # Background color for number of exit pages (Default = "C1B2E2")
LoadPlugin="geoip GEOIP_STANDARD /var/lib/GeoIP/GeoIP.dat"
ExtraTrackedRowsLimit=500
EOF
(For those wondering, this template was derived by removing all comments from the stock awstats model configuration at /etc/awstats/awstats.model.conf and then substituting important values such as SiteDomain with variables so that can be easily changed via sed
)
Because the web server will be using the ITK MPM, each site's requests will be run under its respective owner and group. This causes problems with the standard PHP session setup, which uses one directory owned by "apache" for all session information. To solve this, a session directory will need to be created for each user. Let's create the default one for the apache user now:
chown root.apache /var/lib/php/session
chmod 0771 /var/lib/php/session
mkdir -m 0770 /var/lib/php/session/apache
chown root.apache /var/lib/php/session/apache
You are now ready to add VirtualHosts for your domains. See Adding a new web hosting account section of Administering the server below for more information on how to do so.
The last step is to add the firewall port exceptions for http/https and start Apache:
chkconfig httpd on
service httpd start
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
service iptables save
We will now be configuring /etc/php.d/suhosin.ini. Because suhosin can interfere with some site's functionality, it is best to enable it in simulation mode so that errors are logged and you can configure suhosin accordingly:
suhosin.simulation = On
Limit the maximum PHP memory_limit that scripts can set:
suhosin.memory_limit = 512M
Lastly, disable session encryption (breaks roundcubemail if enabled):
suhosin.session.encrypt = Off
Let's rebuild roundcubemail 0.3 for CentOS 5:
su - myusername
cd ~/rebuilds
fedpkg clone -a roundcubemail
cd roundcubemail
fedpkg switch-branch el6
Edit the spec file roundcubemail.spec
and comment the line Requires: php-pear-Mail-mimeDecode
so that it reads #Requires: php-pear-Mail-mimeDecode
. This is the only change required for CentOS 5, so let's build the package now:
fedpkg local
exit
yum install --nogpgcheck /home/myusername/rebuilds/roundcubemail/noarch/roundcubemail*.rpm
Now it is time to configure roundcubemail. In the file /etc/httpd/conf.d/roundcubemail.conf, add after the line
:
AssignUserId apache apache
As well, by default roundcubemail denies access to any non-localhost clients. To change this, comment out Deny from all
and add Allow from all
:
Order Deny,Allow
#Deny from all
Allow from 127.0.0.1
Allow from all
We will also need to create the roundcubemail database. If you have not setup your database server, follow the database tutorial now.
CREATE DATABASE roundcubemail;
USE roundcubemail;
source /usr/share/doc/roundcubemail-0.3.1/SQL/mysql.initial.sql
GRANT ALL PRIVILEGES ON roundcubemail.* TO 'roundcubemail'@'localhost' IDENTIFIED BY 'random-password';
FLUSH PRIVILEGES;
Then configure /etc/roundcubemail accordingly. You should now be able to access the webmail portal at http://www.yourdomain.com/roundcubemail.
PHP 5.2 is available in the CentOS 5 Testing repository. Certain PHP add-ons, such as php-suhosin, have not been rebuilt so you will need to do that manually.
rpm -e php-suhosin
yum --enablerepo=c5-testing update php\*
Now rebuild and reinstall php-suhosin:
yumdownloader --source php-suhosin
rpmbuild --rebuild php-suhosin-*.src.rpm
Now install the resulting php-suhosin binary RPM and then move the configuration file (/etc/php.d/suhosin.ini.rpmsave
) back into place.
PHP 5.3 is available as of CentOS 5.6 the php53 package. Certain PHP add-ons, such as php-suhosin and php-mcrypt have not been rebuilt so you will need to do that manually. This is a bit tricky as you will need to rebuild the php-extras SRPM among others; I'll leave you to figure out the details. See the instructions above for PHP 5.2 for a rough workflow using php-suhosin as an example.
Two 3rd party MPM modules satisfied my need privilege separation in this setup, the ITK MPM and the Peruser MPM. Each has its own advantages and disadvantages.
ITK's approach is to accept a request, determine which virtual host the request belongs to, fork, set the UID and GID of the fork, serve the request and then to terminate the fork after the request has been completed allowing it to fork again later. Because ITK is very simple in nature and functions similarly to the traditional prefork MPM, it is more compatible with modules like mod_ssl, mod_php and mod_python. That said, it is also slower than because of the fork/kill overhead associated with each request.
Peruser, on the other hand, starts a pool of processes that fork only on startup. There are multiplexers than accept requests, determine which virtual host the request belongs to, and then forwards the request to the appropriate process from the pool. This approach incurs practically no overhead as the process from the pool has already been forked and had its UID/GID changed. Peruser is therefore much faster than ITK; its performance is nearly almost on par with that of the traditional prefork MPM! The downfall of Peruser is that because of this connection passing from the multiplexer to a preforked process, it is more complex and has had a long history of bugs and incompatibilities. Experimental support for mod_ssl was only recently added, and upstream development still seems to have a number of bugs to work out. In addition, because Peruser requires at least one process per virtual host for the pool and then requires multiplexers to handle incoming requests, it is much more resource intensive that the ITK MPM, where a process can dynamically handle a request from any virtual host. For these reasons, I favoured ITK over Peruser.
Keep in mind that ITK does have one security flaw. The request headers must be processed in order to determine which virtual host that request belongs to, and therefore which UID/GID ITK would need to switch to. Because of this, all of the header processing is performed as root (albeit with restricted POSIX capabilities). Should someone be able to take advantage of a flaw in Apache or another module (e.g. mod_ssl), the server could theoretically be rooted.
There is an emphasis on theoretically because an attacker would need to be extremely clever (or lucky) to successfully root the server via a flaw in the request processing given the restricted POSIX capabilities. In most cases, that specific apache process would just crash and life goes on. All in all, I still consider the ITK MPM to be more secure than the standard prefork MPM because a much more likely attack on the server is a hacker breaking in through a flaw in one of the hosted website's scripts (e.g. an outdated CMS installation). In this scenario the ITK MPM protects you completely; the potential damage from the attacker is essentially limited to that user's home directory, since each user's scripts run as their own UID/GID. They do not get access to any other user's scripts, nor to any other user's emails.
The following code will setup the "web_domain_add" script which can be used to create the initial website configurations for a hosting users on your server:
cat << EOF > /root/bin/web_domain_add
#!/bin/sh
username=\$1
shift
domain=\$1
shiftif [ -z \$domain ] || [ "\$username" == "-h" ];then
echo "Usage: \$1 user domain [alias1] [alias2] [...]"
exit 1
fi
aliases="\$domain"
for alias in "\$@";do
aliases="\$aliases \$alias www.\$alias "
done
cat /etc/httpd/vhosts.d/sample.conf | \
sed "s/\\\$WEBDOMAIN/www.\${domain}/g" | \
sed "s/\\\$DOMAIN_ALIASES/\${aliases}/g" | \
sed "s/\\\$USER/\${username}/g" | \
sed "s/\\\$DOMAIN/\${domain}/g" | \
sed "s/^#//g" \
> "/etc/httpd/vhosts.d/\${username}.conf"
echo "*** Writing VHost configuration /etc/httpd/vhosts.d/\${username}.conf"
cat /etc/awstats/awstats.model_custom.conf | \
sed "s/\\\$WEBDOMAIN/www.\${domain}/g" | \
sed "s/\\\$DOMAIN_ALIASES/\${aliases}/g" | \
sed "s/\\\$USER/\${username}/g" | \
sed "s/\\\$DOMAIN/\${domain}/g" \
> /etc/awstats/awstats.www.\${domain}.conf
echo "*** Writing AWStats configuration /etc/awstats/awstats.www.\${domain}.conf"
echo "*** Creating password for AWStats"
htpasswd /home/awstats-htpasswd "\$username"
echo "*** Done"
EOF
chmod +x /root/bin/web_domain_add
In order to create the new website configurations, you must also create a new system user that the VirtualHost will be mapped to via ITK. See the SSH+SFTP tutorial for details on how to add a new restricted system user. Once you have done so, you can run the web_domain_add script:
/root/bin/web_domain_add system_username primarydomain.tld
This will initialize a new configuration for the domain primarydomain.tld
mapped to system_username
. You may optionally specify as many domain aliases as needed, for example:
/root/bin/web_domain_add exampleuser example.com example.net example.org
www.example.com will be used as the primary domain, and the script will automatically alias example.com as well as www.example.net, example.net, www.example.org and example.org as domain aliases for www.example.com.
Note: The script does not create any DNS entries for these domains! That task must be performed separately with your DNS provider.
This how-to will show you how to configure:
As this may seem like a bit complex at first, below is a simple overview of how the system works once put together:
Please ensure that you have followed the instructions in the getting started guide here. This tutorial may require you to rebuild RPMs as well as generate SSL certificates and assumes you have the proper environment described in that guide already setup.
If you have not setup the database server yet, please follow the database how-to first.
Before installing dovecot or postfix, the MySQL database which will be used to authenticate all email clients needs to be initialized. Execute this SQL snippet:
CREATE DATABASE mailconfig;
USE mailconfig;
CREATE TABLE `users` (
`userid` varchar(128) NOT NULL,
`domain` varchar(128) NOT NULL,
`password` varchar(255) NOT NULL,
`home` varchar(255) NOT NULL,
`uid` int(11) NOT NULL,
`gid` int(11) NOT NULL,
PRIMARY KEY (`userid`,`domain`)
) DEFAULT CHARSET=utf8 COMMENT='Stores information about active email accounts';CREATE TABLE `aliases` (
`source` varchar(255) NOT NULL,
`destination` varchar(255) NOT NULL,
PRIMARY KEY (`source`)
) DEFAULT CHARSET=utf8 COMMENT='Alias one address to another';
CREATE TABLE `domain_aliases` (
`source` varchar(128) NOT NULL,
`destination` varchar(128) NOT NULL,
PRIMARY KEY (`source`)
) DEFAULT CHARSET=utf8 COMMENT='Alias all addresses in one domain to another';
GRANT SELECT ON mailconfig.users TO 'mailconfig'@'localhost' IDENTIFIED BY 'random-password';
GRANT SELECT ON mailconfig.aliases TO 'mailconfig'@'localhost' IDENTIFIED BY 'random-password';
GRANT SELECT ON mailconfig.domain_aliases TO 'mailconfig'@'localhost' IDENTIFIED BY 'random-password';
FLUSH PRIVILEGES;
Replace random-password
by a long squence of random characters. The mailconfig user will only be used by scripts, so you will not need to remember this password.
Grab dovecot from the repositories:
yum install dovecot
Dovecot is now ready to be configured. Execute the following to configure the Dovecot to allow imap/pop3 access securely with compatibility for some older clients with broken protocol support:
cat << EOF > /etc/dovecot.conf
protocols = imap imaps pop3 pop3s# Log authentication failures
auth_verbose=yes
# Enable me to debug authentication failures
#auth_debug_passwords=yes
#verbose_ssl = yes
# Maildir storage in [HOME]/[DOMAIN]/mail/[USER]
mail_location = maildir:%h/mail/%d/%n
umask = 0077 # 700 permissions
# for compatability with some older clients
pop3_uidl_format = %08Xu%08Xv
imap_client_workarounds = delay-newmail outlook-idle netscape-eoh
pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
# Increases performance
maildir_copy_with_hardlinks = yes
# Enable SSL/TLS
ssl_disable = no
ssl_cert_file = /etc/pki/dovecot/certs/dovecot.pem
ssl_key_file = /etc/pki/dovecot/private/dovecot.pem
# disable insecure ciphers
ssl_cipher_list = ALL:!LOW:!SSLv2
# Force STARTTLS on non-secure protocols; users *must* have either secure auth
# (CRAM-MD5) or SSL enabled (or both)
disable_plaintext_auth = yes
login_process_per_connection = yes
# Keep username case-sensitive (otherwise delivery to local users with uppercase letters fails
auth_username_format = %u
auth default {
mechanisms = plain login cram-md5
passdb sql {
args = /etc/dovecot-mysql.conf
}
# Indicate that we want to prefetch userdb information from the passdb
userdb prefetch {
}
# The userdb below is used only by deliver.
userdb sql {
args = /etc/dovecot-mysql.conf
}
# Offer SASL auth services
socket listen {
client {
path = /var/run/dovecot/auth-client
mode = 0660
user = dovecot
group = mail # Postfix running as this user
}
master {
path = /var/run/dovecot/auth-master
mode = 0660
user = dovecot
group = mail # User running deliver
}
}
}
protocol lda {
postmaster_address = postmaster@yourdomain.com
auth_socket_path = /var/run/dovecot/auth-master
}
EOF
Once again, substitute yourdomain.com
and the SSL certification/key file locations accordingly. You'll notice we reference the non-existent file /etc/dovecot-mysql.conf
in this configuration. Let's create that now so Dovecot has access to the MySQL database:
cat << EOF > /etc/dovecot-mysql.conf
# Substitutions: %u = user@domain.com, %n = user, %d = domain.com, L prefix=lowercase
driver = mysql
connect = host=/var/lib/mysql/mysql.sock dbname=mailconfig user=mailconfig password=random-password# Password lookups with support for user information prefetching:
password_query = SELECT concat(userid, '@', domain) AS user, password, home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid FROM users WHERE userid = '%Ln' AND domain = '%Ld'
# For deliver user info lookups:
user_query = SELECT home, uid, gid FROM users WHERE userid = '%Ln' AND domain = '%Ld'
EOF
chmod 600 /etc/dovecot-mysql.conf
This creates dovecot's MySQL configuration, /etc/dovecot-mysql.conf
and sets the correct permissions accordingly so that users can't go snooping for the MySQL credentials. Again, replace random-password
with the correct password.
Because the 'deliver' (Dovecot local delivery agent) executable run by Postfix has to deliver mail to different system users, it will need a setuid bit set so that Postfix can run deliver with root privileges, have it change to the appropriate user and then deliver mail to that user's inbox. We can do this easily with two commands:
chown root.mail /usr/libexec/dovecot/deliver
chmod 4750 /usr/libexec/dovecot/deliver
Notice that because the last permission bit is "0", nobody except root or users in the mail group will be able to execute deliver, reducing the security risk introduced by using enabling setuid bit.
Lastly, Dovecot needs to be started and exceptions need to be added to the firewall:
chkconfig dovecot on
service dovecot start
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 110 -j ACCEPT
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 143 -j ACCEPT
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 993 -j ACCEPT
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 995 -j ACCEPT
service iptables save
Normally we could just install Postfix directly from the repository, however the stock CentOS postfix is provided without MySQL support. We can easily enable this support by rebuilding the RPM:
su - normaluser
yumdownloader --source postfix
rpm -i postfix*src*.rpm
cd rpmbuild/SPECS
sed -i.nomysql 's/%define MYSQL 0/%define MYSQL 1/' postfix.spec
rpmbuild -ba postfix.spec
Then install the generated Postfix RPM (rpm -Uhv ~/rpmbuild/RPMS/[arch]/postfix-[version].[arch].rpm
)
Next, we will configure Postfix to accept/relay mail:
cat << EOF > /etc/postfix/main.cf
#
# Basic settings
## Domain/hostname information
mydomain = yourdomain.com
myhostname = mail.yourdomain.com
# Domains that Postfix will use to deliver mail to system users
mydestination = \$myhostname, localhost.\$mydomain, localhost
# Trusted networks that are always granted relay access
# You may remove 192.168.122.0/24 if you are not going to use virtual machines
# with libvirt (default/user networking).
mynetworks = 127.0.0.0/8, 192.168.122.0/24
# Listen on these IP addresses
inet_interfaces = all
# Enter an IP address here and uncomment to get lots of info logged when
# connecting as a client from that IP address.
# debug_peer_list = your.ip.address.here
#
# File/folder locations
#
command_directory = /usr/sbin
config_directory = /etc/postfix
daemon_directory = /usr/libexec/postfix
html_directory = no
manpage_directory = /usr/share/man
sample_directory = /usr/share/doc/postfix-2.3.3/samples
queue_directory = /var/spool/postfix
readme_directory = /usr/share/doc/postfix-2.3.3/README_FILES
mailq_path = /usr/bin/mailq.postfix
newaliases_path = /usr/bin/newaliases.postfix
sendmail_path = /usr/sbin/sendmail.postfix
#
# Security & limits
#
mail_owner = postfix
setgid_group = postdrop
header_size_limit = 51200
# 30MB message size limit
message_size_limit = 31457280
# Must have 5*30MB free in order to accept mail
queue_minfree = 157286400
#
# Dovecot LDA & virtual users configuration
#
dovecot_destination_recipient_limit = 1
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
virtual_alias_domains = mysql:/etc/postfix/mysql-virtual-alias-domains.cf
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_transport = dovecot
#
# Other settings in alphabetical order
#
alias_maps = hash:/etc/aliases
broken_sasl_auth_clients = yes
parent_domain_matches_subdomains = no
recipient_delimiter = +
smtpd_banner = \$myhostname ESMTP \$mail_name: Unauthorized use of this server, for spam, bulk mail or other purposes, is not permitted.
smtpd_client_connection_count_limit = 20
smtpd_client_connection_rate_limit = 60
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, warn_if_reject, reject_non_fqdn_helo_hostname, reject_invalid_hostname
smtpd_recipient_limit = 100
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_path = /var/run/dovecot/auth-client
smtpd_sasl_security_options = noanonymous
smtpd_sasl_type = dovecot
smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_sender, reject_unknown_sender_domain
smtpd_tls_auth_only = no
smtpd_tls_cert_file = /etc/pki/tls/certs/yourdomain.com.pem
smtpd_tls_key_file = /etc/pki/tls/private/yourdomain.com.key
smtpd_tls_loglevel = 1
smtpd_tls_security_level = may
smtpd_tls_session_cache_database = btree:/var/spool/postfix/smtpd_tls_cache
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom
unknown_local_recipient_reject_code = 550
EOF
Be sure to substitute yourdomain.com
for your actual domain name as well as set the smtpd_tls_cert_file
and smtpd_tls_key_file
configuration keys appropriately. You can use the cert.pem
and private/localhost.key
files that get autogenerated for the system, however it is recommended that you generate your own self-signed certificate so that the domain name matches (or even better, purchase validated ones). You may create a self-signed certificate and private key pair by running genkey --days 365 yourdomain.com
and then following the on-screen instructions.
The configuration above references several configuration files that do not exist yet, so we need to create them now. A description of what each file does is available in the comments.
cat << EOF > mysql-virtual-alias-domains.cf
# Generates a list of domain names that postfix should accept mail for, but
# that have no real mailboxes (aliases only). See the virtual(5) manpage for
# more information.# See the mysql_table(5) manpage for information on the format of this file
# and the SQL query.
user = mailconfig
password = random-password
hosts = 127.0.0.1
dbname = mailconfig
query = SELECT DISTINCT(source) FROM domain_aliases
EOF
cat << EOF > mysql-virtual-alias-maps.cf
# This file contains the SQL query for looking up virtual aliases. See the
# virtual(5) manpage for more information. The virtual alias maps allows you
# to forward messages from one address or from a catch-all address to another
# address (or optionally, a script). This file also handles the domain forwarding
# of addresses in one domain to another.
# Caveat: One would expect true domain forwarding to map user accounts as well
# as any existing alias on the source domain to the destination domain.
# For example, consider the use case of virtual user me@domain1.com and alias
# info@domain1.com -> me@domain1.com. After mapping domain2.com to domain1.com,
# one would expect info@domain2.com to map to info@domain1.com which in turn
# maps to me@domain1.com, so info@domain2.com -> me@domain1.com. Implementing
# this type of mapping is rather complex. The commented portion of the query
# below should handle this correctly, however the performance penalties of using
# such a query have not been considered nor tested on a large-scale production
# server. Enable at your own risk.
# See the mysql_table(5) manpage for information on the format of this file
# and the SQL query.
user = mailconfig
password = random-password
hosts = 127.0.0.1
dbname = mailconfig
query = SELECT destination FROM (
SELECT concat(users.userid,'@',domain_aliases.source) AS source,
concat(users.userid,'@',domain_aliases.destination) AS destination
FROM users,domain_aliases
WHERE users.domain=domain_aliases.destination
UNION
SELECT * FROM aliases
# This commented part is experimental. See above for details.
#UNION
#SELECT replace(aliases.source,
# concat('@', SUBSTRING_INDEX(aliases.source, '@', -1)),
# concat('@', domain_aliases.source)
# ) AS source,
# aliases.destination
#FROM aliases,domain_aliases
#WHERE aliases.source regexp concat('@',domain_aliases.destination,'$')
) AS alias WHERE alias.source='%s'
EOF
cat << EOF > mysql-virtual-mailbox-domains.cf
# Generates a list of domain names that postfix should accept mail for, but are
# not part of $mydomains as these domains are for virtual user mailboxes only.
# See the virtual(5) manpage for more information.
# Caveat: The SQL query includes the domains listed in the virtual alias map,
# but there's no real good way to tell if a domain listed there has any real
# mailboxes at all without making the queries very expensive. For practical
# purposes though, it has no real effect if it's listed here or in
# virtual_alias_domains so you can rest easy.
# See the mysql_table(5) manpage for information on the format of this file
# and the SQL query.
user = mailconfig
password = random-password
hosts = 127.0.0.1
dbname = mailconfig
query = SELECT domain FROM (
SELECT DISTINCT(domain) FROM users
UNION
SELECT DISTINCT(SUBSTRING_INDEX(source, '@', -1)) as domain FROM aliases
) virtual_mailbox_domains WHERE domain='%s';
EOF
cat << EOF > mysql-virtual-mailbox-maps.cf
# Queries the list of virtual users to determine if a given user exists. See
# the virtual(5) manpage for more information.
# See the mysql_table(5) manpage for information on the format of this file
# and the SQL query.
user = mailconfig
password = random-password
hosts = 127.0.0.1
dbname = mailconfig
query = SELECT 1 FROM users WHERE concat(userid, '@', domain)='%s'
EOF
chmod 600 /etc/postfix/mysql-virtual-*.cf
Postfix now needs to be configured to add support for additional ports (26, 465, 587) as well as to add support for dovecot's local delivery agent (LDA):
cat << EOF >> /etc/postfix/master.cf
# Users can use port 26 as an alternative to 25 if it is blocked by their ISP
26 inet n - n - - smtpd
# Message submission on port 587 and unofficial use of SMTP+SSL/TLS on port 465
587 inet n - n - - smtpd
smtps inet n - n - - smtpd
-o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
# Dovecot LDA, ignores extensions (user+extension@domain.com --> user@domain.com)
dovecot unix - n n - - pipe
flags=DRhu user=mail:mail argv=/usr/libexec/dovecot/deliver -f \${sender} -d \${user}@\${nexthop}
EOF
Finally, add the firewall port exceptions and start postfix:
chkconfig postfix on
service postfix start
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 25 -j ACCEPT
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 26 -j ACCEPT
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 465 -j ACCEPT
iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 587 -j ACCEPT
service iptables save
First, install amavisd-new and some dependencies:
yum install amavisd-new spamassassin clamd
In the /etc/amavisd/amavisd.conf
configuration file, the following items need to be changed:
$max_servers
to 15
$mydomain
to 'mail.yourdomain.com'
$virus_admin
, $mailfrom_notify_admin
, $mailfrom_notify_recip
, and $mailfrom_notify_spamadmin
each to "postmaster\@$mydomain"
$final_banned_destiny
to D_DISCARD
$final_bad_header_destiny
to D_DISCARD
If you wish to send spam to a certain user (ie spam@yourdomain.com) instead of just discarding it, you may also set the $spam_quarantine_to
and $bad_header_quarantine_to
options.
Next, Postfix needs to be configured so that it makes use of amavisd's spam/virus filtering:
cat << EOF >> /etc/postfix/main.cf
#
# amavisd
#
content_filter = smtp:[127.0.0.1]:10024
default_process_limit = 15
EOF
cat << EOF >> /etc/postfix/master.cf
# Spam filtering
127.0.0.1:10025 inet n - - - 0 smtpd -o content_filter= -o smtpd_sasl_auth_enable=no
EOF
Unfortunately, the default clamd configuration for amavisd contains some errors, preventing the amavisd.clamd service from starting correctly. We need to overwrite it with this configuration:
cat << EOF > /etc/clamd.d/amavisd.conf
# Use system logger.
LogSyslog yes# Specify the type of syslog messages - please refer to 'man syslog'
# for facility names.
LogFacility LOG_MAIL
# This option allows you to save a process identifier of the listening
# daemon (main thread).
PidFile /var/run/amavisd/clamd.pid
# Remove stale socket after unclean shutdown.
# Default: disabled
FixStaleSocket yes
# Run as a selected user (clamd must be started by root).
User amavis
# Path to a local socket file the daemon will listen on.
LocalSocket /var/spool/amavisd/clamd.sock
EOF
Now it is time to enable and start the services.
chkconfig clamd.amavisd on
service clamd.amavisd start
chkconfig amavisd on
service amavisd start
service postfix reload
Unfortunately, no CentOS 5 RPM package is currently available for the response daemon. I have attached the SRPM to this page which you can download and rebuild:
yum install python26 python26-{devel,sqlalchemy,distribute} python-sphinx
su - regularuser
wget http://www.firewing1.com/sites/default/files/response-0.8-1.src_.rpm
rpmbuild --rebuild response-0.8-1.src.rpm
wget http://www.firewing1.com/sites/default/files/MySQL-python26-1.2.3-1.src_.rpm
rpmbuild --rebuild MySQL-python26-1.2.3-1.src.rpm
Note that we are also rebuilding MySQL-python26, a dependency of response. Simply install the resulting two RPMs after the builds finish.
Before configuring Postfix, we need to create the MySQL tables that will be used to keep information about the autoresponders as well as which users have already been sent an autoresponse within a 24-hour period:
USE mailconfig;
CREATE TABLE `autoresponse_record` (
`id` int(11) NOT NULL auto_increment,
`sender_id` int(11) NOT NULL,
`recipient` varchar(255) NOT NULL,
`hit` datetime NOT NULL default '0000-00-00 00:00:00',
`sent` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
UNIQUE KEY `sender_id` (`sender_id`,`recipient`),
CONSTRAINT `autoresponse_sender_refs` FOREIGN KEY (`sender_id`) REFERENCES `autoresponse_config` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='Response - Autoresponse Notification Records';
CREATE TABLE `autoresponse_config` (
`id` int(11) NOT NULL auto_increment,
`address` varchar(255) NOT NULL,
`enabled` tinyint(1) NOT NULL,
`changed` datetime NOT NULL,
`expires` datetime NOT NULL,
`subject` varchar(255) NOT NULL,
`message` longtext NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `address` (`address`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='Response - Autoresponse Configurations';
GRANT SELECT ON mailconfig.autoresponse_config TO 'mailresponse'@'localhost' IDENTIFIED BY 'random-password2';
GRANT SELECT,INSERT,UPDATE ON mailconfig.autoresponse_config TO 'mailresponse'@'localhost' IDENTIFIED BY 'random-password2';
GRANT SELECT,INSERT,UPDATE ON mailconfig.autoresponse_record TO 'mailresponse'@'localhost' IDENTIFIED BY 'random-password2';
FLUSH PRIVILEGES;
exit;
Similar to before, replace random-password2
with a long and complicated password. You'll never have to remember it, it is simply used internally by the response daemon.
In the next file, you'll notice we use port 10026 because ports 10024 and 10025 have already been taken by amavisd-new.
cat << EOF > /etc/postfix/transport_map_response
response.internal response:[127.0.0.1]:10026
EOF
postmap /etc/postfix/transport_map_response
Now we need to inform postfix about the transport map and add the bcc mapping that will forward appropriate messages to the response-lmtpd daemon:
cat << EOF > /etc/postfix/mysql-virtual-autoresponses.cf
# This file controls the bcc map so that users with an autoresponder enabled
# have messages sent through the response transport in addition to their
# normal transport.
user = mailresponse
password = random-password2
hosts = 127.0.0.1
dbname = mailconfig
query = SELECT '%u#%d@response.internal' FROM autoresponse_config WHERE address = '%s' AND enabled=1
EOF
chmod 600 /etc/postfix/mysql-virtual-autoresponses.cf
cat << EOF >> /etc/postfix/main.cf
#
# Response autoresponder
#
response_destination_recipient_limit = 1
transport_maps = hash:/etc/postfix/transport_map_response
recipient_bcc_maps = proxy:mysql:/etc/postfix/mysql-virtual-autoresponses.cf
proxy_read_maps = \$local_recipient_maps \$mydestination \$virtual_alias_maps \$virtual_alias_domains \$virtual_mailbox_maps \$virtual_mailbox_domains \$relay_recipient_maps \$relay_domains \$canonical_maps \$sender_canonical_maps \$recipient_canonical_maps \$relocated_maps \$transport_maps \$mynetworks \$recipient_bcc_maps
EOF
cat << EOF >> /etc/postfix/master.cf
# Response autoresponder
response unix - - n - 4 lmtp -o disable_dns_lookups=yes
EOF
service postfix reload
Of course, you'll need to replace random-password2
in /etc/postfix/mysql-virtual-autoresponses.cf with the random password created earlier for the mailresponse user.
That's it! You can now add a row in the mailconfig.autoresponse_config table to have response-lmtpd automatically reply to any message sent to that user.