Version 1.40.0

Released: 2011-11-24

Log pop+imap bandwidth for dovecot new

Relating to:

dovecot recently logs bytes for imap usage, so bandwidth can now be counted.

Example from maillog:

Sep 11 19:43:26 abc dovecot: imap( Disconnected: Logged out bytes=241/451
Nov 15 02:28:12 abc dovecot: pop3( Disconnected: Logged out top=1/30, retr=1/2210, del=0/110, size=1638002

For imap, DA will add the bytes=241/451 (in/out) together for the total.

For pop, DA will use retr=1/2210, where 2210 is the sent bytes with the retr command.

If the pop3 line contains a bytes= entry, that will be used instead (even though it doesn't by default, it will be more accurate when we you add it to your dovecot.conf. (new systems will have the new log format for pop, see below)

This will add 2 new columns to the bandwidth breakdown page.

And will add pop and imap values to the bandwidth.tally.cache file.

The file:


has also been updated to rotate the dovecote.bytes files which will be created by the da-popb4smtp binary.

The dovecot.bytes files will exist in 2 locations, depending on the login format.

/etc/virtual/ for @domain email accounts.

/usr/local/directadmin/data/users/username/dovecot.bytes for system email accounts.

The contents of the dovecot.bytes files will be dumped into the bandwidth.tally, then deleted (via

New addition to the dovecot.conf for dovecot 2.0, into the section for protocol pop3:

pop3_logout_format = top=%t/%p, retr=%r/%b, del=%d/%m, size=%s, bytes=%i/%o

adding this line will give a more accurate bandwidth logging.

Not adding it will only rely on the sent bytes frm RETR commands, so will be somewhat short of the truth.

Log delete/rename/copy portions of the CMD_FILE_MANAGER new

Log delete/rename/copy portions of the CMD_FILE_MANAGER, to better track User actions.

Because the filemanager is chrooted, the log to the system.log will happen before the action takes place, so if there are any errors, the logs won't show them. new to be called after all backups are finished.

Only applies to backups triggered from:

Admin Level -> Admin Backup/Transfer

Reseller Level -> Manage User Backups

Is not triggered from:

User Level -> Create/Restore Backups

This script is purposed to run after a large number of backups are created, hence it's not called for the User Level backups.

The parameters passed are the same values that are passed to DA via the task.queue for the creation of the backup in question.

This variable can vary, so to see what you'll get, type:

cat /usr/local/directadmin/data/task.queue

after triggering the run of the backup you want.

If you wish to do something after each User backup is called, use this instead:

Add Block IP button to IP/User tables for faster blocking (SKINS) new

With the Brute Force Monitor, add the Block IP button (if exists) right in the tables, so multiple IPs can be blocked at once.

It will call the once per IP.




|*if HAVE_BLOCK_SH="1"|
<input type='button' value='Block IPs' onclick="if (confirm('Are you sure you want to block these IPs?')){document.tableform.action.value = 'block_ips';document.tableform.submit();}">
|*endif| new

Custom script called when deleting a Database User:

Related to custom scripts for databases

environmental variables for script:

username - DA username
database - name of the db
user - name of the user created

Note that will not be called when a database and it's users are being deleted.

It's only called when a single DB user is removed from a database.

This means that any code used in will have to be doubled in

EDIT: should really call this for each DB User being removed, when a DB is deleted as well.

IP ranges and reverse IPs in brute_skip.list (SKINS) new

Optional field/value on the Brute Force Monitor (BFM) page which will allow you to manually type in:

  • IPs, IPv4 and IPv6 (use full/expanded form for IPv6)

  • IP ranges: and 1:2:3:4:5:6:7:8-9 (make sure to use the range type that the logs will see the IP as. If an IP shows up as IPv6 in the logs, use an IPv6 range)

  • (reverse IP lookup, exact match)

  • * (reverse IP lookup, wildcard match, must start with *. Can also be *, as it's just a string match)

which will give you more control over skip list with the BFM.

If you leave the field empty, the "Add to Skip List" button will block the check-boxed IPs listed in the table above.

If you add an IP/range/domain to the field, then that value will be added to the skip list as well as any check-boxed IPs.

When entering or *, this will trigger a reverse IP lookup on the IP using a new script:


If no or * value is added, then the lookup is not done, which will speed up the process (reverse IP lookups can be slow if there are many IPs to check)

The timing of the lookup is after the logs are parsed for that minute.. so a lookup will only ever be done at most once per minute, per IP.




<input type=text name="skip_value" placeholder="Optional IP/Range/Domain" title="Optional IP/Range/Domain" size=24><a target=_blank class=listtitle href="">?</a>

option to allow underscores in db names and db users new

New directadmin.conf option to allow an override for anyone who needs to backup/restore databases or db users with underscores in their names.



in your directadmin.conf.

Internal default is set to 0.

Disk Usage Breakdown - CMD_DU_BREAKDOWN (SKINS) new

New button on the stats page of Users, so they can click "Details" next to their total disk usage.

"Details" button also on the User info page viewed by Admins and Resellers.

CMD_API_DU_BREAKDOWN also exists. It will dump the contents of the user's du_breakdown.list file.

User Level -> Site Summary / Statistics / Logs -> Total Disk Usage (MB) -> [Details]

Reseller/Admin Level -> Show All Users -> Total Disk Usage (MB) -> [Details]




user/du_breakdown.html - see skin

BFM: and (SKINS) new

Two new items for the brute force monitor:

If you create it, it's output should generate a list of IPs which are blocked.

The format will be 1 IP per line, but should also have an = at the end to allow for more data to be associated with the IPs in the future (eg: if we add blocked time or info, etc..)

So for now, make the output like:




The havedata=1 value is optional.

If you include in the the output, then DA will expect dateblocked=123456 (unix timestamp) along with the IP, as well as info=.

Note that either "dateblocked" or "info" can be blank. DA will ensure there is something in the variable before chewing on it.

The havedata=1 value simply changes the table to show more cells, ie: the data you're giving it.


Related for automated unblocking: BFM automated unblock (SKINS)

Called by DA if it exists and will pass the environmental variable:


so that you can remove an IP from your block list.



very top of file:


after the skip list:

<form name=tableform5 action='CMD_BRUTE_FORCE_MONITOR' method=POST>
<input type=hidden name=action value="unblock">
<b>Blockd IPs</b>
<div id="blockedipsdiv" style="overflow: auto; height: 450px; width: 300px; border: 1px solid grey;">
<table id="blockbuttontable" class=list style='width: 50%' cellpadding=3 cellspacing=1>
<tr><td class=listtitle align='right' colspan='5'> <input type='submit' value='UnBlock' name=unblock></td ></tr>
<script type="text/javascript">
function sizeTheDiv()
    var tblwidth=document.getElementById('blockedipstable').offsetWidth;
    if (tblwidth>0)
        if (tblwidth < 300)
            tblwidth = 300;

        if (document.getElementById('blockbuttontable'))
// -->

Ability to hide and block "Domain Setup" page (SKINS) new

New global token:


will be set to yes or no, depending if the User is allowed to run:


The way to control the running of that command is with the commands.allow and/or commands.deny.

For this case, generally, you'd just add:


to the file:


Related feature:

commands.allow and commands.deny for per-user control




Add this around the href to CMD_ADDITIONAL_DOMAINS to hide the link:

 .... url ....

we use "not no" instead of "is yes", to be backwards compatible, in case they use a new skin for an old version of DA.

The old versions of DA would be blank, so "is yes" would fail, but "not no" will succeed if blank.

Suspend reason: user/domain (SKINS) (TEMPLATES) new

When a user/domain is suspended, feature which allows reasons that it was suspended.

Note that the reason is optional. You don't need to pass it, and you don't need to pick something, if you don't want to.

The variable suspended_reason will be stored in both the user.conf and domains/ if either is suspended.

When the user/domain is unsuspended (suspended=no), the variable is deleted.

Uses an environmental variable in the httpd.conf holding the key-word descriptor of why it was suspended. (probably cleaner/simpler), eg:

SetEnv reason bandwidth

which can then be accessed via the /home/reseller/domains/suspended/index.html, index.shtml, or index.php.. whatever works for you which has the ability to read ENV variables.

Variable name is "reason"

Available for Admins/Resellers to use on Users.

Also available for Users to use on Domains.



user_bandwidth=id=12&text=User Bandwidth
user_quota=id=13&text=User Disk Quota
domain_bandwidth=id=14&text=Domain Bandwidth
domain_quota=id=15&text=Domain Quota
reseller_bandwidth=id=16&text=Reseller Bandwidth
reseller_quota=id=17&text=Reseller Quota
billing=id=18&text=Billing Issue

where the id values for the key names are internal language pack id numbers set in:


Note, if you just want to set the text, and not make your entries translateable, then simply don't include an id value.

A sample reason without an id:

smelly=text=He Really Smells Bad

Both cases must have a "text" variable, as it will be the fallback for the event that the id cannot be found in the given language file.

If you wish to customize the suspension_reason.txt file, first copy it to the folder:


and edit the custom copy.


suspension_reason.txt : see above



<IfModule mod_env.c>


Just a minor cosmetic change.

Since Show Admins and Show Resellers now have wider tables, we're changing the header page to be the full path instead of the path with the stats on the right, such that the table doesn't impede on the stats.







to be:






Thread containing examples on index.shtml and index.php usage:

Add a whois to reverse IP lookup, and script new

The IP information page under the Brute Force monitor will now be changed to call a new script:


The script will call:

dig -x "" +noshort

but will also include a whois output (if whois exist) for more information on the IP:

whois ""

Related DA command:


Note that this does add a few seconds to a the lookup time, but IMO worth the few seconds to get more/accurate information on the attacking IP.

domain log rotation to have order new

The tar.gz files in:


will now be rotated in a similar fashion to all logs in /var/log (but still controlled by DA, and not logrotate)

The format will be:


The old format had no pattern to the numbering. Only the timestamp could be used to determine the newest log.

The numbers would be used in a rotation, but they were not in order.

The new format keeps things much cleaner.

The file that does not end in a number is always going to be the newest.

remove clipboard file upon logout new

When CMD_LOGOUT is called, the file:


will be removed, if it exists.

This is done to prevent confusion in the FileManager, where files are still in the clipboard from previous sessions.

Default internal directadmin.conf option will be:


If you do not want the clipboard to be removed on logout, set:


in your directadmin.conf.

Note that this only applies if CMD_LOGOUT is called.

If the client just closes his or her browser, the event will not be triggered. new

Custom script:


used for chaning of User info:





Note that the user.conf may or may not have already been written when this is called, so don't rely on it being written already.

Values passed:

username: DA user who's email was changed

only one of following:

email=button's text
name=button's text
language=button's text
skin=button's text

so you'll need to check which variable exists, and go from there.

All of these values will also be passed, where you'd figure out which button was pressed using the above values (email, name, etc) and use on of these values: (email)
nvalue=John Doe  (name)
lvalue=en  (the language)


Called by an Admin, CMD_API_USER_EXISTS will return exists=1 or exists=0 if the account exists (Any type: Admin, Reseller, User).

error=1|0 is also returned.

If error=1, then text= will be set to the reason.

Multi Server Setup - User Check new

New checkbox in the section:

Admin Level -> Multi Server Setup

which checks the remote boxes for duplicate Usernames.

This is beneficial if you want to prevent duplicate usernames for reasons such as:

  • transfers between boxes to prevent accidental merges

  • you use a single remote MySQL server for multiple boxes

  • long term prediction of full DA clustering, where unique usernames is required (don't ask, distant future)

Feature will use the new API for confirming if a User exists:


Table Row Highlighting (SKINS) new

BETA - disabled by default

only exists in "enhanced" skin at this time.

When your mouse hovers over a row, that entire row will change to a darker background, to more easily track which value you're about to select.

To enable, add the following to your directadmin.conf and restart DA:


The internal default is:


In a future version of DA, this will be enabled by default.

It's disabled for now, for testing purposes.




    BACKGROUND: #b8c0e2;
    white-space: nowrap;
    BACKGROUND: #b8c0e2;
    white-space: nowrap;
    BACKGROUND: #b8c0e2;
    white-space: wrap;

    BACKGROUND: #b8c0e2;
    white-space: wrap;

header.html, in the <head> section:

    <script type="text/javascript">
    <!-- // start preload code
    function tr_add_highlight()
        add_highlight(this, 'list','listhighlight','list2','list2highlight','listwrap','listwraphighlight','listwrap2','listwrap2highlight');
    function tr_remove_highlight()
        add_highlight(this, 'listhighlight','list','list2highlight','list2','listwraphighlight','listwrap','listwrap2highlight','listwrap2');
    function add_highlight(ob,h1,l1,h2,l2,h3,l3,h4,l4)
        var tds = ob.getElementsByTagName('td');
        for(var d=0; d<tds.length; d++)
            switch (tds\[d\].className)
                case h1:tds\[d\].className = l1; break;
                case h2:tds\[d\].className = l2; break;
                case h3:tds\[d\].className = l3; break;
                case h4:tds\[d\].className = l4; break;
    function make_tables_highlightable()
        var tables = document.getElementsByTagName('table');
        for (var tbl=0; tbl<tables.length; tbl++)
            if (tables\[tbl\].className.indexOf('table-highlight') != -1)
                var trs = tables\[tbl\].getElementsByTagName('tr');
                for(var tr=0; tr<trs.length; tr++)
                    trs\[tr\].onmouseover = tr_add_highlight;
                    trs\[tr\].onmouseout = tr_remove_highlight;
    // done with preload code -->

footer.html, just before </body>:

    <script type="text/javascript">
|*endif| not updating IPs fixed

Updated the script to check if an IP is owned (for each ip in the user_ips.list file).

For any owned IP, owned by that User, the IP will be moved from the old Reseller to the new Reseller's ip.list file.

The data/admin/ips/ is also updated for reserller=oldreseller to reseller=newreseller

Include multiple forwarder names in forwarder count for pre-check on limit fixed

Forwarders can be used like:

user1,user2,user3 ->

Ensure that this 1 entry counts as 3 forwarders and check the limit so it's not created if it shouldn't be.

Related thread: sets exim to 755 fixed

The script already did make the call correctly to set it to 4755:

set_file /usr/sbin/exim root $RT_GRP 4755

The issue was that the set_file function called the chmod first, then the chown.

The issue was that a 4755 file becomes reset to 755 when a chown is made on the file.

The simple solution was to put the chown first, then chmod.

In exim mainlog error you'd see would look like this if exim is 755 instead of 4755:

unable to set gid=12 or uid=2345 (euid=8): domain_filter router

If you see that error, type:

chmod 4755 /usr/sbin/exim

or run this fixed

cd /usr/local/directadmin/scripts
./ email

but once done either, confirm it's set correctly:

[root@server scripts]# ls -la /usr/sbin/exim
-rwsr-xr-x  1 root root 856823 Mar  9  2011 /usr/sbin/exim
[root@server scripts]#

where you're looking for:


and not:


Update template filter_userspamfolder not to save to spambox if account doesn't exist fixed

When the option:

"Send the spam to the appropriate users's spam folder."

is used with SpamAssassin, any spam will be saved to the spambox, eg:


However, if spam is sent to a forwarder, where no local mailbox exists, the filter will create that path to save it to the spambox, when it shouldn't.

The fix was to add extra code to the filter template:


where it will check for the account to exist, and if not, it will save the spam to the system account's spambox.


         save |HOME|/imap/$domain/$local_part/Maildir/.INBOX.spam/new/ 660

to be:

        if "${if exists{|HOME|/imap/${domain}/${local_part}}{yes}{no}}" is "yes"
            save |HOME|/imap/$domain/$local_part/Maildir/.INBOX.spam/new/ 660
            save |HOME|/Maildir/.INBOX.spam/new/ 660

Note, if the catch-all is enabled (which I never recommend), the system account's spambox will grow quite rapidly.

Apart from not enabling catch-all accounts, I recommend to enable the spambox purge option in the Admin Settings.

Figuring out the syntax for the "exists" check took a while, as it uses double "if's" for string expansion to convert to a yes/no, then a string test for "yes" on that result.

Somewhat obfuscated, but that's why exim is so powerful.

To force a rewrite of all filter files, use this, as the filter files won't be rewritten with the update, but only after a User saves a change:


Also create a new template file:


for when a basic "spam filter" (word filter) is used to redirect to the spambox.

The same rules apply, where the new double-if is used to ensure the folder exists.

If not, it's saved in the main spambox.

Related to the issue: (bottom part about not scanning forwarders)

however, it wasn't a good solution because spam would then be forwarded to external systems (eg: gmail) without being scanned.

This could potentially cause your server to be blacklisted for sending spam, when it was only forwarding it.

The above fix is good because it allows forwarders to still be scanned, but doesn't save the local copy.

Restore adding duplicate A records if in short vs long form fixed

If the backup stored a full format of the www record (for example):  A

where was a custom IP which did not match the old IP of the User (eg: an external server)

Then when the restore was done, since it doesn't match the dns_a.conf template which just has:

www  A

this caused both to exist in the zone after the restore:  A
www  A

causing an undesired round-robin effect.

Fix was to always shrink the long format to the short www format when doing comparisons.

Note that if it's decided the value should be added (can't find a duplicate) then the original format used will be added (in this case, the is added, if that's what was in the backup)

Options +ExecCGI to .htaccess file in cgi-bin by default fixed

Relating to:

If cgi-bin is enabled when a domain is created, the items from id=7 will be added to the .htaccess file.

Table quicksort was incorrect, plus sort optimizations fixed

The quicksort algorithm for tables (which is only used in the brute force monitor so far) was not sorting correctly.

A rewrite of the algorithm corrected this. Other tables may be changed to use quicksort as well for it's significant performance increase.

Other classes are already using it.

Note that with quicksort, the "subsort" option does not work, so if it's used, those tables will only be able to sorted by 1 column, so it's use will be selective.

(the subsort is the column choice that the data will be sorted by, when the main column sort values match)

Also, optimizations to all quicksorts (configfile, listfile and tables) have been added in the form of inline functions for all of the quicksort function calls (swap, quicksort, compares, etc..)

This has gained roughly a 15% performance improvment on a 100% inverted list. (where it has to flip the list completely to be sorted)

Additional owned IPs had VH in ips.conf fixed

When adding an additional IP to a domain (User Level -> Domain Administration), I've added code to DA to issue a rewrite to the ips.conf.

This will get rid of the VH for the IP.. as it doesn't belong there if the IP is owned. (needs to go away in order to have SSL work correctly for the additional IP)

Allow tar exit code 1 fixed

As of tar 1.16, if a file is being changed as it's being included into a tar.gz file, tar will return exit code 1.

DA previously only accepted error code 0 as valid.

This change will allow the exit code of 1 to be valid and to not stop the backup process.

The usual causes of this error could be things such as incoming, or deleting email as the backup is being created, or changing files mid-backup.


Related error:

Error Compressing the backup file user.reseller.username.tar.gz : /bin/tar: some/file/name.txt: file changed as we read it

Bug with database names in change_database_username.php fixed

Resolved an issues with the change_database_username.php script.

The mysql.db and mysql.user tables were being corrected updated, however, the issue was that MySQL doesn't actually have a rename option.

(They used to have RENAME DATABASE dbname, but it was dropped due to potential loss of data)

Added a rename() call to change /var/lib/mysql/dbname to then new dbname.

The side-effect of this issue, was that if you change the username again, it wouldn't find the DB, since it was never renamed in the first place.

Also, this means that the script will not work if you've got databases on an external box, since a MySQL query won't be able to rename the directory.

Last Updated: