Annotation of loncom/lond, revision 1.165.2.1
1.1 albertel 1: #!/usr/bin/perl
2: # The LearningOnline Network
3: # lond "LON Daemon" Server (port "LOND" 5663)
1.60 www 4: #
1.165.2.1! albertel 5: # $Id: lond,v 1.165 2003/12/12 21:37:42 albertel Exp $
1.60 www 6: #
7: # Copyright Michigan State University Board of Trustees
8: #
9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
10: #
11: # LON-CAPA is free software; you can redistribute it and/or modify
12: # it under the terms of the GNU General Public License as published by
13: # the Free Software Foundation; either version 2 of the License, or
14: # (at your option) any later version.
15: #
16: # LON-CAPA is distributed in the hope that it will be useful,
17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19: # GNU General Public License for more details.
20: #
21: # You should have received a copy of the GNU General Public License
22: # along with LON-CAPA; if not, write to the Free Software
23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24: #
25: # /home/httpd/html/adm/gpl.txt
26: #
1.161 foxr 27:
28:
1.60 www 29: # http://www.lon-capa.org/
30: #
1.54 harris41 31:
1.134 albertel 32: use strict;
1.80 harris41 33: use lib '/home/httpd/lib/perl/';
34: use LONCAPA::Configuration;
35:
1.1 albertel 36: use IO::Socket;
37: use IO::File;
1.126 albertel 38: #use Apache::File;
1.1 albertel 39: use Symbol;
40: use POSIX;
41: use Crypt::IDEA;
42: use LWP::UserAgent();
1.3 www 43: use GDBM_File;
44: use Authen::Krb4;
1.91 albertel 45: use Authen::Krb5;
1.49 albertel 46: use lib '/home/httpd/lib/perl/';
47: use localauth;
1.143 foxr 48: use File::Copy;
1.1 albertel 49:
1.77 foxr 50: my $DEBUG = 0; # Non zero to enable debug log entries.
51:
1.57 www 52: my $status='';
53: my $lastlog='';
54:
1.165.2.1! albertel 55: my $VERSION='$Revision: 1.165 $'; #' stupid emacs
1.121 albertel 56: my $remoteVERSION;
1.115 albertel 57: my $currenthostid;
58: my $currentdomainid;
1.134 albertel 59:
60: my $client;
1.140 foxr 61: my $clientip;
1.161 foxr 62: my $clientname;
1.140 foxr 63:
1.134 albertel 64: my $server;
65: my $thisserver;
66:
1.161 foxr 67: #
68: # Connection type is:
69: # client - All client actions are allowed
70: # manager - only management functions allowed.
71: # both - Both management and client actions are allowed
72: #
73:
74: my $ConnectionType;
75:
1.134 albertel 76: my %hostid;
77: my %hostdom;
78: my %hostip;
1.161 foxr 79:
80: my %managers; # Ip -> manager names
81:
1.141 foxr 82: my %perlvar; # Will have the apache conf defined perl vars.
1.134 albertel 83:
1.96 foxr 84: #
85: # The array below are password error strings."
86: #
1.97 foxr 87: my $lastpwderror = 13; # Largest error number from lcpasswd.
1.96 foxr 88: my @passwderrors = ("ok",
89: "lcpasswd must be run as user 'www'",
90: "lcpasswd got incorrect number of arguments",
91: "lcpasswd did not get the right nubmer of input text lines",
92: "lcpasswd too many simultaneous pwd changes in progress",
93: "lcpasswd User does not exist.",
94: "lcpasswd Incorrect current passwd",
95: "lcpasswd Unable to su to root.",
96: "lcpasswd Cannot set new passwd.",
97: "lcpasswd Username has invalid characters",
1.97 foxr 98: "lcpasswd Invalid characters in password",
99: "11", "12",
100: "lcpasswd Password mismatch");
101:
102:
103: # The array below are lcuseradd error strings.:
104:
105: my $lastadderror = 13;
106: my @adderrors = ("ok",
107: "User ID mismatch, lcuseradd must run as user www",
108: "lcuseradd Incorrect number of command line parameters must be 3",
109: "lcuseradd Incorrect number of stdinput lines, must be 3",
110: "lcuseradd Too many other simultaneous pwd changes in progress",
111: "lcuseradd User does not exist",
1.153 www 112: "lcuseradd Unable to make www member of users's group",
1.97 foxr 113: "lcuseradd Unable to su to root",
114: "lcuseradd Unable to set password",
1.153 www 115: "lcuseradd Usrname has invalid characters",
1.97 foxr 116: "lcuseradd Password has an invalid character",
117: "lcuseradd User already exists",
118: "lcuseradd Could not add user.",
119: "lcuseradd Password mismatch");
120:
1.96 foxr 121:
122: #
1.140 foxr 123: # GetCertificate: Given a transaction that requires a certificate,
124: # this function will extract the certificate from the transaction
125: # request. Note that at this point, the only concept of a certificate
126: # is the hostname to which we are connected.
127: #
128: # Parameter:
129: # request - The request sent by our client (this parameterization may
130: # need to change when we really use a certificate granting
131: # authority.
132: #
133: sub GetCertificate {
134: my $request = shift;
135:
136: return $clientip;
137: }
1.161 foxr 138:
139: #
140: # Return true if client is a manager.
141: #
142: sub isManager {
143: return (($ConnectionType eq "manager") || ($ConnectionType eq "both"));
144: }
145: #
146: # Return tru if client can do client functions
147: #
148: sub isClient {
149: return (($ConnectionType eq "client") || ($ConnectionType eq "both"));
150: }
151:
152:
1.156 foxr 153: #
154: # ReadManagerTable: Reads in the current manager table. For now this is
155: # done on each manager authentication because:
156: # - These authentications are not frequent
157: # - This allows dynamic changes to the manager table
158: # without the need to signal to the lond.
159: #
160:
161: sub ReadManagerTable {
162:
163: # Clean out the old table first..
164:
165: foreach my $key (keys %managers) {
166: delete $managers{$key};
167: }
168:
169: my $tablename = $perlvar{'lonTabDir'}."/managers.tab";
170: if (!open (MANAGERS, $tablename)) {
171: logthis('<font color="red">No manager table. Nobody can manage!!</font>');
172: return;
173: }
174: while(my $host = <MANAGERS>) {
175: chomp($host);
1.161 foxr 176: if (!defined $hostip{$host}) { # This is a non cluster member
177:
178: # The entry is of the form:
179: # cluname:hostname
180: # cluname - A 'cluster hostname' is needed in order to negotiate
181: # the host key.
182: # hostname- The dns name of the host.
183: #
184:
185: my($cluname, $dnsname) = split(/:/, $host);
186: open(MGRPIPE, "/usr/bin/host $dnsname |") || die "Can't make host pipeline";
187: my $dnsinfo = <MGRPIPE>;
188: chomp $dnsinfo;
189: close MGRPIPE;
190: my($jname, $jhas, $jaddress, $hostip) = split(/ /, $dnsinfo);
191: $managers{$hostip} = $cluname;
1.156 foxr 192: } else {
1.161 foxr 193: $managers{$hostip{$host}} = $host; # Use info from cluster tab if clumemeber
1.156 foxr 194: }
195: }
196: }
1.140 foxr 197:
198: #
199: # ValidManager: Determines if a given certificate represents a valid manager.
200: # in this primitive implementation, the 'certificate' is
201: # just the connecting loncapa client name. This is checked
202: # against a valid client list in the configuration.
203: #
204: #
205: sub ValidManager {
206: my $certificate = shift;
207:
1.163 foxr 208: return isManager;
1.140 foxr 209: }
210: #
1.143 foxr 211: # CopyFile: Called as part of the process of installing a
212: # new configuration file. This function copies an existing
213: # file to a backup file.
214: # Parameters:
215: # oldfile - Name of the file to backup.
216: # newfile - Name of the backup file.
217: # Return:
218: # 0 - Failure (errno has failure reason).
219: # 1 - Success.
220: #
221: sub CopyFile {
222: my $oldfile = shift;
223: my $newfile = shift;
224:
225: # The file must exist:
226:
227: if(-e $oldfile) {
228:
229: # Read the old file.
230:
231: my $oldfh = IO::File->new("< $oldfile");
232: if(!$oldfh) {
233: return 0;
234: }
235: my @contents = <$oldfh>; # Suck in the entire file.
236:
237: # write the backup file:
238:
239: my $newfh = IO::File->new("> $newfile");
240: if(!(defined $newfh)){
241: return 0;
242: }
243: my $lines = scalar @contents;
244: for (my $i =0; $i < $lines; $i++) {
245: print $newfh ($contents[$i]);
246: }
247:
248: $oldfh->close;
249: $newfh->close;
250:
251: chmod(0660, $newfile);
252:
253: return 1;
254:
255: } else {
256: return 0;
257: }
258: }
1.157 foxr 259: #
260: # Host files are passed out with externally visible host IPs.
261: # If, for example, we are behind a fire-wall or NAT host, our
262: # internally visible IP may be different than the externally
263: # visible IP. Therefore, we always adjust the contents of the
264: # host file so that the entry for ME is the IP that we believe
265: # we have. At present, this is defined as the entry that
266: # DNS has for us. If by some chance we are not able to get a
267: # DNS translation for us, then we assume that the host.tab file
268: # is correct.
269: # BUGBUGBUG - in the future, we really should see if we can
270: # easily query the interface(s) instead.
271: # Parameter(s):
272: # contents - The contents of the host.tab to check.
273: # Returns:
274: # newcontents - The adjusted contents.
275: #
276: #
277: sub AdjustHostContents {
278: my $contents = shift;
279: my $adjusted;
280: my $me = $perlvar{'lonHostID'};
281:
282: foreach my $line (split(/\n/,$contents)) {
283: if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/))) {
284: chomp($line);
285: my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line);
286: if ($id eq $me) {
287: open(PIPE, " /usr/bin/host $name |") || die "Cant' make host pipeline";
288: my $hostinfo = <PIPE>;
289: close PIPE;
290:
291: my ($hostname, $has, $address, $ipnew) = split(/ /,$hostinfo);
292: &logthis('<font color="green">'.
293: "hostname = $hostname me = $me, name = $name actual ip = $ipnew </font>");
294:
295: if ($hostname eq $name) { # Lookup succeeded..
296: &logthis('<font color="green"> look up ok <font>');
297: $ip = $ipnew;
298: } else {
299: &logthis('<font color="green"> Lookup failed: '
300: .$hostname." ne $name </font>");
301: }
302: # Reconstruct the host line and append to adjusted:
303:
304: my $newline = "$id:$domain:$role:$name:$ip";
305: if($maxcon ne "") { # Not all hosts have loncnew tuning params
306: $newline .= ":$maxcon:$idleto:$mincon";
307: }
308: $adjusted .= $newline."\n";
309:
310: } else { # Not me, pass unmodified.
311: $adjusted .= $line."\n";
312: }
313: } else { # Blank or comment never re-written.
314: $adjusted .= $line."\n"; # Pass blanks and comments as is.
315: }
316: }
317: return $adjusted;
318: }
1.143 foxr 319: #
320: # InstallFile: Called to install an administrative file:
321: # - The file is created with <name>.tmp
322: # - The <name>.tmp file is then mv'd to <name>
323: # This lugubrious procedure is done to ensure that we are never without
324: # a valid, even if dated, version of the file regardless of who crashes
325: # and when the crash occurs.
326: #
327: # Parameters:
328: # Name of the file
329: # File Contents.
330: # Return:
331: # nonzero - success.
332: # 0 - failure and $! has an errno.
333: #
334: sub InstallFile {
335: my $Filename = shift;
336: my $Contents = shift;
337: my $TempFile = $Filename.".tmp";
338:
339: # Open the file for write:
340:
341: my $fh = IO::File->new("> $TempFile"); # Write to temp.
342: if(!(defined $fh)) {
343: &logthis('<font color="red"> Unable to create '.$TempFile."</font>");
344: return 0;
345: }
346: # write the contents of the file:
347:
348: print $fh ($Contents);
349: $fh->close; # In case we ever have a filesystem w. locking
350:
351: chmod(0660, $TempFile);
352:
353: # Now we can move install the file in position.
354:
355: move($TempFile, $Filename);
356:
357: return 1;
358: }
359:
360: #
1.141 foxr 361: # PushFile: Called to do an administrative push of a file.
362: # - Ensure the file being pushed is one we support.
363: # - Backup the old file to <filename.saved>
364: # - Separate the contents of the new file out from the
365: # rest of the request.
366: # - Write the new file.
367: # Parameter:
368: # Request - The entire user request. This consists of a : separated
369: # string pushfile:tablename:contents.
370: # NOTE: The contents may have :'s in it as well making things a bit
371: # more interesting... but not much.
372: # Returns:
373: # String to send to client ("ok" or "refused" if bad file).
374: #
375: sub PushFile {
376: my $request = shift;
377: my ($command, $filename, $contents) = split(":", $request, 3);
378:
379: # At this point in time, pushes for only the following tables are
380: # supported:
381: # hosts.tab ($filename eq host).
382: # domain.tab ($filename eq domain).
383: # Construct the destination filename or reject the request.
384: #
385: # lonManage is supposed to ensure this, however this session could be
386: # part of some elaborate spoof that managed somehow to authenticate.
387: #
388:
389: my $tablefile = $perlvar{'lonTabDir'}.'/'; # need to precede with dir.
390: if ($filename eq "host") {
391: $tablefile .= "hosts.tab";
392: } elsif ($filename eq "domain") {
393: $tablefile .= "domain.tab";
394: } else {
395: return "refused";
396: }
397: #
398: # >copy< the old table to the backup table
399: # don't rename in case system crashes/reboots etc. in the time
400: # window between a rename and write.
401: #
402: my $backupfile = $tablefile;
403: $backupfile =~ s/\.tab$/.old/;
1.143 foxr 404: if(!CopyFile($tablefile, $backupfile)) {
405: &logthis('<font color="green"> CopyFile from '.$tablefile." to ".$backupfile." failed </font>");
406: return "error:$!";
407: }
1.141 foxr 408: &logthis('<font color="green"> Pushfile: backed up '
409: .$tablefile." to $backupfile</font>");
410:
1.157 foxr 411: # If the file being pushed is the host file, we adjust the entry for ourself so that the
412: # IP will be our current IP as looked up in dns. Note this is only 99% good as it's possible
413: # to conceive of conditions where we don't have a DNS entry locally. This is possible in a
414: # network sense but it doesn't make much sense in a LonCAPA sense so we ignore (for now)
415: # that possibilty.
416:
417: if($filename eq "host") {
418: $contents = AdjustHostContents($contents);
419: }
420:
1.141 foxr 421: # Install the new file:
422:
1.143 foxr 423: if(!InstallFile($tablefile, $contents)) {
424: &logthis('<font color="red"> Pushfile: unable to install '
1.145 foxr 425: .$tablefile." $! </font>");
1.143 foxr 426: return "error:$!";
427: }
428: else {
429: &logthis('<font color="green"> Installed new '.$tablefile
430: ."</font>");
431:
432: }
433:
1.141 foxr 434:
435: # Indicate success:
436:
437: return "ok";
438:
439: }
1.145 foxr 440:
441: #
442: # Called to re-init either lonc or lond.
443: #
444: # Parameters:
445: # request - The full request by the client. This is of the form
446: # reinit:<process>
447: # where <process> is allowed to be either of
448: # lonc or lond
449: #
450: # Returns:
451: # The string to be sent back to the client either:
452: # ok - Everything worked just fine.
453: # error:why - There was a failure and why describes the reason.
454: #
455: #
456: sub ReinitProcess {
457: my $request = shift;
458:
1.146 foxr 459:
460: # separate the request (reinit) from the process identifier and
461: # validate it producing the name of the .pid file for the process.
462: #
463: #
464: my ($junk, $process) = split(":", $request);
1.147 foxr 465: my $processpidfile = $perlvar{'lonDaemons'}.'/logs/';
1.146 foxr 466: if($process eq 'lonc') {
467: $processpidfile = $processpidfile."lonc.pid";
1.147 foxr 468: if (!open(PIDFILE, "< $processpidfile")) {
469: return "error:Open failed for $processpidfile";
470: }
471: my $loncpid = <PIDFILE>;
472: close(PIDFILE);
473: logthis('<font color="red"> Reinitializing lonc pid='.$loncpid
474: ."</font>");
475: kill("USR2", $loncpid);
1.146 foxr 476: } elsif ($process eq 'lond') {
1.147 foxr 477: logthis('<font color="red"> Reinitializing self (lond) </font>');
478: &UpdateHosts; # Lond is us!!
1.146 foxr 479: } else {
480: &logthis('<font color="yellow" Invalid reinit request for '.$process
481: ."</font>");
482: return "error:Invalid process identifier $process";
483: }
1.145 foxr 484: return 'ok';
485: }
486:
1.141 foxr 487: #
1.96 foxr 488: # Convert an error return code from lcpasswd to a string value.
489: #
490: sub lcpasswdstrerror {
491: my $ErrorCode = shift;
1.97 foxr 492: if(($ErrorCode < 0) || ($ErrorCode > $lastpwderror)) {
1.96 foxr 493: return "lcpasswd Unrecognized error return value ".$ErrorCode;
494: } else {
1.98 foxr 495: return $passwderrors[$ErrorCode];
1.96 foxr 496: }
497: }
498:
1.97 foxr 499: #
500: # Convert an error return code from lcuseradd to a string value:
501: #
502: sub lcuseraddstrerror {
503: my $ErrorCode = shift;
504: if(($ErrorCode < 0) || ($ErrorCode > $lastadderror)) {
505: return "lcuseradd - Unrecognized error code: ".$ErrorCode;
506: } else {
1.98 foxr 507: return $adderrors[$ErrorCode];
1.97 foxr 508: }
509: }
510:
1.23 harris41 511: # grabs exception and records it to log before exiting
512: sub catchexception {
1.27 albertel 513: my ($error)=@_;
1.25 www 514: $SIG{'QUIT'}='DEFAULT';
515: $SIG{__DIE__}='DEFAULT';
1.165 albertel 516: &status("Catching exception");
1.23 harris41 517: &logthis("<font color=red>CRITICAL: "
1.134 albertel 518: ."ABNORMAL EXIT. Child $$ for server $thisserver died through "
1.27 albertel 519: ."a crash with this error msg->[$error]</font>");
1.57 www 520: &logthis('Famous last words: '.$status.' - '.$lastlog);
1.27 albertel 521: if ($client) { print $client "error: $error\n"; }
1.59 www 522: $server->close();
1.27 albertel 523: die($error);
1.23 harris41 524: }
525:
1.63 www 526: sub timeout {
1.165 albertel 527: &status("Handling Timeout");
1.63 www 528: &logthis("<font color=ref>CRITICAL: TIME OUT ".$$."</font>");
529: &catchexception('Timeout');
530: }
1.22 harris41 531: # -------------------------------- Set signal handlers to record abnormal exits
532:
533: $SIG{'QUIT'}=\&catchexception;
534: $SIG{__DIE__}=\&catchexception;
535:
1.81 matthew 536: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
1.95 harris41 537: &status("Read loncapa.conf and loncapa_apache.conf");
538: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
1.141 foxr 539: %perlvar=%{$perlvarref};
1.80 harris41 540: undef $perlvarref;
1.19 www 541:
1.35 harris41 542: # ----------------------------- Make sure this process is running from user=www
543: my $wwwid=getpwnam('www');
544: if ($wwwid!=$<) {
1.134 albertel 545: my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
546: my $subj="LON: $currenthostid User ID mismatch";
1.37 harris41 547: system("echo 'User ID mismatch. lond must be run as user www.' |\
1.35 harris41 548: mailto $emailto -s '$subj' > /dev/null");
549: exit 1;
550: }
551:
1.19 www 552: # --------------------------------------------- Check if other instance running
553:
554: my $pidfile="$perlvar{'lonDaemons'}/logs/lond.pid";
555:
556: if (-e $pidfile) {
557: my $lfh=IO::File->new("$pidfile");
558: my $pide=<$lfh>;
559: chomp($pide);
1.29 harris41 560: if (kill 0 => $pide) { die "already running"; }
1.19 www 561: }
1.1 albertel 562:
563: # ------------------------------------------------------------- Read hosts file
564:
565:
566:
567: # establish SERVER socket, bind and listen.
568: $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'},
569: Type => SOCK_STREAM,
570: Proto => 'tcp',
571: Reuse => 1,
572: Listen => 10 )
1.29 harris41 573: or die "making socket: $@\n";
1.1 albertel 574:
575: # --------------------------------------------------------- Do global variables
576:
577: # global variables
578:
1.134 albertel 579: my %children = (); # keys are current child process IDs
580: my $children = 0; # current number of children
1.1 albertel 581:
582: sub REAPER { # takes care of dead children
583: $SIG{CHLD} = \&REAPER;
1.165 albertel 584: &status("Handling child death");
1.1 albertel 585: my $pid = wait;
1.67 albertel 586: if (defined($children{$pid})) {
587: &logthis("Child $pid died");
588: $children --;
589: delete $children{$pid};
590: } else {
591: &logthis("Unknown Child $pid died");
592: }
1.165 albertel 593: &status("Finished Handling child death");
1.1 albertel 594: }
595:
596: sub HUNTSMAN { # signal handler for SIGINT
1.165 albertel 597: &status("Killing children (INT)");
1.1 albertel 598: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
599: kill 'INT' => keys %children;
1.59 www 600: &logthis("Free socket: ".shutdown($server,2)); # free up socket
1.1 albertel 601: my $execdir=$perlvar{'lonDaemons'};
602: unlink("$execdir/logs/lond.pid");
1.9 www 603: &logthis("<font color=red>CRITICAL: Shutting down</font>");
1.165 albertel 604: &status("Done killing children");
1.1 albertel 605: exit; # clean up with dignity
606: }
607:
608: sub HUPSMAN { # signal handler for SIGHUP
609: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
1.165 albertel 610: &status("Killing children for restart (HUP)");
1.1 albertel 611: kill 'INT' => keys %children;
1.59 www 612: &logthis("Free socket: ".shutdown($server,2)); # free up socket
1.9 www 613: &logthis("<font color=red>CRITICAL: Restarting</font>");
1.134 albertel 614: my $execdir=$perlvar{'lonDaemons'};
1.30 harris41 615: unlink("$execdir/logs/lond.pid");
1.165 albertel 616: &status("Restarting self (HUP)");
1.1 albertel 617: exec("$execdir/lond"); # here we go again
618: }
619:
1.144 foxr 620: #
1.148 foxr 621: # Kill off hashes that describe the host table prior to re-reading it.
622: # Hashes affected are:
623: # %hostid, %hostdom %hostip
624: #
625: sub KillHostHashes {
626: foreach my $key (keys %hostid) {
627: delete $hostid{$key};
628: }
629: foreach my $key (keys %hostdom) {
630: delete $hostdom{$key};
631: }
632: foreach my $key (keys %hostip) {
633: delete $hostip{$key};
634: }
635: }
636: #
637: # Read in the host table from file and distribute it into the various hashes:
638: #
639: # - %hostid - Indexed by IP, the loncapa hostname.
640: # - %hostdom - Indexed by loncapa hostname, the domain.
641: # - %hostip - Indexed by hostid, the Ip address of the host.
642: sub ReadHostTable {
643:
644: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
645:
646: while (my $configline=<CONFIG>) {
647: my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
648: chomp($ip); $ip=~s/\D+$//;
649: $hostid{$ip}=$id;
650: $hostdom{$id}=$domain;
651: $hostip{$id}=$ip;
652: if ($id eq $perlvar{'lonHostID'}) { $thisserver=$name; }
653: }
654: close(CONFIG);
655: }
656: #
657: # Reload the Apache daemon's state.
1.150 foxr 658: # This is done by invoking /home/httpd/perl/apachereload
659: # a setuid perl script that can be root for us to do this job.
1.148 foxr 660: #
661: sub ReloadApache {
1.150 foxr 662: my $execdir = $perlvar{'lonDaemons'};
663: my $script = $execdir."/apachereload";
664: system($script);
1.148 foxr 665: }
666:
667: #
1.144 foxr 668: # Called in response to a USR2 signal.
669: # - Reread hosts.tab
670: # - All children connected to hosts that were removed from hosts.tab
671: # are killed via SIGINT
672: # - All children connected to previously existing hosts are sent SIGUSR1
673: # - Our internal hosts hash is updated to reflect the new contents of
674: # hosts.tab causing connections from hosts added to hosts.tab to
675: # now be honored.
676: #
677: sub UpdateHosts {
1.165 albertel 678: &status("Reload hosts.tab");
1.147 foxr 679: logthis('<font color="blue"> Updating connections </font>');
1.148 foxr 680: #
681: # The %children hash has the set of IP's we currently have children
682: # on. These need to be matched against records in the hosts.tab
683: # Any ip's no longer in the table get killed off they correspond to
684: # either dropped or changed hosts. Note that the re-read of the table
685: # will take care of new and changed hosts as connections come into being.
686:
687:
688: KillHostHashes;
689: ReadHostTable;
690:
691: foreach my $child (keys %children) {
692: my $childip = $children{$child};
693: if(!$hostid{$childip}) {
1.149 foxr 694: logthis('<font color="blue"> UpdateHosts killing child '
695: ." $child for ip $childip </font>");
1.148 foxr 696: kill('INT', $child);
1.149 foxr 697: } else {
698: logthis('<font color="green"> keeping child for ip '
699: ." $childip (pid=$child) </font>");
1.148 foxr 700: }
701: }
702: ReloadApache;
1.165 albertel 703: &status("Finished reloading hosts.tab");
1.144 foxr 704: }
705:
1.148 foxr 706:
1.57 www 707: sub checkchildren {
1.165 albertel 708: &status("Checking on the children (sending signals)");
1.57 www 709: &initnewstatus();
710: &logstatus();
711: &logthis('Going to check on the children');
1.134 albertel 712: my $docdir=$perlvar{'lonDocRoot'};
1.61 harris41 713: foreach (sort keys %children) {
1.57 www 714: sleep 1;
715: unless (kill 'USR1' => $_) {
716: &logthis ('Child '.$_.' is dead');
717: &logstatus($$.' is dead');
718: }
1.61 harris41 719: }
1.63 www 720: sleep 5;
1.113 albertel 721: $SIG{ALRM} = sub { die "timeout" };
722: $SIG{__DIE__} = 'DEFAULT';
1.165 albertel 723: &status("Checking on the children (waiting for reports)");
1.63 www 724: foreach (sort keys %children) {
725: unless (-e "$docdir/lon-status/londchld/$_.txt") {
1.113 albertel 726: eval {
727: alarm(300);
1.63 www 728: &logthis('Child '.$_.' did not respond');
1.67 albertel 729: kill 9 => $_;
1.131 albertel 730: #$emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
731: #$subj="LON: $currenthostid killed lond process $_";
732: #my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`;
733: #$execdir=$perlvar{'lonDaemons'};
734: #$result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_`;
1.113 albertel 735: alarm(0);
736: }
1.63 www 737: }
738: }
1.113 albertel 739: $SIG{ALRM} = 'DEFAULT';
1.155 albertel 740: $SIG{__DIE__} = \&catchexception;
1.165 albertel 741: &status("Finished checking children");
1.57 www 742: }
743:
1.1 albertel 744: # --------------------------------------------------------------------- Logging
745:
746: sub logthis {
747: my $message=shift;
748: my $execdir=$perlvar{'lonDaemons'};
749: my $fh=IO::File->new(">>$execdir/logs/lond.log");
750: my $now=time;
751: my $local=localtime($now);
1.58 www 752: $lastlog=$local.': '.$message;
1.1 albertel 753: print $fh "$local ($$): $message\n";
754: }
755:
1.77 foxr 756: # ------------------------- Conditional log if $DEBUG true.
757: sub Debug {
758: my $message = shift;
759: if($DEBUG) {
760: &logthis($message);
761: }
762: }
1.161 foxr 763:
764: #
765: # Sub to do replies to client.. this gives a hook for some
766: # debug tracing too:
767: # Parameters:
768: # fd - File open on client.
769: # reply - Text to send to client.
770: # request - Original request from client.
771: #
772: sub Reply {
773: my $fd = shift;
774: my $reply = shift;
775: my $request = shift;
776:
777: print $fd $reply;
778: Debug("Request was $request Reply was $reply");
779:
780: }
1.57 www 781: # ------------------------------------------------------------------ Log status
782:
783: sub logstatus {
1.165 albertel 784: &status("Doing logging");
1.57 www 785: my $docdir=$perlvar{'lonDocRoot'};
1.63 www 786: {
1.57 www 787: my $fh=IO::File->new(">>$docdir/lon-status/londstatus.txt");
1.115 albertel 788: print $fh $$."\t".$currenthostid."\t".$status."\t".$lastlog."\n";
1.63 www 789: $fh->close();
790: }
1.165 albertel 791: &status("Finished londstatus.txt");
1.63 www 792: {
793: my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt");
794: print $fh $status."\n".$lastlog."\n".time;
795: $fh->close();
796: }
1.165 albertel 797: &status("Finished logging");
1.57 www 798: }
799:
800: sub initnewstatus {
801: my $docdir=$perlvar{'lonDocRoot'};
802: my $fh=IO::File->new(">$docdir/lon-status/londstatus.txt");
803: my $now=time;
804: my $local=localtime($now);
805: print $fh "LOND status $local - parent $$\n\n";
1.64 www 806: opendir(DIR,"$docdir/lon-status/londchld");
1.134 albertel 807: while (my $filename=readdir(DIR)) {
1.64 www 808: unlink("$docdir/lon-status/londchld/$filename");
809: }
810: closedir(DIR);
1.57 www 811: }
812:
813: # -------------------------------------------------------------- Status setting
814:
815: sub status {
816: my $what=shift;
817: my $now=time;
818: my $local=localtime($now);
819: $status=$local.': '.$what;
1.103 www 820: $0='lond: '.$what.' '.$local;
1.57 www 821: }
1.11 www 822:
823: # -------------------------------------------------------- Escape Special Chars
824:
825: sub escape {
826: my $str=shift;
827: $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
828: return $str;
829: }
830:
831: # ----------------------------------------------------- Un-Escape Special Chars
832:
833: sub unescape {
834: my $str=shift;
835: $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
836: return $str;
837: }
838:
1.1 albertel 839: # ----------------------------------------------------------- Send USR1 to lonc
840:
841: sub reconlonc {
842: my $peerfile=shift;
843: &logthis("Trying to reconnect for $peerfile");
844: my $loncfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
845: if (my $fh=IO::File->new("$loncfile")) {
846: my $loncpid=<$fh>;
847: chomp($loncpid);
848: if (kill 0 => $loncpid) {
849: &logthis("lonc at pid $loncpid responding, sending USR1");
850: kill USR1 => $loncpid;
851: } else {
1.9 www 852: &logthis(
853: "<font color=red>CRITICAL: "
854: ."lonc at pid $loncpid not responding, giving up</font>");
1.1 albertel 855: }
856: } else {
1.9 www 857: &logthis('<font color=red>CRITICAL: lonc not running, giving up</font>');
1.1 albertel 858: }
859: }
860:
861: # -------------------------------------------------- Non-critical communication
1.11 www 862:
1.1 albertel 863: sub subreply {
864: my ($cmd,$server)=@_;
865: my $peerfile="$perlvar{'lonSockDir'}/$server";
866: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile",
867: Type => SOCK_STREAM,
868: Timeout => 10)
869: or return "con_lost";
870: print $sclient "$cmd\n";
871: my $answer=<$sclient>;
872: chomp($answer);
873: if (!$answer) { $answer="con_lost"; }
874: return $answer;
875: }
876:
877: sub reply {
878: my ($cmd,$server)=@_;
879: my $answer;
1.115 albertel 880: if ($server ne $currenthostid) {
1.1 albertel 881: $answer=subreply($cmd,$server);
882: if ($answer eq 'con_lost') {
883: $answer=subreply("ping",$server);
884: if ($answer ne $server) {
1.115 albertel 885: &logthis("sub reply: answer != server answer is $answer, server is $server");
1.1 albertel 886: &reconlonc("$perlvar{'lonSockDir'}/$server");
887: }
888: $answer=subreply($cmd,$server);
889: }
890: } else {
891: $answer='self_reply';
892: }
893: return $answer;
894: }
895:
1.13 www 896: # -------------------------------------------------------------- Talk to lonsql
897:
1.12 harris41 898: sub sqlreply {
899: my ($cmd)=@_;
900: my $answer=subsqlreply($cmd);
901: if ($answer eq 'con_lost') { $answer=subsqlreply($cmd); }
902: return $answer;
903: }
904:
905: sub subsqlreply {
906: my ($cmd)=@_;
907: my $unixsock="mysqlsock";
908: my $peerfile="$perlvar{'lonSockDir'}/$unixsock";
909: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile",
910: Type => SOCK_STREAM,
911: Timeout => 10)
912: or return "con_lost";
913: print $sclient "$cmd\n";
914: my $answer=<$sclient>;
915: chomp($answer);
916: if (!$answer) { $answer="con_lost"; }
917: return $answer;
918: }
919:
1.1 albertel 920: # -------------------------------------------- Return path to profile directory
1.11 www 921:
1.1 albertel 922: sub propath {
923: my ($udom,$uname)=@_;
924: $udom=~s/\W//g;
925: $uname=~s/\W//g;
1.16 www 926: my $subdir=$uname.'__';
1.1 albertel 927: $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
928: my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
929: return $proname;
930: }
931:
932: # --------------------------------------- Is this the home server of an author?
1.11 www 933:
1.1 albertel 934: sub ishome {
935: my $author=shift;
936: $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
937: my ($udom,$uname)=split(/\//,$author);
938: my $proname=propath($udom,$uname);
939: if (-e $proname) {
940: return 'owner';
941: } else {
942: return 'not_owner';
943: }
944: }
945:
946: # ======================================================= Continue main program
947: # ---------------------------------------------------- Fork once and dissociate
948:
1.134 albertel 949: my $fpid=fork;
1.1 albertel 950: exit if $fpid;
1.29 harris41 951: die "Couldn't fork: $!" unless defined ($fpid);
1.1 albertel 952:
1.29 harris41 953: POSIX::setsid() or die "Can't start new session: $!";
1.1 albertel 954:
955: # ------------------------------------------------------- Write our PID on disk
956:
1.134 albertel 957: my $execdir=$perlvar{'lonDaemons'};
1.1 albertel 958: open (PIDSAVE,">$execdir/logs/lond.pid");
959: print PIDSAVE "$$\n";
960: close(PIDSAVE);
1.9 www 961: &logthis("<font color=red>CRITICAL: ---------- Starting ----------</font>");
1.57 www 962: &status('Starting');
1.1 albertel 963:
1.106 foxr 964:
1.1 albertel 965:
966: # ----------------------------------------------------- Install signal handlers
967:
1.57 www 968:
1.1 albertel 969: $SIG{CHLD} = \&REAPER;
970: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
971: $SIG{HUP} = \&HUPSMAN;
1.57 www 972: $SIG{USR1} = \&checkchildren;
1.144 foxr 973: $SIG{USR2} = \&UpdateHosts;
1.106 foxr 974:
1.148 foxr 975: # Read the host hashes:
976:
977: ReadHostTable;
1.106 foxr 978:
979: # --------------------------------------------------------------
980: # Accept connections. When a connection comes in, it is validated
981: # and if good, a child process is created to process transactions
982: # along the connection.
983:
1.1 albertel 984: while (1) {
1.165 albertel 985: &status('Starting accept');
1.106 foxr 986: $client = $server->accept() or next;
1.165 albertel 987: &status('Accepted '.$client.' off to spawn');
1.106 foxr 988: make_new_child($client);
1.165 albertel 989: &status('Finished spawning');
1.1 albertel 990: }
991:
992: sub make_new_child {
993: my $pid;
994: my $cipher;
995: my $sigset;
1.106 foxr 996:
997: $client = shift;
1.165 albertel 998: &status('Starting new child '.$client);
1.161 foxr 999: &logthis('<font color="green"> Attempting to start child ('.$client.
1000: ")</font>");
1.1 albertel 1001: # block signal for fork
1002: $sigset = POSIX::SigSet->new(SIGINT);
1003: sigprocmask(SIG_BLOCK, $sigset)
1.29 harris41 1004: or die "Can't block SIGINT for fork: $!\n";
1.134 albertel 1005:
1.29 harris41 1006: die "fork: $!" unless defined ($pid = fork);
1.148 foxr 1007:
1008: $client->sockopt(SO_KEEPALIVE, 1); # Enable monitoring of
1009: # connection liveness.
1010:
1011: #
1012: # Figure out who we're talking to so we can record the peer in
1013: # the pid hash.
1014: #
1015: my $caller = getpeername($client);
1016: my ($port,$iaddr)=unpack_sockaddr_in($caller);
1017: $clientip=inet_ntoa($iaddr);
1.1 albertel 1018:
1019: if ($pid) {
1020: # Parent records the child's birth and returns.
1021: sigprocmask(SIG_UNBLOCK, $sigset)
1.29 harris41 1022: or die "Can't unblock SIGINT for fork: $!\n";
1.148 foxr 1023: $children{$pid} = $clientip;
1.1 albertel 1024: $children++;
1.57 www 1025: &status('Started child '.$pid);
1.1 albertel 1026: return;
1027: } else {
1028: # Child can *not* return from this subroutine.
1029: $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before
1.126 albertel 1030: $SIG{CHLD} = 'DEFAULT'; #make this default so that pwauth returns
1031: #don't get intercepted
1.57 www 1032: $SIG{USR1}= \&logstatus;
1.63 www 1033: $SIG{ALRM}= \&timeout;
1.57 www 1034: $lastlog='Forked ';
1035: $status='Forked';
1036:
1.1 albertel 1037: # unblock signals
1038: sigprocmask(SIG_UNBLOCK, $sigset)
1.29 harris41 1039: or die "Can't unblock SIGINT for fork: $!\n";
1.13 www 1040:
1.134 albertel 1041: my $tmpsnum=0;
1.91 albertel 1042: #---------------------------------------------------- kerberos 5 initialization
1043: &Authen::Krb5::init_context();
1044: &Authen::Krb5::init_ets();
1045:
1.161 foxr 1046: &status('Accepted connection');
1.1 albertel 1047: # =============================================================================
1048: # do something with the connection
1049: # -----------------------------------------------------------------------------
1.148 foxr 1050: # see if we know client and check for spoof IP by challenge
1051:
1.161 foxr 1052: ReadManagerTable; # May also be a manager!!
1053:
1054: my $clientrec=($hostid{$clientip} ne undef);
1055: my $ismanager=($managers{$clientip} ne undef);
1056: $clientname = "[unknonwn]";
1057: if($clientrec) { # Establish client type.
1058: $ConnectionType = "client";
1059: $clientname = $hostid{$clientip};
1060: if($ismanager) {
1061: $ConnectionType = "both";
1062: }
1063: } else {
1064: $ConnectionType = "manager";
1065: $clientname = $managers{$clientip};
1066: }
1067: my $clientok;
1068: if ($clientrec || $ismanager) {
1069: &status("Waiting for init from $clientip $clientname");
1070: &logthis('<font color="yellow">INFO: Connection, '.
1071: $clientip.
1072: " ($clientname) connection type = $ConnectionType </font>" );
1073: &status("Connecting $clientip ($clientname))");
1074: my $remotereq=<$client>;
1075: $remotereq=~s/[^\w:]//g;
1076: if ($remotereq =~ /^init/) {
1077: &sethost("sethost:$perlvar{'lonHostID'}");
1078: my $challenge="$$".time;
1079: print $client "$challenge\n";
1080: &status(
1081: "Waiting for challenge reply from $clientip ($clientname)");
1082: $remotereq=<$client>;
1083: $remotereq=~s/\W//g;
1084: if ($challenge eq $remotereq) {
1085: $clientok=1;
1086: print $client "ok\n";
1087: } else {
1088: &logthis(
1089: "<font color=blue>WARNING: $clientip did not reply challenge</font>");
1090: &status('No challenge reply '.$clientip);
1091: }
1.2 www 1092: } else {
1.161 foxr 1093: &logthis(
1094: "<font color=blue>WARNING: "
1095: ."$clientip failed to initialize: >$remotereq< </font>");
1096: &status('No init '.$clientip);
1097: }
1098: } else {
1099: &logthis(
1100: "<font color=blue>WARNING: Unknown client $clientip</font>");
1101: &status('Hung up on '.$clientip);
1102: }
1103: if ($clientok) {
1.1 albertel 1104: # ---------------- New known client connecting, could mean machine online again
1.161 foxr 1105:
1106: foreach my $id (keys(%hostip)) {
1107: if ($hostip{$id} ne $clientip ||
1108: $hostip{$currenthostid} eq $clientip) {
1109: # no need to try to do recon's to myself
1110: next;
1.115 albertel 1111: }
1.161 foxr 1112: &reconlonc("$perlvar{'lonSockDir'}/$id");
1113: }
1114: &logthis("<font color=green>Established connection: $clientname</font>");
1115: &status('Will listen to '.$clientname);
1.1 albertel 1116: # ------------------------------------------------------------ Process requests
1.161 foxr 1117: while (my $userinput=<$client>) {
1.1 albertel 1118: chomp($userinput);
1.79 foxr 1119: Debug("Request = $userinput\n");
1.161 foxr 1120: &status('Processing '.$clientname.': '.$userinput);
1.1 albertel 1121: my $wasenc=0;
1.63 www 1122: alarm(120);
1.1 albertel 1123: # ------------------------------------------------------------ See if encrypted
1124: if ($userinput =~ /^enc/) {
1.161 foxr 1125: if ($cipher) {
1126: my ($cmd,$cmdlength,$encinput)=split(/:/,$userinput);
1127: $userinput='';
1128: for (my $encidx=0;$encidx<length($encinput);$encidx+=16) {
1129: $userinput.=
1130: $cipher->decrypt(
1131: pack("H16",substr($encinput,$encidx,16))
1132: );
1133: }
1134: $userinput=substr($userinput,0,$cmdlength);
1135: $wasenc=1;
1.1 albertel 1136: }
1137: }
1.161 foxr 1138:
1.1 albertel 1139: # ------------------------------------------------------------- Normal commands
1140: # ------------------------------------------------------------------------ ping
1.161 foxr 1141: if ($userinput =~ /^ping/) { # client only
1142: if(isClient) {
1143: print $client "$currenthostid\n";
1144: } else {
1145: Reply($client, "refused\n", $userinput);
1146: }
1.1 albertel 1147: # ------------------------------------------------------------------------ pong
1.161 foxr 1148: }elsif ($userinput =~ /^pong/) { # client only
1149: if(isClient) {
1150: my $reply=&reply("ping",$clientname);
1151: print $client "$currenthostid:$reply\n";
1152: } else {
1153: Reply($client, "refused\n", $userinput);
1154: }
1.1 albertel 1155: # ------------------------------------------------------------------------ ekey
1.161 foxr 1156: } elsif ($userinput =~ /^ekey/) { # ok for both clients & mgrs
1157: my $buildkey=time.$$.int(rand 100000);
1158: $buildkey=~tr/1-6/A-F/;
1159: $buildkey=int(rand 100000).$buildkey.int(rand 100000);
1160: my $key=$currenthostid.$clientname;
1161: $key=~tr/a-z/A-Z/;
1162: $key=~tr/G-P/0-9/;
1163: $key=~tr/Q-Z/0-9/;
1164: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
1165: $key=substr($key,0,32);
1166: my $cipherkey=pack("H32",$key);
1167: $cipher=new IDEA $cipherkey;
1168: print $client "$buildkey\n";
1.1 albertel 1169: # ------------------------------------------------------------------------ load
1.161 foxr 1170: } elsif ($userinput =~ /^load/) { # client only
1171: if (isClient) {
1172: my $loadavg;
1173: {
1174: my $loadfile=IO::File->new('/proc/loadavg');
1175: $loadavg=<$loadfile>;
1176: }
1177: $loadavg =~ s/\s.*//g;
1178: my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'};
1179: print $client "$loadpercent\n";
1180: } else {
1181: Reply($client, "refused\n", $userinput);
1182:
1183: }
1.127 albertel 1184: # -------------------------------------------------------------------- userload
1.161 foxr 1185: } elsif ($userinput =~ /^userload/) { # client only
1186: if(isClient) {
1187: my $userloadpercent=&userload();
1188: print $client "$userloadpercent\n";
1189: } else {
1190: Reply($client, "refused\n", $userinput);
1191:
1192: }
1.137 foxr 1193: #
1194: # Transactions requiring encryption:
1195: #
1.54 harris41 1196: # ----------------------------------------------------------------- currentauth
1.161 foxr 1197: } elsif ($userinput =~ /^currentauth/) {
1198: if (($wasenc==1) && isClient) { # Encoded & client only.
1199: my ($cmd,$udom,$uname)=split(/:/,$userinput);
1200: my $result = GetAuthType($udom, $uname);
1201: if($result eq "nouser") {
1202: print $client "unknown_user\n";
1203: }
1204: else {
1205: print $client "$result\n"
1206: }
1207: } else {
1208: Reply($client, "refused\n", $userinput);
1209:
1210: }
1.137 foxr 1211: #--------------------------------------------------------------------- pushfile
1.161 foxr 1212: } elsif($userinput =~ /^pushfile/) { # encoded & manager.
1213: if(($wasenc == 1) && isManager) {
1214: my $cert = GetCertificate($userinput);
1215: if(ValidManager($cert)) {
1216: my $reply = PushFile($userinput);
1217: print $client "$reply\n";
1218: } else {
1219: print $client "refused\n";
1220: }
1221: } else {
1222: Reply($client, "refused\n", $userinput);
1223:
1224: }
1.137 foxr 1225: #--------------------------------------------------------------------- reinit
1.161 foxr 1226: } elsif($userinput =~ /^reinit/) { # Encoded and manager
1227: if (($wasenc == 1) && isManager) {
1228: my $cert = GetCertificate($userinput);
1229: if(ValidManager($cert)) {
1230: chomp($userinput);
1231: my $reply = ReinitProcess($userinput);
1232: print $client "$reply\n";
1233: } else {
1234: print $client "refused\n";
1235: }
1236: } else {
1237: Reply($client, "refused\n", $userinput);
1238:
1239:
1240: }
1.1 albertel 1241: # ------------------------------------------------------------------------ auth
1.161 foxr 1242: } elsif ($userinput =~ /^auth/) { # Encoded and client only.
1243: if (($wasenc==1) && isClient) {
1244: my ($cmd,$udom,$uname,$upass)=split(/:/,$userinput);
1245: chomp($upass);
1246: $upass=unescape($upass);
1247: my $proname=propath($udom,$uname);
1248: my $passfilename="$proname/passwd";
1249: if (-e $passfilename) {
1250: my $pf = IO::File->new($passfilename);
1251: my $realpasswd=<$pf>;
1252: chomp($realpasswd);
1253: my ($howpwd,$contentpwd)=split(/:/,$realpasswd);
1254: my $pwdcorrect=0;
1255: if ($howpwd eq 'internal') {
1256: &Debug("Internal auth");
1257: $pwdcorrect=
1258: (crypt($upass,$contentpwd) eq $contentpwd);
1259: } elsif ($howpwd eq 'unix') {
1260: &Debug("Unix auth");
1261: if((getpwnam($uname))[1] eq "") { #no such user!
1262: $pwdcorrect = 0;
1263: } else {
1264: $contentpwd=(getpwnam($uname))[1];
1265: my $pwauth_path="/usr/local/sbin/pwauth";
1266: unless ($contentpwd eq 'x') {
1267: $pwdcorrect=
1268: (crypt($upass,$contentpwd) eq
1269: $contentpwd);
1270: }
1271:
1272: elsif (-e $pwauth_path) {
1273: open PWAUTH, "|$pwauth_path" or
1274: die "Cannot invoke authentication";
1275: print PWAUTH "$uname\n$upass\n";
1276: close PWAUTH;
1277: $pwdcorrect=!$?;
1278: }
1279: }
1280: } elsif ($howpwd eq 'krb4') {
1281: my $null=pack("C",0);
1282: unless ($upass=~/$null/) {
1283: my $krb4_error = &Authen::Krb4::get_pw_in_tkt
1284: ($uname,"",$contentpwd,'krbtgt',
1285: $contentpwd,1,$upass);
1286: if (!$krb4_error) {
1287: $pwdcorrect = 1;
1288: } else {
1289: $pwdcorrect=0;
1290: # log error if it is not a bad password
1291: if ($krb4_error != 62) {
1292: &logthis('krb4:'.$uname.','.$contentpwd.','.
1293: &Authen::Krb4::get_err_txt($Authen::Krb4::error));
1294: }
1295: }
1296: }
1297: } elsif ($howpwd eq 'krb5') {
1298: my $null=pack("C",0);
1299: unless ($upass=~/$null/) {
1300: my $krbclient=&Authen::Krb5::parse_name($uname.'@'.$contentpwd);
1301: my $krbservice="krbtgt/".$contentpwd."\@".$contentpwd;
1302: my $krbserver=&Authen::Krb5::parse_name($krbservice);
1303: my $credentials=&Authen::Krb5::cc_default();
1304: $credentials->initialize($krbclient);
1305: my $krbreturn =
1306: &Authen::Krb5::get_in_tkt_with_password(
1307: $krbclient,$krbserver,$upass,$credentials);
1.92 albertel 1308: # unless ($krbreturn) {
1309: # &logthis("Krb5 Error: ".
1310: # &Authen::Krb5::error());
1311: # }
1.161 foxr 1312: $pwdcorrect = ($krbreturn == 1);
1313: } else { $pwdcorrect=0; }
1314: } elsif ($howpwd eq 'localauth') {
1315: $pwdcorrect=&localauth::localauth($uname,$upass,
1316: $contentpwd);
1317: }
1318: if ($pwdcorrect) {
1319: print $client "authorized\n";
1320: } else {
1321: print $client "non_authorized\n";
1322: }
1323: } else {
1324: print $client "unknown_user\n";
1325: }
1326: } else {
1327: Reply($client, "refused\n", $userinput);
1328:
1329: }
1.1 albertel 1330: # ---------------------------------------------------------------------- passwd
1.161 foxr 1331: } elsif ($userinput =~ /^passwd/) { # encoded and client
1332: if (($wasenc==1) && isClient) {
1333: my
1334: ($cmd,$udom,$uname,$upass,$npass)=split(/:/,$userinput);
1335: chomp($npass);
1336: $upass=&unescape($upass);
1337: $npass=&unescape($npass);
1338: &Debug("Trying to change password for $uname");
1339: my $proname=propath($udom,$uname);
1340: my $passfilename="$proname/passwd";
1341: if (-e $passfilename) {
1342: my $realpasswd;
1343: { my $pf = IO::File->new($passfilename);
1344: $realpasswd=<$pf>; }
1345: chomp($realpasswd);
1346: my ($howpwd,$contentpwd)=split(/:/,$realpasswd);
1347: if ($howpwd eq 'internal') {
1348: &Debug("internal auth");
1349: if (crypt($upass,$contentpwd) eq $contentpwd) {
1350: my $salt=time;
1351: $salt=substr($salt,6,2);
1352: my $ncpass=crypt($npass,$salt);
1353: {
1354: my $pf;
1355: if ($pf = IO::File->new(">$passfilename")) {
1356: print $pf "internal:$ncpass\n";
1357: &logthis("Result of password change for $uname: pwchange_success");
1358: print $client "ok\n";
1359: } else {
1360: &logthis("Unable to open $uname passwd to change password");
1361: print $client "non_authorized\n";
1362: }
1363: }
1364:
1365: } else {
1366: print $client "non_authorized\n";
1367: }
1368: } elsif ($howpwd eq 'unix') {
1369: # Unix means we have to access /etc/password
1370: # one way or another.
1371: # First: Make sure the current password is
1372: # correct
1373: &Debug("auth is unix");
1374: $contentpwd=(getpwnam($uname))[1];
1375: my $pwdcorrect = "0";
1376: my $pwauth_path="/usr/local/sbin/pwauth";
1377: unless ($contentpwd eq 'x') {
1378: $pwdcorrect=
1379: (crypt($upass,$contentpwd) eq $contentpwd);
1380: } elsif (-e $pwauth_path) {
1381: open PWAUTH, "|$pwauth_path" or
1382: die "Cannot invoke authentication";
1383: print PWAUTH "$uname\n$upass\n";
1384: close PWAUTH;
1385: &Debug("exited pwauth with $? ($uname,$upass) ");
1386: $pwdcorrect=($? == 0);
1387: }
1388: if ($pwdcorrect) {
1389: my $execdir=$perlvar{'lonDaemons'};
1390: &Debug("Opening lcpasswd pipeline");
1391: my $pf = IO::File->new("|$execdir/lcpasswd > $perlvar{'lonDaemons'}/logs/lcpasswd.log");
1392: print $pf "$uname\n$npass\n$npass\n";
1393: close $pf;
1394: my $err = $?;
1395: my $result = ($err>0 ? 'pwchange_failure'
1396: : 'ok');
1397: &logthis("Result of password change for $uname: ".
1398: &lcpasswdstrerror($?));
1399: print $client "$result\n";
1400: } else {
1401: print $client "non_authorized\n";
1402: }
1403: } else {
1404: print $client "auth_mode_error\n";
1405: }
1406: } else {
1407: print $client "unknown_user\n";
1408: }
1409: } else {
1410: Reply($client, "refused\n", $userinput);
1411:
1412: }
1.31 www 1413: # -------------------------------------------------------------------- makeuser
1.161 foxr 1414: } elsif ($userinput =~ /^makeuser/) { # encoded and client.
1415: &Debug("Make user received");
1416: my $oldumask=umask(0077);
1417: if (($wasenc==1) && isClient) {
1418: my
1419: ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput);
1420: &Debug("cmd =".$cmd." $udom =".$udom.
1421: " uname=".$uname);
1422: chomp($npass);
1423: $npass=&unescape($npass);
1424: my $proname=propath($udom,$uname);
1425: my $passfilename="$proname/passwd";
1426: &Debug("Password file created will be:".
1427: $passfilename);
1428: if (-e $passfilename) {
1429: print $client "already_exists\n";
1430: } elsif ($udom ne $currentdomainid) {
1431: print $client "not_right_domain\n";
1432: } else {
1433: my @fpparts=split(/\//,$proname);
1434: my $fpnow=$fpparts[0].'/'.$fpparts[1].'/'.$fpparts[2];
1435: my $fperror='';
1436: for (my $i=3;$i<=$#fpparts;$i++) {
1437: $fpnow.='/'.$fpparts[$i];
1438: unless (-e $fpnow) {
1439: unless (mkdir($fpnow,0777)) {
1440: $fperror="error: ".($!+0)
1441: ." mkdir failed while attempting "
1.165.2.1! albertel 1442: ."makeuser";
1.161 foxr 1443: }
1444: }
1445: }
1446: unless ($fperror) {
1447: my $result=&make_passwd_file($uname, $umode,$npass,
1448: $passfilename);
1449: print $client $result;
1450: } else {
1451: print $client "$fperror\n";
1452: }
1453: }
1454: } else {
1455: Reply($client, "refused\n", $userinput);
1456:
1457: }
1458: umask($oldumask);
1.55 harris41 1459: # -------------------------------------------------------------- changeuserauth
1.161 foxr 1460: } elsif ($userinput =~ /^changeuserauth/) { # encoded & client
1461: &Debug("Changing authorization");
1462: if (($wasenc==1) && isClient) {
1463: my
1464: ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput);
1465: chomp($npass);
1466: &Debug("cmd = ".$cmd." domain= ".$udom.
1467: "uname =".$uname." umode= ".$umode);
1468: $npass=&unescape($npass);
1469: my $proname=&propath($udom,$uname);
1470: my $passfilename="$proname/passwd";
1471: if ($udom ne $currentdomainid) {
1472: print $client "not_right_domain\n";
1473: } else {
1474: my $result=&make_passwd_file($uname, $umode,$npass,
1475: $passfilename);
1476: print $client $result;
1477: }
1478: } else {
1479: Reply($client, "refused\n", $userinput);
1480:
1481: }
1.1 albertel 1482: # ------------------------------------------------------------------------ home
1.161 foxr 1483: } elsif ($userinput =~ /^home/) { # client clear or encoded
1484: if(isClient) {
1485: my ($cmd,$udom,$uname)=split(/:/,$userinput);
1486: chomp($uname);
1487: my $proname=propath($udom,$uname);
1488: if (-e $proname) {
1489: print $client "found\n";
1490: } else {
1491: print $client "not_found\n";
1492: }
1493: } else {
1494: Reply($client, "refused\n", $userinput);
1495:
1496: }
1.1 albertel 1497: # ---------------------------------------------------------------------- update
1.161 foxr 1498: } elsif ($userinput =~ /^update/) { # client clear or encoded.
1499: if(isClient) {
1500: my ($cmd,$fname)=split(/:/,$userinput);
1501: my $ownership=ishome($fname);
1502: if ($ownership eq 'not_owner') {
1503: if (-e $fname) {
1504: my ($dev,$ino,$mode,$nlink,
1505: $uid,$gid,$rdev,$size,
1506: $atime,$mtime,$ctime,
1507: $blksize,$blocks)=stat($fname);
1508: my $now=time;
1509: my $since=$now-$atime;
1510: if ($since>$perlvar{'lonExpire'}) {
1511: my $reply=
1512: &reply("unsub:$fname","$clientname");
1513: unlink("$fname");
1514: } else {
1515: my $transname="$fname.in.transfer";
1516: my $remoteurl=
1517: &reply("sub:$fname","$clientname");
1518: my $response;
1519: {
1520: my $ua=new LWP::UserAgent;
1521: my $request=new HTTP::Request('GET',"$remoteurl");
1522: $response=$ua->request($request,$transname);
1523: }
1524: if ($response->is_error()) {
1525: unlink($transname);
1526: my $message=$response->status_line;
1527: &logthis(
1528: "LWP GET: $message for $fname ($remoteurl)");
1529: } else {
1530: if ($remoteurl!~/\.meta$/) {
1531: my $ua=new LWP::UserAgent;
1532: my $mrequest=
1533: new HTTP::Request('GET',$remoteurl.'.meta');
1534: my $mresponse=
1535: $ua->request($mrequest,$fname.'.meta');
1536: if ($mresponse->is_error()) {
1537: unlink($fname.'.meta');
1538: }
1539: }
1540: rename($transname,$fname);
1541: }
1542: }
1543: print $client "ok\n";
1544: } else {
1545: print $client "not_found\n";
1546: }
1547: } else {
1548: print $client "rejected\n";
1549: }
1550: } else {
1551: Reply($client, "refused\n", $userinput);
1552:
1553: }
1.85 www 1554: # -------------------------------------- fetch a user file from a remote server
1.161 foxr 1555: } elsif ($userinput =~ /^fetchuserfile/) { # Client clear or enc.
1556: if(isClient) {
1557: my ($cmd,$fname)=split(/:/,$userinput);
1558: my ($udom,$uname,$ufile)=split(/\//,$fname);
1559: my $udir=propath($udom,$uname).'/userfiles';
1560: unless (-e $udir) { mkdir($udir,0770); }
1561: if (-e $udir) {
1562: $ufile=~s/^[\.\~]+//;
1563: $ufile=~s/\///g;
1564: my $destname=$udir.'/'.$ufile;
1565: my $transname=$udir.'/'.$ufile.'.in.transit';
1566: my $remoteurl='http://'.$clientip.'/userfiles/'.$fname;
1567: my $response;
1568: {
1569: my $ua=new LWP::UserAgent;
1570: my $request=new HTTP::Request('GET',"$remoteurl");
1571: $response=$ua->request($request,$transname);
1572: }
1573: if ($response->is_error()) {
1574: unlink($transname);
1575: my $message=$response->status_line;
1576: &logthis("LWP GET: $message for $fname ($remoteurl)");
1577: print $client "failed\n";
1578: } else {
1579: if (!rename($transname,$destname)) {
1580: &logthis("Unable to move $transname to $destname");
1581: unlink($transname);
1582: print $client "failed\n";
1583: } else {
1584: print $client "ok\n";
1585: }
1586: }
1587: } else {
1588: print $client "not_home\n";
1589: }
1590: } else {
1591: Reply($client, "refused\n", $userinput);
1592:
1593: }
1.85 www 1594: # ------------------------------------------ authenticate access to a user file
1.161 foxr 1595: } elsif ($userinput =~ /^tokenauthuserfile/) { # Client only
1596: if(isClient) {
1597: my ($cmd,$fname,$session)=split(/:/,$userinput);
1598: chomp($session);
1599: my $reply='non_auth';
1600: if (open(ENVIN,$perlvar{'lonIDsDir'}.'/'.
1601: $session.'.id')) {
1602: while (my $line=<ENVIN>) {
1603: if ($line=~/userfile\.$fname\=/) { $reply='ok'; }
1604: }
1605: close(ENVIN);
1606: print $client $reply."\n";
1607: } else {
1608: print $client "invalid_token\n";
1609: }
1610: } else {
1611: Reply($client, "refused\n", $userinput);
1612:
1613: }
1.1 albertel 1614: # ----------------------------------------------------------------- unsubscribe
1.161 foxr 1615: } elsif ($userinput =~ /^unsub/) {
1616: if(isClient) {
1617: my ($cmd,$fname)=split(/:/,$userinput);
1618: if (-e $fname) {
1619: print $client &unsub($client,$fname,$clientip);
1620: } else {
1621: print $client "not_found\n";
1622: }
1623: } else {
1624: Reply($client, "refused\n", $userinput);
1625:
1626: }
1.1 albertel 1627: # ------------------------------------------------------------------- subscribe
1.161 foxr 1628: } elsif ($userinput =~ /^sub/) {
1629: if(isClient) {
1630: print $client &subscribe($userinput,$clientip);
1631: } else {
1632: Reply($client, "refused\n", $userinput);
1633:
1634: }
1.102 www 1635: # ------------------------------------------------------------- current version
1.161 foxr 1636: } elsif ($userinput =~ /^currentversion/) {
1637: if(isClient) {
1638: my ($cmd,$fname)=split(/:/,$userinput);
1639: print $client ¤tversion($fname)."\n";
1640: } else {
1641: Reply($client, "refused\n", $userinput);
1642:
1643: }
1.12 harris41 1644: # ------------------------------------------------------------------------- log
1.161 foxr 1645: } elsif ($userinput =~ /^log/) {
1646: if(isClient) {
1647: my ($cmd,$udom,$uname,$what)=split(/:/,$userinput);
1648: chomp($what);
1649: my $proname=propath($udom,$uname);
1650: my $now=time;
1651: {
1652: my $hfh;
1653: if ($hfh=IO::File->new(">>$proname/activity.log")) {
1654: print $hfh "$now:$clientname:$what\n";
1655: print $client "ok\n";
1656: } else {
1657: print $client "error: ".($!+0)
1658: ." IO::File->new Failed "
1659: ."while attempting log\n";
1660: }
1661: }
1662: } else {
1663: Reply($client, "refused\n", $userinput);
1664:
1665: }
1.1 albertel 1666: # ------------------------------------------------------------------------- put
1.161 foxr 1667: } elsif ($userinput =~ /^put/) {
1668: if(isClient) {
1669: my ($cmd,$udom,$uname,$namespace,$what)
1670: =split(/:/,$userinput);
1671: $namespace=~s/\//\_/g;
1672: $namespace=~s/\W//g;
1673: if ($namespace ne 'roles') {
1674: chomp($what);
1675: my $proname=propath($udom,$uname);
1676: my $now=time;
1677: unless ($namespace=~/^nohist\_/) {
1678: my $hfh;
1679: if (
1680: $hfh=IO::File->new(">>$proname/$namespace.hist")
1681: ) { print $hfh "P:$now:$what\n"; }
1682: }
1683: my @pairs=split(/\&/,$what);
1684: my %hash;
1685: if (tie(%hash,'GDBM_File',
1686: "$proname/$namespace.db",
1687: &GDBM_WRCREAT(),0640)) {
1688: foreach my $pair (@pairs) {
1689: my ($key,$value)=split(/=/,$pair);
1690: $hash{$key}=$value;
1.162 matthew 1691: }
1692: if (untie(%hash)) {
1693: print $client "ok\n";
1694: } else {
1695: print $client "error: ".($!+0)
1696: ." untie(GDBM) failed ".
1697: "while attempting put\n";
1698: }
1699: } else {
1700: print $client "error: ".($!)
1701: ." tie(GDBM) Failed ".
1702: "while attempting put\n";
1703: }
1704: } else {
1705: print $client "refused\n";
1706: }
1707: } else {
1708: Reply($client, "refused\n", $userinput);
1709:
1710: }
1711: # ------------------------------------------------------------------- inc
1712: } elsif ($userinput =~ /^inc:/) {
1713: if(isClient) {
1714: my ($cmd,$udom,$uname,$namespace,$what)
1715: =split(/:/,$userinput);
1716: $namespace=~s/\//\_/g;
1717: $namespace=~s/\W//g;
1718: if ($namespace ne 'roles') {
1719: chomp($what);
1720: my $proname=propath($udom,$uname);
1721: my $now=time;
1722: unless ($namespace=~/^nohist\_/) {
1723: my $hfh;
1724: if (
1725: $hfh=IO::File->new(">>$proname/$namespace.hist")
1726: ) { print $hfh "P:$now:$what\n"; }
1727: }
1728: my @pairs=split(/\&/,$what);
1729: my %hash;
1730: if (tie(%hash,'GDBM_File',
1731: "$proname/$namespace.db",
1732: &GDBM_WRCREAT(),0640)) {
1733: foreach my $pair (@pairs) {
1734: my ($key,$value)=split(/=/,$pair);
1735: # We could check that we have a number...
1736: if (! defined($value) || $value eq '') {
1737: $value = 1;
1738: }
1739: $hash{$key}+=$value;
1.161 foxr 1740: }
1741: if (untie(%hash)) {
1742: print $client "ok\n";
1743: } else {
1744: print $client "error: ".($!+0)
1745: ." untie(GDBM) failed ".
1746: "while attempting put\n";
1747: }
1748: } else {
1749: print $client "error: ".($!)
1750: ." tie(GDBM) Failed ".
1751: "while attempting put\n";
1752: }
1753: } else {
1754: print $client "refused\n";
1755: }
1756: } else {
1757: Reply($client, "refused\n", $userinput);
1758:
1759: }
1.6 www 1760: # -------------------------------------------------------------------- rolesput
1.161 foxr 1761: } elsif ($userinput =~ /^rolesput/) {
1762: if(isClient) {
1763: &Debug("rolesput");
1764: if ($wasenc==1) {
1765: my ($cmd,$exedom,$exeuser,$udom,$uname,$what)
1766: =split(/:/,$userinput);
1767: &Debug("cmd = ".$cmd." exedom= ".$exedom.
1768: "user = ".$exeuser." udom=".$udom.
1769: "what = ".$what);
1770: my $namespace='roles';
1771: chomp($what);
1772: my $proname=propath($udom,$uname);
1773: my $now=time;
1774: {
1775: my $hfh;
1776: if (
1777: $hfh=IO::File->new(">>$proname/$namespace.hist")
1778: ) {
1779: print $hfh "P:$now:$exedom:$exeuser:$what\n";
1780: }
1781: }
1782: my @pairs=split(/\&/,$what);
1783: my %hash;
1784: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
1785: foreach my $pair (@pairs) {
1786: my ($key,$value)=split(/=/,$pair);
1787: &ManagePermissions($key, $udom, $uname,
1788: &GetAuthType( $udom,
1789: $uname));
1790: $hash{$key}=$value;
1791: }
1792: if (untie(%hash)) {
1793: print $client "ok\n";
1794: } else {
1795: print $client "error: ".($!+0)
1796: ." untie(GDBM) Failed ".
1797: "while attempting rolesput\n";
1798: }
1799: } else {
1800: print $client "error: ".($!+0)
1801: ." tie(GDBM) Failed ".
1802: "while attempting rolesput\n";
1803: }
1804: } else {
1805: print $client "refused\n";
1806: }
1807: } else {
1808: Reply($client, "refused\n", $userinput);
1809:
1810: }
1.117 www 1811: # -------------------------------------------------------------------- rolesdel
1.161 foxr 1812: } elsif ($userinput =~ /^rolesdel/) {
1813: if(isClient) {
1814: &Debug("rolesdel");
1815: if ($wasenc==1) {
1816: my ($cmd,$exedom,$exeuser,$udom,$uname,$what)
1817: =split(/:/,$userinput);
1818: &Debug("cmd = ".$cmd." exedom= ".$exedom.
1819: "user = ".$exeuser." udom=".$udom.
1820: "what = ".$what);
1821: my $namespace='roles';
1822: chomp($what);
1823: my $proname=propath($udom,$uname);
1824: my $now=time;
1825: {
1826: my $hfh;
1827: if (
1828: $hfh=IO::File->new(">>$proname/$namespace.hist")
1829: ) {
1830: print $hfh "D:$now:$exedom:$exeuser:$what\n";
1831: }
1832: }
1833: my @rolekeys=split(/\&/,$what);
1834: my %hash;
1835: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
1836: foreach my $key (@rolekeys) {
1837: delete $hash{$key};
1838: }
1839: if (untie(%hash)) {
1840: print $client "ok\n";
1841: } else {
1842: print $client "error: ".($!+0)
1843: ." untie(GDBM) Failed ".
1844: "while attempting rolesdel\n";
1845: }
1846: } else {
1847: print $client "error: ".($!+0)
1848: ." tie(GDBM) Failed ".
1849: "while attempting rolesdel\n";
1850: }
1851: } else {
1852: print $client "refused\n";
1853: }
1854: } else {
1855: Reply($client, "refused\n", $userinput);
1856:
1857: }
1.1 albertel 1858: # ------------------------------------------------------------------------- get
1.161 foxr 1859: } elsif ($userinput =~ /^get/) {
1860: if(isClient) {
1861: my ($cmd,$udom,$uname,$namespace,$what)
1862: =split(/:/,$userinput);
1863: $namespace=~s/\//\_/g;
1864: $namespace=~s/\W//g;
1865: chomp($what);
1866: my @queries=split(/\&/,$what);
1867: my $proname=propath($udom,$uname);
1868: my $qresult='';
1869: my %hash;
1870: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
1871: for (my $i=0;$i<=$#queries;$i++) {
1872: $qresult.="$hash{$queries[$i]}&";
1873: }
1874: if (untie(%hash)) {
1875: $qresult=~s/\&$//;
1876: print $client "$qresult\n";
1877: } else {
1878: print $client "error: ".($!+0)
1879: ." untie(GDBM) Failed ".
1880: "while attempting get\n";
1881: }
1882: } else {
1883: if ($!+0 == 2) {
1884: print $client "error:No such file or ".
1885: "GDBM reported bad block error\n";
1886: } else {
1887: print $client "error: ".($!+0)
1888: ." tie(GDBM) Failed ".
1889: "while attempting get\n";
1890: }
1891: }
1892: } else {
1893: Reply($client, "refused\n", $userinput);
1894:
1895: }
1.1 albertel 1896: # ------------------------------------------------------------------------ eget
1.161 foxr 1897: } elsif ($userinput =~ /^eget/) {
1898: if (isClient) {
1899: my ($cmd,$udom,$uname,$namespace,$what)
1900: =split(/:/,$userinput);
1901: $namespace=~s/\//\_/g;
1902: $namespace=~s/\W//g;
1903: chomp($what);
1904: my @queries=split(/\&/,$what);
1905: my $proname=propath($udom,$uname);
1906: my $qresult='';
1907: my %hash;
1908: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
1909: for (my $i=0;$i<=$#queries;$i++) {
1910: $qresult.="$hash{$queries[$i]}&";
1911: }
1912: if (untie(%hash)) {
1913: $qresult=~s/\&$//;
1914: if ($cipher) {
1915: my $cmdlength=length($qresult);
1916: $qresult.=" ";
1917: my $encqresult='';
1918: for
1919: (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
1920: $encqresult.=
1921: unpack("H16",
1922: $cipher->encrypt(substr($qresult,$encidx,8)));
1923: }
1924: print $client "enc:$cmdlength:$encqresult\n";
1925: } else {
1926: print $client "error:no_key\n";
1927: }
1928: } else {
1929: print $client "error: ".($!+0)
1930: ." untie(GDBM) Failed ".
1931: "while attempting eget\n";
1932: }
1933: } else {
1934: print $client "error: ".($!+0)
1935: ." tie(GDBM) Failed ".
1936: "while attempting eget\n";
1937: }
1938: } else {
1939: Reply($client, "refused\n", $userinput);
1940:
1941: }
1.1 albertel 1942: # ------------------------------------------------------------------------- del
1.161 foxr 1943: } elsif ($userinput =~ /^del/) {
1944: if(isClient) {
1945: my ($cmd,$udom,$uname,$namespace,$what)
1946: =split(/:/,$userinput);
1947: $namespace=~s/\//\_/g;
1948: $namespace=~s/\W//g;
1949: chomp($what);
1950: my $proname=propath($udom,$uname);
1951: my $now=time;
1952: unless ($namespace=~/^nohist\_/) {
1953: my $hfh;
1954: if (
1955: $hfh=IO::File->new(">>$proname/$namespace.hist")
1956: ) { print $hfh "D:$now:$what\n"; }
1957: }
1958: my @keys=split(/\&/,$what);
1959: my %hash;
1960: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
1961: foreach my $key (@keys) {
1.165.2.1! albertel 1962: delete($hash{&unescape($key)});
1.161 foxr 1963: }
1964: if (untie(%hash)) {
1965: print $client "ok\n";
1966: } else {
1967: print $client "error: ".($!+0)
1968: ." untie(GDBM) Failed ".
1969: "while attempting del\n";
1970: }
1971: } else {
1972: print $client "error: ".($!+0)
1973: ." tie(GDBM) Failed ".
1974: "while attempting del\n";
1975: }
1976: } else {
1977: Reply($client, "refused\n", $userinput);
1978:
1979: }
1.1 albertel 1980: # ------------------------------------------------------------------------ keys
1.161 foxr 1981: } elsif ($userinput =~ /^keys/) {
1982: if(isClient) {
1983: my ($cmd,$udom,$uname,$namespace)
1984: =split(/:/,$userinput);
1985: $namespace=~s/\//\_/g;
1986: $namespace=~s/\W//g;
1987: my $proname=propath($udom,$uname);
1988: my $qresult='';
1989: my %hash;
1990: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
1991: foreach my $key (keys %hash) {
1992: $qresult.="$key&";
1993: }
1994: if (untie(%hash)) {
1995: $qresult=~s/\&$//;
1996: print $client "$qresult\n";
1997: } else {
1998: print $client "error: ".($!+0)
1999: ." untie(GDBM) Failed ".
2000: "while attempting keys\n";
2001: }
2002: } else {
2003: print $client "error: ".($!+0)
2004: ." tie(GDBM) Failed ".
2005: "while attempting keys\n";
2006: }
2007: } else {
2008: Reply($client, "refused\n", $userinput);
2009:
2010: }
1.105 matthew 2011: # ----------------------------------------------------------------- dumpcurrent
1.161 foxr 2012: } elsif ($userinput =~ /^currentdump/) {
2013: if (isClient) {
2014: my ($cmd,$udom,$uname,$namespace)
2015: =split(/:/,$userinput);
2016: $namespace=~s/\//\_/g;
2017: $namespace=~s/\W//g;
2018: my $qresult='';
2019: my $proname=propath($udom,$uname);
2020: my %hash;
2021: if (tie(%hash,'GDBM_File',
2022: "$proname/$namespace.db",
2023: &GDBM_READER(),0640)) {
2024: # Structure of %data:
2025: # $data{$symb}->{$parameter}=$value;
2026: # $data{$symb}->{'v.'.$parameter}=$version;
2027: # since $parameter will be unescaped, we do not
2028: # have to worry about silly parameter names...
2029: my %data = ();
2030: while (my ($key,$value) = each(%hash)) {
2031: my ($v,$symb,$param) = split(/:/,$key);
2032: next if ($v eq 'version' || $symb eq 'keys');
2033: next if (exists($data{$symb}) &&
2034: exists($data{$symb}->{$param}) &&
2035: $data{$symb}->{'v.'.$param} > $v);
2036: $data{$symb}->{$param}=$value;
2037: $data{$symb}->{'v.'.$param}=$v;
2038: }
2039: if (untie(%hash)) {
2040: while (my ($symb,$param_hash) = each(%data)) {
2041: while(my ($param,$value) = each (%$param_hash)){
2042: next if ($param =~ /^v\./);
2043: $qresult.=$symb.':'.$param.'='.$value.'&';
2044: }
2045: }
2046: chop($qresult);
2047: print $client "$qresult\n";
2048: } else {
2049: print $client "error: ".($!+0)
2050: ." untie(GDBM) Failed ".
2051: "while attempting currentdump\n";
2052: }
2053: } else {
2054: print $client "error: ".($!+0)
2055: ." tie(GDBM) Failed ".
2056: "while attempting currentdump\n";
2057: }
2058: } else {
2059: Reply($client, "refused\n", $userinput);
2060: }
1.1 albertel 2061: # ------------------------------------------------------------------------ dump
1.161 foxr 2062: } elsif ($userinput =~ /^dump/) {
2063: if(isClient) {
2064: my ($cmd,$udom,$uname,$namespace,$regexp)
2065: =split(/:/,$userinput);
2066: $namespace=~s/\//\_/g;
2067: $namespace=~s/\W//g;
2068: if (defined($regexp)) {
2069: $regexp=&unescape($regexp);
2070: } else {
2071: $regexp='.';
2072: }
2073: my $qresult='';
2074: my $proname=propath($udom,$uname);
2075: my %hash;
2076: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
2077: study($regexp);
2078: while (my ($key,$value) = each(%hash)) {
2079: if ($regexp eq '.') {
2080: $qresult.=$key.'='.$value.'&';
2081: } else {
2082: my $unescapeKey = &unescape($key);
2083: if (eval('$unescapeKey=~/$regexp/')) {
2084: $qresult.="$key=$value&";
2085: }
2086: }
2087: }
2088: if (untie(%hash)) {
2089: chop($qresult);
2090: print $client "$qresult\n";
2091: } else {
2092: print $client "error: ".($!+0)
2093: ." untie(GDBM) Failed ".
1.111 matthew 2094: "while attempting dump\n";
1.161 foxr 2095: }
2096: } else {
2097: print $client "error: ".($!+0)
2098: ." tie(GDBM) Failed ".
2099: "while attempting dump\n";
2100: }
2101: } else {
2102: Reply($client, "refused\n", $userinput);
2103:
2104: }
1.7 www 2105: # ----------------------------------------------------------------------- store
1.161 foxr 2106: } elsif ($userinput =~ /^store/) {
2107: if(isClient) {
2108: my ($cmd,$udom,$uname,$namespace,$rid,$what)
2109: =split(/:/,$userinput);
2110: $namespace=~s/\//\_/g;
2111: $namespace=~s/\W//g;
2112: if ($namespace ne 'roles') {
2113: chomp($what);
2114: my $proname=propath($udom,$uname);
2115: my $now=time;
2116: unless ($namespace=~/^nohist\_/) {
2117: my $hfh;
2118: if (
2119: $hfh=IO::File->new(">>$proname/$namespace.hist")
2120: ) { print $hfh "P:$now:$rid:$what\n"; }
2121: }
2122: my @pairs=split(/\&/,$what);
2123: my %hash;
2124: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
2125: my @previouskeys=split(/&/,$hash{"keys:$rid"});
2126: my $key;
2127: $hash{"version:$rid"}++;
2128: my $version=$hash{"version:$rid"};
2129: my $allkeys='';
2130: foreach my $pair (@pairs) {
2131: my ($key,$value)=split(/=/,$pair);
2132: $allkeys.=$key.':';
2133: $hash{"$version:$rid:$key"}=$value;
2134: }
2135: $hash{"$version:$rid:timestamp"}=$now;
2136: $allkeys.='timestamp';
2137: $hash{"$version:keys:$rid"}=$allkeys;
2138: if (untie(%hash)) {
2139: print $client "ok\n";
2140: } else {
2141: print $client "error: ".($!+0)
2142: ." untie(GDBM) Failed ".
2143: "while attempting store\n";
2144: }
2145: } else {
2146: print $client "error: ".($!+0)
2147: ." tie(GDBM) Failed ".
2148: "while attempting store\n";
2149: }
2150: } else {
2151: print $client "refused\n";
2152: }
2153: } else {
2154: Reply($client, "refused\n", $userinput);
2155:
2156: }
1.7 www 2157: # --------------------------------------------------------------------- restore
1.161 foxr 2158: } elsif ($userinput =~ /^restore/) {
2159: if(isClient) {
2160: my ($cmd,$udom,$uname,$namespace,$rid)
2161: =split(/:/,$userinput);
2162: $namespace=~s/\//\_/g;
2163: $namespace=~s/\W//g;
2164: chomp($rid);
2165: my $proname=propath($udom,$uname);
2166: my $qresult='';
2167: my %hash;
2168: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
2169: my $version=$hash{"version:$rid"};
2170: $qresult.="version=$version&";
2171: my $scope;
2172: for ($scope=1;$scope<=$version;$scope++) {
2173: my $vkeys=$hash{"$scope:keys:$rid"};
2174: my @keys=split(/:/,$vkeys);
2175: my $key;
2176: $qresult.="$scope:keys=$vkeys&";
2177: foreach $key (@keys) {
2178: $qresult.="$scope:$key=".$hash{"$scope:$rid:$key"}."&";
2179: }
2180: }
2181: if (untie(%hash)) {
2182: $qresult=~s/\&$//;
2183: print $client "$qresult\n";
2184: } else {
2185: print $client "error: ".($!+0)
2186: ." untie(GDBM) Failed ".
2187: "while attempting restore\n";
2188: }
2189: } else {
2190: print $client "error: ".($!+0)
2191: ." tie(GDBM) Failed ".
2192: "while attempting restore\n";
2193: }
2194: } else {
2195: Reply($client, "refused\n", $userinput);
2196:
2197: }
1.86 www 2198: # -------------------------------------------------------------------- chatsend
1.161 foxr 2199: } elsif ($userinput =~ /^chatsend/) {
2200: if(isClient) {
2201: my ($cmd,$cdom,$cnum,$newpost)=split(/\:/,$userinput);
2202: &chatadd($cdom,$cnum,$newpost);
2203: print $client "ok\n";
2204: } else {
2205: Reply($client, "refused\n", $userinput);
2206:
2207: }
1.86 www 2208: # -------------------------------------------------------------------- chatretr
1.161 foxr 2209: } elsif ($userinput =~ /^chatretr/) {
2210: if(isClient) {
2211: my
2212: ($cmd,$cdom,$cnum,$udom,$uname)=split(/\:/,$userinput);
2213: my $reply='';
2214: foreach (&getchat($cdom,$cnum,$udom,$uname)) {
2215: $reply.=&escape($_).':';
2216: }
2217: $reply=~s/\:$//;
2218: print $client $reply."\n";
2219: } else {
2220: Reply($client, "refused\n", $userinput);
2221:
2222: }
1.12 harris41 2223: # ------------------------------------------------------------------- querysend
1.161 foxr 2224: } elsif ($userinput =~ /^querysend/) {
2225: if(isClient) {
2226: my ($cmd,$query,
2227: $arg1,$arg2,$arg3)=split(/\:/,$userinput);
2228: $query=~s/\n*$//g;
2229: print $client "".
2230: sqlreply("$clientname\&$query".
2231: "\&$arg1"."\&$arg2"."\&$arg3")."\n";
2232: } else {
2233: Reply($client, "refused\n", $userinput);
2234:
2235: }
1.12 harris41 2236: # ------------------------------------------------------------------ queryreply
1.161 foxr 2237: } elsif ($userinput =~ /^queryreply/) {
2238: if(isClient) {
2239: my ($cmd,$id,$reply)=split(/:/,$userinput);
2240: my $store;
2241: my $execdir=$perlvar{'lonDaemons'};
2242: if ($store=IO::File->new(">$execdir/tmp/$id")) {
2243: $reply=~s/\&/\n/g;
2244: print $store $reply;
2245: close $store;
2246: my $store2=IO::File->new(">$execdir/tmp/$id.end");
2247: print $store2 "done\n";
2248: close $store2;
2249: print $client "ok\n";
2250: }
2251: else {
2252: print $client "error: ".($!+0)
2253: ." IO::File->new Failed ".
2254: "while attempting queryreply\n";
2255: }
2256: } else {
2257: Reply($client, "refused\n", $userinput);
2258:
2259: }
1.118 www 2260: # ----------------------------------------------------------------- courseidput
1.161 foxr 2261: } elsif ($userinput =~ /^courseidput/) {
2262: if(isClient) {
2263: my ($cmd,$udom,$what)=split(/:/,$userinput);
2264: chomp($what);
2265: $udom=~s/\W//g;
2266: my $proname=
2267: "$perlvar{'lonUsersDir'}/$udom/nohist_courseids";
2268: my $now=time;
2269: my @pairs=split(/\&/,$what);
2270: my %hash;
2271: if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {
2272: foreach my $pair (@pairs) {
2273: my ($key,$value)=split(/=/,$pair);
2274: $hash{$key}=$value.':'.$now;
2275: }
2276: if (untie(%hash)) {
2277: print $client "ok\n";
2278: } else {
2279: print $client "error: ".($!+0)
2280: ." untie(GDBM) Failed ".
2281: "while attempting courseidput\n";
2282: }
2283: } else {
2284: print $client "error: ".($!+0)
2285: ." tie(GDBM) Failed ".
2286: "while attempting courseidput\n";
2287: }
2288: } else {
2289: Reply($client, "refused\n", $userinput);
2290:
2291: }
1.118 www 2292: # ---------------------------------------------------------------- courseiddump
1.161 foxr 2293: } elsif ($userinput =~ /^courseiddump/) {
2294: if(isClient) {
2295: my ($cmd,$udom,$since,$description)
2296: =split(/:/,$userinput);
2297: if (defined($description)) {
2298: $description=&unescape($description);
2299: } else {
2300: $description='.';
2301: }
2302: unless (defined($since)) { $since=0; }
2303: my $qresult='';
2304: my $proname=
2305: "$perlvar{'lonUsersDir'}/$udom/nohist_courseids";
2306: my %hash;
2307: if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) {
2308: while (my ($key,$value) = each(%hash)) {
2309: my ($descr,$lasttime)=split(/\:/,$value);
2310: if ($lasttime<$since) { next; }
2311: if ($description eq '.') {
2312: $qresult.=$key.'='.$descr.'&';
2313: } else {
2314: my $unescapeVal = &unescape($descr);
2315: if (eval('$unescapeVal=~/$description/i')) {
2316: $qresult.="$key=$descr&";
2317: }
2318: }
2319: }
2320: if (untie(%hash)) {
2321: chop($qresult);
2322: print $client "$qresult\n";
2323: } else {
2324: print $client "error: ".($!+0)
2325: ." untie(GDBM) Failed ".
2326: "while attempting courseiddump\n";
2327: }
2328: } else {
2329: print $client "error: ".($!+0)
2330: ." tie(GDBM) Failed ".
2331: "while attempting courseiddump\n";
2332: }
2333: } else {
2334: Reply($client, "refused\n", $userinput);
2335:
2336: }
1.1 albertel 2337: # ----------------------------------------------------------------------- idput
1.161 foxr 2338: } elsif ($userinput =~ /^idput/) {
2339: if(isClient) {
2340: my ($cmd,$udom,$what)=split(/:/,$userinput);
2341: chomp($what);
2342: $udom=~s/\W//g;
2343: my $proname="$perlvar{'lonUsersDir'}/$udom/ids";
2344: my $now=time;
2345: {
2346: my $hfh;
2347: if (
2348: $hfh=IO::File->new(">>$proname.hist")
2349: ) { print $hfh "P:$now:$what\n"; }
2350: }
2351: my @pairs=split(/\&/,$what);
2352: my %hash;
2353: if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {
2354: foreach my $pair (@pairs) {
2355: my ($key,$value)=split(/=/,$pair);
2356: $hash{$key}=$value;
2357: }
2358: if (untie(%hash)) {
2359: print $client "ok\n";
2360: } else {
2361: print $client "error: ".($!+0)
2362: ." untie(GDBM) Failed ".
2363: "while attempting idput\n";
2364: }
2365: } else {
2366: print $client "error: ".($!+0)
2367: ." tie(GDBM) Failed ".
2368: "while attempting idput\n";
2369: }
2370: } else {
2371: Reply($client, "refused\n", $userinput);
2372:
2373: }
1.1 albertel 2374: # ----------------------------------------------------------------------- idget
1.161 foxr 2375: } elsif ($userinput =~ /^idget/) {
2376: if(isClient) {
2377: my ($cmd,$udom,$what)=split(/:/,$userinput);
2378: chomp($what);
2379: $udom=~s/\W//g;
2380: my $proname="$perlvar{'lonUsersDir'}/$udom/ids";
2381: my @queries=split(/\&/,$what);
2382: my $qresult='';
2383: my %hash;
2384: if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) {
2385: for (my $i=0;$i<=$#queries;$i++) {
2386: $qresult.="$hash{$queries[$i]}&";
2387: }
2388: if (untie(%hash)) {
2389: $qresult=~s/\&$//;
2390: print $client "$qresult\n";
2391: } else {
2392: print $client "error: ".($!+0)
2393: ." untie(GDBM) Failed ".
2394: "while attempting idget\n";
2395: }
2396: } else {
2397: print $client "error: ".($!+0)
2398: ." tie(GDBM) Failed ".
2399: "while attempting idget\n";
2400: }
2401: } else {
2402: Reply($client, "refused\n", $userinput);
2403:
2404: }
1.13 www 2405: # ---------------------------------------------------------------------- tmpput
1.161 foxr 2406: } elsif ($userinput =~ /^tmpput/) {
2407: if(isClient) {
2408: my ($cmd,$what)=split(/:/,$userinput);
2409: my $store;
2410: $tmpsnum++;
2411: my $id=$$.'_'.$clientip.'_'.$tmpsnum;
2412: $id=~s/\W/\_/g;
2413: $what=~s/\n//g;
2414: my $execdir=$perlvar{'lonDaemons'};
2415: if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) {
2416: print $store $what;
2417: close $store;
2418: print $client "$id\n";
2419: }
2420: else {
2421: print $client "error: ".($!+0)
2422: ."IO::File->new Failed ".
2423: "while attempting tmpput\n";
2424: }
2425: } else {
2426: Reply($client, "refused\n", $userinput);
2427:
2428: }
2429:
1.13 www 2430: # ---------------------------------------------------------------------- tmpget
1.161 foxr 2431: } elsif ($userinput =~ /^tmpget/) {
2432: if(isClient) {
2433: my ($cmd,$id)=split(/:/,$userinput);
2434: chomp($id);
2435: $id=~s/\W/\_/g;
2436: my $store;
2437: my $execdir=$perlvar{'lonDaemons'};
2438: if ($store=IO::File->new("$execdir/tmp/$id.tmp")) {
2439: my $reply=<$store>;
2440: print $client "$reply\n";
2441: close $store;
2442: }
2443: else {
2444: print $client "error: ".($!+0)
2445: ."IO::File->new Failed ".
2446: "while attempting tmpget\n";
2447: }
2448: } else {
2449: Reply($client, "refused\n", $userinput);
2450:
2451: }
1.110 www 2452: # ---------------------------------------------------------------------- tmpdel
1.161 foxr 2453: } elsif ($userinput =~ /^tmpdel/) {
2454: if(isClient) {
2455: my ($cmd,$id)=split(/:/,$userinput);
2456: chomp($id);
2457: $id=~s/\W/\_/g;
2458: my $execdir=$perlvar{'lonDaemons'};
2459: if (unlink("$execdir/tmp/$id.tmp")) {
2460: print $client "ok\n";
2461: } else {
2462: print $client "error: ".($!+0)
2463: ."Unlink tmp Failed ".
2464: "while attempting tmpdel\n";
2465: }
2466: } else {
2467: Reply($client, "refused\n", $userinput);
2468:
2469: }
1.5 www 2470: # -------------------------------------------------------------------------- ls
1.161 foxr 2471: } elsif ($userinput =~ /^ls/) {
2472: if(isClient) {
2473: my ($cmd,$ulsdir)=split(/:/,$userinput);
2474: my $ulsout='';
2475: my $ulsfn;
2476: if (-e $ulsdir) {
2477: if(-d $ulsdir) {
2478: if (opendir(LSDIR,$ulsdir)) {
2479: while ($ulsfn=readdir(LSDIR)) {
2480: my @ulsstats=stat($ulsdir.'/'.$ulsfn);
2481: $ulsout.=$ulsfn.'&'.
2482: join('&',@ulsstats).':';
2483: }
2484: closedir(LSDIR);
2485: }
2486: } else {
2487: my @ulsstats=stat($ulsdir);
2488: $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':';
2489: }
2490: } else {
2491: $ulsout='no_such_dir';
2492: }
2493: if ($ulsout eq '') { $ulsout='empty'; }
2494: print $client "$ulsout\n";
2495: } else {
2496: Reply($client, "refused\n", $userinput);
2497:
2498: }
1.136 www 2499: # ----------------------------------------------------------------- setannounce
1.161 foxr 2500: } elsif ($userinput =~ /^setannounce/) {
2501: if (isClient) {
2502: my ($cmd,$announcement)=split(/:/,$userinput);
2503: chomp($announcement);
2504: $announcement=&unescape($announcement);
2505: if (my $store=IO::File->new('>'.$perlvar{'lonDocRoot'}.
2506: '/announcement.txt')) {
2507: print $store $announcement;
2508: close $store;
2509: print $client "ok\n";
2510: } else {
2511: print $client "error: ".($!+0)."\n";
2512: }
2513: } else {
2514: Reply($client, "refused\n", $userinput);
2515:
2516: }
1.51 www 2517: # ------------------------------------------------------------------ Hanging up
1.161 foxr 2518: } elsif (($userinput =~ /^exit/) ||
2519: ($userinput =~ /^init/)) { # no restrictions.
2520: &logthis(
2521: "Client $clientip ($clientname) hanging up: $userinput");
2522: print $client "bye\n";
1.164 foxr 2523: $client->shutdown(2); # shutdown the socket forcibly.
1.161 foxr 2524: $client->close();
2525: last;
2526:
2527: # ---------------------------------- set current host/domain
2528: } elsif ($userinput =~ /^sethost:/) {
2529: if (isClient) {
2530: print $client &sethost($userinput)."\n";
2531: } else {
2532: print $client "refused\n";
2533: }
2534: #---------------------------------- request file (?) version.
2535: } elsif ($userinput =~/^version:/) {
2536: if (isClient) {
2537: print $client &version($userinput)."\n";
2538: } else {
2539: print $client "refused\n";
2540: }
1.1 albertel 2541: # ------------------------------------------------------------- unknown command
1.161 foxr 2542:
2543: } else {
2544: # unknown command
2545: print $client "unknown_cmd\n";
2546: }
1.58 www 2547: # -------------------------------------------------------------------- complete
1.161 foxr 2548: alarm(0);
2549: &status('Listening to '.$clientname);
2550: }
1.59 www 2551: # --------------------------------------------- client unknown or fishy, refuse
1.161 foxr 2552: } else {
2553: print $client "refused\n";
2554: $client->close();
2555: &logthis("<font color=blue>WARNING: "
2556: ."Rejected client $clientip, closing connection</font>");
2557: }
2558: }
2559:
1.1 albertel 2560: # =============================================================================
1.161 foxr 2561:
2562: &logthis("<font color=red>CRITICAL: "
2563: ."Disconnect from $clientip ($clientname)</font>");
2564:
2565:
2566: # this exit is VERY important, otherwise the child will become
2567: # a producer of more and more children, forking yourself into
2568: # process death.
2569: exit;
1.106 foxr 2570:
1.78 foxr 2571: }
2572:
2573:
2574: #
2575: # Checks to see if the input roleput request was to set
2576: # an author role. If so, invokes the lchtmldir script to set
2577: # up a correct public_html
2578: # Parameters:
2579: # request - The request sent to the rolesput subchunk.
2580: # We're looking for /domain/_au
2581: # domain - The domain in which the user is having roles doctored.
2582: # user - Name of the user for which the role is being put.
2583: # authtype - The authentication type associated with the user.
2584: #
2585: sub ManagePermissions
2586: {
2587: my $request = shift;
2588: my $domain = shift;
2589: my $user = shift;
2590: my $authtype= shift;
2591:
2592: # See if the request is of the form /$domain/_au
1.134 albertel 2593: &logthis("ruequest is $request");
1.78 foxr 2594: if($request =~ /^(\/$domain\/_au)$/) { # It's an author rolesput...
2595: my $execdir = $perlvar{'lonDaemons'};
2596: my $userhome= "/home/$user" ;
1.134 albertel 2597: &logthis("system $execdir/lchtmldir $userhome $user $authtype");
1.78 foxr 2598: system("$execdir/lchtmldir $userhome $user $authtype");
2599: }
2600: }
2601: #
2602: # GetAuthType - Determines the authorization type of a user in a domain.
2603:
2604: # Returns the authorization type or nouser if there is no such user.
2605: #
2606: sub GetAuthType
2607: {
2608: my $domain = shift;
2609: my $user = shift;
2610:
1.79 foxr 2611: Debug("GetAuthType( $domain, $user ) \n");
1.78 foxr 2612: my $proname = &propath($domain, $user);
2613: my $passwdfile = "$proname/passwd";
2614: if( -e $passwdfile ) {
2615: my $pf = IO::File->new($passwdfile);
2616: my $realpassword = <$pf>;
2617: chomp($realpassword);
1.79 foxr 2618: Debug("Password info = $realpassword\n");
1.78 foxr 2619: my ($authtype, $contentpwd) = split(/:/, $realpassword);
1.79 foxr 2620: Debug("Authtype = $authtype, content = $contentpwd\n");
1.78 foxr 2621: my $availinfo = '';
1.91 albertel 2622: if($authtype eq 'krb4' or $authtype eq 'krb5') {
1.78 foxr 2623: $availinfo = $contentpwd;
2624: }
1.79 foxr 2625:
1.78 foxr 2626: return "$authtype:$availinfo";
2627: }
2628: else {
1.79 foxr 2629: Debug("Returning nouser");
1.78 foxr 2630: return "nouser";
2631: }
1.1 albertel 2632: }
2633:
1.84 albertel 2634: sub addline {
2635: my ($fname,$hostid,$ip,$newline)=@_;
2636: my $contents;
2637: my $found=0;
2638: my $expr='^'.$hostid.':'.$ip.':';
2639: $expr =~ s/\./\\\./g;
1.134 albertel 2640: my $sh;
1.84 albertel 2641: if ($sh=IO::File->new("$fname.subscription")) {
2642: while (my $subline=<$sh>) {
2643: if ($subline !~ /$expr/) {$contents.= $subline;} else {$found=1;}
2644: }
2645: $sh->close();
2646: }
2647: $sh=IO::File->new(">$fname.subscription");
2648: if ($contents) { print $sh $contents; }
2649: if ($newline) { print $sh $newline; }
2650: $sh->close();
2651: return $found;
1.86 www 2652: }
2653:
2654: sub getchat {
1.122 www 2655: my ($cdom,$cname,$udom,$uname)=@_;
1.87 www 2656: my %hash;
2657: my $proname=&propath($cdom,$cname);
2658: my @entries=();
1.88 albertel 2659: if (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db",
2660: &GDBM_READER(),0640)) {
2661: @entries=map { $_.':'.$hash{$_} } sort keys %hash;
2662: untie %hash;
1.123 www 2663: }
1.124 www 2664: my @participants=();
1.134 albertel 2665: my $cutoff=time-60;
1.123 www 2666: if (tie(%hash,'GDBM_File',"$proname/nohist_inchatroom.db",
1.124 www 2667: &GDBM_WRCREAT(),0640)) {
2668: $hash{$uname.':'.$udom}=time;
1.123 www 2669: foreach (sort keys %hash) {
2670: if ($hash{$_}>$cutoff) {
1.124 www 2671: $participants[$#participants+1]='active_participant:'.$_;
1.123 www 2672: }
2673: }
2674: untie %hash;
1.86 www 2675: }
1.124 www 2676: return (@participants,@entries);
1.86 www 2677: }
2678:
2679: sub chatadd {
1.88 albertel 2680: my ($cdom,$cname,$newchat)=@_;
2681: my %hash;
2682: my $proname=&propath($cdom,$cname);
2683: my @entries=();
1.142 www 2684: my $time=time;
1.88 albertel 2685: if (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db",
2686: &GDBM_WRCREAT(),0640)) {
2687: @entries=map { $_.':'.$hash{$_} } sort keys %hash;
2688: my ($lastid)=($entries[$#entries]=~/^(\w+)\:/);
2689: my ($thentime,$idnum)=split(/\_/,$lastid);
2690: my $newid=$time.'_000000';
2691: if ($thentime==$time) {
2692: $idnum=~s/^0+//;
2693: $idnum++;
2694: $idnum=substr('000000'.$idnum,-6,6);
2695: $newid=$time.'_'.$idnum;
2696: }
2697: $hash{$newid}=$newchat;
2698: my $expired=$time-3600;
2699: foreach (keys %hash) {
2700: my ($thistime)=($_=~/(\d+)\_/);
2701: if ($thistime<$expired) {
1.89 www 2702: delete $hash{$_};
1.88 albertel 2703: }
2704: }
2705: untie %hash;
1.142 www 2706: }
2707: {
2708: my $hfh;
2709: if ($hfh=IO::File->new(">>$proname/chatroom.log")) {
2710: print $hfh "$time:".&unescape($newchat)."\n";
2711: }
1.86 www 2712: }
1.84 albertel 2713: }
2714:
2715: sub unsub {
2716: my ($fname,$clientip)=@_;
2717: my $result;
1.161 foxr 2718: if (unlink("$fname.$clientname")) {
1.84 albertel 2719: $result="ok\n";
2720: } else {
2721: $result="not_subscribed\n";
2722: }
2723: if (-e "$fname.subscription") {
1.161 foxr 2724: my $found=&addline($fname,$clientname,$clientip,'');
1.84 albertel 2725: if ($found) { $result="ok\n"; }
2726: } else {
2727: if ($result != "ok\n") { $result="not_subscribed\n"; }
2728: }
2729: return $result;
2730: }
2731:
1.101 www 2732: sub currentversion {
2733: my $fname=shift;
2734: my $version=-1;
2735: my $ulsdir='';
2736: if ($fname=~/^(.+)\/[^\/]+$/) {
2737: $ulsdir=$1;
2738: }
1.114 albertel 2739: my ($fnamere1,$fnamere2);
2740: # remove version if already specified
1.101 www 2741: $fname=~s/\.\d+\.(\w+(?:\.meta)*)$/\.$1/;
1.114 albertel 2742: # get the bits that go before and after the version number
2743: if ( $fname=~/^(.*\.)(\w+(?:\.meta)*)$/ ) {
2744: $fnamere1=$1;
2745: $fnamere2='.'.$2;
2746: }
1.101 www 2747: if (-e $fname) { $version=1; }
2748: if (-e $ulsdir) {
1.134 albertel 2749: if(-d $ulsdir) {
2750: if (opendir(LSDIR,$ulsdir)) {
2751: my $ulsfn;
2752: while ($ulsfn=readdir(LSDIR)) {
1.101 www 2753: # see if this is a regular file (ignore links produced earlier)
1.134 albertel 2754: my $thisfile=$ulsdir.'/'.$ulsfn;
2755: unless (-l $thisfile) {
1.160 www 2756: if ($thisfile=~/\Q$fnamere1\E(\d+)\Q$fnamere2\E$/) {
1.134 albertel 2757: if ($1>$version) { $version=$1; }
2758: }
2759: }
2760: }
2761: closedir(LSDIR);
2762: $version++;
2763: }
2764: }
2765: }
2766: return $version;
1.101 www 2767: }
2768:
2769: sub thisversion {
2770: my $fname=shift;
2771: my $version=-1;
2772: if ($fname=~/\.(\d+)\.\w+(?:\.meta)*$/) {
2773: $version=$1;
2774: }
2775: return $version;
2776: }
2777:
1.84 albertel 2778: sub subscribe {
2779: my ($userinput,$clientip)=@_;
2780: my $result;
2781: my ($cmd,$fname)=split(/:/,$userinput);
2782: my $ownership=&ishome($fname);
2783: if ($ownership eq 'owner') {
1.101 www 2784: # explitly asking for the current version?
2785: unless (-e $fname) {
2786: my $currentversion=¤tversion($fname);
2787: if (&thisversion($fname)==$currentversion) {
2788: if ($fname=~/^(.+)\.\d+\.(\w+(?:\.meta)*)$/) {
2789: my $root=$1;
2790: my $extension=$2;
2791: symlink($root.'.'.$extension,
2792: $root.'.'.$currentversion.'.'.$extension);
1.102 www 2793: unless ($extension=~/\.meta$/) {
2794: symlink($root.'.'.$extension.'.meta',
2795: $root.'.'.$currentversion.'.'.$extension.'.meta');
2796: }
1.101 www 2797: }
2798: }
2799: }
1.84 albertel 2800: if (-e $fname) {
2801: if (-d $fname) {
2802: $result="directory\n";
2803: } else {
1.161 foxr 2804: if (-e "$fname.$clientname") {&unsub($fname,$clientip);}
1.134 albertel 2805: my $now=time;
1.161 foxr 2806: my $found=&addline($fname,$clientname,$clientip,
2807: "$clientname:$clientip:$now\n");
1.84 albertel 2808: if ($found) { $result="$fname\n"; }
2809: # if they were subscribed to only meta data, delete that
2810: # subscription, when you subscribe to a file you also get
2811: # the metadata
2812: unless ($fname=~/\.meta$/) { &unsub("$fname.meta",$clientip); }
2813: $fname=~s/\/home\/httpd\/html\/res/raw/;
2814: $fname="http://$thisserver/".$fname;
2815: $result="$fname\n";
2816: }
2817: } else {
2818: $result="not_found\n";
2819: }
2820: } else {
2821: $result="rejected\n";
2822: }
2823: return $result;
2824: }
1.91 albertel 2825:
2826: sub make_passwd_file {
1.98 foxr 2827: my ($uname, $umode,$npass,$passfilename)=@_;
1.91 albertel 2828: my $result="ok\n";
2829: if ($umode eq 'krb4' or $umode eq 'krb5') {
2830: {
2831: my $pf = IO::File->new(">$passfilename");
2832: print $pf "$umode:$npass\n";
2833: }
2834: } elsif ($umode eq 'internal') {
2835: my $salt=time;
2836: $salt=substr($salt,6,2);
2837: my $ncpass=crypt($npass,$salt);
2838: {
2839: &Debug("Creating internal auth");
2840: my $pf = IO::File->new(">$passfilename");
2841: print $pf "internal:$ncpass\n";
2842: }
2843: } elsif ($umode eq 'localauth') {
2844: {
2845: my $pf = IO::File->new(">$passfilename");
2846: print $pf "localauth:$npass\n";
2847: }
2848: } elsif ($umode eq 'unix') {
2849: {
2850: my $execpath="$perlvar{'lonDaemons'}/"."lcuseradd";
2851: {
2852: &Debug("Executing external: ".$execpath);
1.98 foxr 2853: &Debug("user = ".$uname.", Password =". $npass);
1.132 matthew 2854: my $se = IO::File->new("|$execpath > $perlvar{'lonDaemons'}/logs/lcuseradd.log");
1.91 albertel 2855: print $se "$uname\n";
2856: print $se "$npass\n";
2857: print $se "$npass\n";
1.97 foxr 2858: }
2859: my $useraddok = $?;
2860: if($useraddok > 0) {
2861: &logthis("Failed lcuseradd: ".&lcuseraddstrerror($useraddok));
1.91 albertel 2862: }
2863: my $pf = IO::File->new(">$passfilename");
2864: print $pf "unix:\n";
2865: }
2866: } elsif ($umode eq 'none') {
2867: {
2868: my $pf = IO::File->new(">$passfilename");
2869: print $pf "none:\n";
2870: }
2871: } else {
2872: $result="auth_mode_error\n";
2873: }
2874: return $result;
1.121 albertel 2875: }
2876:
2877: sub sethost {
2878: my ($remotereq) = @_;
2879: my (undef,$hostid)=split(/:/,$remotereq);
2880: if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; }
2881: if ($hostip{$perlvar{'lonHostID'}} eq $hostip{$hostid}) {
2882: $currenthostid=$hostid;
2883: $currentdomainid=$hostdom{$hostid};
2884: &logthis("Setting hostid to $hostid, and domain to $currentdomainid");
2885: } else {
2886: &logthis("Requested host id $hostid not an alias of ".
2887: $perlvar{'lonHostID'}." refusing connection");
2888: return 'unable_to_set';
2889: }
2890: return 'ok';
2891: }
2892:
2893: sub version {
2894: my ($userinput)=@_;
2895: $remoteVERSION=(split(/:/,$userinput))[1];
2896: return "version:$VERSION";
1.127 albertel 2897: }
2898:
1.128 albertel 2899: #There is a copy of this in lonnet.pm
1.127 albertel 2900: sub userload {
2901: my $numusers=0;
2902: {
2903: opendir(LONIDS,$perlvar{'lonIDsDir'});
2904: my $filename;
2905: my $curtime=time;
2906: while ($filename=readdir(LONIDS)) {
2907: if ($filename eq '.' || $filename eq '..') {next;}
1.138 albertel 2908: my ($mtime)=(stat($perlvar{'lonIDsDir'}.'/'.$filename))[9];
1.159 albertel 2909: if ($curtime-$mtime < 1800) { $numusers++; }
1.127 albertel 2910: }
2911: closedir(LONIDS);
2912: }
2913: my $userloadpercent=0;
2914: my $maxuserload=$perlvar{'lonUserLoadLim'};
2915: if ($maxuserload) {
1.129 albertel 2916: $userloadpercent=100*$numusers/$maxuserload;
1.127 albertel 2917: }
1.130 albertel 2918: $userloadpercent=sprintf("%.2f",$userloadpercent);
1.127 albertel 2919: return $userloadpercent;
1.91 albertel 2920: }
2921:
1.61 harris41 2922: # ----------------------------------- POD (plain old documentation, CPAN style)
2923:
2924: =head1 NAME
2925:
2926: lond - "LON Daemon" Server (port "LOND" 5663)
2927:
2928: =head1 SYNOPSIS
2929:
1.74 harris41 2930: Usage: B<lond>
2931:
2932: Should only be run as user=www. This is a command-line script which
2933: is invoked by B<loncron>. There is no expectation that a typical user
2934: will manually start B<lond> from the command-line. (In other words,
2935: DO NOT START B<lond> YOURSELF.)
1.61 harris41 2936:
2937: =head1 DESCRIPTION
2938:
1.74 harris41 2939: There are two characteristics associated with the running of B<lond>,
2940: PROCESS MANAGEMENT (starting, stopping, handling child processes)
2941: and SERVER-SIDE ACTIVITIES (password authentication, user creation,
2942: subscriptions, etc). These are described in two large
2943: sections below.
2944:
2945: B<PROCESS MANAGEMENT>
2946:
1.61 harris41 2947: Preforker - server who forks first. Runs as a daemon. HUPs.
2948: Uses IDEA encryption
2949:
1.74 harris41 2950: B<lond> forks off children processes that correspond to the other servers
2951: in the network. Management of these processes can be done at the
2952: parent process level or the child process level.
2953:
2954: B<logs/lond.log> is the location of log messages.
2955:
2956: The process management is now explained in terms of linux shell commands,
2957: subroutines internal to this code, and signal assignments:
2958:
2959: =over 4
2960:
2961: =item *
2962:
2963: PID is stored in B<logs/lond.pid>
2964:
2965: This is the process id number of the parent B<lond> process.
2966:
2967: =item *
2968:
2969: SIGTERM and SIGINT
2970:
2971: Parent signal assignment:
2972: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
2973:
2974: Child signal assignment:
2975: $SIG{INT} = 'DEFAULT'; (and SIGTERM is DEFAULT also)
2976: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber
2977: to restart a new child.)
2978:
2979: Command-line invocations:
2980: B<kill> B<-s> SIGTERM I<PID>
2981: B<kill> B<-s> SIGINT I<PID>
2982:
2983: Subroutine B<HUNTSMAN>:
2984: This is only invoked for the B<lond> parent I<PID>.
2985: This kills all the children, and then the parent.
2986: The B<lonc.pid> file is cleared.
2987:
2988: =item *
2989:
2990: SIGHUP
2991:
2992: Current bug:
2993: This signal can only be processed the first time
2994: on the parent process. Subsequent SIGHUP signals
2995: have no effect.
2996:
2997: Parent signal assignment:
2998: $SIG{HUP} = \&HUPSMAN;
2999:
3000: Child signal assignment:
3001: none (nothing happens)
3002:
3003: Command-line invocations:
3004: B<kill> B<-s> SIGHUP I<PID>
3005:
3006: Subroutine B<HUPSMAN>:
3007: This is only invoked for the B<lond> parent I<PID>,
3008: This kills all the children, and then the parent.
3009: The B<lond.pid> file is cleared.
3010:
3011: =item *
3012:
3013: SIGUSR1
3014:
3015: Parent signal assignment:
3016: $SIG{USR1} = \&USRMAN;
3017:
3018: Child signal assignment:
3019: $SIG{USR1}= \&logstatus;
3020:
3021: Command-line invocations:
3022: B<kill> B<-s> SIGUSR1 I<PID>
3023:
3024: Subroutine B<USRMAN>:
3025: When invoked for the B<lond> parent I<PID>,
3026: SIGUSR1 is sent to all the children, and the status of
3027: each connection is logged.
1.144 foxr 3028:
3029: =item *
3030:
3031: SIGUSR2
3032:
3033: Parent Signal assignment:
3034: $SIG{USR2} = \&UpdateHosts
3035:
3036: Child signal assignment:
3037: NONE
3038:
1.74 harris41 3039:
3040: =item *
3041:
3042: SIGCHLD
3043:
3044: Parent signal assignment:
3045: $SIG{CHLD} = \&REAPER;
3046:
3047: Child signal assignment:
3048: none
3049:
3050: Command-line invocations:
3051: B<kill> B<-s> SIGCHLD I<PID>
3052:
3053: Subroutine B<REAPER>:
3054: This is only invoked for the B<lond> parent I<PID>.
3055: Information pertaining to the child is removed.
3056: The socket port is cleaned up.
3057:
3058: =back
3059:
3060: B<SERVER-SIDE ACTIVITIES>
3061:
3062: Server-side information can be accepted in an encrypted or non-encrypted
3063: method.
3064:
3065: =over 4
3066:
3067: =item ping
3068:
3069: Query a client in the hosts.tab table; "Are you there?"
3070:
3071: =item pong
3072:
3073: Respond to a ping query.
3074:
3075: =item ekey
3076:
3077: Read in encrypted key, make cipher. Respond with a buildkey.
3078:
3079: =item load
3080:
3081: Respond with CPU load based on a computation upon /proc/loadavg.
3082:
3083: =item currentauth
3084:
3085: Reply with current authentication information (only over an
3086: encrypted channel).
3087:
3088: =item auth
3089:
3090: Only over an encrypted channel, reply as to whether a user's
3091: authentication information can be validated.
3092:
3093: =item passwd
3094:
3095: Allow for a password to be set.
3096:
3097: =item makeuser
3098:
3099: Make a user.
3100:
3101: =item passwd
3102:
3103: Allow for authentication mechanism and password to be changed.
3104:
3105: =item home
1.61 harris41 3106:
1.74 harris41 3107: Respond to a question "are you the home for a given user?"
3108:
3109: =item update
3110:
3111: Update contents of a subscribed resource.
3112:
3113: =item unsubscribe
3114:
3115: The server is unsubscribing from a resource.
3116:
3117: =item subscribe
3118:
3119: The server is subscribing to a resource.
3120:
3121: =item log
3122:
3123: Place in B<logs/lond.log>
3124:
3125: =item put
3126:
3127: stores hash in namespace
3128:
3129: =item rolesput
3130:
3131: put a role into a user's environment
3132:
3133: =item get
3134:
3135: returns hash with keys from array
3136: reference filled in from namespace
3137:
3138: =item eget
3139:
3140: returns hash with keys from array
3141: reference filled in from namesp (encrypts the return communication)
3142:
3143: =item rolesget
3144:
3145: get a role from a user's environment
3146:
3147: =item del
3148:
3149: deletes keys out of array from namespace
3150:
3151: =item keys
3152:
3153: returns namespace keys
3154:
3155: =item dump
3156:
3157: dumps the complete (or key matching regexp) namespace into a hash
3158:
3159: =item store
3160:
3161: stores hash permanently
3162: for this url; hashref needs to be given and should be a \%hashname; the
3163: remaining args aren't required and if they aren't passed or are '' they will
3164: be derived from the ENV
3165:
3166: =item restore
3167:
3168: returns a hash for a given url
3169:
3170: =item querysend
3171:
3172: Tells client about the lonsql process that has been launched in response
3173: to a sent query.
3174:
3175: =item queryreply
3176:
3177: Accept information from lonsql and make appropriate storage in temporary
3178: file space.
3179:
3180: =item idput
3181:
3182: Defines usernames as corresponding to IDs. (These "IDs" are unique identifiers
3183: for each student, defined perhaps by the institutional Registrar.)
3184:
3185: =item idget
3186:
3187: Returns usernames corresponding to IDs. (These "IDs" are unique identifiers
3188: for each student, defined perhaps by the institutional Registrar.)
3189:
3190: =item tmpput
3191:
3192: Accept and store information in temporary space.
3193:
3194: =item tmpget
3195:
3196: Send along temporarily stored information.
3197:
3198: =item ls
3199:
3200: List part of a user's directory.
3201:
1.135 foxr 3202: =item pushtable
3203:
3204: Pushes a file in /home/httpd/lonTab directory. Currently limited to:
3205: hosts.tab and domain.tab. The old file is copied to *.tab.backup but
3206: must be restored manually in case of a problem with the new table file.
3207: pushtable requires that the request be encrypted and validated via
3208: ValidateManager. The form of the command is:
3209: enc:pushtable tablename <tablecontents> \n
3210: where pushtable, tablename and <tablecontents> will be encrypted, but \n is a
3211: cleartext newline.
3212:
1.74 harris41 3213: =item Hanging up (exit or init)
3214:
3215: What to do when a client tells the server that they (the client)
3216: are leaving the network.
3217:
3218: =item unknown command
3219:
3220: If B<lond> is sent an unknown command (not in the list above),
3221: it replys to the client "unknown_cmd".
1.135 foxr 3222:
1.74 harris41 3223:
3224: =item UNKNOWN CLIENT
3225:
3226: If the anti-spoofing algorithm cannot verify the client,
3227: the client is rejected (with a "refused" message sent
3228: to the client, and the connection is closed.
3229:
3230: =back
1.61 harris41 3231:
3232: =head1 PREREQUISITES
3233:
3234: IO::Socket
3235: IO::File
3236: Apache::File
3237: Symbol
3238: POSIX
3239: Crypt::IDEA
3240: LWP::UserAgent()
3241: GDBM_File
3242: Authen::Krb4
1.91 albertel 3243: Authen::Krb5
1.61 harris41 3244:
3245: =head1 COREQUISITES
3246:
3247: =head1 OSNAMES
3248:
3249: linux
3250:
3251: =head1 SCRIPT CATEGORIES
3252:
3253: Server/Process
3254:
3255: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>