RT#29354: Password Security in Email [customer fields, images, js files]
[freeside.git] / fs_selfservice / FS-SelfService / cgi / selfservice.cgi
old mode 100644 (file)
new mode 100755 (executable)
index 36557b6..aff9bca
-#!/usr/bin/perl -Tw
+#!/usr/bin/perl -w
 
 use strict;
 
 use strict;
-use vars qw($DEBUG $cgi $session_id $form_max $template_dir);
+use vars qw($DEBUG $cgi $session_id $pw_session_id $form_max $template_dir);
 use subs qw(do_template);
 use CGI;
 use CGI::Carp qw(fatalsToBrowser);
 use subs qw(do_template);
 use CGI;
 use CGI::Carp qw(fatalsToBrowser);
+use CGI::Cookie;
 use Text::Template;
 use HTML::Entities;
 use Date::Format;
 use Text::Template;
 use HTML::Entities;
 use Date::Format;
+use Date::Parse 'str2time';
 use Number::Format 1.50;
 use Number::Format 1.50;
-use FS::SelfService qw( login customer_info edit_info invoice
-                        payment_info process_payment 
-                        process_prepay
-                        list_pkgs order_pkg signup_info order_recharge
-                        part_svc_info provision_acct provision_external
-                        unprovision_svc change_pkg domainselector
-                        list_svcs list_svc_usage list_support_usage
-                        myaccount_passwd
-                      );
+use FS::SelfService qw(
+  access_info login_info login customer_info edit_info invoice
+  payment_info process_payment realtime_collect process_prepay
+  list_pkgs order_pkg signup_info order_recharge
+  part_svc_info provision_acct provision_external provision_phone provision_forward
+  unprovision_svc change_pkg suspend_pkg domainselector
+  list_svcs list_svc_usage list_cdr_usage list_support_usage
+  myaccount_passwd list_invoices create_ticket get_ticket did_report
+  adjust_ticket_priority
+  mason_comp port_graph
+  start_thirdparty finish_thirdparty
+  reset_passwd check_reset_passwd process_reset_passwd
+  validate_passwd
+  billing_history
+);
 
 $template_dir = '.';
 
 
 $template_dir = '.';
 
-$DEBUG = 1;
+$DEBUG = 0;
 
 $form_max = 255;
 
 $cgi = new CGI;
 
 
 $form_max = 255;
 
 $cgi = new CGI;
 
-unless ( defined $cgi->param('session') ) {
-  do_template('login',{});
-  exit;
+#order|pw_list XXX ???
+my @actions = ( qw(
+  myaccount
+  tktcreate
+  tktview
+  ticket_priority
+  didreport
+  invoices
+  view_invoice
+  make_payment
+  make_ach_payment
+  make_term_payment
+  make_thirdparty_payment
+  post_thirdparty_payment
+  finish_thirdparty_payment
+  cancel_thirdparty_payment
+  payment_results
+  ach_payment_results
+  recharge_prepay
+  recharge_results
+  logout
+  change_bill
+  change_ship
+  change_pay
+  process_change_bill
+  process_change_ship
+  process_change_pay
+  customer_order_pkg
+  process_order_pkg
+  customer_change_pkg
+  process_change_pkg
+  process_order_recharge
+  provision
+  provision_svc
+  process_svc_acct
+  process_svc_phone
+  process_svc_external
+  process_svc_forward
+  delete_svc
+  view_usage
+  view_usage_details
+  view_cdr_details
+  view_support_details
+  view_port_graph
+  real_port_graph
+  change_password
+  process_change_password
+  customer_suspend_pkg
+  process_suspend_pkg
+  switch_cust
+  history
+  validate_password
+));
+
+my @nologin_actions = (qw(
+  forgot_password
+  do_forgot_password
+  process_forgot_password
+  do_process_forgot_password
+  process_forgot_password_session
+  validate_password_nologin
+));
+push @actions, @nologin_actions;
+my %nologin_actions = map { $_=>1 } @nologin_actions;
+
+my $action = 'myaccount'; # sensible default
+
+if ( $cgi->param('action') =~ /^process_forgot_password_session_(\w+)$/ ) {
+  $action = 'process_forgot_password_session';
+  $pw_session_id = $1;
+} elsif ( $cgi->param('action') =~ /^(\w+)$/ ) {
+  if (grep {$_ eq $1} @actions) {
+    $action = $1;
+  } else {
+    warn "WARNING: unrecognized action '$1'\n";
+  }
 }
 }
