Posts Tagged ‘accounts’

Import Courier-IMAP Maildir E-Mail and IMAP Folders to Zimbra

I have the good fortune of being forced to migrate over 1,100 e-mail accounts from a 15 year old qmail server with Courier-IMAP.

A script is provided at http://wiki.zimbra.com/wiki/Courier-IMAP_Maildir_to_zmmailbox. It should be easy to run as root and import over NFS. Unfortunately, I ran into trouble with the –noVerify flag, possibly due to the older version of Zimbra I’m importing to:

  * Running import process...           0 ...some messages did not import correctly: check /tmp//import-example.com-user--6108

The output of the log looks like:

addMessage --flags "u"  --noValidation "/Inbox" "/mnt/example.com/user/Maildir//cur/1362778133.14175.smtp.example.com:2,"

To test it you’re going to have to look at the prefix in the script:

/opt/zimbra/bin/zmmailbox -m user@example.com -z addMessage --flags "u"  --noValidation "/Inbox" "/mnt/example.com/user/Maildir//cur/1362778133.14175.smtp.example.com:2,"

Which gave me the following error:

ERROR: zclient.CLIENT_ERROR (unknown folder: --noValidation)

I tried putting the –noValidation flag in every position with no luck, and it is listed in the Zimbra wiki article for zmmailbox at http://wiki.zimbra.com/wiki/Zmmailbox so I am left to assume my version simply does not support it. It doesn’t seem to be consequential anyway so I simply removed it from the script.

#!/bin/bash                                                                                                                                                                                                                                                                    
#
# courier/vpopmail Maildir to Zimbra Import
#
# This script can be stored anywhere, but you should run it while in the root
# of the domain's users.  It looks for the file vpasswd which contains a
# line-separated list of users and uses that to import.  You can also run the
# script with a user name to process a single user.  Additionally, you can
# specify a folder name (courier format) to process a single folder for that
# user.

# We assume the folder structure is like this:
# Inbox: <working directory>/<user>/Maildir/<cur|new>
# Subfolder: <working directory>/<user>/Maildir/.Subfolder/<cur|new>
# If this is not what your structure looks like, you need to change the
# "folderpath" variable construction down further in this script.

# This is the command to run to run mailbox commands.
ZMCMD='/opt/zimbra/bin/zmmailbox -z'

# This will be used for temporary/log files during the import process
TEMP='/tmp/'

# We assume the working directory's name is the domain.
# Otherwise, override this with your actual domain name.
domain=`basename ${PWD}`

echo Process ID: $$

if [[ $1 != "" ]] ; then
  USERS=$1;
else
  USERS=`cat vpasswd | cut -f1 -d:`
fi

