summaryrefslogtreecommitdiff
path: root/FS/FS/part_export/shellcommands_expect.pm
blob: c2a4118e212ffe6a1691360ceeee9f3d4c25ec84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package FS::part_export::shellcommands_expect;
use base qw( FS::part_export::shellcommands );

use strict;
use Tie::IxHash;
use Net::OpenSSH;
use Expect;
#use FS::Record qw( qsearch qsearchs );

tie my %options, 'Tie::IxHash',
  'user'      => { label =>'Remote username', default=>'root' },
  'useradd'   => { label => 'Insert commands',    type => 'textarea', },
  'userdel'   => { label => 'Delete commands',    type => 'textarea', },
  'usermod'   => { label => 'Modify commands',    type => 'textarea', },
  'suspend'   => { label => 'Suspend commands',   type => 'textarea', },
  'unsuspend' => { label => 'Unsuspend commands', type => 'textarea', },
  'debug'     => { label => 'Enable debugging',
                   type  => 'checkbox',
                   value => 1,
                 },
;

our %info = (
  'svc'     => 'svc_acct',
  'desc'    => 'Real time export via remote SSH, with interactive ("Expect"-like) scripting, for svc_acct services',
  'options' => \%options,
  'notes'   => q[
Interactively run commands via SSH in a remote terminal, like "Expect".  In
most cases, you probably want a regular shellcommands (or broadband_shellcommands, etc.) export instead, unless
you have a specific need to interact with a terminal-based interface in an
"Expect"-like fashion.
<BR><BR>

Each line specifies a string to match and a command to
run after that string is found, separated by the first space.  For example, to
run "exit" after a prompt ending in "#" is sent, "# exit".  You will need to
<a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">setup SSH for unattended operation</a>.
<BR><BR>

In commands, all variable substitutions of the regular shellcommands (or
broadband_shellcommands, etc.) export are available (use a backslash to escape
a literal $).
]
);

sub _export_command {
  my ( $self, $action, $svc_acct) = (shift, shift, shift);
  my @lines = split("\n", $self->option($action) );

  return '' unless @lines;

  my @commands = ();
  foreach my $line (@lines) {
    my($match, $command) = split(' ', $line, 2);
    my( $command_string ) = $self->_export_subvars( $svc_acct, $command, '' );
    push @commands, [ $match, $command_string ];
  }

  $self->shellcommands_expect_queue( $svc_acct->svcnum, @commands );
}

sub _export_replace {
  my( $self, $new, $old ) = (shift, shift, shift);
  my @lines = split("\n", $self->option('replace') );

  return '' unless @lines;

  my @commands = ();
  foreach my $line (@lines) {
    my($match, $command) = split(' ', $line, 2);
    my( $command_string ) = $self->_export_subvars_replace( $new, $old, $command, '' );
    push @commands, [ $match, $command_string ];
  }

  $self->shellcommands_expect_queue( $new->svcnum, @commands );
}

sub shellcommands_expect_queue {
  my( $self, $svcnum, @commands ) = @_;

  my $queue = new FS::queue {
    'svcnum' => $svcnum,
    'job'    => "FS::part_export::shellcommands_expect::ssh_expect",
  };
  $queue->insert(
    user          => $self->option('user') || 'root',
    host          => $self->machine,
    debug         => $self->option('debug'),
    commands      => \@commands,
  );
}

sub ssh_expect { #subroutine, not method
  my $opt = { @_ };

  my $dest = $opt->{'user'}.'@'.$opt->{'host'};

  open my $def_in, '<', '/dev/null' or die "unable to open /dev/null\n";
  my $ssh = Net::OpenSSH->new( $dest, 'default_stdin_fh' => $def_in );
  # ignore_all_errors doesn't override SSH connection/auth errors--
  # probably correct
  die "Couldn't establish SSH connection to $dest: ". $ssh->error
    if $ssh->error;

  my ($pty, $pid) = $ssh->open2pty
    or die "Couldn't start a remote terminal session";
  my $expect = Expect->init($pty);
  #not useful #$expect->debug($opt->{debug} ? 3 : 0);

  foreach my $line ( @{ $opt->{commands} } ) {
    my( $match, $command ) = @$line;

    warn "Waiting for '$match'\n" if $opt->{debug};

    my $matched = $expect->expect(30, $match);
    unless ( $matched ) {
      my $err = "Never saw '$match'\n";
      warn $err;
      die $err;
    }
    warn "Running '$command'\n" if $opt->{debug};
    $expect->send("$command\n");
  }

  '';
}

1;