+unless ( $nologin_actions{$action} ) {
 
 
-if ( $cgi->param('session') eq 'login' ) {
+  my %cookies = CGI::Cookie->fetch;
 
 
-  $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i
-    or die "illegal username";
-  my $username = $1;
+  my $login_rv = {};
 
 
-  $cgi->param('domain') =~ /^\s*([\w\-\.]{0,$form_max})\s*$/
-    or die "illegal domain";
-  my $domain = $1;
+  if ( exists($cookies{'session'}) ) {
 
 
-  $cgi->param('password') =~ /^(.{0,$form_max})$/
-    or die "illegal password";
-  my $password = $1;
+    $session_id = $cookies{'session'}->value;
 
 
-  my $rv = login(
-    'username' => $username,
-    'domain'   => $domain,
-    'password' => $password,
-  );
-  if ( $rv->{error} ) {
-    do_template('login', {
-      'error'    => $rv->{error},
-      'username' => $username,
-      'domain'   => $domain,
-    } );
+    if ( $session_id eq 'login' ) {
+      # then we've just come back from the login page
+
+      $cgi->param('password') =~ /^(.{0,$form_max})$/;
+      my $password = $1;
+
+      if ( $cgi->param('email') =~ /^\s*([a-z0-9_\-\.\@]{1,$form_max})\s*$/i ) {
+
+        my $email = $1;
+        $login_rv = login(
+          'email'    => $email,
+          'password' => $password
+        );
+
+       if ( $login_rv->{'error'} ) {
+         my $ip = $cgi->remote_addr();
+         warn("login failure [email $email] [ip $ip] [error $login_rv->{error}]");
+       } else {
+         #successful login
+       }
+
+       $session_id = $login_rv->{'session_id'};
+
+      } else {
+
+        $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i;
+        my $username = $1;
+
+        $cgi->param('domain') =~ /^\s*([\w\-\.]{0,$form_max})\s*$/;
+        my $domain = $1;
+
+        if ( $username and $domain and $password ) {
+
+          # authenticate
+          $login_rv = login(
+            'username' => $username,
+            'domain'   => $domain,
+            'password' => $password,
+          );
+          $session_id = $login_rv->{'session_id'};
+
+        } elsif ( $username or $domain or $password ) {
+        
+          my $error = 'Illegal '; #XXX localization...
+          my $count = 0;
+          if ( !$username ) {
+            $error .= 'username';
+            $count++;
+          }
+          if ( !$domain )  {
+            $error .= ', ' if $count;
+            $error .= 'domain';
+            $count++;
+          }
+          if ( !$password ) {
+            $error .= ', ' if $count;
+            $error .= 'and ' if $count > 1;
+            $error .= 'password';
+            $count++;
+          }
+          $error .= '.';
+          $login_rv = {
+            'username'  => $username,
+            'domain'    => $domain,
+            'password'  => $password,
+            'error'     => $error,
+          };
+          $session_id = undef; # attempt login again
+
+        }
+
+      } # else there was no input, so show no error message
+
+    } # else session_id ne 'login'
+
+  } # else there is no session cookie
+
+  if ( !$session_id ) {
+    # show the login page
+    $session_id = 'login'; # set state
+    my $login_info = login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
+
+    do_template('login', { %$login_rv, %$login_info });
     exit;
     exit;
-  } else {
-    $cgi->param('session' => $rv->{session_id} );
-    $cgi->param('action'  => 'myaccount' );
   }
   }
-}
 
 
-$session_id = $cgi->param('session');
+  # at this point $session_id is a real session
 
 
-#order|pw_list XXX ???
-$cgi->param('action') =~
-    /^(myaccount|view_invoice|make_payment|make_ach_payment|payment_results|ach_payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|change_pay|process_change_bill|process_change_ship|process_change_pay|customer_order_pkg|process_order_pkg|customer_change_pkg|process_change_pkg|process_order_recharge|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|view_usage|view_usage_details|view_support_details|change_password|process_change_password)$/
-  or die "unknown action ". $cgi->param('action');
-my $action = $1;
+  if ( ! $login_rv->{'custnum'} && ! $login_rv->{'svcnum'} && $login_rv->{'customers'} ) {
+    #select a customer if we're a multi-contact customer
+    do_template('select_cust', { %$login_rv } );
+    exit;
+  }
+
+}
 
 warn "calling $action sub\n"
   if $DEBUG;
 
 warn "calling $action sub\n"
   if $DEBUG;
@@ -78,10 +224,15 @@ $FS::SelfService::DEBUG = $DEBUG;
 my $result = eval "&$action();";
 die $@ if $@;
 
 my $result = eval "&$action();";
 die $@ if $@;
 
-if ( $result->{error} eq "Can't resume session"
-  || $result->{error} eq "Expired session" ) { #ick
+use Data::Dumper;
+warn Dumper($result) if $DEBUG;
+
+if ( $result->{error} && ( $result->{error} eq "Can't resume session"
+  || $result->{error} eq "Expired session") ) { #ick
 
 
-  do_template('login',{});
+  $session_id = 'login';
+  my $login_info = login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
+  do_template('login', $login_info);
   exit;
 }
 
   exit;
 }
 
@@ -93,12 +244,22 @@ warn "processing template $action\n"
 do_template($action, {
   'session_id' => $session_id,
   'action'     => $action, #so the menu knows what tab we're on...
 do_template($action, {
   'session_id' => $session_id,
   'action'     => $action, #so the menu knows what tab we're on...
+  #%{ payment_info( 'session_id' => $session_id ) },  # cust_paybys for the menu
   %{$result}
 });
 
 #--
 
   %{$result}
 });
 
 #--
 