for user in ${USERS}; do
  echo "Beginning User: $user..."

  if [[ $2 != "" ]] ; then
    FOLDERS="$user/Maildir/$2/cur";
  else
    FOLDERS=`find $user -type d -name cur | sort`
  fi

  echo "$FOLDERS" | while read line; do
    folderdir=`echo ${line} | cut -f3 -d"/"`
    if [[ ${folderdir} == "cur" ]] ; then
      folderdir="";
    fi 
    folder=`echo ${folderdir} | sed 's/^\.//; s%\.%/%g; s%\&-%\&%g'`
    folderpath=${PWD}/${user}/Maildir/${folderdir}/
    
    # If the folder name is blank, this is the top level folder,
    # Zimbra calls it "Inbox" (so do most clients/servers).
    if [[ $folder == "" ]] ; then
      folder="Inbox";
    fi
    # In Courier IMAP, all folders must be children of the root
    # folder, which means Trash, Junk, Sent, Drafts are typically
    # under Inbox. This is not the case with Zimbra, so we will
    # slide these mailboxes to the top level so they behave properly,
    # For all "non-special" mailboxes, we will keep them as children
    # so they remain where the user had them before.
    if [[ $folder != "Trash" && $folder != "Junk" && $folder != "Sent"
       && $folder != "Drafts" && $folder != "Inbox" ]] ; then
      folder="Inbox/${folder}";
    fi
    echo "* Working on Folder $folder..."

    # Courier allows heirarchy where non-folders (literally nothing) are
    # able to have children.  Zimbra does not.  It's also possible that
    # we will process the folders out of heirarchical order for some reason
    # Here we separate the path and make sure all the parent folders exist
    # before trying to create the folder we're working on.
    parts=(`echo $folder | sed 's% %\x1a%g; s%/% %g'`);
    hier="";
    for i in "${parts[@]}"; do
      hier=`echo ${hier}/$i | sed 's%^/%%; s%\x1a% %g'`;
      ${ZMCMD} -m ${user}@${domain} getFolder "/${hier}" >/dev/null 2>&1 ||
      ( echo -n "  + Creating folder $hier... " &&
      ${ZMCMD} -m ${user}@${domain} createFolder "/${hier}" )
    done

    # Figure out how many messages we have
    count=`find "${folderpath}new/" "${folderpath}cur/" -type f | wc -l`;
    imported=0;
    echo "  * $count messages to process..."

    # Define the temporary file names we will need
    importfn="${TEMP}/import-$domain-$user-$folderdir-$$"
    implogfn="${TEMP}/import-$domain-$user-$folderdir-$$-log"
    impflogfn="${TEMP}/import-$domain-$user-$folderdir-$$-flaglog"
    impflagfn="${TEMP}/import-$domain-$user-$folderdir-$$-flags"
    touch "$importfn"

    # Determine the courier extended flag identifiers ("keywords")
    flagid=0
    if [[ -f "${folderpath}courierimapkeywords/:list" ]] ; then
      extflags="YES"
      cat "${folderpath}courierimapkeywords/:list" 2>/dev/null | while read line; do
        # A blank line indicates the end of the definitions.
        if [[ "${line}" == "" ]]; then break; fi

        # To avoid escape character madness, I'm swapping $ with % here.
        flag=`echo ${line} | sed 's/\\\$/%/'`
        echo courierflag[${flagid}]="'$flag'";
        flagid=$(( flagid + 1 ));

        # Create the tag if it doesn't start with '%'
        if [[ `echo ${flag} | grep '%'` == "" ]] ; then
          echo -n "  + Attemping to create tag ${flag}... " >&2
          ${ZMCMD} -m ${user}@${domain} createTag "${flag}" >&2
        fi

      done > "$impflagfn"
      source "$impflagfn"
    fi

    echo -n "  * Queuing messages for import...        " 

    # Find all "cur" or "new" messages in this folder and import them.
    find "${folderpath}new/" "${folderpath}cur/" -type f | while read msg; do
      flags="";
      tags="";
      msgid=`echo $msg | cut -d: -f1 | sed s%.*/%%`

      # Determine the old maildir style flags
      oldflags=`echo $msg | cut -d: -f2`
      # Replied
      if [[ `echo ${oldflags} | grep 'R'` != "" ]] ; then flags="${flags}r"; fi
      # Seen
      if [[ `echo ${oldflags} | grep 'S'` == "" ]] ; then flags="${flags}u"; fi
      # Trashed
      if [[ `echo ${oldflags} | grep 'T'` != "" ]] ; then flags="${flags}x"; fi
      # Draft
      if [[ `echo ${oldflags} | grep 'D'` != "" ]] ; then flags="${flags}d"; fi
      # Flagged
      if [[ `echo ${oldflags} | grep 'F'` != "" ]] ; then flags="${flags}f"; fi

      # Determine the courier-imap extended flags for this message
      if [[ ${extflags} == "YES" ]] ; then
        oldflags2=`grep $msgid "${folderpath}courierimapkeywords/:list" 2>/dev/null | cut -d: -f2`
        for flag in ${oldflags2}; do
          # Forwarded
          if [[ ${courierflag[$flag]} == '%Forwarded' ]] ; then flags="${flags}w"; fi
          # Sent by me
          if [[ ${courierflag[$flag]} == '%MDNSent' ]] ;   then flags="${flags}s"; fi
          # Convert non-system flags to Zimbra tags
          if [[ `echo ${courierflag[$flag]} | grep '%'` == "" ]] ; then
            tags="${tags},${courierflag[$flag]}"
          fi
        done
        # Clean up the tag list for the command line
        if [[ ${tags} != "" ]]; then
          tags=`echo ${tags} | sed "s/^,\?/--tags \'/; s/\$/\'/"`;
        fi
      fi

      # Log the result of flag processing for debugging
      if [[ $flags != "" || $tags != "" ]] ; then
        echo `date +%c` "$msg had flags $oldflags and $oldflags2, now $flags and $tags in folder $folder" >> "$impflogfn"
      fi

      # Add the command to the queue file to import this message
      echo "addMessage --flags \"${flags}\" ${tags} \"/$folder\" \"${msg}\"" >> "$importfn"

      imported=$(( $imported + 1 ));
      printf "\b\b\b\b\b\b\b\b%7d " $imported;
    done

    echo "...done";

    # Since we redirect the queue file to the mailbox tool, we end with "quit"
    echo "quit" >> "$importfn"

    # We're counting "prompts" from the zmmailbox utility here.  The first
    # one comes up before a message is imported, so we start at -1 to offset
    # its existence.
    imported=-1;

    # We do this redirect because running the command for each message is very
    # slow.  We can't just pass the directory to the command, despite Zimbra's
    # support because we can't tag or flag the messages that way.
    echo -n "  * Running import process...             "
    ${ZMCMD} -m $user@$domain < "${importfn}" 2> "${implogfn}" | while read; do
      imported=$(( $imported + 1 ));
      printf "\b\b\b\b\b\b\b\b%7d " $imported;
    done

    if [[ -s "${implogfn}" ]]; then 
      echo "...some messages did not import correctly: check $importfn";
    else
      echo "...done";
    fi
  done
