#!/usr/bin/perl -w # # $Id: svc_acct.export,v 1.36 2002-05-16 14:28:35 ivan Exp $ # # Create and export password, radius and vpopmail password files: # passwd, passwd.adjunct, shadow, acp_passwd, acp_userinfo, acp_dialup # users/assign, domains/vdomain/vpasswd # Also export sendmail and qmail config files. use strict; use vars qw($conf); use Fcntl qw(:flock); use File::Path; use IO::Handle; use FS::Conf; use Net::SSH qw(ssh); use Net::SCP qw(scp); use FS::UID qw(adminsuidsetup datasrc dbh); use FS::Record qw(qsearch qsearchs fields); use FS::svc_acct; use FS::svc_domain; use FS::svc_forward; my $ssh='ssh'; my $rsync='rsync'; my $user = shift or die &usage; adminsuidsetup $user; $conf = new FS::Conf; my $userpolicy = $conf->config('username_policy') if $conf->exists('username_policy'); my @shellmachines = $conf->config('shellmachines') if $conf->exists('shellmachines'); my @bsdshellmachines = $conf->config('bsdshellmachines') if $conf->exists('bsdshellmachines'); my @nismachines = $conf->config('nismachines') if $conf->exists('nismachines'); my @erpcdmachines = $conf->config('erpcdmachines') if $conf->exists('erpcdmachines'); my @radiusmachines = $conf->config('radiusmachines') if $conf->exists('radiusmachines'); my $textradiusprepend = $conf->exists('textradiusprepend') ? $conf->config('textradiusprepend') : ''; warn "using depriciated textradiusprepend file" if $textradiusprepend; my $radiusprepend = $conf->exists('radiusprepend') ? join("\n", $conf->config('radiusprepend')) : ''; my @vpopmailmachines = $conf->config('vpopmailmachines') if $conf->exists('vpopmailmachines'); my $vpopmailrestart = ''; $vpopmailrestart = $conf->config('vpopmailrestart') if $conf->exists('vpopmailrestart'); my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachines[0]) if $vpopmailmachines[0]; my($shellmachine, @qmailmachines); if ( $conf->exists('qmailmachines') ) { $shellmachine = $conf->config('shellmachine'); @qmailmachines = $conf->config('qmailmachines'); } my(@sendmailmachines, $sendmailconfigpath, $sendmailrestart); if ( $conf->exists('sendmailmachines') ) { @sendmailmachines = $conf->config('sendmailmachines'); $sendmailconfigpath = $conf->config('sendmailconfigpath') || '/etc'; $sendmailrestart = $conf->config('sendmailrestart'); } my $mydomain = $conf->config('domain') if $conf->exists('domain'); my(@saltset)= ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' ); require 5.004; #srand(time|$$); my $spooldir = "/usr/local/etc/freeside/export.". datasrc; my $spoollock = "/usr/local/etc/freeside/svc_acct.export.lock.". datasrc; open(EXPORT,"+>>$spoollock") or die "Can't open $spoollock: $!"; select(EXPORT); $|=1; select(STDOUT); unless ( flock(EXPORT,LOCK_EX|LOCK_NB) ) { seek(EXPORT,0,0); my($pid)=; chop($pid); #no reason to start lots of blocking processes die "Is another export process running under pid $pid?\n"; } seek(EXPORT,0,0); print EXPORT $$,"\n"; my(@svc_domain)=qsearch('svc_domain',{}); ( open(MASTER,">$spooldir/master.passwd") and flock(MASTER,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/.master.passwd: $!"; ( open(PASSWD,">$spooldir/passwd") and flock(PASSWD,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/passwd: $!"; ( open(SHADOW,">$spooldir/shadow") and flock(SHADOW,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/shadow: $!"; ( open(ACP_PASSWD,">$spooldir/acp_passwd") and flock(ACP_PASSWD,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/acp_passwd: $!"; ( open(ACP_DIALUP,">$spooldir/acp_dialup") and flock(ACP_DIALUP,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/acp_dialup: $!"; ( open(USERS,">$spooldir/users") and flock(USERS,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/users: $!"; ( open(ASSIGN,">$spooldir/assign") and flock(ASSIGN,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/assign: $!"; ( open(RCPTHOSTS,">$spooldir/rcpthosts") and flock(RCPTHOSTS,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/rcpthosts: $!"; ( open(VPOPRCPTHOSTS,">$spooldir/vpoprcpthosts") and flock(VPOPRCPTHOSTS,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/rcpthosts: $!"; ( open(RECIPIENTMAP,">$spooldir/recipientmap") and flock(RECIPIENTMAP,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/recipientmap: $!"; ( open(VIRTUALDOMAINS,">$spooldir/virtualdomains") and flock(VIRTUALDOMAINS,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/virtualdomains: $!"; ( open(VPOPVIRTUALDOMAINS,">$spooldir/vpopvirtualdomains") and flock(VPOPVIRTUALDOMAINS,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/virtualdomains: $!"; ( open(VIRTUSERTABLE,">$spooldir/virtusertable") and flock(VIRTUSERTABLE,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/virtusertable: $!"; ( open(SENDMAIL_CW,">$spooldir/sendmail.cw") and flock(SENDMAIL_CW,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/sendmail.cw: $!"; chmod 0644, "$spooldir/passwd", "$spooldir/acp_dialup", "$spooldir/assign", "$spooldir/sendmail.cw", "$spooldir/virtusertable", "$spooldir/rcpthosts", "$spooldir/vpoprcpthosts", "$spooldir/recipientmap", "$spooldir/virtualdomains", "$spooldir/vpopvirtualdomains", ; chmod 0600, "$spooldir/master.passwd", "$spooldir/acp_passwd", "$spooldir/shadow", "$spooldir/users", ; rmtree"$spooldir/domains", 0, 1; mkdir "$spooldir/domains", 0700; setpriority(0,0,10); print USERS "$radiusprepend\n"; my %usernames; ## this hack helps keep the passwd files sane my @sendmail; my $svc_domain; foreach $svc_domain (sort {$a->domain cmp $b->domain} @svc_domain) { my($domain)=$svc_domain->domain; print RCPTHOSTS "$domain\n.$domain\n"; print VPOPRCPTHOSTS "$domain\n"; print SENDMAIL_CW "$domain\n"; ### # FORMAT OF THE ASSIGN/USERS FILE HERE print ASSIGN join(":", "+" . $domain . "-", $domain, $vpopuid, $vpopgid, $vpopdir . "/domains/" . $domain, "-", "", "", ), "\n" if $vpopmailmachines[0]; (mkdir "$spooldir/domains/" . $domain, 0700) or die "Can't create $spooldir/domains/" . $domain .": $!"; ( open(QMAILDEFAULT,">$spooldir/domains/" . $domain . "/.qmail-default") and flock(QMAILDEFAULT,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/domains/" . $domain . "/.qmail-default: $!"; ( open(VPASSWD,">$spooldir/domains/" . $domain . "/vpasswd") and flock(VPASSWD,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/domains/" . $domain . "/vpasswd: $!"; my ($svc_acct); if ($svc_domain->getfield('catchall')) { $svc_acct = qsearchs('svc_acct', {'svcnum' => $svc_domain->catchall}); die "Cannot find catchall account for domain $domain\n" unless $svc_acct; my $username = $svc_acct->username; push @sendmail, "\@$domain\t$username\n"; print VIRTUALDOMAINS "$domain:$username-$domain\n", ".$domain:$username-$domain\n", ; ### # FORMAT OF THE .QMAIL-DEFAULT FILE HERE print QMAILDEFAULT "| $vpopdir/bin/vdelivermail \"\" " . $svc_acct->email . "\n" if $vpopmailmachines[0]; }else{ ### # FORMAT OF THE .QMAIL-DEFAULT FILE HERE print QMAILDEFAULT "| $vpopdir/bin/vdelivermail \"\" bounce-no-mailbox\n" if $vpopmailmachines[0]; } print VPOPVIRTUALDOMAINS "$domain:$domain\n"; foreach $svc_acct (qsearch('svc_acct', {'domsvc' => $svc_domain->svcnum})) { my($password)=$svc_acct->getfield('_password'); my($cpassword,$rpassword); #if ( ( length($password) <= 8 ) if ( ( length($password) <= 12 ) && ( $password ne '*' ) && ( $password ne '!!' ) && ( $password ne '' ) ) { $cpassword=crypt($password, $saltset[int(rand(64))].$saltset[int(rand(64))] ); $rpassword=$password; } else { $cpassword=$password; $rpassword='UNIX'; } my $username; if ($mydomain && ($mydomain eq $svc_domain->domain)) { $username=$svc_acct->username; } elsif ($userpolicy =~ /^prepend domsvc$/) { $username=$svc_acct->domsvc . $svc_acct->username; } elsif ($userpolicy =~ /^append domsvc$/) { $username=$svc_acct->username . $svc_acct->domsvc; } elsif ($userpolicy =~ /^append domain$/) { $username=$svc_acct->username . $svc_domain->domain; } elsif ($userpolicy =~ /^append domain$/) { $username=$svc_acct->username . $svc_domain->domain; } elsif ($userpolicy =~ /^append \@domain$/) { $username=$svc_acct->username . '@'. $svc_domain->domain; } else { die "Unknown policy in username_policy\n"; } if ($svc_acct->dir ne '/dev/null' || $svc_acct->slipip ne '') { if ($usernames{$username}++) { die "Duplicate username detected: $username\n"; } } if ( $svc_acct->uid =~ /^(\d+)$/ ) { die "Non-root user ". $svc_acct->username. " has 0 UID!" if $svc_acct->uid == 0 && $svc_acct->username ne 'root'; if ( $svc_acct->dir ne "/dev/null") { ### # FORMAT OF FreeBSD MASTER PASSWD FILE HERE print MASTER join(":", $username, # User name $cpassword, # Encrypted password $svc_acct->uid, # User ID $svc_acct->gid, # Group ID "", # Login Class "0", # Password Change Time "0", # Password Expiration Time $svc_acct->finger, # Users name $svc_acct->dir, # Users home directory $svc_acct->shell, # shell ), "\n" ; ### # FORMAT OF THE PASSWD FILE HERE print PASSWD join(":", $username, 'x', # "##". $username, $svc_acct->uid, $svc_acct->gid, $svc_acct->finger, $svc_acct->dir, $svc_acct->shell, ), "\n"; ### # FORMAT OF THE SHADOW FILE HERE print SHADOW join(":", $username, $cpassword, '', '', '', '', '', '', '', ), "\n"; } } ### # FORMAT OF THE VPASSWD FILE HERE print VPASSWD join(":", $svc_acct->username, $cpassword, '1', '0', $svc_acct->username, "$vpopdir/domains/" . $svc_domain->domain ."/" . $svc_acct->username, 'NOQUOTA', ), "\n"; if ( $svc_acct->slipip ne '' ) { ### # FORMAT OF THE ACP_* FILES HERE print ACP_PASSWD join(":", $username, $cpassword, "0", "0", "", "", "", ), "\n"; my($ip)=$svc_acct->slipip; unless ( $ip eq '0.0.0.0' || $svc_acct->slipip eq '0e0' ) { print ACP_DIALUP $username, "\t*\t", $svc_acct->slipip, "\n"; } my %radreply = $svc_acct->radius_reply; my %radcheck = $svc_acct->radius_check; my $radcheck = join ", ", map { qq($_ = "$radcheck{$_}") } keys %radcheck; $radcheck .= ", " if $radcheck; ### # FORMAT OF THE USERS FILE HERE print USERS $username, qq(\t${textradiusprepend}), $radcheck, # qq(Password = "$rpassword"\n\t), join ",\n\t", map { qq($_ = "$radreply{$_}") } keys %radreply; #if ( $ip && $ip ne '0e0' ) { # #print USERS qq(,\n\tFramed-Address = "$ip"\n\n); # print USERS qq(,\n\tFramed-IP-Address = "$ip"\n\n); #} else { print USERS qq(\n\n); #} } ### # vpopmail directory structure creation (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username, 0700) or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . ": $!"; (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir", 0700) or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir: $!"; (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/cur", 0700) or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/cur: $!"; (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/new", 0700) or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/new: $!"; (mkdir "$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/Maildir/tmp", 0700) or die "Can't create $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . " /Maildir/tmp: $!"; ( open(DOTQMAIL,">$spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/.qmail") and flock(DOTQMAIL,LOCK_EX|LOCK_NB) ) or die "Can't open $spooldir/domains/" . $svc_domain->domain . "/" . $svc_acct->username . "/.qmail: $!"; my($svc_forward); foreach $svc_forward (qsearch('svc_forward', {'srcsvc' => $svc_acct->svcnum})) { my($destination); if ($svc_forward->dstsvc) { my $dst_acct = qsearchs('svc_acct', {'svcnum' => $svc_forward->dstsvc}); my $dst_domain = qsearchs('svc_domain', {'svcnum' => $dst_acct->domsvc}); $destination = $dst_acct->username . '@' . $dst_domain->domain; if ($dst_domain->domain eq $mydomain) { print VIRTUSERTABLE $svc_acct->username . "@" . $svc_domain->domain . "\t" . $dst_acct->username . "\n"; print RECIPIENTMAP $svc_acct->username . "@" . $svc_domain->domain . ":$destination\n"; } } else { $destination = $svc_forward->dst; } ### # FORMAT OF .QMAIL FILES HERE print DOTQMAIL "$destination\n"; } flock(DOTQMAIL,LOCK_UN); close DOTQMAIL; } flock(VPASSWD,LOCK_UN); flock(QMAILDEFAULT,LOCK_UN); close VPASSWD; close QMAILDEFAULT; } ### # FORMAT OF THE ASSIGN/USERS FILE FINAL LINE HERE print ASSIGN ".\n"; print VIRTUSERTABLE @sendmail; flock(MASTER,LOCK_UN); flock(PASSWD,LOCK_UN); flock(SHADOW,LOCK_UN); flock(ACP_DIALUP,LOCK_UN); flock(ACP_PASSWD,LOCK_UN); flock(USERS,LOCK_UN); flock(ASSIGN,LOCK_UN); flock(SENDMAIL_CW,LOCK_UN); flock(VIRTUSERTABLE,LOCK_UN); flock(RCPTHOSTS,LOCK_UN); flock(VPOPRCPTHOSTS,LOCK_UN); flock(RECIPIENTMAP,LOCK_UN); flock(VPOPVIRTUALDOMAINS,LOCK_UN); close MASTER; close PASSWD; close SHADOW; close ACP_DIALUP; close ACP_PASSWD; close USERS; close ASSIGN; close SENDMAIL_CW; close VIRTUSERTABLE; close RCPTHOSTS; close VPOPRCPTHOSTS; close RECIPIENTMAP; close VPOPVIRTUALDOMAINS; ### # export stuff # my($ashellmachine); foreach $ashellmachine (@shellmachines) { my $scp = new Net::SCP; $scp->scp("$spooldir/passwd","root\@$ashellmachine:/etc/passwd.new") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/shadow","root\@$ashellmachine:/etc/shadow.new") or die "scp error: ". $scp->{errstr}; ssh("root\@$ashellmachine", "( ". "mv /etc/passwd.new /etc/passwd; ". "mv /etc/shadow.new /etc/shadow; ". " )" ) == 0 or die "ssh error: $!"; } my($bsdshellmachine); foreach $bsdshellmachine (@bsdshellmachines) { my $scp = new Net::SCP; $scp->scp("$spooldir/passwd","root\@$bsdshellmachine:/etc/passwd.new") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/master.passwd","root\@$bsdshellmachine:/etc/master.passwd.new") or die "scp error: ". $scp->{errstr}; ssh("root\@$bsdshellmachine", "( ". "mv /etc/passwd.new /etc/passwd; ". #"mv /etc/master.passwd.new /etc/master.passwd; ". "pwd_mkdb /etc/master.passwd.new; ". " )" ) == 0 or die "ssh error: $!"; } my($nismachine); foreach $nismachine (@nismachines) { my $scp = new Net::SCP; $scp->scp("$spooldir/passwd","root\@$nismachine:/etc/global/passwd") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/shadow","root\@$nismachine:/etc/global/shadow") or die "scp error: ". $scp->{errstr}; ssh("root\@$nismachine", "( ". "cd /var/yp; make; ". " )" ) == 0 or die "ssh error: $!"; } my($erpcdmachine); foreach $erpcdmachine (@erpcdmachines) { my $scp = new Net::SCP; $scp->scp("$spooldir/acp_passwd","root\@$erpcdmachine:/usr/annex/acp_passwd") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/acp_dialup","root\@$erpcdmachine:/usr/annex/acp_dialup") or die "scp error: ". $scp->{errstr}; ssh("root\@$erpcdmachine", "( ". "kill -USR1 \`cat /usr/annex/erpcd.pid\'". " )" ) == 0 or die "ssh error: $!"; } my($radiusmachine); foreach $radiusmachine (@radiusmachines) { my $scp = new Net::SCP; $scp->scp("$spooldir/users","root\@$radiusmachine:/etc/raddb/users") or die "scp error: ". $scp->{errstr}; ssh("root\@$radiusmachine", "( ". "builddbm". " )" ) == 0 or die "ssh error: $!"; } #my @args = ("/bin/tar", "c", "--force-local", "-C", "$spooldir", "-f", "$spooldir/vpoptarball", "domains"); #system {$args[0]} @args; my($vpopmailmachine); foreach $vpopmailmachine (@vpopmailmachines) { my ($machine, $vpopdir, $vpopuid, $vpopgid) = split (/\s+/, $vpopmailmachine); my $scp = new Net::SCP; # $scp->scp("$spooldir/vpoptarball","root\@$machine:vpoptarball") # or die "scp error: ". $scp->{errstr}; # ssh("root\@$machine", # "( ". # "rm -rf domains; ". # "tar xf vpoptarball; ". # "chown -R $vpopuid:$vpopgid domains; ". # "tar cf vpoptarball domains; ". # "cd $vpopdir; ". # "tar xf ~/vpoptarball; ". # " )" # ) # == 0 or die "ssh error: $!"; chdir $spooldir; my @args = ("$rsync", "-rlpt", "-e", "$ssh", "domains/", "vpopmail\@$machine:$vpopdir/domains/"); system {$args[0]} @args; $scp->scp("$spooldir/assign","root\@$machine:/var/qmail/users/assign") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/vpopvirtualdomains","root\@$machine:/var/qmail/control/virtualdomains") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/vpoprcpthosts","root\@$machine:/var/qmail/control/rcpthosts") or die "scp error: ". $scp->{errstr}; ssh("root\@$machine", "( ". $vpopmailrestart . " )" ) == 0 or die "ssh error: $!"; } my($sendmailmachine); foreach $sendmailmachine (@sendmailmachines) { my $scp = new Net::SCP; $scp->scp("$spooldir/sendmail.cw","root\@$sendmailmachine:$sendmailconfigpath/sendmail.cw.new") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/virtusertable","root\@$sendmailmachine:$sendmailconfigpath/virtusertable.new") or die "scp error: ". $scp->{errstr}; ssh("root\@$sendmailmachine", "( ". "mv $sendmailconfigpath/sendmail.cw.new $sendmailconfigpath/sendmail.cw; ". "mv $sendmailconfigpath/virtusertable.new $sendmailconfigpath/virtusertable; ". $sendmailrestart. " )" ) == 0 or die "ssh error: $!"; } my($qmailmachine); foreach $qmailmachine (@qmailmachines) { my $scp = new Net::SCP; $scp->scp("$spooldir/recipientmap","root\@$qmailmachine:/var/qmail/control/recipientmap") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/virtualdomains","root\@$qmailmachine:/var/qmail/control/virtualdomains") or die "scp error: ". $scp->{errstr}; $scp->scp("$spooldir/rcpthosts","root\@$qmailmachine:/var/qmail/control/rcpthosts") or die "scp error: ". $scp->{errstr}; #ssh("root\@$qmailmachine","/etc/init.d/qmail restart") # == 0 or die "ssh error: $!"; } unlink $spoollock; flock(EXPORT,LOCK_UN); close EXPORT; # sub usage { die "Usage:\n\n svc_acct.export user\n"; }