-sub myaccount { customer_info( 'session_id' => $session_id ); }
+sub switch_cust {
+  $action = 'myaccount';
+  FS::SelfService::switch_cust( 'session_id' => $session_id,
+                                'custnum'    => scalar($cgi->param('custnum')),
+                              );
+}
+
+sub myaccount { 
+  customer_info( 'session_id' => $session_id ); 
+}
 
 sub change_bill { my $payment_info =
                     payment_info( 'session_id' => $session_id );
 
 sub change_bill { my $payment_info =
                     payment_info( 'session_id' => $session_id );
@@ -140,7 +301,7 @@ sub _process_change_info {
 sub process_change_bill {
         _process_change_info( 'change_bill', 
           qw( first last company address1 address2 city state
 sub process_change_bill {
         _process_change_info( 'change_bill', 
           qw( first last company address1 address2 city state
-              county state zip country daytime night fax )
+              county zip country daytime night fax )
         );
 }
 
         );
 }
 
@@ -157,11 +318,28 @@ sub process_change_ship {
 }
 
 sub process_change_pay {
 }
 
 sub process_change_pay {
-        _process_change_info( 'change_pay', 
+        my $postal = $cgi->param( 'postal_invoicing' );
+        my $payby  = $cgi->param( 'payby' );
+        my @list =
           qw( payby payinfo payinfo1 payinfo2 month year payname
               address1 address2 city county state zip country auto paytype
           qw( payby payinfo payinfo1 payinfo2 month year payname
               address1 address2 city county state zip country auto paytype
-              paystate ss stateid stateid_state )
-        );
+              paystate ss stateid stateid_state invoicing_list
+            );
+        push @list, 'postal_invoicing' if $postal;
+        unless (    $payby ne 'BILL'
+                 || $postal
+                 || $cgi->param( 'invoicing_list' )
+               )
+        {
+          $action = 'change_pay';
+          return {
+            %{&change_pay()},
+            $cgi->Vars,
+            'error' => '<FONT COLOR="#FF0000">Postal or email required.</FONT>',
+          };
+        }
+
+        _process_change_info( 'change_pay', @list );
 }
 
 sub view_invoice {
 }
 
 sub view_invoice {
@@ -175,6 +353,59 @@ sub view_invoice {
 
 }
 
 
 }
 
+sub invoices {
+  list_invoices( 'session_id' => $session_id, );
+}
+
+sub history {
+  billing_history( 'session_id' => $session_id, );
+}
+
+sub tktcreate {
+  my $customer_info = customer_info( 'session_id' => $session_id );
+  return $customer_info if ( $customer_info->{'error'} );
+
+  my $requestor = "";
+  if ( $customer_info->{'invoicing_list'} ) {
+    my @requestor = split( /\s*\,\s*/, $customer_info->{'invoicing_list'} );
+    $requestor = $requestor[0] if scalar(@requestor);
+  }
+
+  return { 'requestor' => $requestor }
+    unless ($cgi->param('subject') && $cgi->param('message') &&
+       length($cgi->param('subject')) && length($cgi->param('message')));
+    
+ create_ticket(        'session_id' => $session_id,
+                       'subject' => $cgi->param('subject'),
+                       'message' => $cgi->param('message'), 
+                       'requestor' => $requestor,
+           );
+}
+
+sub tktview {
+ get_ticket(   'session_id' => $session_id,
+               'ticket_id' => ($cgi->param('ticket_id') || ''),
+                'subject'   => ($cgi->param('subject') || ''),
+               'reply'     => ($cgi->param('reply') || ''),
+           );
+}
+
+sub ticket_priority {
+  my %values;
+  foreach ( $cgi->param ) {
+    if ( /^ticket(\d+)$/ ) {
+      # a 'ticket1001' param implies the existence of a 'priority1001' param
+      # but if that's empty, we need to send it as empty rather than forget
+      # it.
+      $values{$1} = $cgi->param("priority$1") || '';
+    }
+  }
+  $action = 'myaccount';
+  # this returns an updated customer_info for myaccount
+  adjust_ticket_priority( 'session_id' => $session_id,
+                          'values'     => \%values );
+}
+
 sub customer_order_pkg {
   my $init_data = signup_info( 'customer_session_id' => $session_id );
   return $init_data if ( $init_data->{'error'} );
 sub customer_order_pkg {
   my $init_data = signup_info( 'customer_session_id' => $session_id );
   return $init_data if ( $init_data->{'error'} );
@@ -182,11 +413,24 @@ sub customer_order_pkg {
   my $customer_info = customer_info( 'session_id' => $session_id );
   return $customer_info if ( $customer_info->{'error'} );
 
   my $customer_info = customer_info( 'session_id' => $session_id );
   return $customer_info if ( $customer_info->{'error'} );
 
+  my $pkgselect = mason_comp(
+    'session_id' => $session_id,
+    'comp'       => '/edit/cust_main/first_pkg/select-part_pkg.html',
+    'args'       => [ 'password_verify' => 1,
+                      'onchange'        => 'enable_order_pkg()',
+                      'relurls'         => 1,
+                      'empty_label'     => 'Select package',
+                    ],
+  );
+
+  $pkgselect = $pkgselect->{'error'} || $pkgselect->{'output'};
+
   return {
     ( map { $_ => $init_data->{$_} }
           qw( part_pkg security_phrase svc_acct_pop ),
     ),
     %$customer_info,
   return {
     ( map { $_ => $init_data->{$_} }
           qw( part_pkg security_phrase svc_acct_pop ),
     ),
     %$customer_info,
+    'pkg_selector' => $pkgselect,
   };
 }
 
   };
 }
 
@@ -212,23 +456,46 @@ sub process_order_pkg {
 
   my $results = '';
 
 
   my $results = '';
 
-  unless ( length($cgi->param('_password')) ) {
-    my $init_data = signup_info( 'customer_session_id' => $session_id );
-    $results = { 'error' => $init_data->{msgcat}{empty_password} };
-    $results = { 'error' => $init_data->{error} } if($init_data->{error});
+  my @params = (qw( custnum pkgpart ));
+  my $svcdb = '';
+  if ( $cgi->param('pkgpart_svcpart') =~ /^(\d+)_(\d+)$/ ) {
+    $cgi->param('pkgpart', $1);
+    $cgi->param('svcpart', $2);
+    push @params, 'svcpart';
+    $svcdb = $cgi->param('svcdb');
+    push @params, 'domsvc' if $svcdb eq 'svc_acct';
+  } else {
+    $svcdb = 'svc_acct';
   }
   }
-  if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
-    my $init_data = signup_info( 'customer_session_id' => $session_id );
-    $results = { 'error' => $init_data->{msgcat}{passwords_dont_match} };
-    $results = { 'error' => $init_data->{error} } if($init_data->{error});
-    $cgi->param('_password', '');
-    $cgi->param('_password2', '');
+
+  if ( $svcdb eq 'svc_acct' ) {
+
+    push @params, qw( username _password _password2 sec_phrase popnum );
+
+    unless ( length($cgi->param('_password')) ) {
+      my $init_data = signup_info( 'customer_session_id' => $session_id );
+      $results = { 'error' => $init_data->{msgcat}{empty_password} };
+      $results = { 'error' => $init_data->{error} } if($init_data->{error});
+    }
+    if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
+      my $init_data = signup_info( 'customer_session_id' => $session_id );
+      $results = { 'error' => $init_data->{msgcat}{passwords_dont_match} };
+      $results = { 'error' => $init_data->{error} } if($init_data->{error});
+      $cgi->param('_password', '');
+      $cgi->param('_password2', '');
+    }
+
+  } elsif ( $svcdb eq 'svc_phone' ) {
+
+    push @params, qw( phonenum sip_password pin phone_name );
+
+  } else {
+    die "$svcdb not handled on process_order_pkg yet";
   }
 
   $results ||= order_pkg (
     'session_id' => $session_id,
   }
 
   $results ||= order_pkg (
     'session_id' => $session_id,
-    map { $_ => $cgi->param($_) }
-        qw( custnum pkgpart username _password _password2 sec_phrase popnum )
+    map { $_ => $cgi->param($_) } @params
   );
 
 
   );
 
 
@@ -269,6 +536,24 @@ sub process_change_pkg {
 
 }
 
 
 }
 
+sub process_suspend_pkg {
+  my $results = '';
+  $results = suspend_pkg (
+    'session_id' => $session_id,
+    map { $_ => $cgi->param($_) } 
+      qw( pkgnum )
+    );
+  if ( $results->{'error'} ) {
+    $action = 'provision';
+    return {
+      'error' => '<FONT COLOR="#FF0000">'. $results->{'error'}. '</FONT>',
+    }
+  }
+  else {
+    return $results;
+  }
+}
+
 sub process_order_recharge {
 
   my $results = '';
 sub process_order_recharge {
 
   my $results = '';
@@ -297,12 +582,31 @@ sub process_order_recharge {
 }
 
 sub make_payment {
 }
 
 sub make_payment {
-  payment_info( 'session_id' => $session_id );
+
+  my $payment_info = payment_info( 'session_id' => $session_id );
+
+  my $amount = 
+    ($payment_info->{'balance'} && ($payment_info->{'balance'} > 0))
+    ? $payment_info->{'balance'}
+    : '';
+
+  my $tr_amount_fee = mason_comp(
+    'session_id' => $session_id,
+    'comp'       => '/elements/tr-amount_fee.html',
+    'args'       => [ 'amount' => $amount,
+                    ],
+  );
+
+  $tr_amount_fee = $tr_amount_fee->{'error'} || $tr_amount_fee->{'output'};
+
+  $payment_info->{'tr_amount_fee'} = $tr_amount_fee;
+
+  $payment_info;
 }
 
 sub payment_results {
 
 }
 
 sub payment_results {
 
-  use Business::CreditCard;
+  use Business::CreditCard 0.30;
 
   #we should only do basic checking here for DoS attacks and things
   #that couldn't be constructed by the web form...  let process_payment() do
 
   #we should only do basic checking here for DoS attacks and things
   #that couldn't be constructed by the web form...  let process_payment() do
@@ -313,14 +617,16 @@ sub payment_results {
   my $amount = $1;
 
   my $payinfo = $cgi->param('payinfo');
   my $amount = $1;
 
   my $payinfo = $cgi->param('payinfo');
-  $payinfo =~ s/\D//g;
-  $payinfo =~ /^(\d{13,16})$/
+  $payinfo =~ s/[^\dx]//g;
+  $payinfo =~ /^([\dx]{13,16}|[\dx]{8,9})$/
     #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
     or die "illegal card"; #!!!
   $payinfo = $1;
     #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
     or die "illegal card"; #!!!
   $payinfo = $1;
-  validate($payinfo)
-    #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
-    or die "invalid card"; #!!!
+  unless ( $payinfo =~ /x/ ) {
+    validate($payinfo)
+      #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+      or die "invalid card"; #!!!
+  }
 
   if ( $cgi->param('card_type') ) {
     cardtype($payinfo) eq $cgi->param('card_type')
 
   if ( $cgi->param('card_type') ) {
     cardtype($payinfo) eq $cgi->param('card_type')
@@ -348,21 +654,31 @@ sub payment_results {
   $cgi->param('city') =~ /^(.{0,80})$/ or die "illegal city";
   my $city = $1;
 
   $cgi->param('city') =~ /^(.{0,80})$/ or die "illegal city";
   my $city = $1;
 
-  $cgi->param('state') =~ /^(.{2})$/ or die "illegal state";
+  $cgi->param('state') =~ /^(.{0,80})$/ or die "illegal state";
   my $state = $1;
 
   $cgi->param('zip') =~ /^(.{0,10})$/ or die "illegal zip";
   my $zip = $1;
 
   my $state = $1;
 
   $cgi->param('zip') =~ /^(.{0,10})$/ or die "illegal zip";
   my $zip = $1;
 
+  $cgi->param('country') =~ /^(.{0,2})$/ or die "illegal country";
+  my $country = $1;
+
   my $save = 0;
   $save = 1 if $cgi->param('save');
 
   my $auto = 0;
   $auto = 1 if $cgi->param('auto');
 
   my $save = 0;
   $save = 1 if $cgi->param('save');
 
   my $auto = 0;
   $auto = 1 if $cgi->param('auto');
 
-  $cgi->param('paybatch') =~ /^([\w\-\.]+)$/ or die "illegal paybatch";
+  $cgi->param('payunique') =~ /^([\w\-\.]*)$/ or die "illegal payunique";
+  my $payunique = $1;
+
+  $cgi->param('paybatch') =~ /^([\w\-\.]*)$/ or die "illegal paybatch";
   my $paybatch = $1;
 
   my $paybatch = $1;
 
+  $cgi->param('discount_term') =~ /^(\d*)$/ or die "illegal discount_term";
+  my $discount_term = $1;
+
+
   process_payment(
     'session_id' => $session_id,
     'payby'      => 'CARD',
   process_payment(
     'session_id' => $session_id,
     'payby'      => 'CARD',
@@ -377,9 +693,12 @@ sub payment_results {
     'city'       => $city,
     'state'      => $state,
     'zip'        => $zip,
     'city'       => $city,
     'state'      => $state,
     'zip'        => $zip,
+    'country'    => $country,
     'save'       => $save,
     'auto'       => $auto,
     'save'       => $save,
     'auto'       => $auto,
+    'payunique'  => $payunique,
     'paybatch'   => $paybatch,
     'paybatch'   => $paybatch,
+    'discount_term' => $discount_term,
   );
 
 }
   );
 
 }
@@ -399,14 +718,16 @@ sub ach_payment_results {
   my $amount = $1;
 
   my $payinfo1 = $cgi->param('payinfo1');
   my $amount = $1;
 
   my $payinfo1 = $cgi->param('payinfo1');
-  $payinfo1=~ /^(\d+)$/
+  $payinfo1 =~ s/[^\dx]//g;
+  $payinfo1 =~ /^([\dx]+)$/
     or die "illegal account"; #!!!
     or die "illegal account"; #!!!
-  $payinfo1= $1;
+  $payinfo1 = $1;
 
   my $payinfo2 = $cgi->param('payinfo2');
 
   my $payinfo2 = $cgi->param('payinfo2');
-  $payinfo2=~ /^(\d+)$/
+  $payinfo2 =~ s/[^\dx]//g;
+  $payinfo2 =~ /^([\dx]+)$/
     or die "illegal ABA/routing code"; #!!!
     or die "illegal ABA/routing code"; #!!!
-  $payinfo2= $1;
+  $payinfo2 = $1;
 
   $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname";
   my $payname = $1;
 
   $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname";
   my $payname = $1;
@@ -456,6 +777,63 @@ sub ach_payment_results {
 
 }
 
 
 }
 
+sub make_thirdparty_payment {
+  my $payment_info = payment_info('session_id' => $session_id);
+  $cgi->param('payby_method') =~ /^(CC|ECHECK|PAYPAL)$/
+    or die "illegal payby method";
+  $payment_info->{'payby_method'} = $1;
+  $payment_info->{'error'} = $cgi->param('error');
+
+  $payment_info;
+}
+
+sub post_thirdparty_payment {
+  $cgi->param('payby_method') =~ /^(CC|ECHECK|PAYPAL)$/
+    or die "illegal payby method";
+  my $method = $1;
+  $cgi->param('amount') =~ /^(\d+(\.\d*)?)$/
+    or die "illegal amount";
+  my $amount = $1;
+  my $result = start_thirdparty(
+    'session_id' => $session_id,
+    'method' => $method, 
+    'amount' => $amount,
+  );
+  if ( $result->{error} ) {
+    $cgi->param('action', 'make_thirdparty_payment');
+    $cgi->param('error', $result->{error});
+    print $cgi->redirect( $cgi->self_url );
+    exit;
+  }
+
+  $result;
+}
+
+sub finish_thirdparty_payment {
+  my %param = $cgi->Vars;
+  finish_thirdparty( 'session_id' => $session_id, %param );
+  # result contains either 'error' => error message, or the payment details
+}
+
+sub cancel_thirdparty_payment {
+  $action = 'make_thirdparty_payment';
+  finish_thirdparty( 'session_id' => $session_id, '_cancel' => 1 );
+}
+
+sub make_term_payment {
+  $cgi->param('amount') =~ /^(\d+\.\d{2})$/
+    or die "illegal payment amount";
+  my $balance = $1;
+  $cgi->param('discount_term') =~ /^(\d+)$/
+    or die "illegal discount term";
+  my $discount_term = $1;
+  $action = 'make_payment';
+  ({ %{payment_info( 'session_id' => $session_id )},
+    'balance' => $balance,
+    'discount_term' => $discount_term,
+  })
+}
+
 sub recharge_prepay {
   customer_info( 'session_id' => $session_id );
 }
 sub recharge_prepay {
   customer_info( 'session_id' => $session_id );
 }
@@ -476,9 +854,20 @@ sub logout {
   FS::SelfService::logout( 'session_id' => $session_id );
 }
 
   FS::SelfService::logout( 'session_id' => $session_id );
 }
 
+sub didreport {
+  my $result = did_report( 'session_id' => $session_id, 
+           'format' => $cgi->param('type'),
+           'recentonly' => $cgi->param('recentonly'),
+       );
+  die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
+  $result;
+}
+
 sub provision {
   my $result = list_pkgs( 'session_id' => $session_id );
   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
 sub provision {
   my $result = list_pkgs( 'session_id' => $session_id );
   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
+  $result->{'pkgpart'} = $cgi->param('pkgpart') if $cgi->param('pkgpart');
+  $result->{'filter'} = $cgi->param('filter') if $cgi->param('filter');
   $result;
 }
 
   $result;
 }
 
@@ -486,7 +875,7 @@ sub provision_svc {
 
   my $result = part_svc_info(
     'session_id' => $session_id,
 
   my $result = part_svc_info(
     'session_id' => $session_id,
-    map { $_ => $cgi->param($_) } qw( pkgnum svcpart ),
+    map { $_ => ($cgi->param($_) || '') } qw( pkgnum svcpart svcnum ),
   );
   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
 
   );
   die $result->{'error'} if exists $result->{'error'} && $result->{'error'};
 
@@ -495,6 +884,47 @@ sub provision_svc {
     or die 'Unknown svcdb '. $result->{'svcdb'};
   $action .= "_$1";
 
     or die 'Unknown svcdb '. $result->{'svcdb'};
   $action .= "_$1";
 
+  $result->{'numavail'} = $cgi->param('numavail');
+  $result->{'lnp'} = $cgi->param('lnp');
+
+  $result;
+}
+
+sub process_svc_phone {
+    my @bulkdid = $cgi->param('bulkdid');
+    my $phonenum = $cgi->param('phonenum');
+    my $lnp = $cgi->param('lnp');
+
+    my $result;
+    if($lnp) {
+       $result = provision_phone (
+           'session_id' => $session_id,
+           'countrycode' => '1',
+            map { $_ => $cgi->param($_) } qw( pkgnum svcpart phonenum 
+               lnp_desired_due_date lnp_other_provider 
+               lnp_other_provider_account )
+       );
+    } else {
+       $result = provision_phone (
+           'session_id' => $session_id,
+           'bulkdid' => [ @bulkdid ],
+           'countrycode' => '1',
+            map { $_ => $cgi->param($_) } qw( pkgnum svcpart phonenum svcnum email forwarddst )
+       );
+    }
+
+    if ( exists $result->{'error'} && $result->{'error'} ) { 
+       $action = 'provision_svc_phone';
+       return {
+         $cgi->Vars,
+         %{ part_svc_info( 'session_id' => $session_id,
+                        map { $_ => $cgi->param($_) } qw( pkgnum svcpart svcnum )
+             )
+         },
+         'error' => $result->{'error'},
+       };
+  }
+
   $result;
 }
 
   $result;
 }
 
@@ -531,6 +961,33 @@ sub process_svc_external {
   );
 }
 
   );
 }
 
+sub process_svc_forward {
+
+  my $result = provision_forward (
+    'session_id' => $session_id,
+    map { $_ => $cgi->param($_) || '' } qw(
+      pkgnum svcpart srcsvc src dstsvc dst )
+  );
+
+  if ( exists $result->{'error'} && $result->{'error'} ) { 
+    #warn "$result $result->{'error'}"; 
+    $action = 'provision_svc_forward';
+    return {
+      $cgi->Vars,
+      %{ part_svc_info( 'session_id' => $session_id,
+                        map { $_ => $cgi->param($_) } qw( svcnum pkgnum svcpart )
+                      )
+      },
+      'error' => $result->{'error'},
+    };
+  } else {
+    #just go to setup services page, results will be visible there
+    $action = 'provision';
+    return provision();
+  }
+
+}
+
 sub delete_svc {
   unprovision_svc(
     'session_id' => $session_id,
 sub delete_svc {
   unprovision_svc(
     'session_id' => $session_id,
@@ -539,19 +996,56 @@ sub delete_svc {
 }
 
 sub view_usage {
 }
 
 sub view_usage {
-  list_svcs(
+  my $res = list_svcs(
     'session_id'  => $session_id,
     'session_id'  => $session_id,
-    'svcdb'       => 'svc_acct',
+    'svcdb'       => [ 'svc_acct', 'svc_phone', 'svc_port', 'svc_pbx' ],
     'ncancelled'  => 1,
   );
     'ncancelled'  => 1,
   );
+  if ($res->{hide_usage}) {
+    $action = 'myaccount';
+    return myaccount();
+  } else {
+    return $res;
+  }
+}
+
+sub real_port_graph {
+    my $svcnum = $cgi->param('svcnum');
+    my $res = port_graph(
+           'session_id'  => $session_id,
+           'svcnum'      => $svcnum,
+           'beginning'   => str2time($cgi->param('start')." 00:00:00"),
+           'ending'      => str2time($cgi->param('end')  ." 23:59:59"),
+           );
+    my @usage = @{$res->{'usage'}};
+    my $png = $usage[0]->{'png'};
+    { 'content' => $png, 'format' => 'png' };
+}
+
+sub view_port_graph {
+    my $svcnum = $cgi->param('svcnum');
+    { 'svcnum' => $svcnum,
+      'start' => $cgi->param($svcnum.'_start'),
+      'end' => $cgi->param($svcnum.'_end'),
+    }
 }
 
 sub view_usage_details {
 }
 
 sub view_usage_details {
-  list_svc_usage(
+      list_svc_usage(
+       'session_id'  => $session_id,
+       'svcnum'      => $cgi->param('svcnum'),
+       'beginning'   => $cgi->param('beginning') || '',
+       'ending'      => $cgi->param('ending') || '',
+      );
+}
+
+sub view_cdr_details {
+  list_cdr_usage(
     'session_id'  => $session_id,
     'svcnum'      => $cgi->param('svcnum'),
     'beginning'   => $cgi->param('beginning') || '',
     'ending'      => $cgi->param('ending') || '',
     'session_id'  => $session_id,
     'svcnum'      => $cgi->param('svcnum'),
     'beginning'   => $cgi->param('beginning') || '',
     'ending'      => $cgi->param('ending') || '',
+    'inbound'     => $cgi->param('inbound') || 0,
   );
 }
 
   );
 }
 
@@ -599,6 +1093,54 @@ sub process_change_password {
 
 }
 
 
 }
 
+sub forgot_password {
+  login_info( 'agentnum' => scalar($cgi->param('agentnum')) );
+}
+
+sub do_forgot_password {
+  reset_passwd(
+    map { $_ => scalar($cgi->param($_)) }
+      qw( agentnum email username domain )
+  );
+}
+
+sub process_forgot_password {
+  check_reset_passwd(
+    map { $_ => scalar($cgi->param($_)) }
+      qw( session_id )
+  );
+}
+
+sub process_forgot_password_session {
+  $action = 'process_forgot_password';
+  check_reset_passwd(
+    'session_id' => $pw_session_id,
+  );
+}
+
+sub do_process_forgot_password {
+  process_reset_passwd(
+    map { $_ => scalar($cgi->param($_)) }
+      qw( session_id new_password new_password2 )
+  );
+}
+
+sub validate_password {
+  validate_passwd(
+    'session_id' => $session_id,
+    map { $_ => scalar($cgi->param($_)) }
+      qw( fieldid svcnum check_password )
+  )
+}
+
+sub validate_password_nologin {
+  $action = 'validate_password'; #use same landing page
+  validate_passwd(
+    map { $_ => scalar($cgi->param($_)) }
+      qw( fieldid check_password )
+  )
+}
+
 #--
 
 sub do_template {
 #--
 
 sub do_template {
@@ -608,30 +1150,80 @@ sub do_template {
   $cgi->delete_all();
   $fill_in->{'selfurl'} = $cgi->self_url;
   $fill_in->{'cgi'} = \$cgi;
   $cgi->delete_all();
   $fill_in->{'selfurl'} = $cgi->self_url;
   $fill_in->{'cgi'} = \$cgi;
+  $fill_in->{'error'} = $cgi->param('error') if $cgi->param('error');
+
+  my $access_info = ($session_id and $session_id ne 'login')
+                      ? access_info( 'session_id' => $session_id )
+                      : {};
+  $fill_in->{$_} = $access_info->{$_} foreach keys %$access_info;
+
+  # update the user's authentication
+  my $timeout = $access_info->{'timeout'} || '3600';
+  my $cookie = CGI::Cookie->new('-name'     => 'session',
+                                '-value'    => $session_id,
+                                '-expires'  => '+'.$timeout.'s',
+                                #'-secure'   => 1, # would be a good idea...
+                               );
+  if ( $name eq 'logout' ) {
+    $cookie->expires(0);
+  }
 
 
-  my $template = new Text::Template( TYPE    => 'FILE',
-                                     SOURCE  => "$template_dir/$name.html",
-                                     DELIMITERS => [ '<%=', '%>' ],
-                                     UNTAINT => 1,                    )
-    or die $Text::Template::ERROR;
-
-  print $cgi->header( '-expires' => 'now' ),
-        $template->fill_in( PACKAGE => 'FS::SelfService::_selfservicecgi',
-                            HASH    => $fill_in
-                          );
+  if ( $fill_in->{'format'} ) {
+    # then override content-type, and return $fill_in->{'content'} instead
+    # of filling in a template
+    if ( $fill_in->{'format'} eq 'csv') {
+      print $cgi->header('-expires' => 'now',
+        '-Content-Type' => 'text/csv',
+        '-Content-Disposition' => "attachment;filename=output.csv",
+      );
+    } elsif ( $fill_in->{'format'} eq 'xls' ) {
+      print $cgi->header('-expires' => 'now',
+        '-Content-Type' => 'application/vnd.ms-excel',
+        '-Content-Disposition' => "attachment;filename=output.xls",
+        '-Content-Length' => length($fill_in->{'content'}),
+      );
+    } elsif ( $fill_in->{'format'} eq 'png' ) {
+      print $cgi->header('-expires' => 'now',
+        '-Content-Type' => 'image/png',
+      );
+    }
+    print $fill_in->{'content'};
+  } else { # the usual case
+    my $source = "$template_dir/$name.html";
+    my $template = new Text::Template(
+      TYPE       => 'FILE',
+      SOURCE     => $source,
+      DELIMITERS => [ '<%=', '%>' ],
+      UNTAINT    => 1,
+    )
+      or die $Text::Template::ERROR;
+
+    my $data = $template->fill_in( 
+      PACKAGE => 'FS::SelfService::_selfservicecgi',
+      HASH    => $fill_in,
+    ) || "Error processing template $source"; # at least print _something_
+    print $cgi->header( '-cookie' => $cookie,
+                        '-expires' => 'now' );
+    print $data;
+  }
 }
 
 #*FS::SelfService::_selfservicecgi::include = \&Text::Template::fill_in_file;
 
 package FS::SelfService::_selfservicecgi;
 
 }
 
 #*FS::SelfService::_selfservicecgi::include = \&Text::Template::fill_in_file;
 
 package FS::SelfService::_selfservicecgi;
 
-#use FS::SelfService qw(regionselector expselect popselector);
 use HTML::Entities;
 use HTML::Entities;
-use FS::SelfService qw(regionselector popselector domainselector);
+use FS::SelfService qw(
+    regionselector popselector domainselector location_form didselector mason_comp
+);
 
 #false laziness w/agent.cgi
 
 #false laziness w/agent.cgi
+use vars qw(@INCLUDE_ARGS);
 sub include {
   my $name = shift;
 sub include {
   my $name = shift;
+
+  @INCLUDE_ARGS = @_;
+
   my $template = new Text::Template( TYPE   => 'FILE',
                                      SOURCE => "$main::template_dir/$name.html",
                                      DELIMITERS => [ '<%=', '%>' ],
   my $template = new Text::Template( TYPE   => 'FILE',
                                      SOURCE => "$main::template_dir/$name.html",
                                      DELIMITERS => [ '<%=', '%>' ],
@@ -645,3 +1237,4 @@ sub include {
 
 }
 
 
 }
 
+