done
echo "Import Process Complete!"

Now my output for a single account is:

Process ID: 11495
Beginning User: user...
* Working on Folder Inbox/Archive...
  + Creating folder Inbox/Archive... 285
  * 0 messages to process...
  * Queuing messages for import...        ...done
  * Running import process...           0 ...done
* Working on Folder Drafts...
  * 0 messages to process...
  * Queuing messages for import...        ...done
  * Running import process...           0 ...done
* Working on Folder Junk...
  * 0 messages to process...
  * Queuing messages for import...        ...done
  * Running import process...           0 ...done
* Working on Folder Sent...
  * 3 messages to process...
  * Queuing messages for import...      3 ...done
  * Running import process...           3 ...done
* Working on Folder Trash...
  * 0 messages to process...
  * Queuing messages for import...        ...done
  * Running import process...           0 ...done
* Working on Folder Inbox/new folder...
  + Creating folder Inbox/new folder... 289
  * 5 messages to process...
  * Queuing messages for import...      5 ...done
  * Running import process...           5 ...done
* Working on Folder Inbox...
  * 1 messages to process...
  * Queuing messages for import...      1 ...done
  * Running import process...           1 ...done
Import Process Complete!

It is important to note that running the import script multiple times will result in the e-mails being imported multiple times and it takes a fair amount of time to perform this procedure on one account nevermind one thousand so a strategy should be formulated for dealing with runoff mail before the MX/target is switched.

Mass Virtual Hosting Part One: Database-Backed User Accounts and Authentication

In situations where the creation of UNIX system accounts (for file system permissions and s/ftp authentication, etc.) should be automated or managed remotely it is possible to use MySQL to store user and group information, thanks to libnss-mysql. There is another project named NSS MySQL which provides the same functionality but for the purposes of this article we will only deal with the former, as it has been more recently updated and is the only one included in Gentoo’s Portage package management system.

NSS stands for Name Service Switch, it provides a convenient way to replace or supplement the traditional /etc/passwd /etc/shadow /etc/groups system with anything from LDAP to MySQL tables. It sits at the layer below PAM (which can only handle authentication) and this allows it to transparently handle user and group lookups and modifications from unmodified system tools. For example, using ls to show a directory listing would pull the correct user and group names from the database and using passwd to change the password of an account in the database would interface with the database and not /etc/shadow.

