1: #!/usr/bin/perl
2: # The LearningOnline Network with CAPA
3: #
4: # lonManage supports remote management of nodes in a LonCAPA cluster.
5: #
6: # $Id: lonManage,v 1.15 2003/09/16 09:49:54 foxr Exp $
7: #
8: # $Id: lonManage,v 1.15 2003/09/16 09:49:54 foxr Exp $
9: #
10: # Copyright Michigan State University Board of Trustees
11: #
12: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
13: ## LON-CAPA is free software; you can redistribute it and/or modify
14: # it under the terms of the GNU General Public License as published by
15: # the Free Software Foundation; either version 2 of the License, or
16: # (at your option) any later version.
17: #
18: # LON-CAPA is distributed in the hope that it will be useful,
19: # but WITHOUT ANY WARRANTY; without even the implied warranty of
20: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21: # GNU General Public License for more details.
22: #
23: # You should have received a copy of the GNU General Public License
24: # along with LON-CAPA; if not, write to the Free Software
25: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26: #
27: # /home/httpd/html/adm/gpl.txt
28: #
29: # http://www.lon-capa.org/
30: #
31: #
32: # lonManage supports management of remot nodes in a lonCAPA cluster.
33: # it is a command line tool. The following command line syntax (usage)
34: # is supported:
35: #
36: # lonManage -push <tablename> newfile host
37: # Push <tablename> to the lonTabs directory. Note that
38: # <tablename> must be one of:
39: # host (hosts.tab)
40: # domain (domain.tab)
41: #
42: # lonManage -reinit lonc host
43: # Sends a HUP signal to the remote systems's lond.
44: #
45: # lonmanage -reinit lond host
46: # Requests the remote system's lond perform the same action as if
47: # it had received a HUP signal.
48: #
49: # In the above syntax, the host above is the hosts.tab name of a host,
50: # not the IP address of the host.
51: #
52: # $Log: lonManage,v $
53: # Revision 1.15 2003/09/16 09:49:54 foxr
54: # Adjust the usage message to reflect what actually will happen on
55: # --reinit={lond|lonc}
56: #
57: # Revision 1.14 2003/09/08 09:45:20 foxr
58: # Remove BUGBUG about comment about authentication as we'll be doing
59: # host based authentication initially (no need for lonManage to do anything),
60: # and certificate based later (need at that time).
61: #
62: # Revision 1.13 2003/08/19 10:26:24 foxr
63: # Initial working version... tested against an unmodified lond this
64: # produces an unknown_cmd response which is about what I'd expect.
65: #
66: # Revision 1.12 2003/08/18 11:08:07 foxr
67: # Debug request building in Transact.
68: #
69: # Revision 1.11 2003/08/18 10:45:32 foxr
70: # Felt strongly enough about hoisting ReadConfiguration into a separate sub
71: # that I did it now before I forgot.
72: #
73: # Revision 1.10 2003/08/18 10:43:31 foxr
74: # Code/test ValidHost. The hosts.tab and the perl variables are read in as
75: # global hashes as a side effect. May later want to clean this up by making
76: # a separate getconfig function and hoisting the config reads into that.
77: #
78: # Revision 1.9 2003/08/18 10:25:46 foxr
79: # Write ReinitProcess function in terms of ValidHost and Transact.
80: #
81: # Revision 1.8 2003/08/18 10:18:21 foxr
82: # Completed PushFile function in terms of
83: # - ValidHost - Determines if target host is valid.
84: # - Transact - Performs one of the valid transactions with the
85: # appropriate lonc<-->lond client/server pairs.
86: #
87: # Revision 1.7 2003/08/18 09:56:01 foxr
88: # 1. Require to be run as root.
89: # 2. Catch case where no operation switch is supplied and put out usage.
90: # 3. skeleton/comments for PushFile function.
91: #
92: # Revision 1.6 2003/08/12 11:02:59 foxr
93: # Implement command switch dispatching.
94: #
95: # Revision 1.5 2003/08/12 10:55:42 foxr
96: # Complete command line parsing (tested)
97: #
98: # Revision 1.4 2003/08/12 10:40:44 foxr
99: # Get switch parsing right.
100: #
101: # Revision 1.3 2003/08/12 10:22:35 foxr
102: # Put in parameter parsing infrastructure
103: #
104: # Revision 1.2 2003/08/12 09:58:49 foxr
105: # Add usage and skeleton documentation.
106: #
107: #
108:
109:
110:
111: # Modules required:
112:
113: use strict; # Because it's good practice.
114: use English; # Cause I like meaningful names.
115: use Getopt::Long;
116: use LONCAPA::Configuration; # To handle configuration I/O.
117: use IO::Socket::UNIX; # To communicate with lonc.
118:
119: # File scoped variables:
120:
121: my %perlvar; # Perl variable defs from apache config.
122: my %hostshash; # Host table as a host indexed hash.
123:
124: #
125: # prints out utility's command usage info.
126: #
127: sub Usage {
128: print "Usage:";
129: print <<USAGE;
130: lonManage --push=<tablename> newfile host
131: Push <tablename> to the lonTabs directory. Note that
132: <tablename> must be one of:
133: host (hosts.tab)
134: domain (domain.tab)
135:
136: lonManage --reinit=lonc host
137: Causes lonc in the remote system to reread hosts.tab and
138: adjust the set of clients that are being maintained to match
139: the new file.
140:
141:
142: lonManage --reinit=lond host
143: Causes lond in the remote system to reread the hosts.tab file
144: and adjust the set of servers to match changes in that file.
145:
146: In the above syntax, the host above is the hosts.tab name of a host,
147: not the IP address of the host.
148: USAGE
149:
150:
151: }
152: #
153: # Lifted from lonnet.pm - and we need to figure out a way to get it back in.
154: # Performas a transaction with lond via the lonc proxy server.
155: # Parameter:
156: # cmd - The text of the request.
157: # host - The host to which the request ultimately goes.
158: # Returns:
159: # The text of the reply from the lond or con_lost if not able to contact
160: # lond/lonc etc.
161: #
162: sub subreply {
163: my ($cmd,$server)=@_;
164: my $peerfile="$perlvar{'lonSockDir'}/$server";
165: my $client=IO::Socket::UNIX->new(Peer =>"$peerfile",
166: Type => SOCK_STREAM,
167: Timeout => 10)
168: or return "con_lost";
169: print $client "$cmd\n";
170: my $answer=<$client>;
171: if (!$answer) { $answer="con_lost"; }
172: chomp($answer);
173: return $answer;
174: }
175: # >>> BUGBUG <<<
176: #
177: # Use Getopt::Long to parse the parameters of the program.
178: #
179: # Return value is a list consisting of:
180: # A 'command' which is one of:
181: # push - table push requested.
182: # reinit - reinit requested.
183: # Additional parameters as follows:
184: # for push: Tablename, hostname
185: # for reinit: Appname hostname
186: #
187: # This function does not validation of the parameters of push and
188: # reinit.
189: #
190: # returns a list. The first element of the list is the operation name
191: # (e.g. reinit or push). The second element is the switch parameter.
192: # for push, this is the table name, for reinit, this is the process name.
193: # Additional elements of the list are the command argument. The count of
194: # command arguments is validated, but not their semantics.
195: #
196: # returns an empty list if the parse fails.
197: #
198:
199: sub ParseArgs {
200: my $pushing = '';
201: my $reinitting = '';
202:
203: if(!GetOptions('push=s' => \$pushing,
204: 'reinit=s' => \$reinitting)) {
205: return ();
206: }
207:
208: # Require exactly one of --push and --reinit
209:
210: my $command = '';
211: my $commandarg = '';
212: my $paramcount = @ARGV; # Number of additional arguments.
213:
214:
215: if($pushing ne '') {
216:
217: # --push takes in addition a table, and a host:
218: #
219: if($paramcount != 2) {
220: return (); # Invalid parameter count.
221: }
222: if($command ne '') {
223: return ();
224: } else {
225:
226: $command = 'push';
227: $commandarg = $pushing;
228: }
229: }
230:
231: if ($reinitting ne '') {
232:
233: # --reinit takes in addition just a host name
234:
235: if($paramcount != 1) {
236: return ();
237: }
238: if($command ne '') {
239: return ();
240: } else {
241: $command = 'reinit';
242: $commandarg = $reinitting;
243: }
244: }
245:
246: # Build the result list:
247:
248: my @result = ($command, $commandarg);
249: my $i;
250: for($i = 0; $i < $paramcount; $i++) {
251: push(@result, $ARGV[$i]);
252: }
253:
254: return @result;
255: }
256: #
257: # Read the loncapa configuration stuff.
258: #
259: sub ReadConfig {
260: my $perlvarref = LONCAPA::Configuration::read_conf('loncapa.conf');
261: %perlvar = %{$perlvarref};
262: my $hoststab = LONCAPA::Configuration::read_hosts(
263: "$perlvar{'lonTabDir'}/hosts.tab");
264: %hostshash = %{$hoststab};
265:
266: }
267: #
268: # Determine if the target host is valid.
269: # This is done by reading the current hosts.tab file.
270: # For the host to be valid, it must be inthe file.
271: #
272: # Parameters:
273: # host - Name of host to check on.
274: # Returns:
275: # true if host is valid.
276: # false if host is invalid.
277: #
278: sub ValidHost {
279: my $host = shift;
280:
281: ReadConfig;
282:
283: return defined $hostshash{$host};
284:
285: }
286:
287:
288:
289: #
290: # Performs a transaction with lonc.
291: # By the time this is called, the transaction has already been
292: # validated by the caller.
293: #
294: # Parameters:
295: #
296: # host - hosts.tab name of the host whose lonc we'll be talking to.
297: # command - The base command we'll be asking lond to execute.
298: # body - [optional] If supplied, this is a command body that is a ref.
299: # to an array of lines that will be appended to the
300: # command.
301: #
302: # NOTE:
303: # The command will be done as an encrypted operation.
304: #
305: sub Transact {
306: my $host = shift;
307: my $command = shift;
308: my $haveBody= 0;
309: my $body;
310: my $i;
311:
312: if(scalar @ARG) {
313: $body = shift;
314: $haveBody = 1;
315: }
316: # Construct the command to send to the server:
317:
318: my $request = "encrypt\:"; # All requests are encrypted.
319: $request .= $command;
320: if($haveBody) {
321: $request .= "\:";
322: my $bodylines = scalar @$body;
323: for($i = 0; $i < $bodylines; $i++) {
324: $request .= $$body[$i];
325: }
326: } else {
327: $request .= "\n";
328: }
329: # Body is now built... transact with lond..
330:
331: my $answer = subreply($request, $host);
332:
333: print "$answer\n";
334:
335: }
336: #
337: # Called to push a file to the remote system.
338: # The only legal files to push are hosts.tab and domain.tab.
339: # Security is somewhat improved by
340: #
341: # - Requiring the user run as root.
342: # - Connecting with lonc rather than lond directly ensuring this is a loncapa
343: # host
344: # - We must appear in the remote host's hosts.tab file.
345: # - The host must appear in our hosts.tab file.
346: #
347: # Parameters:
348: # tablename - must be one of hosts or domain.
349: # tablefile - name of the file containing the table to push.
350: # host - name of the host to push this file to.
351: #
352: # >>>BUGBUG<<< This belongs in lonnet.pm.
353: #
354: sub PushFile {
355: my $tablename = shift;
356: my $tablefile = shift;
357: my $host = shift;
358:
359: # Open the table file:
360:
361: if(!open(TABLEFILE, "<$tablefile")) {
362: die "ENOENT - No such file or directory $tablefile";
363: }
364:
365: # Require that the host be valid:
366:
367: if(!ValidHost($host)) {
368: die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'.
369: }
370: # Read in the file. If the table name is valid, push it.
371:
372: my @table = <TABLEFILE>; # These files are pretty small.
373: close TABLEFILE;
374:
375: if( ($tablename eq "host") ||
376: ($tablename eq "domain")) {
377: Transact($host, "pushfile:$tablename",\@table);
378: } else {
379: die "EINVAL - Invalid parameter. tablename: $tablename must be host or domain";
380: }
381: }
382: #
383: # This function is called to reinitialize a server in a remote host.
384: # The servers that can be reinitialized are:
385: # - lonc - The lonc client process.
386: # - lond - The lond daemon.
387: # NOTE:
388: # Reinitialization in this case means re-scanning the hosts table,
389: # starting new lond/lonc's as approprate and stopping existing lonc/lond's.
390: #
391: # Parameters:
392: # process - The name of the process to reinit (lonc or lond).
393: # host - The host in which this reinit will happen.
394: #
395: # >>>BUGBUG<<<< This belongs in lonnet.pm
396: #
397: sub ReinitProcess {
398: my $process = shift;
399: my $host = shift;
400:
401: # Ensure the host is valid:
402:
403: if(!ValidHost($host)) {
404: die "EHOSTINVAL - Invalid host $host";
405: }
406: # Ensure target process selector is valid:
407:
408: if(($process eq "lonc") ||
409: ($process eq "lond")) {
410: Transact($host, "reinit:$process");
411: } else {
412: die "EINVAL -Invalid parameter. Process $process must be lonc or lond";
413: }
414: }
415: #--------------------------- Entry point: --------------------------
416:
417: # Parse the parameters
418: # If command parsing failed, then print usage:
419:
420: my @params = ParseArgs;
421: my $nparam = @params;
422:
423: if($nparam == 0) {
424: Usage;
425: exit -1;
426: }
427: #
428: # Next, ensure we are running as EID root.
429: #
430: if ($EUID != 0) {
431: die "ENOPRIV - No privilege for requested operation"
432: }
433:
434:
435: # Based on the operation requested invoke the appropriate function:
436:
437: my $operation = shift @params;
438:
439: if($operation eq "push") { # push tablename filename host
440: my $tablename = shift @params;
441: my $tablefile = shift @params;
442: my $host = shift @params;
443: PushFile($tablename, $tablefile, $host);
444:
445: } elsif($operation eq "reinit") { # reinit processname host.
446: my $process = shift @params;
447: my $host = shift @params;
448: ReinitProcess($process, $host);
449: }
450: else {
451: Usage;
452: }
453: exit 0;
454:
455: =head1 NAME
456: lonManage - Command line utility for remote management of lonCAPA
457: cluster nodes.
458:
459: =head1 SYNOPSIS
460:
461: Usage:
462: B<lonManage --push=<tablename> newfile host>
463: Push <tablename> to the lonTabs directory. Note that
464: <tablename> must be one of:
465: hosts (hosts.tab)
466: domain (domain.tab)
467:
468: B<lonManage --reinit=lonc host>
469: Sends a HUP signal to the remote systems's lond.
470:
471: B<lonmanage --reinit=lond host>
472: Requests the remote system's lond perform the same action as if
473: it had received a HUP signal.
474:
475: In the above syntax, the host above is the hosts.tab name of a host,
476: not the IP address of the host.
477:
478:
479: =head1 DESCRIPTION
480:
481: =head1 PREREQUISITES
482:
483: =item strict
484: =item Getopt::Long
485: =item English
486: =item IO::Socket::UNIX
487:
488: =head1 KEY Subroutines.
489:
490: =head1 CATEGORIES
491: Command line utility
492:
493: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>