diff options
Diffstat (limited to 'FS')
36 files changed, 2581 insertions, 202 deletions
diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm index 28b3a06fa..e44ebcc0a 100644 --- a/FS/FS/CGI.pm +++ b/FS/FS/CGI.pm @@ -253,7 +253,7 @@ sub small_custview { my $html = 'Customer #<B>'. $cust_main->custnum. '</B>'. ntable('#e8e8e8'). '<TR><TD>'. ntable("#cccccc",2). - '<TR><TD ALIGN="right" VALIGN="top">Billing</TD><TD BGCOLOR="#ffffff">'. + '<TR><TD ALIGN="right" VALIGN="top">Billing<BR>Address</TD><TD BGCOLOR="#ffffff">'. $cust_main->getfield('last'). ', '. $cust_main->first. '<BR>'; $html .= $cust_main->company. '<BR>' if $cust_main->company; @@ -270,7 +270,7 @@ sub small_custview { my $pre = $cust_main->ship_last ? 'ship_' : ''; $html .= '<TD>'. ntable("#cccccc",2). - '<TR><TD ALIGN="right" VALIGN="top">Service</TD><TD BGCOLOR="#ffffff">'. + '<TR><TD ALIGN="right" VALIGN="top">Service<BR>Address</TD><TD BGCOLOR="#ffffff">'. $cust_main->get("${pre}last"). ', '. $cust_main->get("${pre}first"). '<BR>'; $html .= $cust_main->get("${pre}company"). '<BR>' diff --git a/FS/FS/ClientAPI.pm b/FS/FS/ClientAPI.pm new file mode 100644 index 000000000..f7b8eb028 --- /dev/null +++ b/FS/FS/ClientAPI.pm @@ -0,0 +1,44 @@ +package FS::ClientAPI; + +use strict; +use vars qw(%handler); + +%handler = (); + +#find modules +foreach my $INC ( @INC ) { + foreach my $file ( glob("$INC/FS/ClientAPI/*") ) { + $file =~ /\/(\w+)\.pm$/ or do { + warn "unrecognized ClientAPI file: $file"; + next + }; + my $mod = $1; + #warn "using FS::ClientAPI::$mod"; + eval "use FS::ClientAPI::$mod;"; + die "error using FS::ClientAPI::$mod: $@" if $@; + } +} + +#(sub for modules) +sub register_handlers { + my $self = shift; + my %new_handlers = @_; + foreach my $key ( keys %new_handlers ) { + warn "WARNING: redefining sub $key" if exists $handler{$key}; + #warn "registering $key"; + $handler{$key} = $new_handlers{$key}; + } +} + +#--- + +sub dispatch { + my ( $self, $name ) = ( shift, shift ); + my $sub = $handler{$name} + or die "unknown FS::ClientAPI sub $name (known: ". join(" ", keys %handler ); + #or die "unknown FS::ClientAPI sub $name"; + &{$sub}(@_); +} + +1; + diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm new file mode 100644 index 000000000..674785524 --- /dev/null +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -0,0 +1,136 @@ +package FS::ClientAPI::MyAccount; + +use strict; +use vars qw($cache); +use Digest::MD5 qw(md5_hex); +use Date::Format; +use Cache::SharedMemoryCache; #store in db? +use FS::CGI qw(small_custview); #doh +use FS::Conf; +use FS::Record qw(qsearchs); +use FS::svc_acct; +use FS::svc_domain; +use FS::cust_main; +use FS::cust_bill; + +use FS::ClientAPI; #hmm +FS::ClientAPI->register_handlers( + 'MyAccount/login' => \&login, + 'MyAccount/customer_info' => \&customer_info, + 'MyAccount/invoice' => \&invoice, +); + +#store in db? +my $cache = new Cache::SharedMemoryCache(); + +#false laziness w/FS::ClientAPI::passwd::passwd (needs to handle encrypted pw) +sub login { + my $p = shift; + + my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } ) + or return { error => "Domain not found" }; + + my $svc_acct = + ( length($p->{'password'}) < 13 + && qsearchs( 'svc_acct', { 'username' => $p->{'username'}, + 'domsvc' => $svc_domain->svcnum, + '_password' => $p->{'password'} } ) + ) + || qsearchs( 'svc_acct', { 'username' => $p->{'username'}, + 'domsvc' => $svc_domain->svcnum, + '_password' => $p->{'password'} } ); + + unless ( $svc_acct ) { return { error => 'Incorrect password.' } } + + my $session = { + 'svcnum' => $svc_acct->svcnum, + }; + + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + if ( $cust_pkg ) { + my $cust_main = $cust_pkg->cust_main; + $session->{'custnum'} = $cust_main->custnum; + } + + my $session_id; + do { + $session_id = md5_hex(md5_hex(time(). {}. rand(). $$)) + } until ( ! defined $cache->get($session_id) ); #just in case + + $cache->set( $session_id, $session, '1 hour' ); + + return { 'error' => '', + 'session_id' => $session_id, + }; +} + +sub customer_info { + my $p = shift; + my $session = $cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my %return; + + my $custnum = $session->{'custnum'}; + + if ( $custnum ) { #customer record + + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or return { 'error' => "unknown custnum $custnum" }; + + $return{balance} = $cust_main->balance; + + my @open = map { + { + invnum => $_->invnum, + date => time2str("%b %o, %Y", $_->_date), + owed => $_->owed, + }; + } $cust_main->open_cust_bill; + $return{open_invoices} = \@open; + + my $conf = new FS::Conf; + $return{small_custview} = + small_custview( $cust_main, $conf->config('defaultcountry') ); + + $return{name} = $cust_main->first. ' '. $cust_main->get('last'); + + } else { #no customer record + + my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } ) + or die "unknown svcnum"; + $return{name} = $svc_acct->email; + + } + + + return { 'error' => '', + 'custnum' => $custnum, + %return, + }; + +} + +sub invoice { + my $p = shift; + my $session = $cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $custnum = $session->{'custnum'}; + + my $invnum = $p->{'invnum'}; + + my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum, + 'custnum' => $custnum } ) + or return { 'error' => "Can't find invnum" }; + + #my %return; + + return { 'error' => '', + 'invnum' => $invnum, + 'invoice_text' => join('', $cust_bill->print_text ), + }; + +} + + diff --git a/FS/FS/ClientAPI/passwd.pm b/FS/FS/ClientAPI/passwd.pm new file mode 100644 index 000000000..29606227d --- /dev/null +++ b/FS/FS/ClientAPI/passwd.pm @@ -0,0 +1,56 @@ +package FS::ClientAPI::passwd; + +use strict; +use FS::Record qw(qsearchs); +use FS::svc_acct; +#use FS::svc_domain; + +use FS::ClientAPI; #hmm +FS::ClientAPI->register_handlers( + 'passwd/passwd' => \&passwd, + 'passwd/chfn' => \&chfn, + 'passwd/chsh' => \&chsh, +); + +sub passwd { + my $packet = shift; + + #my $domain = qsearchs('svc_domain', { 'domain' => $packet->{'domain'} } ) + # or return { error => "Domain $domain not found" }; + + my $old_password = $packet->{'old_password'}; + my $new_password = $packet->{'new_password'}; + my $new_gecos = $packet->{'new_gecos'}; + my $new_shell = $packet->{'new_shell'}; + +#false laziness w/FS::ClientAPI::MyAccount::login (needs to handle encrypted pw) + my $svc_acct = + ( length($old_password) < 13 + && qsearchs( 'svc_acct', { 'username' => $packet->{'username'}, + #'domsvc' => $svc_domain->svcnum, + '_password' => $old_password } ) + ) + || qsearchs( 'svc_acct', { 'username' => $packet->{'username'}, + #'domsvc' => $svc_domain->svcnum, + '_password' => $old_password } ); + + unless ( $svc_acct ) { return { error => 'Incorrect password.' } } + + my %hash = $svc_acct->hash; + my $new_svc_acct = new FS::svc_acct ( \%hash ); + $new_svc_acct->setfield('_password', $new_password ) + if $new_password && $new_password ne $old_password; + $new_svc_acct->setfield('finger',$new_gecos) if $new_gecos; + $new_svc_acct->setfield('shell',$new_shell) if $new_shell; + my $error = $new_svc_acct->replace($svc_acct); + + return { error => $error }; + +} + +sub chfn {} + +sub chsh {} + +1; + diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index dbb3682d0..e93eaf3fc 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -221,8 +221,8 @@ httemplate/docs/config.html { 'key' => 'apacheroot', - 'section' => 'apache', - 'description' => 'The directory containing Apache virtual hosts', + 'section' => 'deprecated', + 'description' => '<b>DEPRECATED</b>, add a <i>www_shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. The directory containing Apache virtual hosts', 'type' => 'text', }, @@ -235,8 +235,8 @@ httemplate/docs/config.html { 'key' => 'apachemachine', - 'section' => 'apache', - 'description' => 'A machine with the apacheroot directory and user home directories. The existance of this file enables setup of virtual host directories, and, in conjunction with the `home\' configuration file, symlinks into user home directories.', + 'section' => 'deprecated', + 'description' => '<b>DEPRECATED</b>, add a <i>www_shellcommands</i> <a href="../browse/part_export.cgi">export</a> instead. A machine with the apacheroot directory and user home directories. The existance of this file enables setup of virtual host directories, and, in conjunction with the `home\' configuration file, symlinks into user home directories.', 'type' => 'text', }, @@ -348,7 +348,7 @@ httemplate/docs/config.html { 'key' => 'editreferrals', 'section' => 'UI', - 'description' => 'Enable referral modification for existing customers', + 'description' => 'Enable advertising source modification for existing customers', 'type' => 'checkbox', }, @@ -404,28 +404,28 @@ httemplate/docs/config.html { 'key' => 'icradiusmachines', 'section' => 'deprecated', - 'description' => '<b>DEPRECATED</b>, add a <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to enable radcheck and radreply table population - by default in the Freeside database, or in the database specified by the <a href="http://rootwood.haze.st/aspside/config/config-view.cgi#icradius_secrets">icradius_secrets</a> config option (the radcheck and radreply tables needs to be created manually). You do not need to use MySQL for your Freeside database to export to an ICRADIUS/FreeRADIUS MySQL database with this option. <blockquote><b>ADDITIONAL DEPRECATED FUNCTIONALITY</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - your <a href="ftp://ftp.cheapnet.net/pub/icradius">ICRADIUS</a> machines or <a href="http://www.freeradius.org/">FreeRADIUS</a> (with MySQL authentication) machines, one per line. Machines listed in this file will have the radcheck table exported to them. Each line should contain four items, separted by whitespace: machine name, MySQL database name, MySQL username, and MySQL password. For example: <CODE>"radius.isp.tld radius_db radius_user passw0rd"</CODE></blockquote>', + 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to enable radcheck and radreply table population - by default in the Freeside database, or in the database specified by the <a href="http://rootwood.haze.st/aspside/config/config-view.cgi#icradius_secrets">icradius_secrets</a> config option (the radcheck and radreply tables needs to be created manually). You do not need to use MySQL for your Freeside database to export to an ICRADIUS/FreeRADIUS MySQL database with this option. <blockquote><b>ADDITIONAL DEPRECATED FUNCTIONALITY</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - your <a href="ftp://ftp.cheapnet.net/pub/icradius">ICRADIUS</a> machines or <a href="http://www.freeradius.org/">FreeRADIUS</a> (with MySQL authentication) machines, one per line. Machines listed in this file will have the radcheck table exported to them. Each line should contain four items, separted by whitespace: machine name, MySQL database name, MySQL username, and MySQL password. For example: <CODE>"radius.isp.tld radius_db radius_user passw0rd"</CODE></blockquote>', 'type' => [qw( checkbox textarea )], }, { 'key' => 'icradius_mysqldest', 'section' => 'deprecated', - 'description' => '<b>DEPRECATED</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - Destination directory for the MySQL databases, on the ICRADIUS/FreeRADIUS machines. Defaults to "/usr/local/var/".', + 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> https://billing.crosswind.net/freeside/browse/part_export.cgi">export</a> instead. Used to be the destination directory for the MySQL databases, on the ICRADIUS/FreeRADIUS machines. Defaults to "/usr/local/var/".', 'type' => 'text', }, { 'key' => 'icradius_mysqlsource', 'section' => 'deprecated', - 'description' => '<b>DEPRECATED</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - Source directory for for the MySQL radcheck table files, on the Freeside machine. Defaults to "/usr/local/var/freeside".', + 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> https://billing.crosswind.net/freeside/browse/part_export.cgi">export</a> instead. Used to be the source directory for for the MySQL radcheck table files, on the Freeside machine. Defaults to "/usr/local/var/freeside".', 'type' => 'text', }, { 'key' => 'icradius_secrets', 'section' => 'deprecated', - 'description' => '<b>DEPRECATED</b>, add <i>sqlradius</i> exports to <a href="../browse/part_svc">Service definitions</a> instead. This option used to specify a database for ICRADIUS/FreeRADIUS export. Three lines: DBI data source, username and password.', + 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> https://billing.crosswind.net/freeside/browse/part_export.cgi">export</a> instead. This option used to specify a database for ICRADIUS/FreeRADIUS export. Three lines: DBI data source, username and password.', 'type' => 'textarea', }, @@ -534,8 +534,8 @@ httemplate/docs/config.html { 'key' => 'radiusmachines', - 'section' => 'radius', - 'description' => 'Your RADIUS authentication machines, one per line. This enables export of `/etc/raddb/users\'.', + 'section' => 'deprecated', + 'description' => '<b>DEPRECATED</b>, add an <i>sqlradius</i> <a href="../browse/part_export.cgi">export</a> instead. This option used to export to be: your RADIUS authentication machines, one per line. This enables export of `/etc/raddb/users\'.', 'type' => 'textarea', }, @@ -717,15 +717,15 @@ httemplate/docs/config.html { 'key' => 'radiusprepend', - 'section' => 'radius', - 'description' => 'The contents will be prepended to the top of the RADIUS users file (text exports only).', + 'section' => 'deprecated', + 'description' => '<b>DEPRECATED</b>, real-time text radius now edits an existing file in place - just (turn off freeside-queued and) edit your RADIUS users file directly. The contents used to be be prepended to the top of the RADIUS users file (text exports only).', 'type' => 'textarea', }, { 'key' => 'textradiusprepend', 'section' => 'deprecated', - 'description' => '<b>DEPRECATED</b>, use RADIUS check attributes instead. This option will be removed soon. The contents will be prepended to the first line of a user\'s RADIUS entry in text exports.', + 'description' => '<b>DEPRECATED</b>, use RADIUS check attributes instead. The contents used to be prepended to the first line of a user\'s RADIUS entry in text exports.', 'type' => 'text', }, diff --git a/FS/FS/InitHandler.pm b/FS/FS/InitHandler.pm new file mode 100644 index 000000000..87f507c22 --- /dev/null +++ b/FS/FS/InitHandler.pm @@ -0,0 +1,88 @@ +package FS::InitHandler; + +use strict; +use vars qw($DEBUG); +use FS::UID qw(adminsuidsetup); +use FS::Record; + +$DEBUG = 1; + +sub handler { + + use Date::Format; + use Date::Parse; + use Tie::IxHash; + use HTML::Entities; + use IO::Handle; + use IO::File; + use String::Approx; + use HTML::Widgets::SelectLayers 0.02; + #use FS::UID; + #use FS::Record; + use FS::Conf; + use FS::CGI; + use FS::Msgcat; + + use FS::agent; + use FS::agent_type; + use FS::domain_record; + use FS::cust_bill; + use FS::cust_bill_pay; + use FS::cust_credit; + use FS::cust_credit_bill; + use FS::cust_main; + use FS::cust_main_county; + use FS::cust_pay; + use FS::cust_pkg; + use FS::cust_refund; + use FS::cust_svc; + use FS::nas; + use FS::part_bill_event; + use FS::part_pkg; + use FS::part_referral; + use FS::part_svc; + use FS::pkg_svc; + use FS::port; + use FS::queue; + use FS::raddb; + use FS::session; + use FS::svc_acct; + use FS::svc_acct_pop; + use FS::svc_acct_sm; + use FS::svc_domain; + use FS::svc_forward; + use FS::svc_www; + use FS::type_pkgs; + use FS::part_export; + use FS::part_export_option; + use FS::export_svc; + use FS::msgcat; + + warn "[FS::InitHandler] handler called\n" if $DEBUG; + + #this is sure to be broken on freebsd + $> = $FS::UID::freeside_uid; + + open(MAPSECRETS,"<$FS::UID::conf_dir/mapsecrets") + or die "can't read $FS::UID::conf_dir/mapsecrets: $!"; + + my %seen; + while (<MAPSECRETS>) { + next if /^\s*(#|$)/; + /^([\w\-\.]+)\s(.*)$/ + or do { warn "strange line in mapsecrets: $_"; next; }; + my($user, $datasrc) = ($1, $2); + next if $seen{$datasrc}++; + warn "[FS::InitHandler] preloading $datasrc for $user\n" if $DEBUG; + adminsuidsetup($user); + } + + close MAPSECRETS; + + #lalala probably broken on freebsd + ($<, $>) = ($>, $<); + $< = 0; + +} + +1; diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm index b1e590f2f..8934d49fc 100644 --- a/FS/FS/UID.pm +++ b/FS/FS/UID.pm @@ -171,7 +171,9 @@ Returns the current Freeside user. =cut sub getotaker { - $user; + #$user; + #stupid kludge until schema otaker fields are not 8 chars + substr($user,0,8); } =item cgisetotaker @@ -256,7 +258,7 @@ coderef into the hash %FS::UID::callback : =head1 VERSION -$Id: UID.pm,v 1.16 2002-06-28 08:23:44 ivan Exp $ +$Id: UID.pm,v 1.19 2002-08-29 06:02:52 ivan Exp $ =head1 BUGS diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 5a9fdd09b..5e041ea59 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -11,6 +11,7 @@ use Date::Format; use Mail::Internet 1.44; use Mail::Header; use Text::Template; +use FS::UID qw( datasrc ); use FS::Record qw( qsearch qsearchs ); use FS::cust_main; use FS::cust_bill_pkg; @@ -410,6 +411,154 @@ sub send { } +=item send_csv OPTIONS + +Sends invoice as a CSV data-file to a remote host with the specified protocol. + +Options are: + +protocol - currently only "ftp" +server +username +password +dir + +The file will be named "N-YYYYMMDDHHMMSS.csv" where N is the invoice number +and YYMMDDHHMMSS is a timestamp. + +The fields of the CSV file is as follows: + +record_type, invnum, custnum, _date, charged, first, last, company, address1, address2, city, state, zip, country, pkg, setup, recur, sdate, edate + +=over 4 + +=item record type - B<record_type> is either C<cust_bill> or C<cust_bill_pkg> + +If B<record_type> is C<cust_bill>, this is a primary invoice record. The +last five fields (B<pkg> through B<edate>) are irrelevant, and all other +fields are filled in. + +If B<record_type> is C<cust_bill_pkg>, this is a line item record. Only the +first two fields (B<record_type> and B<invnum>) and the last five fields +(B<pkg> through B<edate>) are filled in. + +=item invnum - invoice number +=item custnum - customer number +=item _date - invoice date +=item charged - total invoice amount +=item first - customer first name +=item last - customer first name +=item company - company name +=item address1 - address line 1 +=item address2 - address line 1 +=item city +=item state +=item zip +=item country + +=item pkg - line item description +=item setup - line item setup fee (only or both of B<setup> and B<recur> will be defined) +=item recur - line item recurring fee (only or both of B<setup> and B<recur> will be defined) +=item sdate - start date for recurring fee +=item edate - end date for recurring fee + +=back + +=cut + +sub send_csv { + my($self, %opt) = @_; + + #part one: create file + + my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill"; + mkdir $spooldir, 0700 unless -d $spooldir; + + my $file = $spooldir. '/'. $self->invnum. time2str('-%Y%m%d%H%M%S.csv', time); + + open(CSV, ">$file") or die "can't open $file: $!"; + + eval "use Text::CSV_XS"; + die $@ if $@; + + my $csv = Text::CSV_XS->new({'always_quote'=>1}); + + my $cust_main = $self->cust_main; + + $csv->combine( + 'cust_bill', + $self->invnum, + $self->custnum, + time2str("%x", $self->_date), + sprintf("%.2f", $self->charged), + ( map { $cust_main->getfield($_) } + qw( first last company address1 address2 city state zip country ) ), + map { '' } (1..5), + ) or die "can't create csv"; + print CSV $csv->string. "\n"; + + #new charges (false laziness w/print_text) + foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) { + + my($pkg, $setup, $recur, $sdate, $edate); + if ( $cust_bill_pkg->pkgnum ) { + + ($pkg, $setup, $recur, $sdate, $edate) = ( + $cust_bill_pkg->cust_pkg->part_pkg->pkg, + ( $cust_bill_pkg->setup != 0 + ? sprintf("%.2f", $cust_bill_pkg->setup ) + : '' ), + ( $cust_bill_pkg->recur != 0 + ? sprintf("%.2f", $cust_bill_pkg->recur ) + : '' ), + time2str("%x", $cust_bill_pkg->sdate), + time2str("%x", $cust_bill_pkg->edate), + ); + + } else { #pkgnum Tax + next unless $cust_bill_pkg->setup != 0; + ($pkg, $setup, $recur, $sdate, $edate) = + ( 'Tax', sprintf("%10.2f",$cust_bill_pkg->setup), '', '', '' ); + } + + $csv->combine( + 'cust_bill_pkg', + $self->invnum, + ( map { '' } (1..11) ), + ($pkg, $setup, $recur, $sdate, $edate) + ) or die "can't create csv"; + print CSV $csv->string. "\n"; + + } + + close CSV or die "can't close CSV: $!"; + + #part two: upload it + + my $net; + if ( $opt{protocol} eq 'ftp' ) { + eval "use Net::FTP;"; + die $@ if $@; + $net = Net::FTP->new($opt{server}) or die @$; + } else { + die "unknown protocol: $opt{protocol}"; + } + + $net->login( $opt{username}, $opt{password} ) + or die "can't FTP to $opt{username}\@$opt{server}: login error: $@"; + + $net->binary or die "can't set binary mode"; + + $net->cwd($opt{dir}) or die "can't cwd to $opt{dir}"; + + $net->put($file) or die "can't put $file: $!"; + + $net->quit; + + unlink $file; + +} + =item comp Pays this invoice with a compliemntary payment. If there is an error, @@ -532,7 +681,8 @@ sub realtime_card { my $capture = new Business::OnlinePayment( $bop_processor, @bop_options ); - $capture->content( + my %capture = ( + type => 'CC', action => $action2, login => $bop_login, password => $bop_password, @@ -540,8 +690,18 @@ sub realtime_card { amount => $amount, authorization => $auth, description => $description, + card_number => $cust_main->payinfo, + expiration => $exp, ); + foreach my $field (qw( authorization_source_code returned_ACI transaction_identifier validation_code + transaction_sequence_num local_transaction_date + local_transaction_time AVS_result_code )) { + $capture{$field} = $transaction->$field() if $transaction->can($field); + } + + $capture->content( %capture ); + $capture->submit(); unless ( $capture->is_success ) { @@ -952,7 +1112,7 @@ sub print_text { =head1 VERSION -$Id: cust_bill.pm,v 1.38 2002-06-26 02:37:48 ivan Exp $ +$Id: cust_bill.pm,v 1.41 2002-09-05 16:51:49 ivan Exp $ =head1 BUGS diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 9ed3f2cab..cfa6b8bb6 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -713,7 +713,8 @@ sub check { my $y = length($2) == 4 ? $2 : "20$2"; $self->paydate("$y-$1-01"); my($nowm,$nowy)=(localtime(time))[4,5]; $nowm++; $nowy+=1900; - return gettext('expired_card') if $y<$nowy || ( $y==$nowy && $1<$nowm ); + return gettext('expired_card') + if !$import && ( $y<$nowy || ( $y==$nowy && $1<$nowm ) ); } if ( $self->payname eq '' && @@ -1734,7 +1735,7 @@ sub credit { $cust_credit->insert; } -=item charge AMOUNT PKG COMMENT +=item charge AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ] Creates a one-time charge for this customer. If there is an error, returns the error, otherwise returns false. @@ -1742,7 +1743,10 @@ the error, otherwise returns false. =cut sub charge { - my ( $self, $amount, $pkg, $comment ) = @_; + my ( $self, $amount ) = ( shift, shift ); + my $pkg = @_ ? shift : 'One-time charge'; + my $comment = @_ ? shift : '$'. sprintf("%.2f",$amount); + my $taxclass = @_ ? shift : ''; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -1756,12 +1760,13 @@ sub charge { my $dbh = dbh; my $part_pkg = new FS::part_pkg ( { - 'pkg' => $pkg || 'One-time charge', - 'comment' => $comment || '$'. sprintf("%.2f",$amount), + 'pkg' => $pkg, + 'comment' => $comment, 'setup' => $amount, 'freq' => 0, 'recur' => '0', 'disabled' => 'Y', + 'taxclass' => $taxclass, } ); my $error = $part_pkg->insert; @@ -1805,7 +1810,8 @@ Returns all the invoices (see L<FS::cust_bill>) for this customer. sub cust_bill { my $self = shift; - qsearch('cust_bill', { 'custnum' => $self->custnum, } ) + sort { $a->_date <=> $b->_date } + qsearch('cust_bill', { 'custnum' => $self->custnum, } ) } =item open_cust_bill @@ -1959,6 +1965,201 @@ sub append_fuzzyfiles { 1; } +=item batch_import + +=cut + +sub batch_import { + my $param = shift; + #warn join('-',keys %$param); + my $fh = $param->{filehandle}; + my $agentnum = $param->{agentnum}; + my $refnum = $param->{refnum}; + my $pkgpart = $param->{pkgpart}; + my @fields = @{$param->{fields}}; + + eval "use Date::Parse;"; + die $@ if $@; + eval "use Text::CSV_XS;"; + die $@ if $@; + + my $csv = new Text::CSV_XS; + #warn $csv; + #warn $fh; + + my $imported = 0; + #my $columns; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + #while ( $columns = $csv->getline($fh) ) { + my $line; + while ( defined($line=<$fh>) ) { + + $csv->parse($line) or do { + $dbh->rollback if $oldAutoCommit; + return "can't parse: ". $csv->error_input(); + }; + + my @columns = $csv->fields(); + #warn join('-',@columns); + + my %cust_main = ( + agentnum => $agentnum, + refnum => $refnum, + country => 'US', #default + payby => 'BILL', #default + paydate => '12/2037', #default + ); + my $billtime = time; + my %cust_pkg = ( pkgpart => $pkgpart ); + foreach my $field ( @fields ) { + if ( $field =~ /^cust_pkg\.(setup|bill|susp|expire|cancel)$/ ) { + #$cust_pkg{$1} = str2time( shift @$columns ); + if ( $1 eq 'setup' ) { + $billtime = str2time(shift @columns); + } else { + $cust_pkg{$1} = str2time( shift @columns ); + } + } else { + #$cust_main{$field} = shift @$columns; + $cust_main{$field} = shift @columns; + } + } + + my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ) if $pkgpart; + my $cust_main = new FS::cust_main ( \%cust_main ); + use Tie::RefHash; + tie my %hash, 'Tie::RefHash'; #this part is important + $hash{$cust_pkg} = [] if $pkgpart; + my $error = $cust_main->insert( \%hash ); + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't insert customer for $line: $error"; + } + + #false laziness w/bill.cgi + $error = $cust_main->bill( 'time' => $billtime ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't bill customer for $line: $error"; + } + + $cust_main->apply_payments; + $cust_main->apply_credits; + + $error = $cust_main->collect(); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't collect customer for $line: $error"; + } + + $imported++; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + return "Empty file!" unless $imported; + + ''; #no error + +} + +=item batch_charge + +=cut + +sub batch_charge { + my $param = shift; + #warn join('-',keys %$param); + my $fh = $param->{filehandle}; + my @fields = @{$param->{fields}}; + + eval "use Date::Parse;"; + die $@ if $@; + eval "use Text::CSV_XS;"; + die $@ if $@; + + my $csv = new Text::CSV_XS; + #warn $csv; + #warn $fh; + + my $imported = 0; + #my $columns; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + #while ( $columns = $csv->getline($fh) ) { + my $line; + while ( defined($line=<$fh>) ) { + + $csv->parse($line) or do { + $dbh->rollback if $oldAutoCommit; + return "can't parse: ". $csv->error_input(); + }; + + my @columns = $csv->fields(); + #warn join('-',@columns); + + my %row = (); + foreach my $field ( @fields ) { + $row{$field} = shift @columns; + } + + my $cust_main = qsearchs('cust_main', { 'custnum' => $row{'custnum'} } ); + unless ( $cust_main ) { + $dbh->rollback if $oldAutoCommit; + return "unknown custnum $row{'custnum'}"; + } + + if ( $row{'amount'} > 0 ) { + my $error = $cust_main->charge($row{'amount'}, $row{'pkg'}); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + $imported++; + } elsif ( $row{'amount'} < 0 ) { + my $error = $cust_main->credit( sprintf( "%.2f", 0-$row{'amount'} ), + $row{'pkg'} ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + $imported++; + } else { + #hmm? + } + + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + return "Empty file!" unless $imported; + + ''; #no error + +} + =back =head1 BUGS diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 8b65ac4bd..12508e1aa 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -229,7 +229,7 @@ sub check { unless qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } ); $self->otaker(getotaker) unless $self->otaker; - $self->otaker =~ /^(\w{0,16})$/ or return "Illegal otaker"; + $self->otaker =~ /^([\w\.\-]{0,16})$/ or return "Illegal otaker"; $self->otaker($1); if ( $self->dbdef_table->column('manual_flag') ) { @@ -679,7 +679,7 @@ sub order { =head1 VERSION -$Id: cust_pkg.pm,v 1.22 2002-05-22 12:17:06 ivan Exp $ +$Id: cust_pkg.pm,v 1.23 2002-08-26 20:40:55 ivan Exp $ =head1 BUGS diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm index 23f948f6a..3c1a3de8e 100644 --- a/FS/FS/part_export.pm +++ b/FS/FS/part_export.pm @@ -526,15 +526,15 @@ tie my %shellcommands_options, 'Tie::IxHash', #'machine' => { label=>'Remote machine' }, 'user' => { label=>'Remote username', default=>'root' }, 'useradd' => { label=>'Insert command', - default=>'useradd -d $dir -m -s $shell -u $uid $username; passwd $username' + default=>'useradd -d $dir -m -s $shell -u $uid -p $crypt_password $username' #default=>'cp -pr /etc/skel $dir; chown -R $uid.$gid $dir' }, 'useradd_stdin' => { label=>'Insert command STDIN', type =>'textarea', - default=>"\$_password\n\$_password\n", + default=>'', }, 'userdel' => { label=>'Delete command', - default=>'userdel $username', + default=>'userdel -r $username', #default=>'rm -rf $dir', }, 'userdel_stdin' => { label=>'Delete command STDIN', @@ -542,7 +542,7 @@ tie my %shellcommands_options, 'Tie::IxHash', default=>'', }, 'usermod' => { label=>'Modify command', - default=>'usermod -d $new_dir -l $new_username -s $new_shell -u $new_uid $old_username; passwd $new_username', + default=>'usermod -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username', #default=>'[ -d $old_dir ] && mv $old_dir $new_dir || ( '. # 'chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; '. # 'find . -depth -print | cpio -pdm $new_dir; '. @@ -552,7 +552,7 @@ tie my %shellcommands_options, 'Tie::IxHash', }, 'usermod_stdin' => { label=>'Modify command STDIN', type =>'textarea', - default=>"\$_password\n\$_password\n", + default=>'', }, ; @@ -581,6 +581,32 @@ tie my %shellcommands_withdomain_options, 'Tie::IxHash', }, ; +tie my %www_shellcommands_options, 'Tie::IxHash', + 'user' => { lable=>'Remote username', default=>'root' }, + 'useradd' => { label=>'Insert command', + default=>'mkdir /var/www/$zone; chown $username /var/www/$zone; ln -s /var/www/$zone $homedir/$zone', + }, + 'userdel' => { label=>'Delete command', + default=>'[ -n "$zone" ] && rm -rf /var/www/$zone; rm $homedir/$zone', + }, + 'usermod' => { label=>'Modify command', + default=>'[ -n "$old_zone" ] && rm $old_homedir/$old_zone; [ "$old_zone" != "$new_zone" -a -n "$new_zone" ] && mv /var/www/$old_zone /var/www/$new_zone; [ "$old_username" != "$new_username" ] && chown -R $new_username /var/www/$new_zone; ln -s /var/www/$new_zone $new_homedir/$new_zone', + }, +; + +tie my %domain_shellcommands_options, 'Tie::IxHash', + 'user' => { lable=>'Remote username', default=>'root' }, + 'useradd' => { label=>'Insert command', + default=>'', + }, + 'userdel' => { label=>'Delete command', + default=>'', + }, + 'usermod' => { label=>'Modify command', + default=>'', + }, +; + tie my %textradius_options, 'Tie::IxHash', 'user' => { label=>'Remote username', default=>'root' }, 'users' => { label=>'users file location', default=>'/etc/raddb/users' }, @@ -615,7 +641,7 @@ tie my %infostreet_options, 'Tie::IxHash', ; tie my %vpopmail_options, 'Tie::IxHash', - 'machine' => { label=>'vpopmail machine', }, + #'machine' => { label=>'vpopmail machine', }, 'dir' => { label=>'directory', }, # ?more info? default? 'uid' => { label=>'vpopmail uid' }, 'gid' => { label=>'vpopmail gid' }, @@ -636,6 +662,37 @@ tie my %bind_slave_options, 'Tie::IxHash', default => '/etc/bind/named.conf' }, ; +tie my %http_options, 'Tie::IxHash', + 'method' => { label =>'Method', + type =>'select', + #options =>[qw(POST GET)], + options =>[qw(POST)], + default =>'POST' }, + 'url' => { label => 'URL', default => 'http://', }, + 'insert_data' => { + label => 'Insert data', + type => 'textarea', + default => join("\n", + 'DomainName $svc_x->domain', + 'Email ( grep { $_ ne "POST" } $svc_x->cust_svc->cust_pkg->cust_main->invoicing_list)[0]', + 'test 1', + 'reseller $svc_x->cust_svc->cust_pkg->part_pkg->pkg =~ /reseller/i', + ), + }, + 'delete_data' => { + label => 'Delete data', + type => 'textarea', + default => join("\n", + ), + }, + 'replace_data' => { + label => 'Replace data', + type => 'textarea', + default => join("\n", + ), + }, +; + tie my %sqlmail_options, 'Tie::IxHash', 'datasrc' => { label=>'DBI data source' }, 'username' => { label=>'Database username' }, @@ -675,7 +732,7 @@ tie my %sqlmail_options, 'Tie::IxHash', 'desc' => 'Real-time export via remote SSH (i.e. useradd, userdel, etc.)', 'options' => \%shellcommands_options, 'nodomain' => 'Y', - 'notes' => 'Run remote commands via SSH. Usernames are considered unique (also see shellcommands_withdomain). You probably want this if the commands you are running will not accept a domain as a parameter. You will need to <a href="../docs/ssh.html">setup SSH for unattended operation</a>.', + 'notes' => 'Run remote commands via SSH. Usernames are considered unique (also see shellcommands_withdomain). You probably want this if the commands you are running will not accept a domain as a parameter. You will need to <a href="../docs/ssh.html">setup SSH for unattended operation</a>.<BR><BR>Use these buttons for some useful presets:<UL><LI><INPUT TYPE="button" VALUE="Linux/NetBSD" onClick=\'this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username"; this.form.useradd_stdin.value = ""; this.form.userdel.value = "userdel -r $username"; this.form.userdel_stdin.value=""; this.form.usermod.value = "usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -p $new_crypt_password $old_username"; this.form.usermod_stdin.value = "";\'><LI><INPUT TYPE="button" VALUE="FreeBSD" onClick=\'this.form.useradd.value = "pw useradd $username -d $dir -m -s $shell -u $uid -c $finger -h 0"; this.form.useradd_stdin.value = "$_password\n"; this.form.userdel.value = "pw userdel $username -r"; this.form.userdel_stdin.value=""; this.form.usermod.value = "pw usermod $old_username -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -c $new_finger -h 0"; this.form.usermod_stdin.value = "$new__password\n";\'><LI><INPUT TYPE="button" VALUE="Just maintain directories (use with sysvshell or bsdshell)" onClick=\'this.form.useradd.value = "cp -pr /etc/skel $dir; chown -R $uid.$gid $dir"; this.form.useradd_stdin.value = ""; this.form.usermod.value = "[ -d $old_dir ] && mv $old_dir $new_dir || ( chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; find . -depth -print | cpio -pdm $new_dir; chmod u-t $new_dir; chown -R $uid.$gid $new_dir; rm -rf $old_dir )"; this.form.usermod_stdin.value = ""; this.form.userdel.value = "rm -rf $dir"; this.form.userdel_stdin.value="";\'></UL>', }, 'shellcommands_withdomain' => { @@ -721,7 +778,7 @@ tie my %sqlmail_options, 'Tie::IxHash', 'vpopmail' => { 'desc' => 'Real-time export to vpopmail text files', 'options' => \%vpopmail_options, - 'notes' => 'Real time export to <a href="http://inter7.com/vpopmail/">vpopmail</a> text files (...extended description from jeff?...)', + 'notes' => 'Real time export to <a href="http://inter7.com/vpopmail/">vpopmail</a> text files. <a href="http://search.cpan.org/search?dist=File-Rsync">File::Rsync</a> must be installed, and you will need to <a href="../docs/ssh.html">setup SSH for unattended operation</a>', }, }, @@ -740,6 +797,12 @@ tie my %sqlmail_options, 'Tie::IxHash', 'notes' => 'Batch export of BIND configuration file to a secondary nameserver. Zones are slaved from the listed masters. <a href="http://search.cpan.org/search?dist=File-Rsync">File::Rsync</a> must be installed. Run bin/bind.export to export the files.', }, + 'http' => { + 'desc' => 'Send an HTTP or HTTPS GET or POST request', + 'options' => \%http_options, + 'notes' => 'Send an HTTP or HTTPS GET or POST to the specified URL. <a href="http://search.cpan.org/search?dist=libwww-perl">libwww-perl</a> must be installed. For HTTPS support, <a href="http://search.cpan.org/search?dist=Crypt-SSLeay">Crypt::SSLeay</a> or <a href="http://search.cpan.org/search?dist=IO-Socket-SSL">IO::Socket::SSL</a> is required.', + }, + 'sqlmail' => { 'desc' => 'Real-time export to SQL-backed mail server', 'options' => \%sqlmail_options, @@ -747,6 +810,12 @@ tie my %sqlmail_options, 'Tie::IxHash', 'notes' => 'Database schema can be made to work with Courier IMAP and Exim. Others could work but are untested. (...extended description from pc-intouch?...)', }, + 'domain_shellcommands' => { + 'desc' => 'Run remote commands via SSH, for domains.', + 'options' => \%domain_shellcommands_options, + 'notes' => 'Run remote commands via SSH, for domains. You will need to <a href="../docs/ssh.html">setup SSH for unattended operation</a>.', + }, + }, @@ -763,9 +832,9 @@ tie my %sqlmail_options, 'Tie::IxHash', 'svc_www' => { 'www_shellcommands' => { - 'desc' => 'www_shellcommands', - 'options' => {}, # \%www_shellcommands_options, - 'notes' => 'svc_www commands', + 'desc' => 'Run remote commands via SSH, for virtual web sites.', + 'options' => \%www_shellcommands_options, + 'notes' => 'Run remote commands via SSH, for virtual web sites. You will need to <a href="../docs/ssh.html">setup SSH for unattended operation</a>.', }, }, diff --git a/FS/FS/part_export/domain_shellcommands.pm b/FS/FS/part_export/domain_shellcommands.pm new file mode 100644 index 000000000..5b3cd5d79 --- /dev/null +++ b/FS/FS/part_export/domain_shellcommands.pm @@ -0,0 +1,115 @@ +package FS::part_export::domain_shellcommands; + +use strict; +use vars qw(@ISA); +use FS::part_export; + +@ISA = qw(FS::part_export); + +sub rebless { shift; } + +sub _export_insert { + my($self) = shift; + $self->_export_command('useradd', @_); +} + +sub _export_delete { + my($self) = shift; + $self->_export_command('userdel', @_); +} + +sub _export_command { + my ( $self, $action, $svc_domain) = (shift, shift, shift); + my $command = $self->option($action); + + #set variable for the command + no strict 'vars'; + { + no strict 'refs'; + ${$_} = $svc_domain->getfield($_) foreach $svc_domain->fields; + } + +# my $domain_record = $svc_www->domain_record; # or die ? +# my $zone = $domain_record->reczone; # or die ? +# unless ( $zone =~ /\.$/ ) { +# my $svc_domain = $domain_record->svc_domain; # or die ? +# $zone .= '.'. $svc_domain->domain; +# } + +# my $svc_acct = $svc_www->svc_acct; # or die ? +# my $username = $svc_acct->username; +# my $homedir = $svc_acct->dir; # or die ? + + #done setting variables for the command + + $self->shellcommands_queue( $svc_domain->svcnum, + user => $self->option('user')||'root', + host => $self->machine, + command => eval(qq("$command")), + ); +} + +sub _export_replace { + my($self, $new, $old ) = (shift, shift, shift); + my $command = $self->option('usermod'); + + #set variable for the command + no strict 'vars'; + { + no strict 'refs'; + ${"old_$_"} = $old->getfield($_) foreach $old->fields; + ${"new_$_"} = $new->getfield($_) foreach $new->fields; + } +# my $old_domain_record = $old->domain_record; # or die ? +# my $old_zone = $old_domain_record->reczone; # or die ? +# unless ( $old_zone =~ /\.$/ ) { +# my $old_svc_domain = $old_domain_record->svc_domain; # or die ? +# $old_zone .= '.'. $old_svc_domain->domain; +# } +# +# my $old_svc_acct = $old->svc_acct; # or die ? +# my $old_username = $old_svc_acct->username; +# my $old_homedir = $old_svc_acct->dir; # or die ? +# +# my $new_domain_record = $new->domain_record; # or die ? +# my $new_zone = $new_domain_record->reczone; # or die ? +# unless ( $new_zone =~ /\.$/ ) { +# my $new_svc_domain = $new_domain_record->svc_domain; # or die ? +# $new_zone .= '.'. $new_svc_domain->domain; +# } + +# my $new_svc_acct = $new->svc_acct; # or die ? +# my $new_username = $new_svc_acct->username; +# my $new_homedir = $new_svc_acct->dir; # or die ? + + #done setting variables for the command + + $self->shellcommands_queue( $new->svcnum, + user => $self->option('user')||'root', + host => $self->machine, + command => eval(qq("$command")), + ); +} + +#a good idea to queue anything that could fail or take any time +sub shellcommands_queue { + my( $self, $svcnum ) = (shift, shift); + my $queue = new FS::queue { + 'svcnum' => $svcnum, + 'job' => "FS::part_export::domain_shellcommands::ssh_cmd", + }; + $queue->insert( @_ ); +} + +sub ssh_cmd { #subroutine, not method + use Net::SSH '0.07'; + &Net::SSH::ssh_cmd( { @_ } ); +} + +#sub shellcommands_insert { #subroutine, not method +#} +#sub shellcommands_replace { #subroutine, not method +#} +#sub shellcommands_delete { #subroutine, not method +#} + diff --git a/FS/FS/part_export/http.pm b/FS/FS/part_export/http.pm new file mode 100644 index 000000000..0e02f0f8e --- /dev/null +++ b/FS/FS/part_export/http.pm @@ -0,0 +1,88 @@ +package FS::part_export::http; + +use vars qw(@ISA); +use FS::part_export; + +@ISA = qw(FS::part_export); + +sub rebless { shift; } + +sub _export_insert { + my $self = shift; + $self->_export_command('insert', @_); +} + +sub _export_delete { + my $self = shift; + $self->_export_command('delete', @_); +} + +sub _export_command { + my( $self, $action, $svc_x ) = ( shift, shift, shift ); + + return unless $self->option("${action}_data"); + + $self->http_queue( $svc_x->svcnum, + $self->option('method'), + $self->option('url'), + map { + /^\s*(\S+)\s+(.*)$/ or /()()/; + my( $field, $value_expression ) = ( $1, $2 ); + my $value = eval $value_expression; + die $@ if $@; + ( $field, $value ); + } split(/\n/, $self->option("${action}_data") ) + ); + +} + +sub _export_replace { + my( $self, $new, $old ) = ( shift, shift, shift ); + + return unless $self->option('replace_data'); + + $self->http_queue( $svc_x->svcnum, + $self->option('method'), + $self->option('url'), + map { + /^\s*(\S+)\s+(.*)$/ or /()()/; + my( $field, $value_expression ) = ( $1, $2 ); + die $@ if $@; + ( $field, $value ); + } split(/\n/, $self->option('replace_data') ) + ); + +} + +sub http_queue { + my($self, $svcnum) = (shift, shift); + my $queue = new FS::queue { + 'svcnum' => $svcnum, + 'job' => "FS::part_export::http::http", + }; + $queue->insert( @_ ); +} + +sub http { + my($method, $url, @data) = @_; + + $method = lc($method); + + eval "use LWP::UserAgent;"; + die "using LWP::UserAgent: $@" if $@; + eval "use HTTP::Request::Common;"; + die "using HTTP::Request::Common: $@" if $@; + + my $ua = LWP::UserAgent->new; + + #my $response = $ua->$method( + # $url, \%data, + # 'Content-Type'=>'application/x-www-form-urlencoded' + #); + my $req = HTTP::Request::Common::POST( $url, \@data ); + my $response = $ua->request($req); + + die $response->error_as_HTML if $response->is_error; + +} + diff --git a/FS/FS/part_export/infostreet.pm b/FS/FS/part_export/infostreet.pm index 2464e5dee..f2d519932 100644 --- a/FS/FS/part_export/infostreet.pm +++ b/FS/FS/part_export/infostreet.pm @@ -145,7 +145,7 @@ sub infostreet_setContact { 'getAccountID', $username); foreach my $field ( keys %contact_info ) { infostreet_command($url, $is_username, $is_password, $groupID, - 'setContactField', $accountID, $field, $contact_info{$field} ); + 'setContactField', [ 'int'=>$accountID ], $field, $contact_info{$field} ); } } @@ -164,6 +164,15 @@ sub infostreet_command { #subroutine, not method } eval "use Frontier::Client;"; + die $@ if $@; + + eval 'sub Frontier::RPC2::String::repr { + my $self = shift; + my $value = $$self; + $value =~ s/([&<>\"])/$Frontier::RPC2::char_entities{$1}/ge; + $value; + }'; + die $@ if $@; my $conn = Frontier::Client->new( url => $url ); my $key_result = $conn->call( 'authenticate', $username, $password, $groupID); @@ -172,7 +181,15 @@ sub infostreet_command { #subroutine, not method my $key = $key_result{data}; #my $result = $conn->call($method, $key, @args); - my $result = $conn->call($method, $key, map { $conn->string($_) } @args); + my $result = $conn->call( $method, $key, + map { + if ( ref($_) ) { + my( $type, $value) = @{$_}; + $conn->$type($value); + } else { + $conn->string($_); + } + } @args ); my %result = _infostreet_parse($result); die $result{error} unless $result{success}; diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index 870d7f1ee..a514f9375 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -1,10 +1,13 @@ package FS::part_export::shellcommands; -use vars qw(@ISA); +use vars qw(@ISA @saltset); +use String::ShellQuote; use FS::part_export; @ISA = qw(FS::part_export); +@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' ); + sub rebless { shift; } sub _export_insert { @@ -21,11 +24,20 @@ sub _export_command { my ( $self, $action, $svc_acct) = (shift, shift, shift); my $command = $self->option($action); my $stdin = $self->option($action."_stdin"); - no strict 'refs'; - ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields; + no strict 'vars'; + { + no strict 'refs'; + ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields; + } + $finger = shell_quote $finger; + $quoted_password = shell_quote $_password; + $domain = $svc_acct->domain; + $crypt_password = ''; #surpress "used only once" warnings + $crypt_password = crypt( $svc_acct->_password, + $saltset[int(rand(64))].$saltset[int(rand(64))] ); $self->shellcommands_queue( $svc_acct->svcnum, - user => $self->options('user')||'root', - host => $self->options('machine'), + user => $self->option('user')||'root', + host => $self->machine, command => eval(qq("$command")), stdin_string => eval(qq("$stdin")), ); @@ -35,12 +47,21 @@ sub _export_replace { my($self, $new, $old ) = (shift, shift, shift); my $command = $self->option('usermod'); my $stdin = $self->option('usermod_stdin'); - no strict 'refs'; - ${"old_$_"} = $old->getfield($_) foreach $old->fields; - ${"new_$_"} = $new->getfield($_) foreach $new->fields; + no strict 'vars'; + { + no strict 'refs'; + ${"old_$_"} = $old->getfield($_) foreach $old->fields; + ${"new_$_"} = $new->getfield($_) foreach $new->fields; + } + $new_finger = shell_quote $new_finger; + $quoted_new__password = shell_quote $new__password; + $new_domain = $new->domain; + $new_crypt_password = ''; #surpress "used only once" warnings + $new_crypt_password = crypt( $new->_password, + $saltset[int(rand(64))].$saltset[int(rand(64))]); $self->shellcommands_queue( $new->svcnum, - user => $self->options('user')||'root', - host => $self->options('machine'), + user => $self->option('user')||'root', + host => $self->machine, command => eval(qq("$command")), stdin_string => eval(qq("$stdin")), ); @@ -57,7 +78,7 @@ sub shellcommands_queue { } sub ssh_cmd { #subroutine, not method - use Net::SSH '0.06'; + use Net::SSH '0.07'; &Net::SSH::ssh_cmd( { @_ } ); } diff --git a/FS/FS/part_export/textradius.pm b/FS/FS/part_export/textradius.pm index 9a0468f6d..1492f2672 100644 --- a/FS/FS/part_export/textradius.pm +++ b/FS/FS/part_export/textradius.pm @@ -1,33 +1,37 @@ package FS::part_export::textradius; -use vars qw(@ISA); +use vars qw(@ISA $prefix); +use Fcntl qw(:flock); +use FS::UID qw(datasrc); use FS::part_export; @ISA = qw(FS::part_export); +$prefix = "/usr/local/etc/freeside/export."; + sub rebless { shift; } sub _export_insert { my($self, $svc_acct) = (shift, shift); $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'insert', - $svc_acct->username, $svc_acct->_password ); + $svc_acct->username, $svc_acct->radius_check, '-', $svc_acct->radius_reply); ref($err_or_queue) ? '' : $err_or_queue; } sub _export_replace { my( $self, $new, $old ) = (shift, shift, shift); - #return "can't change username with textradius" - # if $old->username ne $new->username; + return "can't (yet?) change username with textradius" + if $old->username ne $new->username; #return '' unless $old->_password ne $new->_password; - $err_or_queue = $self->textradius_queue( $new->svcnum, - 'replace', $new->username, $new->_password ); + $err_or_queue = $self->textradius_queue( $new->svcnum, 'insert', + $new->username, $new->radius_check, '-', $new->radius_reply); ref($err_or_queue) ? '' : $err_or_queue; } sub _export_delete { my( $self, $svc_acct ) = (shift, shift); - $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, - 'delete', $svc_acct->username ); + $err_or_queue = $self->textradius_queue( $svc_acct->svcnum, 'delete', + $svc_acct->username ); ref($err_or_queue) ? '' : $err_or_queue; } @@ -38,13 +42,125 @@ sub textradius_queue { 'svcnum' => $svcnum, 'job' => "FS::part_export::textradius::textradius_$method", }; - $queue->insert( @_ ) or $queue; + $queue->insert( + $self->option('user')||'root', + $self->machine, + $self->option('users'), + @_, + ) or $queue; } sub textradius_insert { #subroutine, not method + my( $user, $host, $users, $username, @attributes ) = @_; + + #silly arg processing + my($att, @check); + push @check, $att while @attributes && ($att=shift @attributes) ne '-'; + my %check = @check; + my %reply = @attributes; + + my $file = textradius_download($user, $host, $users); + + eval "use RADIUS::UserFile;"; + die $@ if $@; + + my $userfile = new RADIUS::UserFile( + File => $file, + Who => [ $username ], + Check_Items => [ keys %check ], + ) or die "error parsing $file"; + + $userfile->remove($username); + $userfile->add( + Who => $username, + Attributes => { %check, %reply }, + Comment => 'user added by Freeside', + ) or die "error adding to $file"; + + $userfile->update( Who => [ $username ] ) + or die "error updating $file"; + + textradius_upload($user, $host, $users); + } -sub textradius_replace { #subroutine, not method -} + sub textradius_delete { #subroutine, not method + my( $user, $host, $users, $username ) = @_; + + my $file = textradius_download($user, $host, $users); + + eval "use RADIUS::UserFile;"; + die $@ if $@; + + my $userfile = new RADIUS::UserFile( + File => $file, + Who => [ $username ], + ) or die "error parsing $file"; + + $userfile->remove($username); + + $userfile->update( Who => [ $username ] ) + or die "error updating $file"; + + textradius_upload($user, $host, $users); +} + +sub textradius_download { + my( $user, $host, $users ) = @_; + + my $dir = $prefix. datasrc; + mkdir $dir, 0700 or die $! unless -d $dir; + $dir .= "/$host"; + mkdir $dir, 0700 or die $! unless -d $dir; + + my $dest = "$dir/users"; + + eval "use File::Rsync;"; + die $@ if $@; + my $rsync = File::Rsync->new({ rsh => 'ssh' }); + + open(LOCK, "+>>$dest.lock") + and flock(LOCK,LOCK_EX) + or die "can't open $dest.lock: $!"; + + $rsync->exec( { + src => "$user\@$host:$users", + dest => $dest, + } ); # true/false return value from exec is not working, alas + if ( $rsync->err ) { + die "error downloading $user\@$host:$users : ". + 'exit status: '. $rsync->status. ', '. + 'STDERR: '. join(" / ", $rsync->err). ', '. + 'STDOUT: '. join(" / ", $rsync->out); + } + + $dest; +} + +sub textradius_upload { + my( $user, $host, $users ) = @_; + + my $dir = $prefix. datasrc. "/$host"; + + eval "use File::Rsync;"; + die $@ if $@; + my $rsync = File::Rsync->new({ + rsh => 'ssh', + #dry_run => 1, + }); + $rsync->exec( { + src => "$dir/users", + dest => "$user\@$host:$users", + } ); # true/false return value from exec is not working, alas + if ( $rsync->err ) { + die "error uploading to $user\@$host:$users : ". + 'exit status: '. $rsync->status. ', '. + 'STDERR: '. join(" / ", $rsync->err). ', '. + 'STDOUT: '. join(" / ", $rsync->out); + } + + flock(LOCK,LOCK_UN); + close LOCK; + } diff --git a/FS/FS/part_export/vpopmail.pm b/FS/FS/part_export/vpopmail.pm index 6a486faa1..561e2742a 100644 --- a/FS/FS/part_export/vpopmail.pm +++ b/FS/FS/part_export/vpopmail.pm @@ -1,6 +1,7 @@ package FS::part_export::vpopmail; -use vars qw(@ISA @saltset $exportdir $rsync $ssh); +use vars qw(@ISA @saltset $exportdir); +use Fcntl qw(:flock); use File::Path; use FS::UID qw( datasrc ); use FS::part_export; @@ -9,9 +10,6 @@ use FS::part_export; @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' ); -$rsync = "rsync"; -$ssh = "ssh"; - sub rebless { shift; } sub _export_insert { @@ -20,6 +18,7 @@ sub _export_insert { $svc_acct->username, crypt($svc_acct->_password,$saltset[int(rand(64))].$saltset[int(rand(64))]), $svc_acct->domain, + $svc_acct->quota, ); } @@ -47,7 +46,7 @@ sub _export_replace { return '' unless $old->_password ne $new->_password; $self->vpopmail_queue( $new->svcnum, 'replace', - $new->username, $cpassword, $new->domain ); + $new->username, $cpassword, $new->domain, $new->quota ); } sub _export_delete { @@ -59,14 +58,22 @@ sub _export_delete { #a good idea to queue anything that could fail or take any time sub vpopmail_queue { my( $self, $svcnum, $method ) = (shift, shift, shift); + my $exportdir = "/usr/local/etc/freeside/export." . datasrc; + mkdir $exportdir, 0700 or die $! unless -d $exportdir; + $exportdir .= "/vpopmail"; + mkdir $exportdir, 0700 or die $! unless -d $exportdir; + $exportdir .= '/'. $self->machine; + mkdir $exportdir, 0700 or die $! unless -d $exportdir; + mkdir "$exportdir/domains", 0700 or die $! unless -d "$exportdir/domains"; + my $queue = new FS::queue { 'svcnum' => $svcnum, 'job' => "FS::part_export::vpopmail::vpopmail_$method", }; $queue->insert( $exportdir, - $self->option('machine'), + $self->machine, $self->option('dir'), $self->option('uid'), $self->option('gid'), @@ -76,8 +83,11 @@ sub vpopmail_queue { sub vpopmail_insert { #subroutine, not method my( $exportdir, $machine, $dir, $uid, $gid ) = splice @_,0,5; - my( $username, $password, $domain ) = @_; - + my( $username, $password, $domain, $quota ) = @_; + + mkdir "$exportdir/domains/$domain", 0700 or die $! + unless -d "$exportdir/domains/$domain"; + (open(VPASSWD, ">>$exportdir/domains/$domain/vpasswd") and flock(VPASSWD,LOCK_EX) ) or die "can't open vpasswd file for $username\@$domain: ". @@ -87,9 +97,9 @@ sub vpopmail_insert { #subroutine, not method $password, '1', '0', - $username, + $finger, "$dir/domains/$domain/$username", - 'NOQUOTA', + $quota ? $quota.'S' : 'NOQUOTA', ), "\n"; flock(VPASSWD,LOCK_UN); @@ -118,10 +128,21 @@ sub vpopmail_replace { #subroutine, not method or die "Can't open $exportdir/domains/$domain/vpasswd.tmp: $!"; while (<VPASSWD>) { - my ($mailbox, $pw, @rest) = split(':', $_); - print VPASSWDTMP $_ unless $username eq $mailbox; - print VPASSWDTMP join (':', ($mailbox, $password, @rest)) - if $username eq $mailbox; + my ($mailbox, $pw, $vuid, $vgid, $vfinger, $vdir, $vquota, @rest) = + split(':', $_); + if ( $username ne $mailbox ) { + print VPASSWDTMP $_; + next + } + print VPASSWDTMP join (':', + $mailbox, + $password, + '1', + '0', + $finger, + $dir, + $quota ? $quota.'S' : 'NOQUOTA', + ), "\n"; } close(VPASSWDTMP); @@ -171,9 +192,28 @@ sub vpopmail_sync { my( $exportdir, $machine, $dir, $uid, $gid ) = splice @_,0,5; chdir $exportdir; - my @args = ( $rsync, "-rlpt", "-e", $ssh, "domains/", - "vpopmail\@$machine:$dir/domains/" ); - system {$args[0]} @args; +# my @args = ( $rsync, "-rlpt", "-e", $ssh, "domains/", +# "vpopmail\@$machine:$dir/domains/" ); +# system {$args[0]} @args; + + eval "use File::Rsync;"; + die $@ if $@; + + my $rsync = File::Rsync->new({ rsh => 'ssh' }); + + $rsync->exec( { + recursive => 1, + perms => 1, + times => 1, + src => "$exportdir/domains/", + dest => "vpopmail\@$machine:$dir/domains/", + } ); # true/false return value from exec is not working, alas + if ( $rsync->err ) { + die "error uploading to vpopmail\@$machine:$dir/domains/ : ". + 'exit status: '. $rsync->status. ', '. + 'STDERR: '. join(" / ", $rsync->err). ', '. + 'STDOUT: '. join(" / ", $rsync->out); + } } diff --git a/FS/FS/part_export/www_shellcommands.pm b/FS/FS/part_export/www_shellcommands.pm index 870d7f1ee..e5b95dc1f 100644 --- a/FS/FS/part_export/www_shellcommands.pm +++ b/FS/FS/part_export/www_shellcommands.pm @@ -1,5 +1,6 @@ -package FS::part_export::shellcommands; +package FS::part_export::www_shellcommands; +use strict; use vars qw(@ISA); use FS::part_export; @@ -18,31 +19,74 @@ sub _export_delete { } sub _export_command { - my ( $self, $action, $svc_acct) = (shift, shift, shift); + my ( $self, $action, $svc_www) = (shift, shift, shift); my $command = $self->option($action); - my $stdin = $self->option($action."_stdin"); - no strict 'refs'; - ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields; - $self->shellcommands_queue( $svc_acct->svcnum, - user => $self->options('user')||'root', - host => $self->options('machine'), + + #set variable for the command + no strict 'vars'; + { + no strict 'refs'; + ${$_} = $svc_www->getfield($_) foreach $svc_www->fields; + } + my $domain_record = $svc_www->domain_record; # or die ? + my $zone = $domain_record->reczone; # or die ? + unless ( $zone =~ /\.$/ ) { + my $svc_domain = $domain_record->svc_domain; # or die ? + $zone .= '.'. $svc_domain->domain; + } + + my $svc_acct = $svc_www->svc_acct; # or die ? + my $username = $svc_acct->username; + my $homedir = $svc_acct->dir; # or die ? + + #done setting variables for the command + + $self->shellcommands_queue( $svc_www->svcnum, + user => $self->option('user')||'root', + host => $self->machine, command => eval(qq("$command")), - stdin_string => eval(qq("$stdin")), ); } sub _export_replace { my($self, $new, $old ) = (shift, shift, shift); my $command = $self->option('usermod'); - my $stdin = $self->option('usermod_stdin'); - no strict 'refs'; - ${"old_$_"} = $old->getfield($_) foreach $old->fields; - ${"new_$_"} = $new->getfield($_) foreach $new->fields; + + #set variable for the command + no strict 'vars'; + { + no strict 'refs'; + ${"old_$_"} = $old->getfield($_) foreach $old->fields; + ${"new_$_"} = $new->getfield($_) foreach $new->fields; + } + my $old_domain_record = $old->domain_record; # or die ? + my $old_zone = $old_domain_record->reczone; # or die ? + unless ( $old_zone =~ /\.$/ ) { + my $old_svc_domain = $old_domain_record->svc_domain; # or die ? + $old_zone .= '.'. $old_svc_domain->domain; + } + + my $old_svc_acct = $old->svc_acct; # or die ? + my $old_username = $old_svc_acct->username; + my $old_homedir = $old_svc_acct->dir; # or die ? + + my $new_domain_record = $new->domain_record; # or die ? + my $new_zone = $new_domain_record->reczone; # or die ? + unless ( $new_zone =~ /\.$/ ) { + my $new_svc_domain = $new_domain_record->svc_domain; # or die ? + $new_zone .= '.'. $new_svc_domain->domain; + } + + my $new_svc_acct = $new->svc_acct; # or die ? + my $new_username = $new_svc_acct->username; + my $new_homedir = $new_svc_acct->dir; # or die ? + + #done setting variables for the command + $self->shellcommands_queue( $new->svcnum, - user => $self->options('user')||'root', - host => $self->options('machine'), + user => $self->option('user')||'root', + host => $self->machine, command => eval(qq("$command")), - stdin_string => eval(qq("$stdin")), ); } @@ -51,13 +95,13 @@ sub shellcommands_queue { my( $self, $svcnum ) = (shift, shift); my $queue = new FS::queue { 'svcnum' => $svcnum, - 'job' => "FS::part_export::shellcommands::ssh_cmd", + 'job' => "FS::part_export::www_shellcommands::ssh_cmd", }; $queue->insert( @_ ); } sub ssh_cmd { #subroutine, not method - use Net::SSH '0.06'; + use Net::SSH '0.07'; &Net::SSH::ssh_cmd( { @_ } ); } diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index e914636e4..9c33e9a3b 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -297,7 +297,7 @@ sub payby { =head1 VERSION -$Id: part_pkg.pm,v 1.16 2002-06-10 01:39:50 khoff Exp $ +$Id: part_pkg.pm,v 1.14 2002-05-09 12:38:39 ivan Exp $ =head1 BUGS diff --git a/FS/FS/pkg_svc.pm b/FS/FS/pkg_svc.pm index 3c544ffd8..1812dbf29 100644 --- a/FS/FS/pkg_svc.pm +++ b/FS/FS/pkg_svc.pm @@ -137,7 +137,7 @@ sub part_svc { =head1 VERSION -$Id: pkg_svc.pm,v 1.3 2002-06-10 01:39:50 khoff Exp $ +$Id: pkg_svc.pm,v 1.1 1999-08-04 09:03:53 ivan Exp $ =head1 BUGS diff --git a/FS/FS/queue.pm b/FS/FS/queue.pm index 1de19b7b5..d35dc883f 100644 --- a/FS/FS/queue.pm +++ b/FS/FS/queue.pm @@ -196,7 +196,7 @@ sub check { || $self->ut_anything('job') || $self->ut_numbern('_date') || $self->ut_enum('status',['', qw( new locked failed )]) - || $self->ut_textn('statustext') + || $self->ut_anything('statustext') || $self->ut_numbern('svcnum') ; return $error if $error; @@ -385,7 +385,7 @@ END =head1 VERSION -$Id: queue.pm,v 1.14 2002-06-14 11:22:53 ivan Exp $ +$Id: queue.pm,v 1.15 2002-07-02 06:48:59 ivan Exp $ =head1 BUGS diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 2bbbdcbb7..e62cdd7bb 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -19,6 +19,7 @@ use FS::Conf; use FS::Record qw( qsearch qsearchs fields dbh ); use FS::svc_Common; use Net::SSH; +use FS::cust_svc; use FS::part_svc; use FS::svc_acct_pop; use FS::svc_acct_sm; @@ -226,19 +227,27 @@ sub insert { #new duplicate username checking + my $part_svc = qsearchs('part_svc', { 'svcpart' => $self->svcpart } ); + unless ( $part_svc ) { + $dbh->rollback if $oldAutoCommit; + return 'unknown svcpart '. $self->svcpart; + } + my @dup_user = qsearch( 'svc_acct', { 'username' => $self->username } ); - my @dup_userdomain = qsearchs( 'svc_acct', { 'username' => $self->username, - 'domsvc' => $self->domsvc } ); + my @dup_userdomain = qsearch( 'svc_acct', { 'username' => $self->username, + 'domsvc' => $self->domsvc } ); + my @dup_uid; + if ( $part_svc->part_svc_column('uid')->columnflag ne 'F' + && $self->username !~ /^(toor|(hyla)?fax)$/ ) { + @dup_uid = qsearch( 'svc_acct', { 'uid' => $self->uid } ); + } else { + @dup_uid = (); + } - if ( @dup_user || @dup_userdomain ) { + if ( @dup_user || @dup_userdomain || @dup_uid ) { my $exports = FS::part_export::export_info('svc_acct'); - my( %conflict_user_svcpart, %conflict_userdomain_svcpart ); - - my $part_svc = qsearchs('part_svc', { 'svcpart' => $self->svcpart } ); - unless ( $part_svc ) { - $dbh->rollback if $oldAutoCommit; - return 'unknown svcpart '. $self->svcpart; - } + my %conflict_user_svcpart; + my %conflict_userdomain_svcpart = ( $self->svcpart => 'SELF', ); foreach my $part_export ( $part_svc->part_export ) { @@ -269,6 +278,7 @@ sub insert { foreach my $dup_user ( @dup_user ) { my $dup_svcpart = $dup_user->cust_svc->svcpart; if ( exists($conflict_user_svcpart{$dup_svcpart}) ) { + $dbh->rollback if $oldAutoCommit; return "duplicate username: conflicts with svcnum ". $dup_user->svcnum. " via exportnum ". $conflict_user_svcpart{$dup_svcpart}; } @@ -276,10 +286,22 @@ sub insert { foreach my $dup_userdomain ( @dup_userdomain ) { my $dup_svcpart = $dup_userdomain->cust_svc->svcpart; - if ( exists($conflict_user_svcpart{$dup_svcpart}) ) { + if ( exists($conflict_userdomain_svcpart{$dup_svcpart}) ) { + $dbh->rollback if $oldAutoCommit; return "duplicate username\@domain: conflicts with svcnum ". $dup_userdomain->svcnum. " via exportnum ". - $conflict_user_svcpart{$dup_svcpart}; + $conflict_userdomain_svcpart{$dup_svcpart}; + } + } + + foreach my $dup_uid ( @dup_uid ) { + my $dup_svcpart = $dup_uid->cust_svc->svcpart; + if ( exists($conflict_user_svcpart{$dup_svcpart}) + || exists($conflict_userdomain_svcpart{$dup_svcpart}) ) { + $dbh->rollback if $oldAutoCommit; + return "duplicate uid: conflicts with svcnum". $dup_uid->svcnum. + "via exportnum ". $conflict_user_svcpart{$dup_svcpart} + || $conflict_userdomain_svcpart{$dup_svcpart}; } } @@ -287,15 +309,6 @@ sub insert { #see? i told you it was more complicated - my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $self->svcpart } ); - return "Unknown svcpart" unless $part_svc; - return "uid ". $self->uid. " in use" - if $part_svc->part_svc_column('uid')->columnflag ne 'F' - && qsearchs( 'svc_acct', { 'uid' => $self->uid } ) - && $self->username !~ /^(hyla)?fax$/ - && $self->username !~ /^toor$/ #FreeBSD - ; - my @jobnums; $error = $self->SUPER::insert(\@jobnums); if ( $error ) { @@ -693,15 +706,9 @@ sub check { && $recref->{username} ne 'root' && $recref->{username} ne 'toor'; -# $error = $self->ut_textn('finger'); -# return $error if $error; - $self->getfield('finger') =~ - /^([\w \t\!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\*\<\>]*)$/ - or return "Illegal finger: ". $self->getfield('finger'); - $self->setfield('finger', $1); $recref->{dir} =~ /^([\/\w\-\.\&]*)$/ - or return "Illegal directory"; + or return "Illegal directory: ". $recref->{dir}; $recref->{dir} = $1; return "Illegal directory" if $recref->{dir} =~ /(^|\/)\.+(\/|$)/; #no .. component @@ -733,22 +740,25 @@ sub check { $recref->{shell} = '/bin/sync'; } - $recref->{quota} =~ /^(\d*)$/ or return "Illegal quota (unimplemented)"; - $recref->{quota} = $1; - } else { $recref->{gid} ne '' ? return "Can't have gid without uid" : ( $recref->{gid}='' ); - $recref->{finger} ne '' ? - return "Can't have finger-name without uid" : ( $recref->{finger}='' ); $recref->{dir} ne '' ? return "Can't have directory without uid" : ( $recref->{dir}='' ); $recref->{shell} ne '' ? return "Can't have shell without uid" : ( $recref->{shell}='' ); - $recref->{quota} ne '' ? - return "Can't have quota without uid" : ( $recref->{quota}='' ); } + # $error = $self->ut_textn('finger'); + # return $error if $error; + $self->getfield('finger') =~ + /^([\w \t\!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\*\<\>]*)$/ + or return "Illegal finger: ". $self->getfield('finger'); + $self->setfield('finger', $1); + + $recref->{quota} =~ /^(\d*)$/ or return "Illegal quota"; + $recref->{quota} = $1; + unless ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) { unless ( $recref->{slipip} eq '0e0' ) { $recref->{slipip} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ @@ -779,7 +789,7 @@ sub check { #$recref->{password} = $1. # crypt($3,$saltset[int(rand(64))].$saltset[int(rand(64))] #; - } elsif ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([\w\.\/\$]{13,34})$/ ) { + } elsif ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([\w\.\/\$\;]{13,34})$/ ) { $recref->{_password} = $1.$3; } elsif ( $recref->{_password} eq '*' ) { $recref->{_password} = '*'; @@ -826,8 +836,8 @@ sub radius_reply { #$attrib =~ s/_/\-/g; ( $FS::raddb::attrib{lc($attrib)}, $self->getfield($column) ); } grep { /^radius_/ && $self->getfield($_) } fields( $self->table ); - if ( $self->ip && $self->ip ne '0e0' ) { - $reply{'Framed-IP-Address'} = $self->ip; + if ( $self->slipip && $self->slipip ne '0e0' ) { + $reply{'Framed-IP-Address'} = $self->slipip; } %reply; } @@ -876,7 +886,7 @@ sub domain { =item svc_domain Returns the FS::svc_domain record for this account's domain (see -L<FS::svc_domain>. +L<FS::svc_domain>). =cut diff --git a/FS/FS/svc_www.pm b/FS/FS/svc_www.pm index 6415a3012..d7a42c8ae 100644 --- a/FS/FS/svc_www.pm +++ b/FS/FS/svc_www.pm @@ -1,7 +1,7 @@ package FS::svc_www; use strict; -use vars qw(@ISA $conf $apacheroot $apachemachine $apacheip $nossh_hack ); +use vars qw(@ISA $conf $apacheip); #use FS::Record qw( qsearch qsearchs ); use FS::Record qw( qsearchs dbh ); use FS::svc_Common; @@ -9,15 +9,12 @@ use FS::cust_svc; use FS::domain_record; use FS::svc_acct; use FS::svc_domain; -use Net::SSH qw(ssh); @ISA = qw( FS::svc_Common ); #ask FS::UID to run this stuff for us later $FS::UID::callback{'FS::svc_www'} = sub { $conf = new FS::Conf; - $apacheroot = $conf->config('apacheroot'); - $apachemachine = $conf->config('apachemachine'); $apacheip = $conf->config('apacheip'); }; @@ -85,20 +82,6 @@ otherwise returns false. The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be defined. An FS::cust_svc record will be created and inserted. -If the configuration values (see L<FS::Conf>) I<apachemachine>, and -I<apacheroot> exist, the command: - - mkdir $apacheroot/$zone; - chown $username $apacheroot/$zone; - ln -s $apacheroot/$zone $homedir/$zone - -I<$zone> is the DNS A record pointed to by I<recnum> -I<$username> is the username pointed to by I<usersvc> -I<$homedir> is that user's home directory - -is executed on I<apachemachine> via ssh. This behaviour can be surpressed by -setting $FS::svc_www::nossh_hack true. - =cut sub insert { @@ -147,37 +130,6 @@ sub insert { return $error; } - my $domain_record = qsearchs('domain_record', { 'recnum' => $self->recnum } ); # or die ? - my $zone = $domain_record->reczone; - # or die ? - unless ( $zone =~ /\.$/ ) { - my $dom_svcnum = $domain_record->svcnum; - my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $dom_svcnum } ); - # or die ? - $zone .= '.'. $svc_domain->domain; - } - - my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $self->usersvc } ); - # or die ? - my $username = $svc_acct->username; - # or die ? - my $homedir = $svc_acct->dir; - # or die ? - - if ( $apachemachine - && $apacheroot - && $zone - && $username - && $homedir - && ! $nossh_hack - ) { - ssh("root\@$apachemachine", - "mkdir $apacheroot/$zone; ". - "chown $username $apacheroot/$zone; ". - "ln -s $apacheroot/$zone $homedir/$zone" - ); - } - $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } @@ -285,6 +237,30 @@ sub check { ''; #no error } +=item domain_record + +Returns the FS::domain_record record for this web virtual host's zone (see +L<FS::domain_record>). + +=cut + +sub domain_record { + my $self = shift; + qsearchs('domain_record', { 'recnum' => $self->recnum } ); +} + +=item svc_acct + +Returns the FS::svc_acct record for this web virtual host's owner (see +L<FS::svc_acct>). + +=cut + +sub svc_acct { + my $self = shift; + qsearchs('svc_acct', { 'svcnum' => $self->usersvc } ); +} + =back =head1 BUGS diff --git a/FS/MANIFEST b/FS/MANIFEST index c932f3aa0..fff95c8c8 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -7,8 +7,11 @@ bin/freeside-bill bin/freeside-daily bin/freeside-email bin/freeside-queued +bin/freeside-addoutsource +bin/freeside-addoutsourceuser bin/freeside-apply-credits bin/freeside-adduser +bin/freeside-setup bin/freeside-setinvoice bin/freeside-overdue bin/freeside-receivables-report @@ -16,8 +19,13 @@ bin/freeside-tax-report bin/freeside-cc-receipts-report bin/freeside-credit-report bin/freeside-expiration-alerter +bin/freeside-reexport FS.pm FS/CGI.pm +FS/InitHandler.pm +FS/ClientAPI.pm +FS/ClientAPI/passwd.pm +FS/ClientAPI/MyAccount.pm FS/Conf.pm FS/ConfItem.pm FS/Record.pm @@ -54,6 +62,8 @@ FS/part_export/bind_slave.pm FS/part_export/bsdshell.pm FS/part_export/cp.pm FS/part_export/cyrus.pm +FS/part_export/domain_shellcommands.pm +FS/part_export/http.pm FS/part_export/infostreet.pm FS/part_export/null.pm FS/part_export/shellcommands.pm @@ -93,6 +103,8 @@ FS/cust_tax_exempt.pm t/agent.t t/agent_type.t t/CGI.t +t/InitHandler.t +t/ClientAPI.t t/Conf.t t/ConfItem.t t/Record.t @@ -124,6 +136,8 @@ t/part_export-bind_slave.t t/part_export-bsdshell.t t/part_export-cp.t t/part_export-cyrus.t +t/part_export-domain_shellcommands.t +t/part_export-http.t t/part_export-infostreet.t t/part_export-null.t t/part_export-shellcommands.t diff --git a/FS/bin/freeside-addoutsource b/FS/bin/freeside-addoutsource new file mode 100644 index 000000000..5cec17f46 --- /dev/null +++ b/FS/bin/freeside-addoutsource @@ -0,0 +1,24 @@ +#!/bin/sh + +domain=$1 + +createdb $domain && \ +\ +mkdir /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \ +\ +chown freeside /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \ +\ +cp /home/ivan/freeside/conf/[a-z]* /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain && \ +\ +touch /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain/secrets && \ +\ +chown freeside /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain/secrets && \ +\ +chmod 600 /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain/secrets && \ +\ +echo -e "DBI:Pg:host=localhost;dbname=$domain\nfreeside\n" >/usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=$domain/secrets && \ +\ +mkdir /usr/local/etc/freeside/counters.DBI:Pg:host=localhost\;dbname=$domain && \ +mkdir /usr/local/etc/freeside/cache.DBI:Pg:host=localhost\;dbname=$domain && \ +mkdir /usr/local/etc/freeside/export.DBI:Pg:host=localhost\;dbname=$domain + diff --git a/FS/bin/freeside-addoutsourceuser b/FS/bin/freeside-addoutsourceuser new file mode 100644 index 000000000..bbad8aa3f --- /dev/null +++ b/FS/bin/freeside-addoutsourceuser @@ -0,0 +1,15 @@ +#!/bin/sh + +username=$1 +domain=$2 +password=$3 + +freeside-adduser -h /usr/local/etc/freeside/htpasswd \ + -s conf.DBI:Pg:host=localhost\;dbname=$domain/secrets \ + -b \ + $username $password 2>/dev/null + +[ -e /usr/local/etc/freeside/dbdef.DBI:Pg:host=localhost\;dbname=$domain ] \ + || ( freeside-setup $username 2>/dev/null; \ + /home/ivan/freeside/bin/populate-msgcat $username; 2>/dev/null ) + diff --git a/FS/bin/freeside-adduser b/FS/bin/freeside-adduser index 9d424634b..424123226 100644 --- a/FS/bin/freeside-adduser +++ b/FS/bin/freeside-adduser @@ -1,21 +1,23 @@ #!/usr/bin/perl -w # -# $Id: freeside-adduser,v 1.4 2002-02-06 14:58:05 ivan Exp $ +# $Id: freeside-adduser,v 1.7 2002-08-25 01:16:30 ivan Exp $ use strict; -use vars qw($opt_h $opt_c $opt_s); +use vars qw($opt_h $opt_b $opt_c $opt_s); use Getopt::Std; my $FREESIDE_CONF = "/usr/local/etc/freeside"; -getopts("ch:s:"); +getopts("bch:s:"); die &usage if $opt_c && ! $opt_h; my $user = shift or die &usage; if ( $opt_h ) { my @args = ( 'htpasswd' ); + push @args, '-b' if $opt_b; push @args, '-c' if $opt_c; push @args, $opt_h, $user; + push @args, shift if $opt_b; system(@args) == 0 or die "htpasswd failed: $?"; } @@ -27,7 +29,7 @@ print MAPSECRETS "$user $secretfile\n"; close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!"; sub usage { - die "Usage:\n\n freeside-adduser [ -h htpasswd_file [ -c ] ] [ -s secretfile ] username" + die "Usage:\n\n freeside-adduser [ -h htpasswd_file [ -c ] [ -b ] ] [ -s secretfile ] username" } =head1 NAME @@ -45,13 +47,15 @@ sales/tech folks) to the web interface, not for adding customer accounts. -h: Also call htpasswd for this user with the given filename - -c: Passed to htpasswd + -c: Passed to htpasswd(1) -s: Specify an alternate secret file + -b: same as htpasswd(1), probably insecure, not recommended + =head1 SEE ALSO -L<htpasswd>, base Freeside documentation +L<htpasswd>(1), base Freeside documentation =cut diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily index e6f02df33..142b0c73a 100755 --- a/FS/bin/freeside-daily +++ b/FS/bin/freeside-daily @@ -4,7 +4,7 @@ use strict; use Fcntl qw(:flock); use Date::Parse; use Getopt::Std; -use FS::UID qw(adminsuidsetup); +use FS::UID qw(adminsuidsetup driver_name dbh); use FS::Record qw(qsearch qsearchs); use FS::cust_main; @@ -41,6 +41,13 @@ foreach $cust_main ( @cust_main ) { } +if ( driver_name eq 'Pg' ) { + foreach my $statement ( 'vacuum', 'vacuum analyze' ) { + my $sth = dbh->prepare($statement) or die dbh->errstr; + $sth->execute or die $sth->errstr; + } +} + # subroutines sub untaint_argv { diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued index 42d00cebe..311fe62f9 100644 --- a/FS/bin/freeside-queued +++ b/FS/bin/freeside-queued @@ -15,7 +15,7 @@ use FS::queue_depend; # no autoloading just yet use FS::cust_main; use FS::svc_acct; -use Net::SSH 0.06; +use Net::SSH 0.07; use FS::part_export; $max_kids = '10'; #guess it should be a config file... @@ -36,8 +36,20 @@ $sigint = 0; $SIG{INT} = sub { warn "SIGINT received; shutting down\n"; $sigint++; }; $SIG{TERM} = sub { warn "SIGTERM received; shutting down\n"; $sigterm++; }; -$> = $FS::UID::freeside_uid unless $>; -$< = $>; +my $freeside_gid = scalar(getgrnam('freeside')) + or die "can't setgid to freeside group\n"; +$) = $freeside_gid; +$( = $freeside_gid; +#if freebsd can't setuid(), presumably it can't setgid() either. grr fleabsd +($(,$)) = ($),$(); +$) = $freeside_gid; + +$> = $FS::UID::freeside_uid; +$< = $FS::UID::freeside_uid; +#freebsd is sofa king broken, won't setuid() +($<,$>) = ($>,$<); +$> = $FS::UID::freeside_uid; + $ENV{HOME} = (getpwuid($>))[7]; #for ssh adminsuidsetup $user; diff --git a/FS/bin/freeside-reexport b/FS/bin/freeside-reexport new file mode 100644 index 000000000..b5c50a422 --- /dev/null +++ b/FS/bin/freeside-reexport @@ -0,0 +1,62 @@ +#!/usr/bin/perl -Tw + +use strict; +use FS::UID qw(adminsuidsetup); +use FS::Record qw(qsearch qsearchs); +use FS::part_export; +use FS::svc_acct; +use FS::cust_svc; + +my $user = shift or die &usage; +adminsuidsetup $user; + +my $export_x = shift or die &usage; +my @part_export; +if ( $export_x =~ /^(\d+)$/ ) { + @part_export = qsearchs('part_export', { exportnum=>$1 } ) + or die "exportnum $export_x not found\n"; +} else { + @part_export = qsearch('part_export', { exporttype=>$export_x } ) + or die "no exports of type $export_x found\n"; +} + +my $svc_something = shift or die &usage; +my $svc_x; +if ( $svc_something =~ /^(\d+)$/ ) { + my $cust_svc = qsearchs('cust_svc', { svcnum=>$1 } ) + or die "svcnum $svc_something not found\n"; + $svc_x = $cust_svc->svc_x; +} else { + $svc_x = qsearchs('svc_acct', { username=>$svc_something } ) + or die "username $svc_something not found\n"; +} + +foreach my $part_export ( @part_export ) { + my $error = $part_export->export_insert($svc_x); + die $error if $error; +} + + +sub usage { + die "Usage:\n\n freeside-reexport user exportnum|exporttype svcnum|username\n"; +} + +=head1 NAME + +freeside-reexport - Command line tool to re-trigger export jobs for existing services + +=head1 SYNOPSIS + + freeside-reexport user exportnum|exporttype svcnum|username + +=head1 DESCRIPTION + + Re-queues the export job for the specified exportnum or exporttype(s) and + specified service (selected by svcnum or username). + +=head1 SEE ALSO + +L<freeside-sqlradius-reset>, L<FS::part_export> + +=cut + diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup new file mode 100755 index 000000000..78a03385c --- /dev/null +++ b/FS/bin/freeside-setup @@ -0,0 +1,1038 @@ +#!/usr/bin/perl -Tw + +#to delay loading dbdef until we're ready +BEGIN { $FS::Record::setup_hack = 1; } + +use strict; +use vars qw($opt_s); +use Getopt::Std; +use DBI; +use DBIx::DBSchema 0.20; +use DBIx::DBSchema::Table; +use DBIx::DBSchema::Column; +use DBIx::DBSchema::ColGroup::Unique; +use DBIx::DBSchema::ColGroup::Index; +use FS::UID qw(adminsuidsetup datasrc checkeuid getsecrets); +use FS::Record; +use FS::cust_main_county; +use FS::raddb; +use FS::part_bill_event; + +die "Not running uid freeside!" unless checkeuid(); + +my %attrib2db = + map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib; + +getopts("s"); +my $user = shift or die &usage; +getsecrets($user); + +#needs to match FS::Record +my($dbdef_file) = "/usr/local/etc/freeside/dbdef.". datasrc; + +### + +#print "\nEnter the maximum username length: "; +#my($username_len)=&getvalue; +my $username_len = 32; #usernamemax config file + +#print "\n\n", <<END, ":"; +#Freeside tracks the RADIUS User-Name, check attribute Password and +#reply attribute Framed-IP-Address for each user. You can specify additional +#check and reply attributes (or you can add them later with the +#fs-radius-add-check and fs-radius-add-reply programs). +# +#First enter any additional RADIUS check attributes you need to track for each +#user, separated by whitespace. +#END +#my @check_attributes = map { $attrib2db{lc($_)} or die "unknown attribute $_"; } +# split(" ",&getvalue); +# +#print "\n\n", <<END, ":"; +#Now enter any additional reply attributes you need to track for each user, +#separated by whitespace. +#END +#my @attributes = map { $attrib2db{lc($_)} or die "unknown attribute $_"; } +# split(" ",&getvalue); +# +#print "\n\n", <<END, ":"; +#Do you wish to enable the tracking of a second, separate shipping/service +#address? +#END +#my $ship = &_yesno; +# +#sub getvalue { +# my($x)=scalar(<STDIN>); +# chop $x; +# $x; +#} +# +#sub _yesno { +# print " [y/N]:"; +# my $x = scalar(<STDIN>); +# $x =~ /^y/i; +#} + +my @check_attributes = (); #add later +my @attributes = (); #add later +my $ship = $opt_s; + +### + +my($char_d) = 80; #default maxlength for text fields + +#my(@date_type) = ( 'timestamp', '', '' ); +my(@date_type) = ( 'int', 'NULL', '' ); +my(@perl_type) = ( 'text', 'NULL', '' ); +my @money_type = ( 'decimal', '', '10,2' ); + +### +# create a dbdef object from the old data structure +### + +my(%tables)=&tables_hash_hack; + +#turn it into objects +my($dbdef) = new DBIx::DBSchema ( map { + my(@columns); + while (@{$tables{$_}{'columns'}}) { + my($name,$type,$null,$length)=splice @{$tables{$_}{'columns'}}, 0, 4; + push @columns, new DBIx::DBSchema::Column ( $name,$type,$null,$length ); + } + DBIx::DBSchema::Table->new( + $_, + $tables{$_}{'primary_key'}, + DBIx::DBSchema::ColGroup::Unique->new($tables{$_}{'unique'}), + DBIx::DBSchema::ColGroup::Index->new($tables{$_}{'index'}), + @columns, + ); +} (keys %tables) ); + +my $cust_main = $dbdef->table('cust_main'); +unless ($ship) { #remove ship_ from cust_main + $cust_main->delcolumn($_) foreach ( grep /^ship_/, $cust_main->columns ); +} else { #add indices on ship_last and ship_company + push @{$cust_main->index->lol_ref}, ( ['ship_last'], ['ship_company'] ) +} + +#add radius attributes to svc_acct + +my($svc_acct)=$dbdef->table('svc_acct'); + +my($attribute); +foreach $attribute (@attributes) { + $svc_acct->addcolumn ( new DBIx::DBSchema::Column ( + 'radius_'. $attribute, + 'varchar', + 'NULL', + $char_d, + )); +} + +foreach $attribute (@check_attributes) { + $svc_acct->addcolumn( new DBIx::DBSchema::Column ( + 'rc_'. $attribute, + 'varchar', + 'NULL', + $char_d, + )); +} + +##make part_svc table (but now as object) +# +#my($part_svc)=$dbdef->table('part_svc'); +# +##because of svc_acct_pop +##foreach (grep /^svc_/, $dbdef->tables) { +##foreach (qw(svc_acct svc_acct_sm svc_charge svc_domain svc_wo)) { +#foreach (qw(svc_acct svc_domain svc_forward svc_www)) { +# my($table)=$dbdef->table($_); +# my($col); +# foreach $col ( $table->columns ) { +# next if $col =~ /^svcnum$/; +# $part_svc->addcolumn( new DBIx::DBSchema::Column ( +# $table->name. '__' . $table->column($col)->name, +# 'varchar', #$table->column($col)->type, +# 'NULL', +# $char_d, #$table->column($col)->length, +# )); +# $part_svc->addcolumn ( new DBIx::DBSchema::Column ( +# $table->name. '__'. $table->column($col)->name . "_flag", +# 'char', +# 'NULL', +# 1, +# )); +# } +#} + +#create history tables (false laziness w/create-history-tables) +foreach my $table ( grep { ! /^h_/ } $dbdef->tables ) { + my $tableobj = $dbdef->table($table) + or die "unknown table $table"; + + die "unique->lol_ref undefined for $table" + unless defined $tableobj->unique->lol_ref; + die "index->lol_ref undefined for $table" + unless defined $tableobj->index->lol_ref; + + my $h_tableobj = DBIx::DBSchema::Table->new( { + name => "h_$table", + primary_key => 'historynum', + unique => DBIx::DBSchema::ColGroup::Unique->new( [] ), + 'index' => DBIx::DBSchema::ColGroup::Index->new( [ + @{$tableobj->unique->lol_ref}, + @{$tableobj->index->lol_ref} + ] ), + columns => [ + DBIx::DBSchema::Column->new( { + 'name' => 'historynum', + 'type' => 'serial', + 'null' => 'NOT NULL', + 'length' => '', + 'default' => '', + 'local' => '', + } ), + DBIx::DBSchema::Column->new( { + 'name' => 'history_date', + 'type' => 'int', + 'null' => 'NULL', + 'length' => '', + 'default' => '', + 'local' => '', + } ), + DBIx::DBSchema::Column->new( { + 'name' => 'history_user', + 'type' => 'varchar', + 'null' => 'NOT NULL', + 'length' => '80', + 'default' => '', + 'local' => '', + } ), + DBIx::DBSchema::Column->new( { + 'name' => 'history_action', + 'type' => 'varchar', + 'null' => 'NOT NULL', + 'length' => '80', + 'default' => '', + 'local' => '', + } ), + map { $tableobj->column($_) } $tableobj->columns + ], + } ); + $dbdef->addtable($h_tableobj); +} + +#important +$dbdef->save($dbdef_file); +&FS::Record::reload_dbdef($dbdef_file); + +### +# create 'em +### + +my($dbh)=adminsuidsetup $user; + +#create tables +$|=1; + +foreach my $statement ( $dbdef->sql($dbh) ) { + $dbh->do( $statement ) + or die "CREATE error: ". $dbh->errstr. "\ndoing statement: $statement"; +} + +#not really sample data (and shouldn't default to US) + +#cust_main_county + +#USPS state codes +foreach ( qw( +AL AK AS AZ AR CA CO CT DC DE FM FL GA GU HI ID IL IN IA KS KY LA +ME MH MD MA MI MN MS MO MT NC ND NE NH NJ NM NV NY MP OH OK OR PA PW PR RI +SC SD TN TX UT VT VI VA WA WV WI WY AE AA AP +) ) { + my($cust_main_county)=new FS::cust_main_county({ + 'state' => $_, + 'tax' => 0, + 'country' => 'US', + }); + my($error); + $error=$cust_main_county->insert; + die $error if $error; +} + +#AU "offical" state codes ala mark.williamson@ebbs.com.au (Mark Williamson) +foreach ( qw( +VIC NSW NT QLD TAS ACT WA SA +) ) { + my($cust_main_county)=new FS::cust_main_county({ + 'state' => $_, + 'tax' => 0, + 'country' => 'AU', + }); + my($error); + $error=$cust_main_county->insert; + die $error if $error; +} + +#ISO 2-letter country codes (same as country TLDs) except US and AU +foreach ( qw( +AF AL DZ AS AD AO AI AQ AG AR AM AW AT AZ BS BH BD BB BY BE BZ BJ BM BT BO +BA BW BV BR IO BN BG BF BI KH CM CA CV KY CF TD CL CN CX CC CO KM CG CK CR CI +HR CU CY CZ DK DJ DM DO TP EC EG SV GQ ER EE ET FK FO FJ FI FR FX GF PF TF GA +GM GE DE GH GI GR GL GD GP GU GT GN GW GY HT HM HN HK HU IS IN ID IR IQ IE IL +IT JM JP JO KZ KE KI KP KR KW KG LA LV LB LS LR LY LI LT LU MO MK MG MW MY MV +ML MT MH MQ MR MU YT MX FM MD MC MN MS MA MZ MM NA NR NP NL AN NC NZ NI NE NG +NU NF MP NO OM PK PW PA PG PY PE PH PN PL PT PR QA RE RO RU RW KN LC VC WS SM +ST SA SN SC SL SG SK SI SB SO ZA GS ES LK SH PM SD SR SJ SZ SE CH SY TW TJ TZ +TH TG TK TO TT TN TR TM TC TV UG UA AE GB UM UY UZ VU VA VE VN VG VI WF EH +YE YU ZR ZM ZW +) ) { + my($cust_main_county)=new FS::cust_main_county({ + 'tax' => 0, + 'country' => $_, + }); + my($error); + $error=$cust_main_county->insert; + die $error if $error; +} + +#billing events +foreach my $aref ( + [ 'COMP', 'Comp invoice', '$cust_bill->comp();', 30, 'comp' ], + [ 'CARD', 'Batch card', '$cust_bill->batch_card();', 40, 'batch-card' ], + [ 'BILL', 'Send invoice', '$cust_bill->send();', 50, 'send' ], +) { + + my $part_bill_event = new FS::part_bill_event({ + 'payby' => $aref->[0], + 'event' => $aref->[1], + 'eventcode' => $aref->[2], + 'seconds' => 0, + 'weight' => $aref->[3], + 'plan' => $aref->[4], + }); + my($error); + $error=$part_bill_event->insert; + die $error if $error; + +} + +$dbh->commit or die $dbh->errstr; +$dbh->disconnect or die $dbh->errstr; + +#print "Freeside database initialized sucessfully\n"; + +sub usage { + die "Usage:\n freeside-setup [ -s ] user\n"; +} + +### +# Now it becomes an object. much better. +### +sub tables_hash_hack { + + #note that s/(date|change)/_$1/; to avoid keyword conflict. + #put a kludge in FS::Record to catch this or? (pry need some date-handling + #stuff anyway also) + + my(%tables)=( #yech.} + + 'agent' => { + 'columns' => [ + 'agentnum', 'int', '', '', + 'agent', 'varchar', '', $char_d, + 'typenum', 'int', '', '', + 'freq', 'int', 'NULL', '', + 'prog', @perl_type, + ], + 'primary_key' => 'agentnum', + 'unique' => [], + 'index' => [ ['typenum'] ], + }, + + 'agent_type' => { + 'columns' => [ + 'typenum', 'int', '', '', + 'atype', 'varchar', '', $char_d, + ], + 'primary_key' => 'typenum', + 'unique' => [], + 'index' => [], + }, + + 'type_pkgs' => { + 'columns' => [ + 'typenum', 'int', '', '', + 'pkgpart', 'int', '', '', + ], + 'primary_key' => '', + 'unique' => [ ['typenum', 'pkgpart'] ], + 'index' => [ ['typenum'] ], + }, + + 'cust_bill' => { + 'columns' => [ + 'invnum', 'int', '', '', + 'custnum', 'int', '', '', + '_date', @date_type, + 'charged', @money_type, + 'printed', 'int', '', '', + 'closed', 'char', 'NULL', 1, + ], + 'primary_key' => 'invnum', + 'unique' => [], + 'index' => [ ['custnum'] ], + }, + + 'cust_bill_event' => { + 'columns' => [ + 'eventnum', 'int', '', '', + 'invnum', 'int', '', '', + 'eventpart', 'int', '', '', + '_date', @date_type, + 'status', 'varchar', '', $char_d, + 'statustext', 'text', 'NULL', '', + ], + 'primary_key' => 'eventnum', + #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ], + 'unique' => [], + 'index' => [ ['invnum'], ['status'] ], + }, + + 'part_bill_event' => { + 'columns' => [ + 'eventpart', 'int', '', '', + 'payby', 'char', '', 4, + 'event', 'varchar', '', $char_d, + 'eventcode', @perl_type, + 'seconds', 'int', 'NULL', '', + 'weight', 'int', '', '', + 'plan', 'varchar', 'NULL', $char_d, + 'plandata', 'text', 'NULL', '', + 'disabled', 'char', 'NULL', 1, + ], + 'primary_key' => 'eventpart', + 'unique' => [], + 'index' => [ ['payby'] ], + }, + + 'cust_bill_pkg' => { + 'columns' => [ + 'pkgnum', 'int', '', '', + 'invnum', 'int', '', '', + 'setup', @money_type, + 'recur', @money_type, + 'sdate', @date_type, + 'edate', @date_type, + ], + 'primary_key' => '', + 'unique' => [ ['pkgnum', 'invnum'] ], + 'index' => [ ['invnum'] ], + }, + + 'cust_credit' => { + 'columns' => [ + 'crednum', 'int', '', '', + 'custnum', 'int', '', '', + '_date', @date_type, + 'amount', @money_type, + 'otaker', 'varchar', '', 8, + 'reason', 'text', 'NULL', '', + 'closed', 'char', 'NULL', 1, + ], + 'primary_key' => 'crednum', + 'unique' => [], + 'index' => [ ['custnum'] ], + }, + + 'cust_credit_bill' => { + 'columns' => [ + 'creditbillnum', 'int', '', '', + 'crednum', 'int', '', '', + 'invnum', 'int', '', '', + '_date', @date_type, + 'amount', @money_type, + ], + 'primary_key' => 'creditbillnum', + 'unique' => [], + 'index' => [ ['crednum'], ['invnum'] ], + }, + + 'cust_main' => { + 'columns' => [ + 'custnum', 'int', '', '', + 'agentnum', 'int', '', '', +# 'titlenum', 'int', 'NULL', '', + 'last', 'varchar', '', $char_d, +# 'middle', 'varchar', 'NULL', $char_d, + 'first', 'varchar', '', $char_d, + 'ss', 'char', 'NULL', 11, + 'company', 'varchar', 'NULL', $char_d, + 'address1', 'varchar', '', $char_d, + 'address2', 'varchar', 'NULL', $char_d, + 'city', 'varchar', '', $char_d, + 'county', 'varchar', 'NULL', $char_d, + 'state', 'varchar', 'NULL', $char_d, + 'zip', 'varchar', '', 10, + 'country', 'char', '', 2, + 'daytime', 'varchar', 'NULL', 20, + 'night', 'varchar', 'NULL', 20, + 'fax', 'varchar', 'NULL', 12, + 'ship_last', 'varchar', 'NULL', $char_d, +# 'ship_middle', 'varchar', 'NULL', $char_d, + 'ship_first', 'varchar', 'NULL', $char_d, + 'ship_company', 'varchar', 'NULL', $char_d, + 'ship_address1', 'varchar', 'NULL', $char_d, + 'ship_address2', 'varchar', 'NULL', $char_d, + 'ship_city', 'varchar', 'NULL', $char_d, + 'ship_county', 'varchar', 'NULL', $char_d, + 'ship_state', 'varchar', 'NULL', $char_d, + 'ship_zip', 'varchar', 'NULL', 10, + 'ship_country', 'char', 'NULL', 2, + 'ship_daytime', 'varchar', 'NULL', 20, + 'ship_night', 'varchar', 'NULL', 20, + 'ship_fax', 'varchar', 'NULL', 12, + 'payby', 'char', '', 4, + 'payinfo', 'varchar', 'NULL', $char_d, + #'paydate', @date_type, + 'paydate', 'varchar', 'NULL', 10, + 'payname', 'varchar', 'NULL', $char_d, + 'tax', 'char', 'NULL', 1, + 'otaker', 'varchar', '', 8, + 'refnum', 'int', '', '', + 'referral_custnum', 'int', 'NULL', '', + 'comments', 'text', 'NULL', '', + ], + 'primary_key' => 'custnum', + 'unique' => [], + #'index' => [ ['last'], ['company'] ], + 'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ] ], + }, + + 'cust_main_invoice' => { + 'columns' => [ + 'destnum', 'int', '', '', + 'custnum', 'int', '', '', + 'dest', 'varchar', '', $char_d, + ], + 'primary_key' => 'destnum', + 'unique' => [], + 'index' => [ ['custnum'], ], + }, + + 'cust_main_county' => { #county+state+country are checked off the + #cust_main_county for validation and to provide + # a tax rate. + 'columns' => [ + 'taxnum', 'int', '', '', + 'state', 'varchar', 'NULL', $char_d, + 'county', 'varchar', 'NULL', $char_d, + 'country', 'char', '', 2, + 'taxclass', 'varchar', 'NULL', $char_d, + 'exempt_amount', @money_type, + 'tax', 'real', '', '', #tax % + ], + 'primary_key' => 'taxnum', + 'unique' => [], + # 'unique' => [ ['taxnum'], ['state', 'county'] ], + 'index' => [], + }, + + 'cust_pay' => { + 'columns' => [ + 'paynum', 'int', '', '', + #now cust_bill_pay #'invnum', 'int', '', '', + 'custnum', 'int', '', '', + 'paid', @money_type, + '_date', @date_type, + 'payby', 'char', '', 4, # CARD/BILL/COMP, should be index into + # payment type table. + 'payinfo', 'varchar', 'NULL', 16, #see cust_main above + 'paybatch', 'varchar', 'NULL', $char_d, #for auditing purposes. + 'closed', 'char', 'NULL', 1, + ], + 'primary_key' => 'paynum', + 'unique' => [], + 'index' => [ [ 'custnum' ], [ 'paybatch' ] ], + }, + + 'cust_bill_pay' => { + 'columns' => [ + 'billpaynum', 'int', '', '', + 'invnum', 'int', '', '', + 'paynum', 'int', '', '', + 'amount', @money_type, + '_date', @date_type + ], + 'primary_key' => 'billpaynum', + 'unique' => [], + 'index' => [ [ 'paynum' ], [ 'invnum' ] ], + }, + + 'cust_pay_batch' => { #what's this used for again? list of customers + #in current CARD batch? (necessarily CARD?) + 'columns' => [ + 'paybatchnum', 'int', '', '', + 'invnum', 'int', '', '', + 'custnum', 'int', '', '', + 'last', 'varchar', '', $char_d, + 'first', 'varchar', '', $char_d, + 'address1', 'varchar', '', $char_d, + 'address2', 'varchar', 'NULL', $char_d, + 'city', 'varchar', '', $char_d, + 'state', 'varchar', 'NULL', $char_d, + 'zip', 'varchar', '', 10, + 'country', 'char', '', 2, +# 'trancode', 'int', '', '', + 'cardnum', 'varchar', '', 16, + #'exp', @date_type, + 'exp', 'varchar', '', 11, + 'payname', 'varchar', 'NULL', $char_d, + 'amount', @money_type, + ], + 'primary_key' => 'paybatchnum', + 'unique' => [], + 'index' => [ ['invnum'], ['custnum'] ], + }, + + 'cust_pkg' => { + 'columns' => [ + 'pkgnum', 'int', '', '', + 'custnum', 'int', '', '', + 'pkgpart', 'int', '', '', + 'otaker', 'varchar', '', 8, + 'setup', @date_type, + 'bill', @date_type, + 'susp', @date_type, + 'cancel', @date_type, + 'expire', @date_type, + 'manual_flag', 'char', 'NULL', 1, + ], + 'primary_key' => 'pkgnum', + 'unique' => [], + 'index' => [ ['custnum'] ], + }, + + 'cust_refund' => { + 'columns' => [ + 'refundnum', 'int', '', '', + #now cust_credit_refund #'crednum', 'int', '', '', + 'custnum', 'int', '', '', + '_date', @date_type, + 'refund', @money_type, + 'otaker', 'varchar', '', 8, + 'reason', 'varchar', '', $char_d, + 'payby', 'char', '', 4, # CARD/BILL/COMP, should be index + # into payment type table. + 'payinfo', 'varchar', 'NULL', 16, #see cust_main above + 'paybatch', 'varchar', 'NULL', $char_d, + 'closed', 'char', 'NULL', 1, + ], + 'primary_key' => 'refundnum', + 'unique' => [], + 'index' => [], + }, + + 'cust_credit_refund' => { + 'columns' => [ + 'creditrefundnum', 'int', '', '', + 'crednum', 'int', '', '', + 'refundnum', 'int', '', '', + 'amount', @money_type, + '_date', @date_type + ], + 'primary_key' => 'creditrefundnum', + 'unique' => [], + 'index' => [ [ 'crednum', 'refundnum' ] ], + }, + + + 'cust_svc' => { + 'columns' => [ + 'svcnum', 'int', '', '', + 'pkgnum', 'int', 'NULL', '', + 'svcpart', 'int', '', '', + ], + 'primary_key' => 'svcnum', + 'unique' => [], + 'index' => [ ['svcnum'], ['pkgnum'], ['svcpart'] ], + }, + + 'part_pkg' => { + 'columns' => [ + 'pkgpart', 'int', '', '', + 'pkg', 'varchar', '', $char_d, + 'comment', 'varchar', '', $char_d, + 'setup', @perl_type, + 'freq', 'int', '', '', #billing frequency (months) + 'recur', @perl_type, + 'setuptax', 'char', 'NULL', 1, + 'recurtax', 'char', 'NULL', 1, + 'plan', 'varchar', 'NULL', $char_d, + 'plandata', 'text', 'NULL', '', + 'disabled', 'char', 'NULL', 1, + 'taxclass', 'varchar', 'NULL', $char_d, + ], + 'primary_key' => 'pkgpart', + 'unique' => [], + 'index' => [], + }, + +# 'part_title' => { +# 'columns' => [ +# 'titlenum', 'int', '', '', +# 'title', 'varchar', '', $char_d, +# ], +# 'primary_key' => 'titlenum', +# 'unique' => [ [] ], +# 'index' => [ [] ], +# }, + + 'pkg_svc' => { + 'columns' => [ + 'pkgpart', 'int', '', '', + 'svcpart', 'int', '', '', + 'quantity', 'int', '', '', + ], + 'primary_key' => '', + 'unique' => [ ['pkgpart', 'svcpart'] ], + 'index' => [ ['pkgpart'] ], + }, + + 'part_referral' => { + 'columns' => [ + 'refnum', 'int', '', '', + 'referral', 'varchar', '', $char_d, + ], + 'primary_key' => 'refnum', + 'unique' => [], + 'index' => [], + }, + + 'part_svc' => { + 'columns' => [ + 'svcpart', 'int', '', '', + 'svc', 'varchar', '', $char_d, + 'svcdb', 'varchar', '', $char_d, + 'disabled', 'char', 'NULL', 1, + ], + 'primary_key' => 'svcpart', + 'unique' => [], + 'index' => [], + }, + + 'part_svc_column' => { + 'columns' => [ + 'columnnum', 'int', '', '', + 'svcpart', 'int', '', '', + 'columnname', 'varchar', '', 64, + 'columnvalue', 'varchar', 'NULL', $char_d, + 'columnflag', 'char', 'NULL', 1, + ], + 'primary_key' => 'columnnum', + 'unique' => [ [ 'svcpart', 'columnname' ] ], + 'index' => [ [ 'svcpart' ] ], + }, + + #(this should be renamed to part_pop) + 'svc_acct_pop' => { + 'columns' => [ + 'popnum', 'int', '', '', + 'city', 'varchar', '', $char_d, + 'state', 'varchar', '', $char_d, + 'ac', 'char', '', 3, + 'exch', 'char', '', 3, + 'loc', 'char', 'NULL', 4, #NULL for legacy purposes + ], + 'primary_key' => 'popnum', + 'unique' => [], + 'index' => [ [ 'state' ] ], + }, + + 'part_pop_local' => { + 'columns' => [ + 'localnum', 'int', '', '', + 'popnum', 'int', '', '', + 'city', 'varchar', 'NULL', $char_d, + 'state', 'char', 'NULL', 2, + 'npa', 'char', '', 3, + 'nxx', 'char', '', 3, + ], + 'primary_key' => 'localnum', + 'unique' => [], + 'index' => [ [ 'npa', 'nxx' ], [ 'popnum' ] ], + }, + + 'svc_acct' => { + 'columns' => [ + 'svcnum', 'int', '', '', + 'username', 'varchar', '', $username_len, #unique (& remove dup code) + '_password', 'varchar', '', 50, #13 for encryped pw's plus ' *SUSPENDED* (mp5 passwords can be 34) + 'sec_phrase', 'varchar', 'NULL', $char_d, + 'popnum', 'int', 'NULL', '', + 'uid', 'int', 'NULL', '', + 'gid', 'int', 'NULL', '', + 'finger', 'varchar', 'NULL', $char_d, + 'dir', 'varchar', 'NULL', $char_d, + 'shell', 'varchar', 'NULL', $char_d, + 'quota', 'varchar', 'NULL', $char_d, + 'slipip', 'varchar', 'NULL', 15, #four TINYINTs, bah. + 'seconds', 'int', 'NULL', '', #uhhhh + 'domsvc', 'int', '', '', + ], + 'primary_key' => 'svcnum', + #'unique' => [ [ 'username', 'domsvc' ] ], + 'unique' => [], + 'index' => [ ['username'], ['domsvc'] ], + }, + +# 'svc_acct_sm' => { +# 'columns' => [ +# 'svcnum', 'int', '', '', +# 'domsvc', 'int', '', '', +# 'domuid', 'int', '', '', +# 'domuser', 'varchar', '', $char_d, +# ], +# 'primary_key' => 'svcnum', +# 'unique' => [ [] ], +# 'index' => [ ['domsvc'], ['domuid'] ], +# }, + + #'svc_charge' => { + # 'columns' => [ + # 'svcnum', 'int', '', '', + # 'amount', @money_type, + # ], + # 'primary_key' => 'svcnum', + # 'unique' => [ [] ], + # 'index' => [ [] ], + #}, + + 'svc_domain' => { + 'columns' => [ + 'svcnum', 'int', '', '', + 'domain', 'varchar', '', $char_d, + 'catchall', 'int', 'NULL', '', + ], + 'primary_key' => 'svcnum', + 'unique' => [ ['domain'] ], + 'index' => [], + }, + + 'domain_record' => { + 'columns' => [ + 'recnum', 'int', '', '', + 'svcnum', 'int', '', '', + 'reczone', 'varchar', '', $char_d, + 'recaf', 'char', '', 2, + 'rectype', 'char', '', 5, + 'recdata', 'varchar', '', $char_d, + ], + 'primary_key' => 'recnum', + 'unique' => [], + 'index' => [ ['svcnum'] ], + }, + + 'svc_forward' => { + 'columns' => [ + 'svcnum', 'int', '', '', + 'srcsvc', 'int', '', '', + 'dstsvc', 'int', '', '', + 'dst', 'varchar', 'NULL', $char_d, + ], + 'primary_key' => 'svcnum', + 'unique' => [], + 'index' => [ ['srcsvc'], ['dstsvc'] ], + }, + + 'svc_www' => { + 'columns' => [ + 'svcnum', 'int', '', '', + 'recnum', 'int', '', '', + 'usersvc', 'int', '', '', + ], + 'primary_key' => 'svcnum', + 'unique' => [], + 'index' => [], + }, + + #'svc_wo' => { + # 'columns' => [ + # 'svcnum', 'int', '', '', + # 'svcnum', 'int', '', '', + # 'svcnum', 'int', '', '', + # 'worker', 'varchar', '', $char_d, + # '_date', @date_type, + # ], + # 'primary_key' => 'svcnum', + # 'unique' => [ [] ], + # 'index' => [ [] ], + #}, + + 'prepay_credit' => { + 'columns' => [ + 'prepaynum', 'int', '', '', + 'identifier', 'varchar', '', $char_d, + 'amount', @money_type, + 'seconds', 'int', 'NULL', '', + ], + 'primary_key' => 'prepaynum', + 'unique' => [ ['identifier'] ], + 'index' => [], + }, + + 'port' => { + 'columns' => [ + 'portnum', 'int', '', '', + 'ip', 'varchar', 'NULL', 15, + 'nasport', 'int', 'NULL', '', + 'nasnum', 'int', '', '', + ], + 'primary_key' => 'portnum', + 'unique' => [], + 'index' => [], + }, + + 'nas' => { + 'columns' => [ + 'nasnum', 'int', '', '', + 'nas', 'varchar', '', $char_d, + 'nasip', 'varchar', '', 15, + 'nasfqdn', 'varchar', '', $char_d, + 'last', 'int', '', '', + ], + 'primary_key' => 'nasnum', + 'unique' => [ [ 'nas' ], [ 'nasip' ] ], + 'index' => [ [ 'last' ] ], + }, + + 'session' => { + 'columns' => [ + 'sessionnum', 'int', '', '', + 'portnum', 'int', '', '', + 'svcnum', 'int', '', '', + 'login', @date_type, + 'logout', @date_type, + ], + 'primary_key' => 'sessionnum', + 'unique' => [], + 'index' => [ [ 'portnum' ] ], + }, + + 'queue' => { + 'columns' => [ + 'jobnum', 'int', '', '', + 'job', 'text', '', '', + '_date', 'int', '', '', + 'status', 'varchar', '', $char_d, + 'statustext', 'text', 'NULL', '', + 'svcnum', 'int', 'NULL', '', + ], + 'primary_key' => 'jobnum', + 'unique' => [], + 'index' => [ [ 'svcnum' ], [ 'status' ] ], + }, + + 'queue_arg' => { + 'columns' => [ + 'argnum', 'int', '', '', + 'jobnum', 'int', '', '', + 'arg', 'text', 'NULL', '', + ], + 'primary_key' => 'argnum', + 'unique' => [], + 'index' => [ [ 'jobnum' ] ], + }, + + 'queue_depend' => { + 'columns' => [ + 'dependnum', 'int', '', '', + 'jobnum', 'int', '', '', + 'depend_jobnum', 'int', '', '', + ], + 'primary_key' => 'dependnum', + 'unique' => [], + 'index' => [ [ 'jobnum' ], [ 'depend_jobnum' ] ], + }, + + 'export_svc' => { + 'columns' => [ + 'exportsvcnum' => 'int', '', '', + 'exportnum' => 'int', '', '', + 'svcpart' => 'int', '', '', + ], + 'primary_key' => 'exportsvcnum', + 'unique' => [ [ 'exportnum', 'svcpart' ] ], + 'index' => [ [ 'exportnum' ], [ 'svcpart' ] ], + }, + + 'part_export' => { + 'columns' => [ + 'exportnum', 'int', '', '', + #'svcpart', 'int', '', '', + 'machine', 'varchar', '', $char_d, + 'exporttype', 'varchar', '', $char_d, + 'nodomain', 'char', 'NULL', 1, + ], + 'primary_key' => 'exportnum', + 'unique' => [], + 'index' => [ [ 'machine' ], [ 'exporttype' ] ], + }, + + 'part_export_option' => { + 'columns' => [ + 'optionnum', 'int', '', '', + 'exportnum', 'int', '', '', + 'optionname', 'varchar', '', $char_d, + 'optionvalue', 'text', 'NULL', '', + ], + 'primary_key' => 'optionnum', + 'unique' => [], + 'index' => [ [ 'exportnum' ], [ 'optionname' ] ], + }, + + 'radius_usergroup' => { + 'columns' => [ + 'usergroupnum', 'int', '', '', + 'svcnum', 'int', '', '', + 'groupname', 'varchar', '', $char_d, + ], + 'primary_key' => 'usergroupnum', + 'unique' => [], + 'index' => [ [ 'svcnum' ], [ 'groupname' ] ], + }, + + 'msgcat' => { + 'columns' => [ + 'msgnum', 'int', '', '', + 'msgcode', 'varchar', '', $char_d, + 'locale', 'varchar', '', 16, + 'msg', 'text', '', '', + ], + 'primary_key' => 'msgnum', + 'unique' => [ [ 'msgcode', 'locale' ] ], + 'index' => [], + }, + + 'cust_tax_exempt' => { + 'columns' => [ + 'exemptnum', 'int', '', '', + 'custnum', 'int', '', '', + 'taxnum', 'int', '', '', + 'year', 'int', '', '', + 'month', 'int', '', '', + 'amount', @money_type, + ], + 'primary_key' => 'exemptnum', + 'unique' => [ [ 'custnum', 'taxnum', 'year', 'month' ] ], + 'index' => [], + }, + + + + ); + + %tables; + +} + diff --git a/FS/bin/freeside-sqlradius-reset b/FS/bin/freeside-sqlradius-reset index 41f3358f6..9d3a6a700 100755 --- a/FS/bin/freeside-sqlradius-reset +++ b/FS/bin/freeside-sqlradius-reset @@ -46,7 +46,7 @@ foreach my $export ( @exports ) { sub usage { #die "Usage:\n\n sqlradius_reset user machine\n"; - die "Usage:\n\n sqlradius_reset user\n"; + die "Usage:\n\n freeside-sqlradius-reset user\n"; } =head1 NAME @@ -66,7 +66,7 @@ B<username> is a username added by freeside-adduser. =head1 SEE ALSO -<FS::part_export>, L<FS::part_export::sqlradius> +L<freeside-reexport>, L<FS::part_export>, L<FS::part_export::sqlradius> =cut diff --git a/FS/t/ClientAPI.t b/FS/t/ClientAPI.t new file mode 100644 index 000000000..973d8dada --- /dev/null +++ b/FS/t/ClientAPI.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::ClientAPI; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/InitHandler.t b/FS/t/InitHandler.t new file mode 100644 index 000000000..0ce60c833 --- /dev/null +++ b/FS/t/InitHandler.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::InitHandler; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/part_export-domain_shellcommands.t b/FS/t/part_export-domain_shellcommands.t new file mode 100644 index 000000000..a2a44fbfb --- /dev/null +++ b/FS/t/part_export-domain_shellcommands.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::part_export::domain_shellcommands; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/part_export-http.t b/FS/t/part_export-http.t new file mode 100644 index 000000000..ea41b939f --- /dev/null +++ b/FS/t/part_export-http.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::part_export::http; +$loaded=1; +print "ok 1\n"; |