Once you have installed libnss-mysql (emerge libnss-mysql on Gentoo – add it to your package.keywords to grab the last, CVS-based and bug-fixed release) create a database somewhere accessible to both the intended management interface and the system where account information is to be supplemented. Sharing this database across multiple libnss-mysql enabled hosts allows one to maintain a consistent authentication system across them.

# The tables ...
CREATE TABLE groups (
  name varchar(16) NOT NULL default '',
  password varchar(34) NOT NULL default 'x',
  gid int(11) NOT NULL auto_increment,
  PRIMARY KEY  (gid)
) TYPE=MyISAM AUTO_INCREMENT=5000;

CREATE TABLE grouplist (
  rowid int(11) NOT NULL auto_increment,
  gid int(11) NOT NULL default '0',
  username char(16) NOT NULL default '',
  PRIMARY KEY  (rowid)
) TYPE=MyISAM;

CREATE TABLE users (
  username varchar(16) NOT NULL default '',
  uid int(11) NOT NULL auto_increment,
  gid int(11) NOT NULL default '5000',
  gecos varchar(128) NOT NULL default '',
  homedir varchar(255) NOT NULL default '',
  shell varchar(64) NOT NULL default '/bin/bash',
  password varchar(34) NOT NULL default 'x',
  lstchg bigint(20) NOT NULL default '1',
  min bigint(20) NOT NULL default '0',
  max bigint(20) NOT NULL default '99999',
  warn bigint(20) NOT NULL default '0',
  inact bigint(20) NOT NULL default '0',
  expire bigint(20) NOT NULL default '-1',
  flag bigint(20) unsigned NOT NULL default '0',
  PRIMARY KEY  (uid),
  UNIQUE KEY username (username),
  KEY uid (uid)
) TYPE=MyISAM AUTO_INCREMENT=5000;

# The permissions ...
GRANT USAGE ON *.* TO `nss-root`@`localhost` IDENTIFIED BY 'rootpass';
GRANT USAGE ON *.* TO `nss-user`@`localhost` IDENTIFIED BY 'userpass';

GRANT Select (`username`, `uid`, `gid`, `gecos`, `homedir`, `shell`, `password`,
              `lstchg`, `min`, `max`, `warn`, `inact`, `expire`, `flag`)
             ON `auth`.`users`
             TO 'nss-root'@'localhost';
GRANT Select (`name`, `password`, `gid`)
             ON `auth`.`groups`
             TO 'nss-root'@'localhost';

GRANT Select (`username`, `uid`, `gid`, `gecos`, `homedir`, `shell`)
             ON `auth`.`users`
             TO 'nss-user'@'localhost';
GRANT Select (`name`, `password`, `gid`)
             ON `auth`.`groups`
             TO 'nss-user'@'localhost';

GRANT Select (`username`, `gid`)
             ON `auth`.`grouplist`
             TO 'nss-user'@'localhost';
GRANT Select (`username`, `gid`)
             ON `auth`.`grouplist`
             TO 'nss-root'@'localhost';

Modify the permissions section to reflect your needs. libnss-mysql uses a so-called ‘user’ account for routine operations and a ‘root’ account for privileged operations, designated by nss-user and nss-root respectively above. Next we must configure the library, edit /etc/libnss-mysql.cfg and change only the authentication information at the bottom of the file:

host        localhost
database    auth
username    nss-user
password    userpass
#socket      /var/lib/mysql/mysql.sock
#port        3306

The SELECT queries at the top of the file can be modified later to integrate into an existing user database of your own design, it is important that only the columns (under any name) specified in the original queries are returned and in that order. Next alter /etc/libnss-mysql-root.cfg to reflect the libnss-mysql ‘root’ user’s MySQL credentials (for obvious reasons these files should have restrictive permissions):

username    nss-root
password    rootpass

