Subversion Repositories sysadmin_scripts

Rev

Rev 61 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
23 rodolico 1
#! /usr/bin/perl -w
2
 
3
#    vpn - Manages OpenVPN sessions
4
#    Copyright (C) 2016  R. W. Rodolico
5
#
6
#    This program is free software: you can redistribute it and/or modify
7
#    it under the terms of the GNU General Public License as published by
8
#    the Free Software Foundation, either version 2 of the License, or
9
#    (at your option) any later version.
10
#
11
#    This program is distributed in the hope that it will be useful,
12
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#    GNU General Public License for more details.
15
#
16
#    You should have received a copy of the GNU General Public License
17
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
#
19
#    HISTORY:
20
#    v0.1 - 20160311 RWR
21
#       Initial Release
22
#    v0.2 - 20160312 RWR
23
#       Added --chdir parameter to allow relative processing of files
24
#             from the .ovpn config file
25
#       Added --version parameter to display version information
26
#       Created copyright using GNUv2
27
 
28
 
29
$main::VERSION = '0.2';
30
 
31
 
32
use Getopt::Long qw(:config auto_version bundling );
33
use Pod::Usage qw(pod2usage);
34
 
35
my $configDirs = '/etc/openvpn';
36
my $logDir = '/var/log/openvpn';
37
my $pidDir = '/var/run/openvpn';
38
my $statusDir = '/var/run/openvpn';
39
my $timeOut = 60 * 60; # number of seconds of inactivity to close session
40
 
41
# These variables are for getOpt, and control the operation of the script
42
# I left them all global
43
my $kill = '';
44
my $show = '';
45
my $destination = '';
46
my $quiet = '';
47
my $verbose = '';
48
my $help = 0;
49
my $man = 0;
50
my $chdir = 0;
51
 
52
 
53
# check if Directories exist and, if root, creates them if needed 
54
sub validateDirectories {
55
   my @errors;
56
   foreach my $dir ( $logDir, $pidDir, $statusDir ) {
57
      if ( ! -d $dir ) {
58
         if ( $< ) {
59
            push @errors,$dir;
60
         } else {
61
            `sudo mkdir -p $dir`;
62
         }
63
      }
64
   }
65
   if ( @errors ) {
66
      die "The following directories do not exist, rerun as root user\n\t" .
67
          join( "\n\t", @errors ) . "\n";
68
   } 
69
}
70
 
71
# get a pid from a session name, and verify it with a ps
72
# returns the PID, or an empty string if not found
73
# SIDE EFFECT: Will remove the .pid file if the process is not
74
# actually running
75
sub getPid {
76
   my $sessionName = shift;
77
   my $pidFile = "$pidDir/$sessionName.pid";
78
   my $pid = '';
79
   if ( -f $pidFile && open PID,"<$pidFile" ) {
80
      $pid = <PID>;
81
      close PID;
82
      chomp $pid;
83
      print "Found pid file " if $verbose;
84
      print "\tChecking if pid $pid exists\n" if $verbose;
85
      if ( `ps --pid $pid --no-headers -o pid` ) {
86
         print "\tFound PID $pid\n" if $verbose;
87
         return $pid;
88
      } else {
89
         if ( $< ) {
90
            print STDERR "Invalid PID file $pidDir/$sessionName.pid but can not remove as non-root user\n";
91
         } else {
92
            print STDERR "Invalid PID file $pidDir/$sessionName.pid removed\n" unless $quiet;
93
            `rm "$pidDir/$sessionName.pid"`;
94
         }
95
         return '';
96
      }
97
   }
98
   return $pid;
99
}
100
 
101
# get all available sessions and their status
102
# returns them in a hash
103
sub getSessions {
104
   my %sessions;
105
   my @possibleSessions = `ls $configDirs`;
106
   my @active;
107
   chomp @possibleSessions;
108
   @possibleSessions = grep{ -d "$configDirs/$_" } @possibleSessions;
109
   foreach my $thisSession ( @possibleSessions ) {
110
      if ( $pid = &getPid( $thisSession ) ) {
111
         $sessions{$thisSession}{'pidFile'} = "$pidDir/$thisSession.pid";
112
         $sessions{$thisSession}{'logFile'} = "$logDir/$thisSession.log";
113
         $sessions{$thisSession}{'statusFile'} = "$statusDir/$thisSession.status";
114
         $sessions{$thisSession}{'pid'} = $pid;
115
      } else {
116
         $sessions{$thisSession}{'pid'} = 0;
117
      }
118
   }
119
   return \%sessions;
120
}
121
 
