Shit happens, they say, and even the most advanced and careful administrators may fail. So did I: instead of syncing /etc/passwd to Postfix chroot I deleted them both. What was really bad about this was the fact that the server is shared webhosting, having (at the moment of writing these lines) total of 288 accounts created. I don't have a idea why I didn't restore it from backup; probably I was so shocked by what I did that it just didn't come to my mind. Anyway, here comes the story.
First, I checked possible sources of data for recovery: /etc/shadow, /etc/passwd-, /etc/group, list of homedirs, and /etc/mail/virtusertable for virtual e-mail accounts. After checking all of them I wrote two tiny bash scripts which generate pieces of resulting passwd.
genpasswd.sh
#!/bin/sh
rm usrs
COMMON_GID=502
NUM_SYS_USERS=50
for USER in `cut -d : -f 1 /etc/shadow|egrep -v 'virtuser|ftp'|sed -n "$NUM_SYS_USERS,$ p"` do
UID=`ls -l /var/www/$USER|awk '{print $3}'` > /dev/null 2>&1
GID=`cat /etc/group|grep -w $USER|grep -v $COMMON_GID|cut -d : -f 3`
echo -n "$USER:x:$UID:$GID:$USER:/var/www/$USER/data:/bin/date\n" >> usrs
done
genmailpasswd.sh
#!/bin/sh
for USER in `cat service_users` do
EMAIL=`grep $USER /etc/mail/virtusertable|awk '{print $1}'`
MAILBOX=`echo $EMAIL|cut -d @ -f 1`
DOMAIN=`echo $EMAIL|cut -d @ -f 2`
UID=`echo $USER|cut -d _ -f 2`
PARENT_USER=`locate $DOMAIN|grep email|cut -d \/ -f 4|head -1`
GID=`id -g $PARENT_USER`
PASSWD_ENTRY="$USER:x:$UID:$GID::/var/www/$PARENT_USER/data/email/$DOMAIN/$MAILBOX:/sbin/nologin"
echo $PASSWD_ENTRY
done
A short explanation about the scripts:
COMMON_GID - GID of the service group to which has all users' accounts belong
NUM_SYS_USERS - number of service users (daemons, etc.) to skip while reading shadow;
usrs - temporary file which will hold almost-ready piece of passwd; I didn't want to mess with string formatting inside the script, so to make it ready for pasting inside /etc/passwd one will have to use vim/sed a little: `:%s/\/nologin/\/nologin\r/g` `:%s/:x:\n/:x:/g :wq`