libnss-mysql makes use of persistent DB connections, which can be problematic in multithreaded environments as a new connection is established for each thread. For that reason it is recommended to pass the connections through something like mysql-proxy or nsscache/nscd. The problem can also be mitigated by reducing the timeout for persistent connections (default 8 hours) on the MySQL server to something like 1 minute, however this may not be a great idea if the MySQL server is used for other purposes (web hosting etc). Now that we have configured libnss-mysql we must teach NSS to use it. Alter these lines in /etc/nsswitch.conf to reflect:

passwd: files mysql
shadow: files mysql
group:  files mysql

files means first read the traditional /etc/passwd shadow and group files, failing lookup use libnss-mysql (mysql). This lets regular system accounts which we have already created remain valid, important because we don’t want to have to import root, your regular user account(s) and all of the daemon accounts. More importantly still if connectivity to the database is lost it is still possible to log in to perform administrative duties. You will note that we created the groups and user tables with an AUTO_INCREMENT set to 5000. Most systems begin creating local user accounts at UID 1000, this amount of space between file-based accounts and database-backed accounts should be enough to keep accounts from overlapping, however your situation may require adjustment. Let’s create a test account and see how we did:

INSERT INTO users (username,gecos,homedir,password)
    VALUES ('test', 'Test Account', '/home/test', ENCRYPT('test'));
INSERT INTO groups (name)
    VALUES ('test');
INSERT INTO grouplist (gid,username)
    VALUES (5000,'test');

Here we have created a user named test, with the password ‘test’, in the group test. The gid value in the last INSERT query should be changed to reflect the UID of the new users in subsequent runs. Create /home/test and chown test:test /home/test. When you ls -lsah /home do you see test and test under user and group next to the test directory? If so congratulations, you have successfully configured libnss-mysql. You may wish to further test your configuration by logging in via ssh/sftp.

It should be easy to see how one can manipulate these queries to add, delete and manage system accounts directly from inside your web-based management interface or other applications, the potential here is limitless.

Addendum:
I encountered a problem setting up a new mysql (server) installation on a host that was configured to use libnss-mysql. The message goes something like:

#  /usr/bin/mysql_install_db
Installing MySQL system tables...    
100706  9:09:15 - mysqld got signal 11 ;
This could be because you hit a bug. It is also possible that this binary
or one of the libraries it was linked against is corrupt, improperly built,
or misconfigured. This error can also be caused by malfunctioning hardware.
We will try our best to scrape up some info that will hopefully help diagnose
the problem, but since we have already crashed, something is definitely wrong
and this may fail.                                                           

key_buffer_size=0
read_buffer_size=262144
max_used_connections=0
max_connections=100    
threads_connected=0    
It is possible that mysqld could use up to
key_buffer_size + (read_buffer_size + sort_buffer_size)*max_connections = 76800 K
bytes of memory                                                                  
Hope that's ok; if not, decrease some variables in the equation.                 

thd=(nil)
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went   
terribly wrong...                                                      
Cannot determine thread, fp=0x841099c, backtrace may not be correct.   
Bogus stack limit or frame pointer, fp=0x841099c, stack_bottom=0xbffe0000, thread_stack=196608, aborting backtrace.
The manual page at http://dev.mysql.com/doc/mysql/en/crashing.html contains                                        
information that should help you find out what is causing the crash.                                               

This crash occured while the server was calling initgroups(). This is
often due to the use of a mysqld that is statically linked against glibc
and configured to use LDAP in /etc/nsswitch.conf. You will need to either
upgrade to a version of glibc that does not have this problem (2.3.4 or  
later when used with nscd), disable LDAP in your nsswitch.conf, or use a
mysqld that is not statically linked.                                    
Installation of system tables failed!                                    

Examine the logs in /var/lib/mysql for more information.
You can try to start the mysqld daemon with:            
/usr/sbin/mysqld --skip-grant &                         
and use the command line tool                           
/usr/bin/mysql to connect to the mysql                  
database and look at the grant tables:                  

shell> /usr/bin/mysql -u root mysql
mysql> show tables                 

Try 'mysqld --help' if you have problems with paths. Using --log
gives you a log in /var/lib/mysql that may be helpful.          