122
 
123
# displays all available sessions and their status
124
sub printSessions {
125
   my $sessions = &getSessions();
126
   print '-'x40 . "\nActive\tSession\t\tPID\n";
127
   foreach my $session ( sort keys %$sessions ) {
128
      print $$sessions{$session}{'pid'} ? "*" : " ";
129
      print "\t$session" . ' 'x (15 - length( $session ));
130
      if ( $$sessions{$session}{'pid'} ) {
131
         print "\t" . $$sessions{$session}{'pid'};
132
      }
133
      print "\n";
134
   }
135
   print '-'x40 . "\n";
136
   print "Status files located in $statusDir\n" if $verbose;
137
   print "Log Files located in $logDir\n" if $verbose;
138
   print "PID files located in $pidDir\n" if $verbose;
139
}
140
 
141
# start a connection. Can only be done as root user.
142
sub startConnection {
143
   my $destination = shift;
144
   my $configFile = "$configDirs/$destination/$destination.ovpn";
145
   chdir( "$configDirs/$destination" ) if $chdir;
146
   if ( -f $configFile && ! &getPid($destination) ) {
147
      my $command = 'openvpn' .
148
                    " --daemon $destination" .
149
                    " --inactive $timeOut" .
150
                    " --writepid $pidDir/$destination.pid" .
151
                    " --log $logDir/$destination.log" .
152
                    " --status $statusDir/$destination.status" .
153
                    " --config $configFile";
154
      print "$command\n" if $verbose;
155
      system ( $command );
156
      if ( &getPid ( $destination ) ) {
157
         return "$destination is active";
158
      } else {
159
         return "There was a failure in the command, check $logDir/$destination.log\nCommand was\n$command";
160
      }
161
   } else {
162
      return "Could not open '$configFile'";
163
   }
164
   return "The connection is already active";
165
}
166
 
167
# kill all active connections
168
sub killALL {
169
   my $sessions = &getSessions();
170
   foreach my $session ( keys %$sessions ) {
171
      if ( $$sessions{$session}{'pid'} ) {
172
         $status = &killConnection( $session );
173
         print "$status\n" unless $quiet;
174
      } # if
175
   } # foreach
176
} # killAll
177
 
178
 
179
# kills a connection
180
sub killConnection {
181
   my $connection = shift;
182
   my $pid = &getPid( $connection );
183
   if ( $pid ) {
184
      `kill $pid`;
185
      `rm "$pidDir/$connection.pid"`;
186
      return "Session $connection killed and pidfile removed\n";
187
   } else {
188
      return "$connection not running\n";
189
   }
190
}
191
 
192
#### some housekeeping
193
&validateDirectories(); # check if directories exist and, if root, create them if needed
194
 
195
# process options
196
GetOptions( 
197
   'kill|k=s' => \$kill, 
198
   'display|d' => \$show, 
199
   'start|s=s' => \$destination, 
200
   'timeout|t=i' => \$timeOut,
201
   'quiet|q' => \$quiet,
202
   'chdir|c' => \$chdir,
203
   'verbose|v' => \$verbose,
204
   'help|?' => \$help,
205
   'man' => \$man
206
);
207
 
208
pod2usage(1) if $help;
209
pod2usage( -exitval => 0, -verbose => 2 ) if $man;
210
 
211
# process rest of command line if it is there (name of connection)
212
$destination = shift if @ARGV > 0;
213
$show = 1 unless $destination || $kill; # if no destination given, default to show
214
 
215
#### main program
216
 
217
if ( $kill ) {
218
   die "Kill requires you to be root, use sudo\n" if $<;
219
   $status = ( $kill eq 'ALL' ) ? &killALL() : &killConnection( $kill );
220
   print "$status\n" unless $quiet;
221
} elsif ( $destination ) {
222
   die "Start requires you to be root, use sudo\n" if $<;
223
   my $status =  &startConnection( $destination ) unless &getPid( $destination );
224
   print "$status\n" unless $quiet;
225
}
226
 