The latest information about MySQL is available on the web at
http://www.mysql.com                                         
Please consult the MySQL manual section: 'Problems running mysql_install_db',
and the manual section that describes problems on your OS.                   
Another information source is the MySQL email archive.                       
Please check all of the above before mailing us!                             
And if you do mail us, you MUST use the /usr/bin/mysqlbug script!

Of course, I was running glibc 2.11.2 and have nothing in the way of LDAP configured. Unfortunately there is no workaround, you must run the mysql daemon on  a separate server that does not use libnss-mysql.

Brute Force and Flood Protection for Web Forms

In the last article I told you any username-and-password authentication system that is exposed to the Internet is inherently vulnerable to dictionary and brute force attack. If you must use such an authentication scheme you can defend it by implementing rate control. If you block an attacker from trying to log in for one hour after three failed attempts it would take them a year to try just under 3,000 combinations. In cryptanalytic terms that is abysmal and the odds are on your side that the attacker will have moved on by then.

While porting your ban system to fail2ban might be a great idea it’s probably overkill for situations where you have hundreds of legitimate users who might often forget their credentials; IP-bans are not generally considered good customer service. Many sites, including Google, will present the user with a CAPTCHA after three failed attempts and that’s great but those are getting easier to crack every day.

For the sake of the pseudocode in this article we’re going to assume you want to block the  potential attacker and politely tell them they have either a) failed to log in too many times, please come back in an hour or b) posted too recently, please try again. Since we want to be able to rate control two (and perhaps more in the future) different things and we don’t want to make a mess of our database let’s make one table called ‘greylist’ and use the type column to differentiate:

CREATE TABLE `demo_cat`.`greylist` (
`type` VARCHAR( 30 ) NOT NULL ,
`date` INT NOT NULL ,
`ip` VARCHAR( 15 ) NOT NULL ,
PRIMARY KEY ( `ip` ) ,
INDEX ( `date` )
);

Now in your login script for argument’s sake we’ll say $outcome is a boolean representation of if the authentication was successful or not and $delay is the period of time we want to measure for in seconds. We’ll start off by clearing everything that’s out of date, a relatively inexpensive query to run every time there’s a failure. After the table has been updated we’ll add an entry for the current failure and take a tally of all the entries for the user’s IP. If the tally exceeds the retry $threshold we’ll tell them to buzz off for an hour, change their password, show a captcha or whatever suits your site best.

<?php

if(!$outcome)
{
   $ip = mysql_real_escape_string($_SERVER['REMOTE_ADDR']);
   mysql_query("delete from `greylist` where `type` = 'login' and `date` < '".time()-$offset."'");
   mysql_query("insert into `greylist` (`type`, `date`, `ip`) values ('login', '".time()."', '$ip'')");
   $result = mysql_query("select `ip` from `greylist` where `type` = 'login' and `ip` = '$ip'");
   if(mysql_num_rows($result > $threshold))
   {
      // Too many tries, what now?
   }
   else
   {
      // Please try again
   }
}

?>

It is as simple as that. Now let’s use this to flood-protect our comments box:

<?php

if($_POST)
{
   $ip = mysql_real_escape_string($_SERVER['REMOTE_ADDR']);
   mysql_query("delete from `greylist` where `type` = 'comment' and `date` < '".time()-$offset."'");
   mysql_query("insert into `greylist` (`type`, `date`, `ip`) values ('comment', '".time()."', '$ip'')");
   $result = mysql_query("select `ip` from `greylist` where `type` = 'comment' and `ip` = '$ip'");
   if(mysql_num_rows($result > $threshold))
   {
      // You posted too recently, please wait x seconds before trying again.
   }
   else
   {
      // Continue...
   }
}

?>

A more sophisticated implementation of this concept is in use at Ychan, where users’ posting patterns are analyzed to determine if they are computers, legitimate humans or computers trying to look like humans.

Return top
foxpa.ws
Online Marketing Toplist
Internet
Technology Blogs - Blog Rankings

Internet Blogs - BlogCatalog Blog Directory

Technology blogs
Bad Karma Networks

Please Donate!


Made in Canada  •  There's a fox in the Gibson!  •  2010-12