227
&printSessions() if $show;
228
 
229
1;
230
 
231
__END__
232
 
233
=head1 NAME
234
 
235
vpn
236
 
237
=head1 SYNOPSIS
238
 
239
  vpn             Show status of all available sessions
240
  vpn session     Start a session (must be root)
241
  vpn [options]
242
 
243
Controls a set of OpenVPN connections, starting, stopping, and auto-timeouts.
244
 
245
=head1 OPTIONS
246
 
247
=over 3
248
 
249
=item B<--kill|-k> I<session>
250
 
251
Kill the named session if running. The keyword ALL (case sensitive) will kill all running sessions
252
 
253
=item B<--display|-d>
254
 
255
Display all available sessions and their current status
256
 
257
=item B<--destination> I<session>
258
 
259
Work with a particular destination
260
 
261
=item B<--start|-ss> I<session>
262
 
263
Start a session. Will check if session already running and not attempt a second connection.
264
 
265
=item B<--timeout|-t> I<seconds>
266
 
267
Set idle timeout, in seconds
268
 
269
=item B<--version>
270
 
271
Display version information
272
 
273
=item B<--chdir|-c>
274
 
275
Causes a chdir to be run before the actual openvpn command is executed. Useful if your pkcs12 file entry does not have a fully qualified path.
276
 
277
=item B<--verbose|-v>
278
 
279
Shows some extra information while processing.
280
 
281
=item B<--help|-?>
282
 
283
This screen
284
 
285
=item B<--man>
286
 
287
Prints the full man page
288
 
289
=back
290
 
291
=head1 DESCRIPTION
292
 
293
Each possible session is assumed to be stored in subdirectories of
294
$configDirs, with a configuration file of the same name as the subdirectory
295
and a .ovpn suffix. Any paths in the configuration file must be fully qualified
296
(ie pkcs12, etc...).
297
 
298
For example
299
   $configDirs
300
      +--- vpn1
301
      |  +--- vpn1.ovpn
302
      |  |
303
      |  +--- other files (such as pkcs12)
304
      |
305
      +--- vpn2
306
         +--- vpn2.ovpn
307
         |
308
         +--- other files (such as pkcs12)
309
 
310
Based on this, the command vpn vpn2 would look in $configDirs for vpn2.ovpn, then run openvpn using that configuration file.
311
 
312
B<NOTE>: if the configuration file does not have the fully qualified path to any files used (such as pkcs12 files), it will not be able to use them. You can modify this with the -chdir option, which will move into the directory before calling openvpn
313
 
314
The script will then create several files
315
 
316
=over 3
317
 
318
=item B<Log File> .log
319
 
320
Created in $logDir (default /var/log/openvpn). In this case, it would be /var/log/openvpn/vpn1.log
321
 
322
=item B<Status File> .status
323
 
324
Created in $statusDir (default /var/run/openvpn). In this case, would be /var/run/openvpn/vpn1.status
325
 
326
=item B<PID File> .pid
327
 
328
Created in $pidDir (default /var/run/openvpn). In this case would be /var/run/openvpn/vpn1.pid
329
 
330
=back
331
 
332
The Log and Status files are recreated each time a session is started (ie, stopping and starting vpn1 would overwrite the old files). The Pid file is automatically removed when a session is killed.
333
 
334
 
335
=head1 CAVEATS
336
 
337
Sometimes, the pid files can become out of sync with reality, especially with a reboot. When --show or --kill ALL are called, a cleanup on these files is done. You can, as a part of reboot, safely call vpn with the --kill ALL function.
338
 
339
Most of the options require elevated privileges as openvpn creates virtual devices on the system. While vpn --show can display the status of all possible sessions, it will complain if there is an old session file (.pid) it can not remove. It will still show the sessions though. Either manually remove the file, or run again with elevated privileges.
340
 
341
The script checks for the existance of the required directories (log, pid and status) and will attempt to create them if they don't exist. If you are not running with elevated privileges, it will complain, then exit.
342
 
343