File:
[LON-CAPA] /
loncom /
lond
Revision
1.215:
download - view:
text,
annotated -
select for diffs
Tue Jul 27 11:21:48 2004 UTC (20 years, 3 months ago) by
foxr
Branches:
MAIN
CVS tags:
HEAD
Added pong handler to the set of hash dispatched handlers. 2 down and many
more to go... but at least most of them are already written in my private
branch.
1: #!/usr/bin/perl
2: # The LearningOnline Network
3: # lond "LON Daemon" Server (port "LOND" 5663)
4: #
5: # $Id: lond,v 1.215 2004/07/27 11:21:48 foxr Exp $
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: #
27:
28:
29: # http://www.lon-capa.org/
30: #
31:
32: use strict;
33: use lib '/home/httpd/lib/perl/';
34: use LONCAPA::Configuration;
35:
36: use IO::Socket;
37: use IO::File;
38: #use Apache::File;
39: use Symbol;
40: use POSIX;
41: use Crypt::IDEA;
42: use LWP::UserAgent();
43: use GDBM_File;
44: use Authen::Krb4;
45: use Authen::Krb5;
46: use lib '/home/httpd/lib/perl/';
47: use localauth;
48: use localenroll;
49: use File::Copy;
50: use LONCAPA::ConfigFileEdit;
51: use LONCAPA::lonlocal;
52: use LONCAPA::lonssl;
53:
54: my $DEBUG = 0; # Non zero to enable debug log entries.
55:
56: my $status='';
57: my $lastlog='';
58:
59: my $VERSION='$Revision: 1.215 $'; #' stupid emacs
60: my $remoteVERSION;
61: my $currenthostid="default";
62: my $currentdomainid;
63:
64: my $client;
65: my $clientip; # IP address of client.
66: my $clientdns; # DNS name of client.
67: my $clientname; # LonCAPA name of client.
68:
69: my $server;
70: my $thisserver; # DNS of us.
71:
72: my $keymode;
73:
74: my $cipher; # Cipher key negotiated with client
75: my $tmpsnum = 0; # Id of tmpputs.
76:
77: #
78: # Connection type is:
79: # client - All client actions are allowed
80: # manager - only management functions allowed.
81: # both - Both management and client actions are allowed
82: #
83:
84: my $ConnectionType;
85:
86: my %hostid; # ID's for hosts in cluster by ip.
87: my %hostdom; # LonCAPA domain for hosts in cluster.
88: my %hostip; # IPs for hosts in cluster.
89: my %hostdns; # ID's of hosts looked up by DNS name.
90:
91: my %managers; # Ip -> manager names
92:
93: my %perlvar; # Will have the apache conf defined perl vars.
94:
95: #
96: # The hash below is used for command dispatching, and is therefore keyed on the request keyword.
97: # Each element of the hash contains a reference to an array that contains:
98: # A reference to a sub that executes the request corresponding to the keyword.
99: # A flag that is true if the request must be encoded to be acceptable.
100: # A mask with bits as follows:
101: # CLIENT_OK - Set when the function is allowed by ordinary clients
102: # MANAGER_OK - Set when the function is allowed to manager clients.
103: #
104: my $CLIENT_OK = 1;
105: my $MANAGER_OK = 2;
106: my %Dispatcher;
107:
108:
109: #
110: # The array below are password error strings."
111: #
112: my $lastpwderror = 13; # Largest error number from lcpasswd.
113: my @passwderrors = ("ok",
114: "lcpasswd must be run as user 'www'",
115: "lcpasswd got incorrect number of arguments",
116: "lcpasswd did not get the right nubmer of input text lines",
117: "lcpasswd too many simultaneous pwd changes in progress",
118: "lcpasswd User does not exist.",
119: "lcpasswd Incorrect current passwd",
120: "lcpasswd Unable to su to root.",
121: "lcpasswd Cannot set new passwd.",
122: "lcpasswd Username has invalid characters",
123: "lcpasswd Invalid characters in password",
124: "11", "12",
125: "lcpasswd Password mismatch");
126:
127:
128: # The array below are lcuseradd error strings.:
129:
130: my $lastadderror = 13;
131: my @adderrors = ("ok",
132: "User ID mismatch, lcuseradd must run as user www",
133: "lcuseradd Incorrect number of command line parameters must be 3",
134: "lcuseradd Incorrect number of stdinput lines, must be 3",
135: "lcuseradd Too many other simultaneous pwd changes in progress",
136: "lcuseradd User does not exist",
137: "lcuseradd Unable to make www member of users's group",
138: "lcuseradd Unable to su to root",
139: "lcuseradd Unable to set password",
140: "lcuseradd Usrname has invalid characters",
141: "lcuseradd Password has an invalid character",
142: "lcuseradd User already exists",
143: "lcuseradd Could not add user.",
144: "lcuseradd Password mismatch");
145:
146:
147:
148: #
149: # Statistics that are maintained and dislayed in the status line.
150: #
151: my $Transactions = 0; # Number of attempted transactions.
152: my $Failures = 0; # Number of transcations failed.
153:
154: # ResetStatistics:
155: # Resets the statistics counters:
156: #
157: sub ResetStatistics {
158: $Transactions = 0;
159: $Failures = 0;
160: }
161:
162:
163:
164: #------------------------------------------------------------------------
165: #
166: # LocalConnection
167: # Completes the formation of a locally authenticated connection.
168: # This function will ensure that the 'remote' client is really the
169: # local host. If not, the connection is closed, and the function fails.
170: # If so, initcmd is parsed for the name of a file containing the
171: # IDEA session key. The fie is opened, read, deleted and the session
172: # key returned to the caller.
173: #
174: # Parameters:
175: # $Socket - Socket open on client.
176: # $initcmd - The full text of the init command.
177: #
178: # Implicit inputs:
179: # $clientdns - The DNS name of the remote client.
180: # $thisserver - Our DNS name.
181: #
182: # Returns:
183: # IDEA session key on success.
184: # undef on failure.
185: #
186: sub LocalConnection {
187: my ($Socket, $initcmd) = @_;
188: Debug("Attempting local connection: $initcmd client: $clientdns me: $thisserver");
189: if($clientdns ne $thisserver) {
190: &logthis('<font color="red"> LocalConnection rejecting non local: '
191: ."$clientdns ne $thisserver </font>");
192: close $Socket;
193: return undef;
194: }
195: else {
196: chomp($initcmd); # Get rid of \n in filename.
197: my ($init, $type, $name) = split(/:/, $initcmd);
198: Debug(" Init command: $init $type $name ");
199:
200: # Require that $init = init, and $type = local: Otherwise
201: # the caller is insane:
202:
203: if(($init ne "init") && ($type ne "local")) {
204: &logthis('<font color = "red"> LocalConnection: caller is insane! '
205: ."init = $init, and type = $type </font>");
206: close($Socket);;
207: return undef;
208:
209: }
210: # Now get the key filename:
211:
212: my $IDEAKey = lonlocal::ReadKeyFile($name);
213: return $IDEAKey;
214: }
215: }
216: #------------------------------------------------------------------------------
217: #
218: # SSLConnection
219: # Completes the formation of an ssh authenticated connection. The
220: # socket is promoted to an ssl socket. If this promotion and the associated
221: # certificate exchange are successful, the IDEA key is generated and sent
222: # to the remote peer via the SSL tunnel. The IDEA key is also returned to
223: # the caller after the SSL tunnel is torn down.
224: #
225: # Parameters:
226: # Name Type Purpose
227: # $Socket IO::Socket::INET Plaintext socket.
228: #
229: # Returns:
230: # IDEA key on success.
231: # undef on failure.
232: #
233: sub SSLConnection {
234: my $Socket = shift;
235:
236: Debug("SSLConnection: ");
237: my $KeyFile = lonssl::KeyFile();
238: if(!$KeyFile) {
239: my $err = lonssl::LastError();
240: &logthis("<font color=\"red\"> CRITICAL"
241: ."Can't get key file $err </font>");
242: return undef;
243: }
244: my ($CACertificate,
245: $Certificate) = lonssl::CertificateFile();
246:
247:
248: # If any of the key, certificate or certificate authority
249: # certificate filenames are not defined, this can't work.
250:
251: if((!$Certificate) || (!$CACertificate)) {
252: my $err = lonssl::LastError();
253: &logthis("<font color=\"red\"> CRITICAL"
254: ."Can't get certificates: $err </font>");
255:
256: return undef;
257: }
258: Debug("Key: $KeyFile CA: $CACertificate Cert: $Certificate");
259:
260: # Indicate to our peer that we can procede with
261: # a transition to ssl authentication:
262:
263: print $Socket "ok:ssl\n";
264:
265: Debug("Approving promotion -> ssl");
266: # And do so:
267:
268: my $SSLSocket = lonssl::PromoteServerSocket($Socket,
269: $CACertificate,
270: $Certificate,
271: $KeyFile);
272: if(! ($SSLSocket) ) { # SSL socket promotion failed.
273: my $err = lonssl::LastError();
274: &logthis("<font color=\"red\"> CRITICAL "
275: ."SSL Socket promotion failed: $err </font>");
276: return undef;
277: }
278: Debug("SSL Promotion successful");
279:
280: #
281: # The only thing we'll use the socket for is to send the IDEA key
282: # to the peer:
283:
284: my $Key = lonlocal::CreateCipherKey();
285: print $SSLSocket "$Key\n";
286:
287: lonssl::Close($SSLSocket);
288:
289: Debug("Key exchange complete: $Key");
290:
291: return $Key;
292: }
293: #
294: # InsecureConnection:
295: # If insecure connections are allowd,
296: # exchange a challenge with the client to 'validate' the
297: # client (not really, but that's the protocol):
298: # We produce a challenge string that's sent to the client.
299: # The client must then echo the challenge verbatim to us.
300: #
301: # Parameter:
302: # Socket - Socket open on the client.
303: # Returns:
304: # 1 - success.
305: # 0 - failure (e.g.mismatch or insecure not allowed).
306: #
307: sub InsecureConnection {
308: my $Socket = shift;
309:
310: # Don't even start if insecure connections are not allowed.
311:
312: if(! $perlvar{londAllowInsecure}) { # Insecure connections not allowed.
313: return 0;
314: }
315:
316: # Fabricate a challenge string and send it..
317:
318: my $challenge = "$$".time; # pid + time.
319: print $Socket "$challenge\n";
320: &status("Waiting for challenge reply");
321:
322: my $answer = <$Socket>;
323: $answer =~s/\W//g;
324: if($challenge eq $answer) {
325: return 1;
326: }
327: else {
328: logthis("<font color='blue'>WARNING client did not respond to challenge</font>");
329: &status("No challenge reqply");
330: return 0;
331: }
332:
333:
334: }
335:
336: #
337: # GetCertificate: Given a transaction that requires a certificate,
338: # this function will extract the certificate from the transaction
339: # request. Note that at this point, the only concept of a certificate
340: # is the hostname to which we are connected.
341: #
342: # Parameter:
343: # request - The request sent by our client (this parameterization may
344: # need to change when we really use a certificate granting
345: # authority.
346: #
347: sub GetCertificate {
348: my $request = shift;
349:
350: return $clientip;
351: }
352:
353: #
354: # Return true if client is a manager.
355: #
356: sub isManager {
357: return (($ConnectionType eq "manager") || ($ConnectionType eq "both"));
358: }
359: #
360: # Return tru if client can do client functions
361: #
362: sub isClient {
363: return (($ConnectionType eq "client") || ($ConnectionType eq "both"));
364: }
365:
366:
367: #
368: # ReadManagerTable: Reads in the current manager table. For now this is
369: # done on each manager authentication because:
370: # - These authentications are not frequent
371: # - This allows dynamic changes to the manager table
372: # without the need to signal to the lond.
373: #
374:
375: sub ReadManagerTable {
376:
377: # Clean out the old table first..
378:
379: foreach my $key (keys %managers) {
380: delete $managers{$key};
381: }
382:
383: my $tablename = $perlvar{'lonTabDir'}."/managers.tab";
384: if (!open (MANAGERS, $tablename)) {
385: logthis('<font color="red">No manager table. Nobody can manage!!</font>');
386: return;
387: }
388: while(my $host = <MANAGERS>) {
389: chomp($host);
390: if ($host =~ "^#") { # Comment line.
391: next;
392: }
393: if (!defined $hostip{$host}) { # This is a non cluster member
394: # The entry is of the form:
395: # cluname:hostname
396: # cluname - A 'cluster hostname' is needed in order to negotiate
397: # the host key.
398: # hostname- The dns name of the host.
399: #
400: my($cluname, $dnsname) = split(/:/, $host);
401:
402: my $ip = gethostbyname($dnsname);
403: if(defined($ip)) { # bad names don't deserve entry.
404: my $hostip = inet_ntoa($ip);
405: $managers{$hostip} = $cluname;
406: logthis('<font color="green"> registering manager '.
407: "$dnsname as $cluname with $hostip </font>\n");
408: }
409: } else {
410: logthis('<font color="green"> existing host'." $host</font>\n");
411: $managers{$hostip{$host}} = $host; # Use info from cluster tab if clumemeber
412: }
413: }
414: }
415:
416: #
417: # ValidManager: Determines if a given certificate represents a valid manager.
418: # in this primitive implementation, the 'certificate' is
419: # just the connecting loncapa client name. This is checked
420: # against a valid client list in the configuration.
421: #
422: #
423: sub ValidManager {
424: my $certificate = shift;
425:
426: return isManager;
427: }
428: #
429: # CopyFile: Called as part of the process of installing a
430: # new configuration file. This function copies an existing
431: # file to a backup file.
432: # Parameters:
433: # oldfile - Name of the file to backup.
434: # newfile - Name of the backup file.
435: # Return:
436: # 0 - Failure (errno has failure reason).
437: # 1 - Success.
438: #
439: sub CopyFile {
440:
441: my ($oldfile, $newfile) = @_;
442:
443: # The file must exist:
444:
445: if(-e $oldfile) {
446:
447: # Read the old file.
448:
449: my $oldfh = IO::File->new("< $oldfile");
450: if(!$oldfh) {
451: return 0;
452: }
453: my @contents = <$oldfh>; # Suck in the entire file.
454:
455: # write the backup file:
456:
457: my $newfh = IO::File->new("> $newfile");
458: if(!(defined $newfh)){
459: return 0;
460: }
461: my $lines = scalar @contents;
462: for (my $i =0; $i < $lines; $i++) {
463: print $newfh ($contents[$i]);
464: }
465:
466: $oldfh->close;
467: $newfh->close;
468:
469: chmod(0660, $newfile);
470:
471: return 1;
472:
473: } else {
474: return 0;
475: }
476: }
477: #
478: # Host files are passed out with externally visible host IPs.
479: # If, for example, we are behind a fire-wall or NAT host, our
480: # internally visible IP may be different than the externally
481: # visible IP. Therefore, we always adjust the contents of the
482: # host file so that the entry for ME is the IP that we believe
483: # we have. At present, this is defined as the entry that
484: # DNS has for us. If by some chance we are not able to get a
485: # DNS translation for us, then we assume that the host.tab file
486: # is correct.
487: # BUGBUGBUG - in the future, we really should see if we can
488: # easily query the interface(s) instead.
489: # Parameter(s):
490: # contents - The contents of the host.tab to check.
491: # Returns:
492: # newcontents - The adjusted contents.
493: #
494: #
495: sub AdjustHostContents {
496: my $contents = shift;
497: my $adjusted;
498: my $me = $perlvar{'lonHostID'};
499:
500: foreach my $line (split(/\n/,$contents)) {
501: if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/))) {
502: chomp($line);
503: my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line);
504: if ($id eq $me) {
505: my $ip = gethostbyname($name);
506: my $ipnew = inet_ntoa($ip);
507: $ip = $ipnew;
508: # Reconstruct the host line and append to adjusted:
509:
510: my $newline = "$id:$domain:$role:$name:$ip";
511: if($maxcon ne "") { # Not all hosts have loncnew tuning params
512: $newline .= ":$maxcon:$idleto:$mincon";
513: }
514: $adjusted .= $newline."\n";
515:
516: } else { # Not me, pass unmodified.
517: $adjusted .= $line."\n";
518: }
519: } else { # Blank or comment never re-written.
520: $adjusted .= $line."\n"; # Pass blanks and comments as is.
521: }
522: }
523: return $adjusted;
524: }
525: #
526: # InstallFile: Called to install an administrative file:
527: # - The file is created with <name>.tmp
528: # - The <name>.tmp file is then mv'd to <name>
529: # This lugubrious procedure is done to ensure that we are never without
530: # a valid, even if dated, version of the file regardless of who crashes
531: # and when the crash occurs.
532: #
533: # Parameters:
534: # Name of the file
535: # File Contents.
536: # Return:
537: # nonzero - success.
538: # 0 - failure and $! has an errno.
539: #
540: sub InstallFile {
541:
542: my ($Filename, $Contents) = @_;
543: my $TempFile = $Filename.".tmp";
544:
545: # Open the file for write:
546:
547: my $fh = IO::File->new("> $TempFile"); # Write to temp.
548: if(!(defined $fh)) {
549: &logthis('<font color="red"> Unable to create '.$TempFile."</font>");
550: return 0;
551: }
552: # write the contents of the file:
553:
554: print $fh ($Contents);
555: $fh->close; # In case we ever have a filesystem w. locking
556:
557: chmod(0660, $TempFile);
558:
559: # Now we can move install the file in position.
560:
561: move($TempFile, $Filename);
562:
563: return 1;
564: }
565:
566:
567: #
568: # ConfigFileFromSelector: converts a configuration file selector
569: # (one of host or domain at this point) into a
570: # configuration file pathname.
571: #
572: # Parameters:
573: # selector - Configuration file selector.
574: # Returns:
575: # Full path to the file or undef if the selector is invalid.
576: #
577: sub ConfigFileFromSelector {
578: my $selector = shift;
579: my $tablefile;
580:
581: my $tabledir = $perlvar{'lonTabDir'}.'/';
582: if ($selector eq "hosts") {
583: $tablefile = $tabledir."hosts.tab";
584: } elsif ($selector eq "domain") {
585: $tablefile = $tabledir."domain.tab";
586: } else {
587: return undef;
588: }
589: return $tablefile;
590:
591: }
592: #
593: # PushFile: Called to do an administrative push of a file.
594: # - Ensure the file being pushed is one we support.
595: # - Backup the old file to <filename.saved>
596: # - Separate the contents of the new file out from the
597: # rest of the request.
598: # - Write the new file.
599: # Parameter:
600: # Request - The entire user request. This consists of a : separated
601: # string pushfile:tablename:contents.
602: # NOTE: The contents may have :'s in it as well making things a bit
603: # more interesting... but not much.
604: # Returns:
605: # String to send to client ("ok" or "refused" if bad file).
606: #
607: sub PushFile {
608: my $request = shift;
609: my ($command, $filename, $contents) = split(":", $request, 3);
610:
611: # At this point in time, pushes for only the following tables are
612: # supported:
613: # hosts.tab ($filename eq host).
614: # domain.tab ($filename eq domain).
615: # Construct the destination filename or reject the request.
616: #
617: # lonManage is supposed to ensure this, however this session could be
618: # part of some elaborate spoof that managed somehow to authenticate.
619: #
620:
621:
622: my $tablefile = ConfigFileFromSelector($filename);
623: if(! (defined $tablefile)) {
624: return "refused";
625: }
626: #
627: # >copy< the old table to the backup table
628: # don't rename in case system crashes/reboots etc. in the time
629: # window between a rename and write.
630: #
631: my $backupfile = $tablefile;
632: $backupfile =~ s/\.tab$/.old/;
633: if(!CopyFile($tablefile, $backupfile)) {
634: &logthis('<font color="green"> CopyFile from '.$tablefile." to ".$backupfile." failed </font>");
635: return "error:$!";
636: }
637: &logthis('<font color="green"> Pushfile: backed up '
638: .$tablefile." to $backupfile</font>");
639:
640: # If the file being pushed is the host file, we adjust the entry for ourself so that the
641: # IP will be our current IP as looked up in dns. Note this is only 99% good as it's possible
642: # to conceive of conditions where we don't have a DNS entry locally. This is possible in a
643: # network sense but it doesn't make much sense in a LonCAPA sense so we ignore (for now)
644: # that possibilty.
645:
646: if($filename eq "host") {
647: $contents = AdjustHostContents($contents);
648: }
649:
650: # Install the new file:
651:
652: if(!InstallFile($tablefile, $contents)) {
653: &logthis('<font color="red"> Pushfile: unable to install '
654: .$tablefile." $! </font>");
655: return "error:$!";
656: }
657: else {
658: &logthis('<font color="green"> Installed new '.$tablefile
659: ."</font>");
660:
661: }
662:
663:
664: # Indicate success:
665:
666: return "ok";
667:
668: }
669:
670: #
671: # Called to re-init either lonc or lond.
672: #
673: # Parameters:
674: # request - The full request by the client. This is of the form
675: # reinit:<process>
676: # where <process> is allowed to be either of
677: # lonc or lond
678: #
679: # Returns:
680: # The string to be sent back to the client either:
681: # ok - Everything worked just fine.
682: # error:why - There was a failure and why describes the reason.
683: #
684: #
685: sub ReinitProcess {
686: my $request = shift;
687:
688:
689: # separate the request (reinit) from the process identifier and
690: # validate it producing the name of the .pid file for the process.
691: #
692: #
693: my ($junk, $process) = split(":", $request);
694: my $processpidfile = $perlvar{'lonDaemons'}.'/logs/';
695: if($process eq 'lonc') {
696: $processpidfile = $processpidfile."lonc.pid";
697: if (!open(PIDFILE, "< $processpidfile")) {
698: return "error:Open failed for $processpidfile";
699: }
700: my $loncpid = <PIDFILE>;
701: close(PIDFILE);
702: logthis('<font color="red"> Reinitializing lonc pid='.$loncpid
703: ."</font>");
704: kill("USR2", $loncpid);
705: } elsif ($process eq 'lond') {
706: logthis('<font color="red"> Reinitializing self (lond) </font>');
707: &UpdateHosts; # Lond is us!!
708: } else {
709: &logthis('<font color="yellow" Invalid reinit request for '.$process
710: ."</font>");
711: return "error:Invalid process identifier $process";
712: }
713: return 'ok';
714: }
715: # Validate a line in a configuration file edit script:
716: # Validation includes:
717: # - Ensuring the command is valid.
718: # - Ensuring the command has sufficient parameters
719: # Parameters:
720: # scriptline - A line to validate (\n has been stripped for what it's worth).
721: #
722: # Return:
723: # 0 - Invalid scriptline.
724: # 1 - Valid scriptline
725: # NOTE:
726: # Only the command syntax is checked, not the executability of the
727: # command.
728: #
729: sub isValidEditCommand {
730: my $scriptline = shift;
731:
732: # Line elements are pipe separated:
733:
734: my ($command, $key, $newline) = split(/\|/, $scriptline);
735: &logthis('<font color="green"> isValideditCommand checking: '.
736: "Command = '$command', Key = '$key', Newline = '$newline' </font>\n");
737:
738: if ($command eq "delete") {
739: #
740: # key with no newline.
741: #
742: if( ($key eq "") || ($newline ne "")) {
743: return 0; # Must have key but no newline.
744: } else {
745: return 1; # Valid syntax.
746: }
747: } elsif ($command eq "replace") {
748: #
749: # key and newline:
750: #
751: if (($key eq "") || ($newline eq "")) {
752: return 0;
753: } else {
754: return 1;
755: }
756: } elsif ($command eq "append") {
757: if (($key ne "") && ($newline eq "")) {
758: return 1;
759: } else {
760: return 0;
761: }
762: } else {
763: return 0; # Invalid command.
764: }
765: return 0; # Should not get here!!!
766: }
767: #
768: # ApplyEdit - Applies an edit command to a line in a configuration
769: # file. It is the caller's responsiblity to validate the
770: # edit line.
771: # Parameters:
772: # $directive - A single edit directive to apply.
773: # Edit directives are of the form:
774: # append|newline - Appends a new line to the file.
775: # replace|key|newline - Replaces the line with key value 'key'
776: # delete|key - Deletes the line with key value 'key'.
777: # $editor - A config file editor object that contains the
778: # file being edited.
779: #
780: sub ApplyEdit {
781:
782: my ($directive, $editor) = @_;
783:
784: # Break the directive down into its command and its parameters
785: # (at most two at this point. The meaning of the parameters, if in fact
786: # they exist depends on the command).
787:
788: my ($command, $p1, $p2) = split(/\|/, $directive);
789:
790: if($command eq "append") {
791: $editor->Append($p1); # p1 - key p2 null.
792: } elsif ($command eq "replace") {
793: $editor->ReplaceLine($p1, $p2); # p1 - key p2 = newline.
794: } elsif ($command eq "delete") {
795: $editor->DeleteLine($p1); # p1 - key p2 null.
796: } else { # Should not get here!!!
797: die "Invalid command given to ApplyEdit $command"
798: }
799: }
800: #
801: # AdjustOurHost:
802: # Adjusts a host file stored in a configuration file editor object
803: # for the true IP address of this host. This is necessary for hosts
804: # that live behind a firewall.
805: # Those hosts have a publicly distributed IP of the firewall, but
806: # internally must use their actual IP. We assume that a given
807: # host only has a single IP interface for now.
808: # Formal Parameters:
809: # editor - The configuration file editor to adjust. This
810: # editor is assumed to contain a hosts.tab file.
811: # Strategy:
812: # - Figure out our hostname.
813: # - Lookup the entry for this host.
814: # - Modify the line to contain our IP
815: # - Do a replace for this host.
816: sub AdjustOurHost {
817: my $editor = shift;
818:
819: # figure out who I am.
820:
821: my $myHostName = $perlvar{'lonHostID'}; # LonCAPA hostname.
822:
823: # Get my host file entry.
824:
825: my $ConfigLine = $editor->Find($myHostName);
826: if(! (defined $ConfigLine)) {
827: die "AdjustOurHost - no entry for me in hosts file $myHostName";
828: }
829: # figure out my IP:
830: # Use the config line to get my hostname.
831: # Use gethostbyname to translate that into an IP address.
832: #
833: my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon) = split(/:/,$ConfigLine);
834: my $BinaryIp = gethostbyname($name);
835: my $ip = inet_ntoa($ip);
836: #
837: # Reassemble the config line from the elements in the list.
838: # Note that if the loncnew items were not present before, they will
839: # be now even if they would be empty
840: #
841: my $newConfigLine = $id;
842: foreach my $item ($domain, $role, $name, $ip, $maxcon, $idleto, $mincon) {
843: $newConfigLine .= ":".$item;
844: }
845: # Replace the line:
846:
847: $editor->ReplaceLine($id, $newConfigLine);
848:
849: }
850: #
851: # ReplaceConfigFile:
852: # Replaces a configuration file with the contents of a
853: # configuration file editor object.
854: # This is done by:
855: # - Copying the target file to <filename>.old
856: # - Writing the new file to <filename>.tmp
857: # - Moving <filename.tmp> -> <filename>
858: # This laborious process ensures that the system is never without
859: # a configuration file that's at least valid (even if the contents
860: # may be dated).
861: # Parameters:
862: # filename - Name of the file to modify... this is a full path.
863: # editor - Editor containing the file.
864: #
865: sub ReplaceConfigFile {
866:
867: my ($filename, $editor) = @_;
868:
869: CopyFile ($filename, $filename.".old");
870:
871: my $contents = $editor->Get(); # Get the contents of the file.
872:
873: InstallFile($filename, $contents);
874: }
875: #
876: #
877: # Called to edit a configuration table file
878: # Parameters:
879: # request - The entire command/request sent by lonc or lonManage
880: # Return:
881: # The reply to send to the client.
882: #
883: sub EditFile {
884: my $request = shift;
885:
886: # Split the command into it's pieces: edit:filetype:script
887:
888: my ($request, $filetype, $script) = split(/:/, $request,3); # : in script
889:
890: # Check the pre-coditions for success:
891:
892: if($request != "edit") { # Something is amiss afoot alack.
893: return "error:edit request detected, but request != 'edit'\n";
894: }
895: if( ($filetype ne "hosts") &&
896: ($filetype ne "domain")) {
897: return "error:edit requested with invalid file specifier: $filetype \n";
898: }
899:
900: # Split the edit script and check it's validity.
901:
902: my @scriptlines = split(/\n/, $script); # one line per element.
903: my $linecount = scalar(@scriptlines);
904: for(my $i = 0; $i < $linecount; $i++) {
905: chomp($scriptlines[$i]);
906: if(!isValidEditCommand($scriptlines[$i])) {
907: return "error:edit with bad script line: '$scriptlines[$i]' \n";
908: }
909: }
910:
911: # Execute the edit operation.
912: # - Create a config file editor for the appropriate file and
913: # - execute each command in the script:
914: #
915: my $configfile = ConfigFileFromSelector($filetype);
916: if (!(defined $configfile)) {
917: return "refused\n";
918: }
919: my $editor = ConfigFileEdit->new($configfile);
920:
921: for (my $i = 0; $i < $linecount; $i++) {
922: ApplyEdit($scriptlines[$i], $editor);
923: }
924: # If the file is the host file, ensure that our host is
925: # adjusted to have our ip:
926: #
927: if($filetype eq "host") {
928: AdjustOurHost($editor);
929: }
930: # Finally replace the current file with our file.
931: #
932: ReplaceConfigFile($configfile, $editor);
933:
934: return "ok\n";
935: }
936:
937: #---------------------------------------------------------------
938: #
939: # Manipulation of hash based databases (factoring out common code
940: # for later use as we refactor.
941: #
942: # Ties a domain level resource file to a hash.
943: # If requested a history entry is created in the associated hist file.
944: #
945: # Parameters:
946: # domain - Name of the domain in which the resource file lives.
947: # namespace - Name of the hash within that domain.
948: # how - How to tie the hash (e.g. GDBM_WRCREAT()).
949: # loghead - Optional parameter, if present a log entry is created
950: # in the associated history file and this is the first part
951: # of that entry.
952: # logtail - Goes along with loghead, The actual logentry is of the
953: # form $loghead:<timestamp>:logtail.
954: # Returns:
955: # Reference to a hash bound to the db file or alternatively undef
956: # if the tie failed.
957: #
958: sub tie_domain_hash {
959: my ($domain,$namespace,$how,$loghead,$logtail) = @_;
960:
961: # Filter out any whitespace in the domain name:
962:
963: $domain =~ s/\W//g;
964:
965: # We have enough to go on to tie the hash:
966:
967: my $user_top_dir = $perlvar{'lonUsersDir'};
968: my $domain_dir = $user_top_dir."/$domain";
969: my $resource_file = $domain_dir."/$namespace.db";
970: my %hash;
971: if(tie(%hash, 'GDBM_File', $resource_file, $how, 0640)) {
972: if (defined($loghead)) { # Need to log the operation.
973: my $logFh = IO::File->new(">>$domain_dir/$namespace.hist");
974: if($logFh) {
975: my $timestamp = time;
976: print $logFh "$loghead:$timestamp:$logtail\n";
977: }
978: $logFh->close;
979: }
980: return \%hash; # Return the tied hash.
981: } else {
982: return undef; # Tie failed.
983: }
984: }
985:
986: #
987: # Ties a user's resource file to a hash.
988: # If necessary, an appropriate history
989: # log file entry is made as well.
990: # This sub factors out common code from the subs that manipulate
991: # the various gdbm files that keep keyword value pairs.
992: # Parameters:
993: # domain - Name of the domain the user is in.
994: # user - Name of the 'current user'.
995: # namespace - Namespace representing the file to tie.
996: # how - What the tie is done to (e.g. GDBM_WRCREAT().
997: # loghead - Optional first part of log entry if there may be a
998: # history file.
999: # what - Optional tail of log entry if there may be a history
1000: # file.
1001: # Returns:
1002: # hash to which the database is tied. It's up to the caller to untie.
1003: # undef if the has could not be tied.
1004: #
1005: sub tie_user_hash {
1006: my ($domain,$user,$namespace,$how,$loghead,$what) = @_;
1007:
1008: $namespace=~s/\//\_/g; # / -> _
1009: $namespace=~s/\W//g; # whitespace eliminated.
1010: my $proname = propath($domain, $user);
1011:
1012: # Tie the database.
1013:
1014: my %hash;
1015: if(tie(%hash, 'GDBM_File', "$proname/$namespace.db",
1016: $how, 0640)) {
1017: # If this is a namespace for which a history is kept,
1018: # make the history log entry:
1019: if (($namespace =~/^nohist\_/) && (defined($loghead))) {
1020: my $args = scalar @_;
1021: Debug(" Opening history: $namespace $args");
1022: my $hfh = IO::File->new(">>$proname/$namespace.hist");
1023: if($hfh) {
1024: my $now = time;
1025: print $hfh "$loghead:$now:$what\n";
1026: }
1027: $hfh->close;
1028: }
1029: return \%hash;
1030: } else {
1031: return undef;
1032: }
1033:
1034: }
1035:
1036: #--------------------- Request Handlers --------------------------------------------
1037: #
1038: # By convention each request handler registers itself prior to the sub
1039: # declaration:
1040: #
1041:
1042: # Handles ping requests.
1043: # Parameters:
1044: # $cmd - the actual keyword that invoked us.
1045: # $tail - the tail of the request that invoked us.
1046: # $replyfd- File descriptor connected to the client
1047: # Implicit Inputs:
1048: # $currenthostid - Global variable that carries the name of the host we are
1049: # known as.
1050: # Returns:
1051: # 1 - Ok to continue processing.
1052: # 0 - Program should exit.
1053: # Side effects:
1054: # Reply information is sent to the client.
1055:
1056: sub ping_handler {
1057: my ($cmd, $tail, $client) = @_;
1058: Debug("$cmd $tail $client .. $currenthostid:");
1059:
1060: Reply( $client,"$currenthostid\n","$cmd:$tail");
1061:
1062: return 1;
1063: }
1064: ®ister_handler("ping", \&ping_handler, 0, 1, 1); # Ping unencoded, client or manager.
1065:
1066: #
1067: # Handles pong requests. Pong replies with our current host id, and
1068: # the results of a ping sent to us via our lonc.
1069: #
1070: # Parameters:
1071: # $cmd - the actual keyword that invoked us.
1072: # $tail - the tail of the request that invoked us.
1073: # $replyfd- File descriptor connected to the client
1074: # Implicit Inputs:
1075: # $currenthostid - Global variable that carries the name of the host we are
1076: # connected to.
1077: # Returns:
1078: # 1 - Ok to continue processing.
1079: # 0 - Program should exit.
1080: # Side effects:
1081: # Reply information is sent to the client.
1082:
1083: sub pong_handler {
1084: my ($cmd, $tail, $replyfd) = @_;
1085:
1086: my $reply=&reply("ping",$clientname);
1087: &Reply( $replyfd, "$currenthostid:$reply\n", "$cmd:$tail");
1088: return 1;
1089: }
1090: ®ister_handler("pong", \&pong_handler, 0, 1, 1); # Pong unencoded, client or manager
1091:
1092:
1093:
1094: #---------------------------------------------------------------
1095: #
1096: # Getting, decoding and dispatching requests:
1097: #
1098:
1099: #
1100: # Get a Request:
1101: # Gets a Request message from the client. The transaction
1102: # is defined as a 'line' of text. We remove the new line
1103: # from the text line.
1104: #
1105: sub get_request {
1106: my $input = <$client>;
1107: chomp($input);
1108:
1109: Debug("get_request: Request = $input\n");
1110:
1111: &status('Processing '.$clientname.':'.$input);
1112:
1113: return $input;
1114: }
1115: #---------------------------------------------------------------
1116: #
1117: # Process a request. This sub should shrink as each action
1118: # gets farmed out into a separat sub that is registered
1119: # with the dispatch hash.
1120: #
1121: # Parameters:
1122: # user_input - The request received from the client (lonc).
1123: # Returns:
1124: # true to keep processing, false if caller should exit.
1125: #
1126: sub process_request {
1127: my ($userinput) = @_; # Easier for now to break style than to
1128: # fix all the userinput -> user_input.
1129: my $wasenc = 0; # True if request was encrypted.
1130: # ------------------------------------------------------------ See if encrypted
1131: if ($userinput =~ /^enc/) {
1132: $userinput = decipher($userinput);
1133: $wasenc=1;
1134: if(!$userinput) { # Cipher not defined.
1135: &Failure($client, "error: Encrypted data without negotated key");
1136: return 0;
1137: }
1138: }
1139: Debug("process_request: $userinput\n");
1140:
1141: #
1142: # The 'correct way' to add a command to lond is now to
1143: # write a sub to execute it and Add it to the command dispatch
1144: # hash via a call to register_handler.. The comments to that
1145: # sub should give you enough to go on to show how to do this
1146: # along with the examples that are building up as this code
1147: # is getting refactored. Until all branches of the
1148: # if/elseif monster below have been factored out into
1149: # separate procesor subs, if the dispatch hash is missing
1150: # the command keyword, we will fall through to the remainder
1151: # of the if/else chain below in order to keep this thing in
1152: # working order throughout the transmogrification.
1153:
1154: my ($command, $tail) = split(/:/, $userinput, 2);
1155: chomp($command);
1156: chomp($tail);
1157: $tail =~ s/(\r)//; # This helps people debugging with e.g. telnet.
1158: $command =~ s/(\r)//; # And this too for parameterless commands.
1159: if(!$tail) {
1160: $tail =""; # defined but blank.
1161: }
1162:
1163: &Debug("Command received: $command, encoded = $wasenc");
1164:
1165: if(defined $Dispatcher{$command}) {
1166:
1167: my $dispatch_info = $Dispatcher{$command};
1168: my $handler = $$dispatch_info[0];
1169: my $need_encode = $$dispatch_info[1];
1170: my $client_types = $$dispatch_info[2];
1171: Debug("Matched dispatch hash: mustencode: $need_encode "
1172: ."ClientType $client_types");
1173:
1174: # Validate the request:
1175:
1176: my $ok = 1;
1177: my $requesterprivs = 0;
1178: if(&isClient()) {
1179: $requesterprivs |= $CLIENT_OK;
1180: }
1181: if(&isManager()) {
1182: $requesterprivs |= $MANAGER_OK;
1183: }
1184: if($need_encode && (!$wasenc)) {
1185: Debug("Must encode but wasn't: $need_encode $wasenc");
1186: $ok = 0;
1187: }
1188: if(($client_types & $requesterprivs) == 0) {
1189: Debug("Client not privileged to do this operation");
1190: $ok = 0;
1191: }
1192:
1193: if($ok) {
1194: Debug("Dispatching to handler $command $tail");
1195: my $keep_going = &$handler($command, $tail, $client);
1196: return $keep_going;
1197: } else {
1198: Debug("Refusing to dispatch because client did not match requirements");
1199: Failure($client, "refused\n", $userinput);
1200: return 1;
1201: }
1202:
1203: }
1204:
1205: #------------------- Commands not yet in spearate handlers. --------------
1206:
1207: # ------------------------------------------------------------------------ ekey
1208: if ($userinput =~ /^ekey/) { # ok for both clients & mgrs
1209: my $buildkey=time.$$.int(rand 100000);
1210: $buildkey=~tr/1-6/A-F/;
1211: $buildkey=int(rand 100000).$buildkey.int(rand 100000);
1212: my $key=$currenthostid.$clientname;
1213: $key=~tr/a-z/A-Z/;
1214: $key=~tr/G-P/0-9/;
1215: $key=~tr/Q-Z/0-9/;
1216: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
1217: $key=substr($key,0,32);
1218: my $cipherkey=pack("H32",$key);
1219: $cipher=new IDEA $cipherkey;
1220: print $client "$buildkey\n";
1221: # ------------------------------------------------------------------------ load
1222: } elsif ($userinput =~ /^load/) { # client only
1223: if (isClient) {
1224: my $loadavg;
1225: {
1226: my $loadfile=IO::File->new('/proc/loadavg');
1227: $loadavg=<$loadfile>;
1228: }
1229: $loadavg =~ s/\s.*//g;
1230: my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'};
1231: print $client "$loadpercent\n";
1232: } else {
1233: Reply($client, "refused\n", $userinput);
1234:
1235: }
1236: # -------------------------------------------------------------------- userload
1237: } elsif ($userinput =~ /^userload/) { # client only
1238: if(isClient) {
1239: my $userloadpercent=&userload();
1240: print $client "$userloadpercent\n";
1241: } else {
1242: Reply($client, "refused\n", $userinput);
1243:
1244: }
1245: #
1246: # Transactions requiring encryption:
1247: #
1248: # ----------------------------------------------------------------- currentauth
1249: } elsif ($userinput =~ /^currentauth/) {
1250: if (($wasenc==1) && isClient) { # Encoded & client only.
1251: my ($cmd,$udom,$uname)=split(/:/,$userinput);
1252: my $result = GetAuthType($udom, $uname);
1253: if($result eq "nouser") {
1254: print $client "unknown_user\n";
1255: }
1256: else {
1257: print $client "$result\n";
1258: }
1259: } else {
1260: Reply($client, "refused\n", $userinput);
1261:
1262: }
1263: #--------------------------------------------------------------------- pushfile
1264: } elsif($userinput =~ /^pushfile/) { # encoded & manager.
1265: if(($wasenc == 1) && isManager) {
1266: my $cert = GetCertificate($userinput);
1267: if(ValidManager($cert)) {
1268: my $reply = PushFile($userinput);
1269: print $client "$reply\n";
1270: } else {
1271: print $client "refused\n";
1272: }
1273: } else {
1274: Reply($client, "refused\n", $userinput);
1275:
1276: }
1277: #--------------------------------------------------------------------- reinit
1278: } elsif($userinput =~ /^reinit/) { # Encoded and manager
1279: if (($wasenc == 1) && isManager) {
1280: my $cert = GetCertificate($userinput);
1281: if(ValidManager($cert)) {
1282: chomp($userinput);
1283: my $reply = ReinitProcess($userinput);
1284: print $client "$reply\n";
1285: } else {
1286: print $client "refused\n";
1287: }
1288: } else {
1289: Reply($client, "refused\n", $userinput);
1290: }
1291: #------------------------------------------------------------------------- edit
1292: } elsif ($userinput =~ /^edit/) { # encoded and manager:
1293: if(($wasenc ==1) && (isManager)) {
1294: my $cert = GetCertificate($userinput);
1295: if(ValidManager($cert)) {
1296: my($command, $filetype, $script) = split(/:/, $userinput);
1297: if (($filetype eq "hosts") || ($filetype eq "domain")) {
1298: if($script ne "") {
1299: Reply($client, EditFile($userinput));
1300: } else {
1301: Reply($client,"refused\n",$userinput);
1302: }
1303: } else {
1304: Reply($client,"refused\n",$userinput);
1305: }
1306: } else {
1307: Reply($client,"refused\n",$userinput);
1308: }
1309: } else {
1310: Reply($client,"refused\n",$userinput);
1311: }
1312: # ------------------------------------------------------------------------ auth
1313: } elsif ($userinput =~ /^auth/) { # Encoded and client only.
1314: if (($wasenc==1) && isClient) {
1315: my ($cmd,$udom,$uname,$upass)=split(/:/,$userinput);
1316: chomp($upass);
1317: $upass=unescape($upass);
1318: my $proname=propath($udom,$uname);
1319: my $passfilename="$proname/passwd";
1320: if (-e $passfilename) {
1321: my $pf = IO::File->new($passfilename);
1322: my $realpasswd=<$pf>;
1323: chomp($realpasswd);
1324: my ($howpwd,$contentpwd)=split(/:/,$realpasswd);
1325: my $pwdcorrect=0;
1326: if ($howpwd eq 'internal') {
1327: &Debug("Internal auth");
1328: $pwdcorrect=
1329: (crypt($upass,$contentpwd) eq $contentpwd);
1330: } elsif ($howpwd eq 'unix') {
1331: &Debug("Unix auth");
1332: if((getpwnam($uname))[1] eq "") { #no such user!
1333: $pwdcorrect = 0;
1334: } else {
1335: $contentpwd=(getpwnam($uname))[1];
1336: my $pwauth_path="/usr/local/sbin/pwauth";
1337: unless ($contentpwd eq 'x') {
1338: $pwdcorrect=
1339: (crypt($upass,$contentpwd) eq
1340: $contentpwd);
1341: }
1342:
1343: elsif (-e $pwauth_path) {
1344: open PWAUTH, "|$pwauth_path" or
1345: die "Cannot invoke authentication";
1346: print PWAUTH "$uname\n$upass\n";
1347: close PWAUTH;
1348: $pwdcorrect=!$?;
1349: }
1350: }
1351: } elsif ($howpwd eq 'krb4') {
1352: my $null=pack("C",0);
1353: unless ($upass=~/$null/) {
1354: my $krb4_error = &Authen::Krb4::get_pw_in_tkt
1355: ($uname,"",$contentpwd,'krbtgt',
1356: $contentpwd,1,$upass);
1357: if (!$krb4_error) {
1358: $pwdcorrect = 1;
1359: } else {
1360: $pwdcorrect=0;
1361: # log error if it is not a bad password
1362: if ($krb4_error != 62) {
1363: &logthis('krb4:'.$uname.','.
1364: &Authen::Krb4::get_err_txt($Authen::Krb4::error));
1365: }
1366: }
1367: }
1368: } elsif ($howpwd eq 'krb5') {
1369: my $null=pack("C",0);
1370: unless ($upass=~/$null/) {
1371: my $krbclient=&Authen::Krb5::parse_name($uname.'@'.$contentpwd);
1372: my $krbservice="krbtgt/".$contentpwd."\@".$contentpwd;
1373: my $krbserver=&Authen::Krb5::parse_name($krbservice);
1374: my $credentials=&Authen::Krb5::cc_default();
1375: $credentials->initialize($krbclient);
1376: my $krbreturn =
1377: &Authen::Krb5::get_in_tkt_with_password(
1378: $krbclient,$krbserver,$upass,$credentials);
1379: # unless ($krbreturn) {
1380: # &logthis("Krb5 Error: ".
1381: # &Authen::Krb5::error());
1382: # }
1383: $pwdcorrect = ($krbreturn == 1);
1384: } else { $pwdcorrect=0; }
1385: } elsif ($howpwd eq 'localauth') {
1386: $pwdcorrect=&localauth::localauth($uname,$upass,
1387: $contentpwd);
1388: }
1389: if ($pwdcorrect) {
1390: print $client "authorized\n";
1391: } else {
1392: print $client "non_authorized\n";
1393: }
1394: } else {
1395: print $client "unknown_user\n";
1396: }
1397: } else {
1398: Reply($client, "refused\n", $userinput);
1399:
1400: }
1401: # ---------------------------------------------------------------------- passwd
1402: } elsif ($userinput =~ /^passwd/) { # encoded and client
1403: if (($wasenc==1) && isClient) {
1404: my
1405: ($cmd,$udom,$uname,$upass,$npass)=split(/:/,$userinput);
1406: chomp($npass);
1407: $upass=&unescape($upass);
1408: $npass=&unescape($npass);
1409: &Debug("Trying to change password for $uname");
1410: my $proname=propath($udom,$uname);
1411: my $passfilename="$proname/passwd";
1412: if (-e $passfilename) {
1413: my $realpasswd;
1414: { my $pf = IO::File->new($passfilename);
1415: $realpasswd=<$pf>; }
1416: chomp($realpasswd);
1417: my ($howpwd,$contentpwd)=split(/:/,$realpasswd);
1418: if ($howpwd eq 'internal') {
1419: &Debug("internal auth");
1420: if (crypt($upass,$contentpwd) eq $contentpwd) {
1421: my $salt=time;
1422: $salt=substr($salt,6,2);
1423: my $ncpass=crypt($npass,$salt);
1424: {
1425: my $pf;
1426: if ($pf = IO::File->new(">$passfilename")) {
1427: print $pf "internal:$ncpass\n";
1428: &logthis("Result of password change for $uname: pwchange_success");
1429: print $client "ok\n";
1430: } else {
1431: &logthis("Unable to open $uname passwd to change password");
1432: print $client "non_authorized\n";
1433: }
1434: }
1435:
1436: } else {
1437: print $client "non_authorized\n";
1438: }
1439: } elsif ($howpwd eq 'unix') {
1440: # Unix means we have to access /etc/password
1441: # one way or another.
1442: # First: Make sure the current password is
1443: # correct
1444: &Debug("auth is unix");
1445: $contentpwd=(getpwnam($uname))[1];
1446: my $pwdcorrect = "0";
1447: my $pwauth_path="/usr/local/sbin/pwauth";
1448: unless ($contentpwd eq 'x') {
1449: $pwdcorrect=
1450: (crypt($upass,$contentpwd) eq $contentpwd);
1451: } elsif (-e $pwauth_path) {
1452: open PWAUTH, "|$pwauth_path" or
1453: die "Cannot invoke authentication";
1454: print PWAUTH "$uname\n$upass\n";
1455: close PWAUTH;
1456: &Debug("exited pwauth with $? ($uname,$upass) ");
1457: $pwdcorrect=($? == 0);
1458: }
1459: if ($pwdcorrect) {
1460: my $execdir=$perlvar{'lonDaemons'};
1461: &Debug("Opening lcpasswd pipeline");
1462: my $pf = IO::File->new("|$execdir/lcpasswd > $perlvar{'lonDaemons'}/logs/lcpasswd.log");
1463: print $pf "$uname\n$npass\n$npass\n";
1464: close $pf;
1465: my $err = $?;
1466: my $result = ($err>0 ? 'pwchange_failure'
1467: : 'ok');
1468: &logthis("Result of password change for $uname: ".
1469: &lcpasswdstrerror($?));
1470: print $client "$result\n";
1471: } else {
1472: print $client "non_authorized\n";
1473: }
1474: } else {
1475: print $client "auth_mode_error\n";
1476: }
1477: } else {
1478: print $client "unknown_user\n";
1479: }
1480: } else {
1481: Reply($client, "refused\n", $userinput);
1482:
1483: }
1484: # -------------------------------------------------------------------- makeuser
1485: } elsif ($userinput =~ /^makeuser/) { # encoded and client.
1486: &Debug("Make user received");
1487: my $oldumask=umask(0077);
1488: if (($wasenc==1) && isClient) {
1489: my
1490: ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput);
1491: &Debug("cmd =".$cmd." $udom =".$udom.
1492: " uname=".$uname);
1493: chomp($npass);
1494: $npass=&unescape($npass);
1495: my $proname=propath($udom,$uname);
1496: my $passfilename="$proname/passwd";
1497: &Debug("Password file created will be:".
1498: $passfilename);
1499: if (-e $passfilename) {
1500: print $client "already_exists\n";
1501: } elsif ($udom ne $currentdomainid) {
1502: print $client "not_right_domain\n";
1503: } else {
1504: my @fpparts=split(/\//,$proname);
1505: my $fpnow=$fpparts[0].'/'.$fpparts[1].'/'.$fpparts[2];
1506: my $fperror='';
1507: for (my $i=3;$i<=$#fpparts;$i++) {
1508: $fpnow.='/'.$fpparts[$i];
1509: unless (-e $fpnow) {
1510: unless (mkdir($fpnow,0777)) {
1511: $fperror="error: ".($!+0)
1512: ." mkdir failed while attempting "
1513: ."makeuser";
1514: }
1515: }
1516: }
1517: unless ($fperror) {
1518: my $result=&make_passwd_file($uname, $umode,$npass,
1519: $passfilename);
1520: print $client $result;
1521: } else {
1522: print $client "$fperror\n";
1523: }
1524: }
1525: } else {
1526: Reply($client, "refused\n", $userinput);
1527:
1528: }
1529: umask($oldumask);
1530: # -------------------------------------------------------------- changeuserauth
1531: } elsif ($userinput =~ /^changeuserauth/) { # encoded & client
1532: &Debug("Changing authorization");
1533: if (($wasenc==1) && isClient) {
1534: my
1535: ($cmd,$udom,$uname,$umode,$npass)=split(/:/,$userinput);
1536: chomp($npass);
1537: &Debug("cmd = ".$cmd." domain= ".$udom.
1538: "uname =".$uname." umode= ".$umode);
1539: $npass=&unescape($npass);
1540: my $proname=&propath($udom,$uname);
1541: my $passfilename="$proname/passwd";
1542: if ($udom ne $currentdomainid) {
1543: print $client "not_right_domain\n";
1544: } else {
1545: my $result=&make_passwd_file($uname, $umode,$npass,
1546: $passfilename);
1547: print $client $result;
1548: }
1549: } else {
1550: Reply($client, "refused\n", $userinput);
1551:
1552: }
1553: # ------------------------------------------------------------------------ home
1554: } elsif ($userinput =~ /^home/) { # client clear or encoded
1555: if(isClient) {
1556: my ($cmd,$udom,$uname)=split(/:/,$userinput);
1557: chomp($uname);
1558: my $proname=propath($udom,$uname);
1559: if (-e $proname) {
1560: print $client "found\n";
1561: } else {
1562: print $client "not_found\n";
1563: }
1564: } else {
1565: Reply($client, "refused\n", $userinput);
1566:
1567: }
1568: # ---------------------------------------------------------------------- update
1569: } elsif ($userinput =~ /^update/) { # client clear or encoded.
1570: if(isClient) {
1571: my ($cmd,$fname)=split(/:/,$userinput);
1572: my $ownership=ishome($fname);
1573: if ($ownership eq 'not_owner') {
1574: if (-e $fname) {
1575: my ($dev,$ino,$mode,$nlink,
1576: $uid,$gid,$rdev,$size,
1577: $atime,$mtime,$ctime,
1578: $blksize,$blocks)=stat($fname);
1579: my $now=time;
1580: my $since=$now-$atime;
1581: if ($since>$perlvar{'lonExpire'}) {
1582: my $reply=
1583: &reply("unsub:$fname","$clientname");
1584: unlink("$fname");
1585: } else {
1586: my $transname="$fname.in.transfer";
1587: my $remoteurl=
1588: &reply("sub:$fname","$clientname");
1589: my $response;
1590: {
1591: my $ua=new LWP::UserAgent;
1592: my $request=new HTTP::Request('GET',"$remoteurl");
1593: $response=$ua->request($request,$transname);
1594: }
1595: if ($response->is_error()) {
1596: unlink($transname);
1597: my $message=$response->status_line;
1598: &logthis(
1599: "LWP GET: $message for $fname ($remoteurl)");
1600: } else {
1601: if ($remoteurl!~/\.meta$/) {
1602: my $ua=new LWP::UserAgent;
1603: my $mrequest=
1604: new HTTP::Request('GET',$remoteurl.'.meta');
1605: my $mresponse=
1606: $ua->request($mrequest,$fname.'.meta');
1607: if ($mresponse->is_error()) {
1608: unlink($fname.'.meta');
1609: }
1610: }
1611: rename($transname,$fname);
1612: }
1613: }
1614: print $client "ok\n";
1615: } else {
1616: print $client "not_found\n";
1617: }
1618: } else {
1619: print $client "rejected\n";
1620: }
1621: } else {
1622: Reply($client, "refused\n", $userinput);
1623:
1624: }
1625: # -------------------------------------- fetch a user file from a remote server
1626: } elsif ($userinput =~ /^fetchuserfile/) { # Client clear or enc.
1627: if(isClient) {
1628: my ($cmd,$fname)=split(/:/,$userinput);
1629: my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);
1630: my $udir=propath($udom,$uname).'/userfiles';
1631: unless (-e $udir) { mkdir($udir,0770); }
1632: if (-e $udir) {
1633: $ufile=~s/^[\.\~]+//;
1634: my $path = $udir;
1635: if ($ufile =~m|(.+)/([^/]+)$|) {
1636: my @parts=split('/',$1);
1637: foreach my $part (@parts) {
1638: $path .= '/'.$part;
1639: if ((-e $path)!=1) {
1640: mkdir($path,0770);
1641: }
1642: }
1643: }
1644: my $destname=$udir.'/'.$ufile;
1645: my $transname=$udir.'/'.$ufile.'.in.transit';
1646: my $remoteurl='http://'.$clientip.'/userfiles/'.$fname;
1647: my $response;
1648: {
1649: my $ua=new LWP::UserAgent;
1650: my $request=new HTTP::Request('GET',"$remoteurl");
1651: $response=$ua->request($request,$transname);
1652: }
1653: if ($response->is_error()) {
1654: unlink($transname);
1655: my $message=$response->status_line;
1656: &logthis("LWP GET: $message for $fname ($remoteurl)");
1657: print $client "failed\n";
1658: } else {
1659: if (!rename($transname,$destname)) {
1660: &logthis("Unable to move $transname to $destname");
1661: unlink($transname);
1662: print $client "failed\n";
1663: } else {
1664: print $client "ok\n";
1665: }
1666: }
1667: } else {
1668: print $client "not_home\n";
1669: }
1670: } else {
1671: Reply($client, "refused\n", $userinput);
1672: }
1673: # --------------------------------------------------------- remove a user file
1674: } elsif ($userinput =~ /^removeuserfile/) { # Client clear or enc.
1675: if(isClient) {
1676: my ($cmd,$fname)=split(/:/,$userinput);
1677: my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);
1678: &logthis("$udom - $uname - $ufile");
1679: if ($ufile =~m|/\.\./|) {
1680: # any files paths with /../ in them refuse
1681: # to deal with
1682: print $client "refused\n";
1683: } else {
1684: my $udir=propath($udom,$uname);
1685: if (-e $udir) {
1686: my $file=$udir.'/userfiles/'.$ufile;
1687: if (-e $file) {
1688: unlink($file);
1689: if (-e $file) {
1690: print $client "failed\n";
1691: } else {
1692: print $client "ok\n";
1693: }
1694: } else {
1695: print $client "not_found\n";
1696: }
1697: } else {
1698: print $client "not_home\n";
1699: }
1700: }
1701: } else {
1702: Reply($client, "refused\n", $userinput);
1703: }
1704: # ------------------------------------------ authenticate access to a user file
1705: } elsif ($userinput =~ /^tokenauthuserfile/) { # Client only
1706: if(isClient) {
1707: my ($cmd,$fname,$session)=split(/:/,$userinput);
1708: chomp($session);
1709: my $reply='non_auth';
1710: if (open(ENVIN,$perlvar{'lonIDsDir'}.'/'.
1711: $session.'.id')) {
1712: while (my $line=<ENVIN>) {
1713: if ($line=~ m|userfile\.\Q$fname\E\=|) { $reply='ok'; }
1714: }
1715: close(ENVIN);
1716: print $client $reply."\n";
1717: } else {
1718: print $client "invalid_token\n";
1719: }
1720: } else {
1721: Reply($client, "refused\n", $userinput);
1722:
1723: }
1724: # ----------------------------------------------------------------- unsubscribe
1725: } elsif ($userinput =~ /^unsub/) {
1726: if(isClient) {
1727: my ($cmd,$fname)=split(/:/,$userinput);
1728: if (-e $fname) {
1729: print $client &unsub($fname,$clientip);
1730: } else {
1731: print $client "not_found\n";
1732: }
1733: } else {
1734: Reply($client, "refused\n", $userinput);
1735:
1736: }
1737: # ------------------------------------------------------------------- subscribe
1738: } elsif ($userinput =~ /^sub/) {
1739: if(isClient) {
1740: print $client &subscribe($userinput,$clientip);
1741: } else {
1742: Reply($client, "refused\n", $userinput);
1743:
1744: }
1745: # ------------------------------------------------------------- current version
1746: } elsif ($userinput =~ /^currentversion/) {
1747: if(isClient) {
1748: my ($cmd,$fname)=split(/:/,$userinput);
1749: print $client ¤tversion($fname)."\n";
1750: } else {
1751: Reply($client, "refused\n", $userinput);
1752:
1753: }
1754: # ------------------------------------------------------------------------- log
1755: } elsif ($userinput =~ /^log/) {
1756: if(isClient) {
1757: my ($cmd,$udom,$uname,$what)=split(/:/,$userinput);
1758: chomp($what);
1759: my $proname=propath($udom,$uname);
1760: my $now=time;
1761: {
1762: my $hfh;
1763: if ($hfh=IO::File->new(">>$proname/activity.log")) {
1764: print $hfh "$now:$clientname:$what\n";
1765: print $client "ok\n";
1766: } else {
1767: print $client "error: ".($!+0)
1768: ." IO::File->new Failed "
1769: ."while attempting log\n";
1770: }
1771: }
1772: } else {
1773: Reply($client, "refused\n", $userinput);
1774:
1775: }
1776: # ------------------------------------------------------------------------- put
1777: } elsif ($userinput =~ /^put/) {
1778: if(isClient) {
1779: my ($cmd,$udom,$uname,$namespace,$what)
1780: =split(/:/,$userinput,5);
1781: $namespace=~s/\//\_/g;
1782: $namespace=~s/\W//g;
1783: if ($namespace ne 'roles') {
1784: chomp($what);
1785: my $proname=propath($udom,$uname);
1786: my $now=time;
1787: my @pairs=split(/\&/,$what);
1788: my %hash;
1789: if (tie(%hash,'GDBM_File',
1790: "$proname/$namespace.db",
1791: &GDBM_WRCREAT(),0640)) {
1792: unless ($namespace=~/^nohist\_/) {
1793: my $hfh;
1794: if ($hfh=IO::File->new(">>$proname/$namespace.hist")) { print $hfh "P:$now:$what\n"; }
1795: }
1796:
1797: foreach my $pair (@pairs) {
1798: my ($key,$value)=split(/=/,$pair);
1799: $hash{$key}=$value;
1800: }
1801: if (untie(%hash)) {
1802: print $client "ok\n";
1803: } else {
1804: print $client "error: ".($!+0)
1805: ." untie(GDBM) failed ".
1806: "while attempting put\n";
1807: }
1808: } else {
1809: print $client "error: ".($!)
1810: ." tie(GDBM) Failed ".
1811: "while attempting put\n";
1812: }
1813: } else {
1814: print $client "refused\n";
1815: }
1816: } else {
1817: Reply($client, "refused\n", $userinput);
1818:
1819: }
1820: # ------------------------------------------------------------------- inc
1821: } elsif ($userinput =~ /^inc:/) {
1822: if(isClient) {
1823: my ($cmd,$udom,$uname,$namespace,$what)
1824: =split(/:/,$userinput);
1825: $namespace=~s/\//\_/g;
1826: $namespace=~s/\W//g;
1827: if ($namespace ne 'roles') {
1828: chomp($what);
1829: my $proname=propath($udom,$uname);
1830: my $now=time;
1831: my @pairs=split(/\&/,$what);
1832: my %hash;
1833: if (tie(%hash,'GDBM_File',
1834: "$proname/$namespace.db",
1835: &GDBM_WRCREAT(),0640)) {
1836: unless ($namespace=~/^nohist\_/) {
1837: my $hfh;
1838: if ($hfh=IO::File->new(">>$proname/$namespace.hist")) { print $hfh "P:$now:$what\n"; }
1839: }
1840: foreach my $pair (@pairs) {
1841: my ($key,$value)=split(/=/,$pair);
1842: # We could check that we have a number...
1843: if (! defined($value) || $value eq '') {
1844: $value = 1;
1845: }
1846: $hash{$key}+=$value;
1847: }
1848: if (untie(%hash)) {
1849: print $client "ok\n";
1850: } else {
1851: print $client "error: ".($!+0)
1852: ." untie(GDBM) failed ".
1853: "while attempting inc\n";
1854: }
1855: } else {
1856: print $client "error: ".($!)
1857: ." tie(GDBM) Failed ".
1858: "while attempting inc\n";
1859: }
1860: } else {
1861: print $client "refused\n";
1862: }
1863: } else {
1864: Reply($client, "refused\n", $userinput);
1865:
1866: }
1867: # -------------------------------------------------------------------- rolesput
1868: } elsif ($userinput =~ /^rolesput/) {
1869: if(isClient) {
1870: &Debug("rolesput");
1871: if ($wasenc==1) {
1872: my ($cmd,$exedom,$exeuser,$udom,$uname,$what)
1873: =split(/:/,$userinput);
1874: &Debug("cmd = ".$cmd." exedom= ".$exedom.
1875: "user = ".$exeuser." udom=".$udom.
1876: "what = ".$what);
1877: my $namespace='roles';
1878: chomp($what);
1879: my $proname=propath($udom,$uname);
1880: my $now=time;
1881: my @pairs=split(/\&/,$what);
1882: my %hash;
1883: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
1884: {
1885: my $hfh;
1886: if ($hfh=IO::File->new(">>$proname/$namespace.hist")) {
1887: print $hfh "P:$now:$exedom:$exeuser:$what\n";
1888: }
1889: }
1890:
1891: foreach my $pair (@pairs) {
1892: my ($key,$value)=split(/=/,$pair);
1893: &ManagePermissions($key, $udom, $uname,
1894: &GetAuthType( $udom,
1895: $uname));
1896: $hash{$key}=$value;
1897: }
1898: if (untie(%hash)) {
1899: print $client "ok\n";
1900: } else {
1901: print $client "error: ".($!+0)
1902: ." untie(GDBM) Failed ".
1903: "while attempting rolesput\n";
1904: }
1905: } else {
1906: print $client "error: ".($!+0)
1907: ." tie(GDBM) Failed ".
1908: "while attempting rolesput\n";
1909: }
1910: } else {
1911: print $client "refused\n";
1912: }
1913: } else {
1914: Reply($client, "refused\n", $userinput);
1915:
1916: }
1917: # -------------------------------------------------------------------- rolesdel
1918: } elsif ($userinput =~ /^rolesdel/) {
1919: if(isClient) {
1920: &Debug("rolesdel");
1921: if ($wasenc==1) {
1922: my ($cmd,$exedom,$exeuser,$udom,$uname,$what)
1923: =split(/:/,$userinput);
1924: &Debug("cmd = ".$cmd." exedom= ".$exedom.
1925: "user = ".$exeuser." udom=".$udom.
1926: "what = ".$what);
1927: my $namespace='roles';
1928: chomp($what);
1929: my $proname=propath($udom,$uname);
1930: my $now=time;
1931: my @rolekeys=split(/\&/,$what);
1932: my %hash;
1933: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
1934: {
1935: my $hfh;
1936: if ($hfh=IO::File->new(">>$proname/$namespace.hist")) {
1937: print $hfh "D:$now:$exedom:$exeuser:$what\n";
1938: }
1939: }
1940: foreach my $key (@rolekeys) {
1941: delete $hash{$key};
1942: }
1943: if (untie(%hash)) {
1944: print $client "ok\n";
1945: } else {
1946: print $client "error: ".($!+0)
1947: ." untie(GDBM) Failed ".
1948: "while attempting rolesdel\n";
1949: }
1950: } else {
1951: print $client "error: ".($!+0)
1952: ." tie(GDBM) Failed ".
1953: "while attempting rolesdel\n";
1954: }
1955: } else {
1956: print $client "refused\n";
1957: }
1958: } else {
1959: Reply($client, "refused\n", $userinput);
1960:
1961: }
1962: # ------------------------------------------------------------------------- get
1963: } elsif ($userinput =~ /^get/) {
1964: if(isClient) {
1965: my ($cmd,$udom,$uname,$namespace,$what)
1966: =split(/:/,$userinput);
1967: $namespace=~s/\//\_/g;
1968: $namespace=~s/\W//g;
1969: chomp($what);
1970: my @queries=split(/\&/,$what);
1971: my $proname=propath($udom,$uname);
1972: my $qresult='';
1973: my %hash;
1974: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
1975: for (my $i=0;$i<=$#queries;$i++) {
1976: $qresult.="$hash{$queries[$i]}&";
1977: }
1978: if (untie(%hash)) {
1979: $qresult=~s/\&$//;
1980: print $client "$qresult\n";
1981: } else {
1982: print $client "error: ".($!+0)
1983: ." untie(GDBM) Failed ".
1984: "while attempting get\n";
1985: }
1986: } else {
1987: if ($!+0 == 2) {
1988: print $client "error:No such file or ".
1989: "GDBM reported bad block error\n";
1990: } else {
1991: print $client "error: ".($!+0)
1992: ." tie(GDBM) Failed ".
1993: "while attempting get\n";
1994: }
1995: }
1996: } else {
1997: Reply($client, "refused\n", $userinput);
1998:
1999: }
2000: # ------------------------------------------------------------------------ eget
2001: } elsif ($userinput =~ /^eget/) {
2002: if (isClient) {
2003: my ($cmd,$udom,$uname,$namespace,$what)
2004: =split(/:/,$userinput);
2005: $namespace=~s/\//\_/g;
2006: $namespace=~s/\W//g;
2007: chomp($what);
2008: my @queries=split(/\&/,$what);
2009: my $proname=propath($udom,$uname);
2010: my $qresult='';
2011: my %hash;
2012: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
2013: for (my $i=0;$i<=$#queries;$i++) {
2014: $qresult.="$hash{$queries[$i]}&";
2015: }
2016: if (untie(%hash)) {
2017: $qresult=~s/\&$//;
2018: if ($cipher) {
2019: my $cmdlength=length($qresult);
2020: $qresult.=" ";
2021: my $encqresult='';
2022: for
2023: (my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
2024: $encqresult.=
2025: unpack("H16",
2026: $cipher->encrypt(substr($qresult,$encidx,8)));
2027: }
2028: print $client "enc:$cmdlength:$encqresult\n";
2029: } else {
2030: print $client "error:no_key\n";
2031: }
2032: } else {
2033: print $client "error: ".($!+0)
2034: ." untie(GDBM) Failed ".
2035: "while attempting eget\n";
2036: }
2037: } else {
2038: print $client "error: ".($!+0)
2039: ." tie(GDBM) Failed ".
2040: "while attempting eget\n";
2041: }
2042: } else {
2043: Reply($client, "refused\n", $userinput);
2044:
2045: }
2046: # ------------------------------------------------------------------------- del
2047: } elsif ($userinput =~ /^del/) {
2048: if(isClient) {
2049: my ($cmd,$udom,$uname,$namespace,$what)
2050: =split(/:/,$userinput);
2051: $namespace=~s/\//\_/g;
2052: $namespace=~s/\W//g;
2053: chomp($what);
2054: my $proname=propath($udom,$uname);
2055: my $now=time;
2056: my @keys=split(/\&/,$what);
2057: my %hash;
2058: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
2059: unless ($namespace=~/^nohist\_/) {
2060: my $hfh;
2061: if ($hfh=IO::File->new(">>$proname/$namespace.hist")) { print $hfh "D:$now:$what\n"; }
2062: }
2063: foreach my $key (@keys) {
2064: delete($hash{$key});
2065: }
2066: if (untie(%hash)) {
2067: print $client "ok\n";
2068: } else {
2069: print $client "error: ".($!+0)
2070: ." untie(GDBM) Failed ".
2071: "while attempting del\n";
2072: }
2073: } else {
2074: print $client "error: ".($!+0)
2075: ." tie(GDBM) Failed ".
2076: "while attempting del\n";
2077: }
2078: } else {
2079: Reply($client, "refused\n", $userinput);
2080:
2081: }
2082: # ------------------------------------------------------------------------ keys
2083: } elsif ($userinput =~ /^keys/) {
2084: if(isClient) {
2085: my ($cmd,$udom,$uname,$namespace)
2086: =split(/:/,$userinput);
2087: $namespace=~s/\//\_/g;
2088: $namespace=~s/\W//g;
2089: my $proname=propath($udom,$uname);
2090: my $qresult='';
2091: my %hash;
2092: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
2093: foreach my $key (keys %hash) {
2094: $qresult.="$key&";
2095: }
2096: if (untie(%hash)) {
2097: $qresult=~s/\&$//;
2098: print $client "$qresult\n";
2099: } else {
2100: print $client "error: ".($!+0)
2101: ." untie(GDBM) Failed ".
2102: "while attempting keys\n";
2103: }
2104: } else {
2105: print $client "error: ".($!+0)
2106: ." tie(GDBM) Failed ".
2107: "while attempting keys\n";
2108: }
2109: } else {
2110: Reply($client, "refused\n", $userinput);
2111:
2112: }
2113: # ----------------------------------------------------------------- dumpcurrent
2114: } elsif ($userinput =~ /^currentdump/) {
2115: if (isClient) {
2116: my ($cmd,$udom,$uname,$namespace)
2117: =split(/:/,$userinput);
2118: $namespace=~s/\//\_/g;
2119: $namespace=~s/\W//g;
2120: my $qresult='';
2121: my $proname=propath($udom,$uname);
2122: my %hash;
2123: if (tie(%hash,'GDBM_File',
2124: "$proname/$namespace.db",
2125: &GDBM_READER(),0640)) {
2126: # Structure of %data:
2127: # $data{$symb}->{$parameter}=$value;
2128: # $data{$symb}->{'v.'.$parameter}=$version;
2129: # since $parameter will be unescaped, we do not
2130: # have to worry about silly parameter names...
2131: my %data = ();
2132: while (my ($key,$value) = each(%hash)) {
2133: my ($v,$symb,$param) = split(/:/,$key);
2134: next if ($v eq 'version' || $symb eq 'keys');
2135: next if (exists($data{$symb}) &&
2136: exists($data{$symb}->{$param}) &&
2137: $data{$symb}->{'v.'.$param} > $v);
2138: $data{$symb}->{$param}=$value;
2139: $data{$symb}->{'v.'.$param}=$v;
2140: }
2141: if (untie(%hash)) {
2142: while (my ($symb,$param_hash) = each(%data)) {
2143: while(my ($param,$value) = each (%$param_hash)){
2144: next if ($param =~ /^v\./);
2145: $qresult.=$symb.':'.$param.'='.$value.'&';
2146: }
2147: }
2148: chop($qresult);
2149: print $client "$qresult\n";
2150: } else {
2151: print $client "error: ".($!+0)
2152: ." untie(GDBM) Failed ".
2153: "while attempting currentdump\n";
2154: }
2155: } else {
2156: print $client "error: ".($!+0)
2157: ." tie(GDBM) Failed ".
2158: "while attempting currentdump\n";
2159: }
2160: } else {
2161: Reply($client, "refused\n", $userinput);
2162: }
2163: # ------------------------------------------------------------------------ dump
2164: } elsif ($userinput =~ /^dump/) {
2165: if(isClient) {
2166: my ($cmd,$udom,$uname,$namespace,$regexp)
2167: =split(/:/,$userinput);
2168: $namespace=~s/\//\_/g;
2169: $namespace=~s/\W//g;
2170: if (defined($regexp)) {
2171: $regexp=&unescape($regexp);
2172: } else {
2173: $regexp='.';
2174: }
2175: my $qresult='';
2176: my $proname=propath($udom,$uname);
2177: my %hash;
2178: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
2179: while (my ($key,$value) = each(%hash)) {
2180: if ($regexp eq '.') {
2181: $qresult.=$key.'='.$value.'&';
2182: } else {
2183: my $unescapeKey = &unescape($key);
2184: if (eval('$unescapeKey=~/$regexp/')) {
2185: $qresult.="$key=$value&";
2186: }
2187: }
2188: }
2189: if (untie(%hash)) {
2190: chop($qresult);
2191: print $client "$qresult\n";
2192: } else {
2193: print $client "error: ".($!+0)
2194: ." untie(GDBM) Failed ".
2195: "while attempting dump\n";
2196: }
2197: } else {
2198: print $client "error: ".($!+0)
2199: ." tie(GDBM) Failed ".
2200: "while attempting dump\n";
2201: }
2202: } else {
2203: Reply($client, "refused\n", $userinput);
2204:
2205: }
2206: # ----------------------------------------------------------------------- store
2207: } elsif ($userinput =~ /^store/) {
2208: if(isClient) {
2209: my ($cmd,$udom,$uname,$namespace,$rid,$what)
2210: =split(/:/,$userinput);
2211: $namespace=~s/\//\_/g;
2212: $namespace=~s/\W//g;
2213: if ($namespace ne 'roles') {
2214: chomp($what);
2215: my $proname=propath($udom,$uname);
2216: my $now=time;
2217: my @pairs=split(/\&/,$what);
2218: my %hash;
2219: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_WRCREAT(),0640)) {
2220: unless ($namespace=~/^nohist\_/) {
2221: my $hfh;
2222: if ($hfh=IO::File->new(">>$proname/$namespace.hist")) {
2223: print $hfh "P:$now:$rid:$what\n";
2224: }
2225: }
2226: my @previouskeys=split(/&/,$hash{"keys:$rid"});
2227: my $key;
2228: $hash{"version:$rid"}++;
2229: my $version=$hash{"version:$rid"};
2230: my $allkeys='';
2231: foreach my $pair (@pairs) {
2232: my ($key,$value)=split(/=/,$pair);
2233: $allkeys.=$key.':';
2234: $hash{"$version:$rid:$key"}=$value;
2235: }
2236: $hash{"$version:$rid:timestamp"}=$now;
2237: $allkeys.='timestamp';
2238: $hash{"$version:keys:$rid"}=$allkeys;
2239: if (untie(%hash)) {
2240: print $client "ok\n";
2241: } else {
2242: print $client "error: ".($!+0)
2243: ." untie(GDBM) Failed ".
2244: "while attempting store\n";
2245: }
2246: } else {
2247: print $client "error: ".($!+0)
2248: ." tie(GDBM) Failed ".
2249: "while attempting store\n";
2250: }
2251: } else {
2252: print $client "refused\n";
2253: }
2254: } else {
2255: Reply($client, "refused\n", $userinput);
2256:
2257: }
2258: # --------------------------------------------------------------------- restore
2259: } elsif ($userinput =~ /^restore/) {
2260: if(isClient) {
2261: my ($cmd,$udom,$uname,$namespace,$rid)
2262: =split(/:/,$userinput);
2263: $namespace=~s/\//\_/g;
2264: $namespace=~s/\W//g;
2265: chomp($rid);
2266: my $proname=propath($udom,$uname);
2267: my $qresult='';
2268: my %hash;
2269: if (tie(%hash,'GDBM_File',"$proname/$namespace.db",&GDBM_READER(),0640)) {
2270: my $version=$hash{"version:$rid"};
2271: $qresult.="version=$version&";
2272: my $scope;
2273: for ($scope=1;$scope<=$version;$scope++) {
2274: my $vkeys=$hash{"$scope:keys:$rid"};
2275: my @keys=split(/:/,$vkeys);
2276: my $key;
2277: $qresult.="$scope:keys=$vkeys&";
2278: foreach $key (@keys) {
2279: $qresult.="$scope:$key=".$hash{"$scope:$rid:$key"}."&";
2280: }
2281: }
2282: if (untie(%hash)) {
2283: $qresult=~s/\&$//;
2284: print $client "$qresult\n";
2285: } else {
2286: print $client "error: ".($!+0)
2287: ." untie(GDBM) Failed ".
2288: "while attempting restore\n";
2289: }
2290: } else {
2291: print $client "error: ".($!+0)
2292: ." tie(GDBM) Failed ".
2293: "while attempting restore\n";
2294: }
2295: } else {
2296: Reply($client, "refused\n", $userinput);
2297:
2298: }
2299: # -------------------------------------------------------------------- chatsend
2300: } elsif ($userinput =~ /^chatsend/) {
2301: if(isClient) {
2302: my ($cmd,$cdom,$cnum,$newpost)=split(/\:/,$userinput);
2303: &chatadd($cdom,$cnum,$newpost);
2304: print $client "ok\n";
2305: } else {
2306: Reply($client, "refused\n", $userinput);
2307:
2308: }
2309: # -------------------------------------------------------------------- chatretr
2310: } elsif ($userinput =~ /^chatretr/) {
2311: if(isClient) {
2312: my
2313: ($cmd,$cdom,$cnum,$udom,$uname)=split(/\:/,$userinput);
2314: my $reply='';
2315: foreach (&getchat($cdom,$cnum,$udom,$uname)) {
2316: $reply.=&escape($_).':';
2317: }
2318: $reply=~s/\:$//;
2319: print $client $reply."\n";
2320: } else {
2321: Reply($client, "refused\n", $userinput);
2322:
2323: }
2324: # ------------------------------------------------------------------- querysend
2325: } elsif ($userinput =~ /^querysend/) {
2326: if (isClient) {
2327: my ($cmd,$query,
2328: $arg1,$arg2,$arg3)=split(/\:/,$userinput);
2329: $query=~s/\n*$//g;
2330: print $client "".
2331: sqlreply("$clientname\&$query".
2332: "\&$arg1"."\&$arg2"."\&$arg3")."\n";
2333: } else {
2334: Reply($client, "refused\n", $userinput);
2335:
2336: }
2337: # ------------------------------------------------------------------ queryreply
2338: } elsif ($userinput =~ /^queryreply/) {
2339: if(isClient) {
2340: my ($cmd,$id,$reply)=split(/:/,$userinput);
2341: my $store;
2342: my $execdir=$perlvar{'lonDaemons'};
2343: if ($store=IO::File->new(">$execdir/tmp/$id")) {
2344: $reply=~s/\&/\n/g;
2345: print $store $reply;
2346: close $store;
2347: my $store2=IO::File->new(">$execdir/tmp/$id.end");
2348: print $store2 "done\n";
2349: close $store2;
2350: print $client "ok\n";
2351: }
2352: else {
2353: print $client "error: ".($!+0)
2354: ." IO::File->new Failed ".
2355: "while attempting queryreply\n";
2356: }
2357: } else {
2358: Reply($client, "refused\n", $userinput);
2359:
2360: }
2361: # ----------------------------------------------------------------- courseidput
2362: } elsif ($userinput =~ /^courseidput/) {
2363: if(isClient) {
2364: my ($cmd,$udom,$what)=split(/:/,$userinput);
2365: chomp($what);
2366: $udom=~s/\W//g;
2367: my $proname=
2368: "$perlvar{'lonUsersDir'}/$udom/nohist_courseids";
2369: my $now=time;
2370: my @pairs=split(/\&/,$what);
2371: my %hash;
2372: if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {
2373: foreach my $pair (@pairs) {
2374: my ($key,$descr,$inst_code)=split(/=/,$pair);
2375: $hash{$key}=$descr.':'.$inst_code.':'.$now;
2376: }
2377: if (untie(%hash)) {
2378: print $client "ok\n";
2379: } else {
2380: print $client "error: ".($!+0)
2381: ." untie(GDBM) Failed ".
2382: "while attempting courseidput\n";
2383: }
2384: } else {
2385: print $client "error: ".($!+0)
2386: ." tie(GDBM) Failed ".
2387: "while attempting courseidput\n";
2388: }
2389: } else {
2390: Reply($client, "refused\n", $userinput);
2391:
2392: }
2393: # ---------------------------------------------------------------- courseiddump
2394: } elsif ($userinput =~ /^courseiddump/) {
2395: if(isClient) {
2396: my ($cmd,$udom,$since,$description)
2397: =split(/:/,$userinput);
2398: if (defined($description)) {
2399: $description=&unescape($description);
2400: } else {
2401: $description='.';
2402: }
2403: unless (defined($since)) { $since=0; }
2404: my $qresult='';
2405: my $proname=
2406: "$perlvar{'lonUsersDir'}/$udom/nohist_courseids";
2407: my %hash;
2408: if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) {
2409: while (my ($key,$value) = each(%hash)) {
2410: my ($descr,$lasttime,$inst_code);
2411: if ($value =~ m/^([^\:]*):([^\:]*):(\d+)$/) {
2412: ($descr,$inst_code,$lasttime)=($1,$2,$3);
2413: } else {
2414: ($descr,$lasttime) = split(/\:/,$value);
2415: }
2416: if ($lasttime<$since) { next; }
2417: if ($description eq '.') {
2418: $qresult.=$key.'='.$descr.':'.$inst_code.'&';
2419: } else {
2420: my $unescapeVal = &unescape($descr);
2421: if (eval('$unescapeVal=~/\Q$description\E/i')) {
2422: $qresult.=$key.'='.$descr.':'.$inst_code.'&';
2423: }
2424: }
2425: }
2426: if (untie(%hash)) {
2427: chop($qresult);
2428: print $client "$qresult\n";
2429: } else {
2430: print $client "error: ".($!+0)
2431: ." untie(GDBM) Failed ".
2432: "while attempting courseiddump\n";
2433: }
2434: } else {
2435: print $client "error: ".($!+0)
2436: ." tie(GDBM) Failed ".
2437: "while attempting courseiddump\n";
2438: }
2439: } else {
2440: Reply($client, "refused\n", $userinput);
2441:
2442: }
2443: # ----------------------------------------------------------------------- idput
2444: } elsif ($userinput =~ /^idput/) {
2445: if(isClient) {
2446: my ($cmd,$udom,$what)=split(/:/,$userinput);
2447: chomp($what);
2448: $udom=~s/\W//g;
2449: my $proname="$perlvar{'lonUsersDir'}/$udom/ids";
2450: my $now=time;
2451: my @pairs=split(/\&/,$what);
2452: my %hash;
2453: if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_WRCREAT(),0640)) {
2454: {
2455: my $hfh;
2456: if ($hfh=IO::File->new(">>$proname.hist")) {
2457: print $hfh "P:$now:$what\n";
2458: }
2459: }
2460: foreach my $pair (@pairs) {
2461: my ($key,$value)=split(/=/,$pair);
2462: $hash{$key}=$value;
2463: }
2464: if (untie(%hash)) {
2465: print $client "ok\n";
2466: } else {
2467: print $client "error: ".($!+0)
2468: ." untie(GDBM) Failed ".
2469: "while attempting idput\n";
2470: }
2471: } else {
2472: print $client "error: ".($!+0)
2473: ." tie(GDBM) Failed ".
2474: "while attempting idput\n";
2475: }
2476: } else {
2477: Reply($client, "refused\n", $userinput);
2478:
2479: }
2480: # ----------------------------------------------------------------------- idget
2481: } elsif ($userinput =~ /^idget/) {
2482: if(isClient) {
2483: my ($cmd,$udom,$what)=split(/:/,$userinput);
2484: chomp($what);
2485: $udom=~s/\W//g;
2486: my $proname="$perlvar{'lonUsersDir'}/$udom/ids";
2487: my @queries=split(/\&/,$what);
2488: my $qresult='';
2489: my %hash;
2490: if (tie(%hash,'GDBM_File',"$proname.db",&GDBM_READER(),0640)) {
2491: for (my $i=0;$i<=$#queries;$i++) {
2492: $qresult.="$hash{$queries[$i]}&";
2493: }
2494: if (untie(%hash)) {
2495: $qresult=~s/\&$//;
2496: print $client "$qresult\n";
2497: } else {
2498: print $client "error: ".($!+0)
2499: ." untie(GDBM) Failed ".
2500: "while attempting idget\n";
2501: }
2502: } else {
2503: print $client "error: ".($!+0)
2504: ." tie(GDBM) Failed ".
2505: "while attempting idget\n";
2506: }
2507: } else {
2508: Reply($client, "refused\n", $userinput);
2509:
2510: }
2511: # ---------------------------------------------------------------------- tmpput
2512: } elsif ($userinput =~ /^tmpput/) {
2513: if(isClient) {
2514: my ($cmd,$what)=split(/:/,$userinput);
2515: my $store;
2516: $tmpsnum++;
2517: my $id=$$.'_'.$clientip.'_'.$tmpsnum;
2518: $id=~s/\W/\_/g;
2519: $what=~s/\n//g;
2520: my $execdir=$perlvar{'lonDaemons'};
2521: if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) {
2522: print $store $what;
2523: close $store;
2524: print $client "$id\n";
2525: }
2526: else {
2527: print $client "error: ".($!+0)
2528: ."IO::File->new Failed ".
2529: "while attempting tmpput\n";
2530: }
2531: } else {
2532: Reply($client, "refused\n", $userinput);
2533:
2534: }
2535:
2536: # ---------------------------------------------------------------------- tmpget
2537: } elsif ($userinput =~ /^tmpget/) {
2538: if(isClient) {
2539: my ($cmd,$id)=split(/:/,$userinput);
2540: chomp($id);
2541: $id=~s/\W/\_/g;
2542: my $store;
2543: my $execdir=$perlvar{'lonDaemons'};
2544: if ($store=IO::File->new("$execdir/tmp/$id.tmp")) {
2545: my $reply=<$store>;
2546: print $client "$reply\n";
2547: close $store;
2548: }
2549: else {
2550: print $client "error: ".($!+0)
2551: ."IO::File->new Failed ".
2552: "while attempting tmpget\n";
2553: }
2554: } else {
2555: Reply($client, "refused\n", $userinput);
2556:
2557: }
2558: # ---------------------------------------------------------------------- tmpdel
2559: } elsif ($userinput =~ /^tmpdel/) {
2560: if(isClient) {
2561: my ($cmd,$id)=split(/:/,$userinput);
2562: chomp($id);
2563: $id=~s/\W/\_/g;
2564: my $execdir=$perlvar{'lonDaemons'};
2565: if (unlink("$execdir/tmp/$id.tmp")) {
2566: print $client "ok\n";
2567: } else {
2568: print $client "error: ".($!+0)
2569: ."Unlink tmp Failed ".
2570: "while attempting tmpdel\n";
2571: }
2572: } else {
2573: Reply($client, "refused\n", $userinput);
2574:
2575: }
2576: # ----------------------------------------- portfolio directory list (portls)
2577: } elsif ($userinput =~ /^portls/) {
2578: if(isClient) {
2579: my ($cmd,$uname,$udom)=split(/:/,$userinput);
2580: my $udir=propath($udom,$uname).'/userfiles/portfolio';
2581: my $dirLine='';
2582: my $dirContents='';
2583: if (opendir(LSDIR,$udir.'/')){
2584: while ($dirLine = readdir(LSDIR)){
2585: $dirContents = $dirContents.$dirLine.'<br />';
2586: }
2587: } else {
2588: $dirContents = "No directory found\n";
2589: }
2590: print $client $dirContents."\n";
2591: } else {
2592: Reply($client, "refused\n", $userinput);
2593: }
2594: # -------------------------------------------------------------------------- ls
2595: } elsif ($userinput =~ /^ls/) {
2596: if(isClient) {
2597: my $obs;
2598: my $rights;
2599: my ($cmd,$ulsdir)=split(/:/,$userinput);
2600: my $ulsout='';
2601: my $ulsfn;
2602: if (-e $ulsdir) {
2603: if(-d $ulsdir) {
2604: if (opendir(LSDIR,$ulsdir)) {
2605: while ($ulsfn=readdir(LSDIR)) {
2606: undef $obs, $rights;
2607: my @ulsstats=stat($ulsdir.'/'.$ulsfn);
2608: #We do some obsolete checking here
2609: if(-e $ulsdir.'/'.$ulsfn.".meta") {
2610: open(FILE, $ulsdir.'/'.$ulsfn.".meta");
2611: my @obsolete=<FILE>;
2612: foreach my $obsolete (@obsolete) {
2613: if($obsolete =~ m|(<obsolete>)(on)|) { $obs = 1; }
2614: if($obsolete =~ m|(<copyright>)(default)|) { $rights = 1; }
2615: }
2616: }
2617: $ulsout.=$ulsfn.'&'.join('&',@ulsstats);
2618: if($obs eq '1') { $ulsout.="&1"; }
2619: else { $ulsout.="&0"; }
2620: if($rights eq '1') { $ulsout.="&1:"; }
2621: else { $ulsout.="&0:"; }
2622: }
2623: closedir(LSDIR);
2624: }
2625: } else {
2626: my @ulsstats=stat($ulsdir);
2627: $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':';
2628: }
2629: } else {
2630: $ulsout='no_such_dir';
2631: }
2632: if ($ulsout eq '') { $ulsout='empty'; }
2633: print $client "$ulsout\n";
2634: } else {
2635: Reply($client, "refused\n", $userinput);
2636:
2637: }
2638: # ----------------------------------------------------------------- setannounce
2639: } elsif ($userinput =~ /^setannounce/) {
2640: if (isClient) {
2641: my ($cmd,$announcement)=split(/:/,$userinput);
2642: chomp($announcement);
2643: $announcement=&unescape($announcement);
2644: if (my $store=IO::File->new('>'.$perlvar{'lonDocRoot'}.
2645: '/announcement.txt')) {
2646: print $store $announcement;
2647: close $store;
2648: print $client "ok\n";
2649: } else {
2650: print $client "error: ".($!+0)."\n";
2651: }
2652: } else {
2653: Reply($client, "refused\n", $userinput);
2654:
2655: }
2656: # ------------------------------------------------------------------ Hanging up
2657: } elsif (($userinput =~ /^exit/) ||
2658: ($userinput =~ /^init/)) { # no restrictions.
2659: &logthis(
2660: "Client $clientip ($clientname) hanging up: $userinput");
2661: print $client "bye\n";
2662: $client->shutdown(2); # shutdown the socket forcibly.
2663: $client->close();
2664: return 0;
2665:
2666: # ---------------------------------- set current host/domain
2667: } elsif ($userinput =~ /^sethost:/) {
2668: if (isClient) {
2669: print $client &sethost($userinput)."\n";
2670: } else {
2671: print $client "refused\n";
2672: }
2673: #---------------------------------- request file (?) version.
2674: } elsif ($userinput =~/^version:/) {
2675: if (isClient) {
2676: print $client &version($userinput)."\n";
2677: } else {
2678: print $client "refused\n";
2679: }
2680: #------------------------------- is auto-enrollment enabled?
2681: } elsif ($userinput =~/^autorun:/) {
2682: if (isClient) {
2683: my ($cmd,$cdom) = split(/:/,$userinput);
2684: my $outcome = &localenroll::run($cdom);
2685: print $client "$outcome\n";
2686: } else {
2687: print $client "0\n";
2688: }
2689: #------------------------------- get official sections (for auto-enrollment).
2690: } elsif ($userinput =~/^autogetsections:/) {
2691: if (isClient) {
2692: my ($cmd,$coursecode,$cdom)=split(/:/,$userinput);
2693: my @secs = &localenroll::get_sections($coursecode,$cdom);
2694: my $seclist = &escape(join(':',@secs));
2695: print $client "$seclist\n";
2696: } else {
2697: print $client "refused\n";
2698: }
2699: #----------------------- validate owner of new course section (for auto-enrollment).
2700: } elsif ($userinput =~/^autonewcourse:/) {
2701: if (isClient) {
2702: my ($cmd,$inst_course_id,$owner,$cdom)=split(/:/,$userinput);
2703: my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom);
2704: print $client "$outcome\n";
2705: } else {
2706: print $client "refused\n";
2707: }
2708: #-------------- validate course section in schedule of classes (for auto-enrollment).
2709: } elsif ($userinput =~/^autovalidatecourse:/) {
2710: if (isClient) {
2711: my ($cmd,$inst_course_id,$cdom)=split(/:/,$userinput);
2712: my $outcome=&localenroll::validate_courseID($inst_course_id,$cdom);
2713: print $client "$outcome\n";
2714: } else {
2715: print $client "refused\n";
2716: }
2717: #--------------------------- create password for new user (for auto-enrollment).
2718: } elsif ($userinput =~/^autocreatepassword:/) {
2719: if (isClient) {
2720: my ($cmd,$authparam,$cdom)=split(/:/,$userinput);
2721: my ($create_passwd,$authchk);
2722: ($authparam,$create_passwd,$authchk) = &localenroll::create_password($authparam,$cdom);
2723: print $client &escape($authparam.':'.$create_passwd.':'.$authchk)."\n";
2724: } else {
2725: print $client "refused\n";
2726: }
2727: #--------------------------- read and remove temporary files (for auto-enrollment).
2728: } elsif ($userinput =~/^autoretrieve:/) {
2729: if (isClient) {
2730: my ($cmd,$filename) = split(/:/,$userinput);
2731: my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename;
2732: if ( (-e $source) && ($filename ne '') ) {
2733: my $reply = '';
2734: if (open(my $fh,$source)) {
2735: while (<$fh>) {
2736: chomp($_);
2737: $_ =~ s/^\s+//g;
2738: $_ =~ s/\s+$//g;
2739: $reply .= $_;
2740: }
2741: close($fh);
2742: print $client &escape($reply)."\n";
2743: # unlink($source);
2744: } else {
2745: print $client "error\n";
2746: }
2747: } else {
2748: print $client "error\n";
2749: }
2750: } else {
2751: print $client "refused\n";
2752: }
2753: #--------------------- read and retrieve institutional code format (for support form).
2754: } elsif ($userinput =~/^autoinstcodeformat:/) {
2755: if (isClient) {
2756: my $reply;
2757: my($cmd,$cdom,$course) = split(/:/,$userinput);
2758: my @pairs = split/\&/,$course;
2759: my %instcodes = ();
2760: my %codes = ();
2761: my @codetitles = ();
2762: my %cat_titles = ();
2763: my %cat_order = ();
2764: foreach (@pairs) {
2765: my ($key,$value) = split/=/,$_;
2766: $instcodes{&unescape($key)} = &unescape($value);
2767: }
2768: my $formatreply = &localenroll::instcode_format($cdom,\%instcodes,\%codes,\@codetitles,\%cat_titles,\%cat_order);
2769: if ($formatreply eq 'ok') {
2770: my $codes_str = &hash2str(%codes);
2771: my $codetitles_str = &array2str(@codetitles);
2772: my $cat_titles_str = &hash2str(%cat_titles);
2773: my $cat_order_str = &hash2str(%cat_order);
2774: print $client $codes_str.':'.$codetitles_str.':'.$cat_titles_str.':'.$cat_order_str."\n";
2775: }
2776: } else {
2777: print $client "refused\n";
2778: }
2779: # ------------------------------------------------------------- unknown command
2780:
2781: } else {
2782: # unknown command
2783: print $client "unknown_cmd\n";
2784: }
2785: # -------------------------------------------------------------------- complete
2786: Debug("process_request - returning 1");
2787: return 1;
2788: }
2789: #
2790: # Decipher encoded traffic
2791: # Parameters:
2792: # input - Encoded data.
2793: # Returns:
2794: # Decoded data or undef if encryption key was not yet negotiated.
2795: # Implicit input:
2796: # cipher - This global holds the negotiated encryption key.
2797: #
2798: sub decipher {
2799: my ($input) = @_;
2800: my $output = '';
2801:
2802:
2803: if($cipher) {
2804: my($enc, $enclength, $encinput) = split(/:/, $input);
2805: for(my $encidx = 0; $encidx < length($encinput); $encidx += 16) {
2806: $output .=
2807: $cipher->decrypt(pack("H16", substr($encinput, $encidx, 16)));
2808: }
2809: return substr($output, 0, $enclength);
2810: } else {
2811: return undef;
2812: }
2813: }
2814:
2815: #
2816: # Register a command processor. This function is invoked to register a sub
2817: # to process a request. Once registered, the ProcessRequest sub can automatically
2818: # dispatch requests to an appropriate sub, and do the top level validity checking
2819: # as well:
2820: # - Is the keyword recognized.
2821: # - Is the proper client type attempting the request.
2822: # - Is the request encrypted if it has to be.
2823: # Parameters:
2824: # $request_name - Name of the request being registered.
2825: # This is the command request that will match
2826: # against the hash keywords to lookup the information
2827: # associated with the dispatch information.
2828: # $procedure - Reference to a sub to call to process the request.
2829: # All subs get called as follows:
2830: # Procedure($cmd, $tail, $replyfd, $key)
2831: # $cmd - the actual keyword that invoked us.
2832: # $tail - the tail of the request that invoked us.
2833: # $replyfd- File descriptor connected to the client
2834: # $must_encode - True if the request must be encoded to be good.
2835: # $client_ok - True if it's ok for a client to request this.
2836: # $manager_ok - True if it's ok for a manager to request this.
2837: # Side effects:
2838: # - On success, the Dispatcher hash has an entry added for the key $RequestName
2839: # - On failure, the program will die as it's a bad internal bug to try to
2840: # register a duplicate command handler.
2841: #
2842: sub register_handler {
2843: my ($request_name,$procedure,$must_encode, $client_ok,$manager_ok) = @_;
2844:
2845: # Don't allow duplication#
2846:
2847: if (defined $Dispatcher{$request_name}) {
2848: die "Attempting to define a duplicate request handler for $request_name\n";
2849: }
2850: # Build the client type mask:
2851:
2852: my $client_type_mask = 0;
2853: if($client_ok) {
2854: $client_type_mask |= $CLIENT_OK;
2855: }
2856: if($manager_ok) {
2857: $client_type_mask |= $MANAGER_OK;
2858: }
2859:
2860: # Enter the hash:
2861:
2862: my @entry = ($procedure, $must_encode, $client_type_mask);
2863:
2864: $Dispatcher{$request_name} = \@entry;
2865:
2866:
2867: }
2868:
2869:
2870: #------------------------------------------------------------------
2871:
2872:
2873:
2874:
2875: #
2876: # Convert an error return code from lcpasswd to a string value.
2877: #
2878: sub lcpasswdstrerror {
2879: my $ErrorCode = shift;
2880: if(($ErrorCode < 0) || ($ErrorCode > $lastpwderror)) {
2881: return "lcpasswd Unrecognized error return value ".$ErrorCode;
2882: } else {
2883: return $passwderrors[$ErrorCode];
2884: }
2885: }
2886:
2887: #
2888: # Convert an error return code from lcuseradd to a string value:
2889: #
2890: sub lcuseraddstrerror {
2891: my $ErrorCode = shift;
2892: if(($ErrorCode < 0) || ($ErrorCode > $lastadderror)) {
2893: return "lcuseradd - Unrecognized error code: ".$ErrorCode;
2894: } else {
2895: return $adderrors[$ErrorCode];
2896: }
2897: }
2898:
2899: # grabs exception and records it to log before exiting
2900: sub catchexception {
2901: my ($error)=@_;
2902: $SIG{'QUIT'}='DEFAULT';
2903: $SIG{__DIE__}='DEFAULT';
2904: &status("Catching exception");
2905: &logthis("<font color='red'>CRITICAL: "
2906: ."ABNORMAL EXIT. Child $$ for server $thisserver died through "
2907: ."a crash with this error msg->[$error]</font>");
2908: &logthis('Famous last words: '.$status.' - '.$lastlog);
2909: if ($client) { print $client "error: $error\n"; }
2910: $server->close();
2911: die($error);
2912: }
2913:
2914: sub timeout {
2915: &status("Handling Timeout");
2916: &logthis("<font color='red'>CRITICAL: TIME OUT ".$$."</font>");
2917: &catchexception('Timeout');
2918: }
2919: # -------------------------------- Set signal handlers to record abnormal exits
2920:
2921: $SIG{'QUIT'}=\&catchexception;
2922: $SIG{__DIE__}=\&catchexception;
2923:
2924: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
2925: &status("Read loncapa.conf and loncapa_apache.conf");
2926: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
2927: %perlvar=%{$perlvarref};
2928: undef $perlvarref;
2929:
2930: # ----------------------------- Make sure this process is running from user=www
2931: my $wwwid=getpwnam('www');
2932: if ($wwwid!=$<) {
2933: my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
2934: my $subj="LON: $currenthostid User ID mismatch";
2935: system("echo 'User ID mismatch. lond must be run as user www.' |\
2936: mailto $emailto -s '$subj' > /dev/null");
2937: exit 1;
2938: }
2939:
2940: # --------------------------------------------- Check if other instance running
2941:
2942: my $pidfile="$perlvar{'lonDaemons'}/logs/lond.pid";
2943:
2944: if (-e $pidfile) {
2945: my $lfh=IO::File->new("$pidfile");
2946: my $pide=<$lfh>;
2947: chomp($pide);
2948: if (kill 0 => $pide) { die "already running"; }
2949: }
2950:
2951: # ------------------------------------------------------------- Read hosts file
2952:
2953:
2954:
2955: # establish SERVER socket, bind and listen.
2956: $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'},
2957: Type => SOCK_STREAM,
2958: Proto => 'tcp',
2959: Reuse => 1,
2960: Listen => 10 )
2961: or die "making socket: $@\n";
2962:
2963: # --------------------------------------------------------- Do global variables
2964:
2965: # global variables
2966:
2967: my %children = (); # keys are current child process IDs
2968:
2969: sub REAPER { # takes care of dead children
2970: $SIG{CHLD} = \&REAPER;
2971: &status("Handling child death");
2972: my $pid;
2973: do {
2974: $pid = waitpid(-1,&WNOHANG());
2975: if (defined($children{$pid})) {
2976: &logthis("Child $pid died");
2977: delete($children{$pid});
2978: } elsif ($pid > 0) {
2979: &logthis("Unknown Child $pid died");
2980: }
2981: } while ( $pid > 0 );
2982: foreach my $child (keys(%children)) {
2983: $pid = waitpid($child,&WNOHANG());
2984: if ($pid > 0) {
2985: &logthis("Child $child - $pid looks like we missed it's death");
2986: delete($children{$pid});
2987: }
2988: }
2989: &status("Finished Handling child death");
2990: }
2991:
2992: sub HUNTSMAN { # signal handler for SIGINT
2993: &status("Killing children (INT)");
2994: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
2995: kill 'INT' => keys %children;
2996: &logthis("Free socket: ".shutdown($server,2)); # free up socket
2997: my $execdir=$perlvar{'lonDaemons'};
2998: unlink("$execdir/logs/lond.pid");
2999: &logthis("<font color='red'>CRITICAL: Shutting down</font>");
3000: &status("Done killing children");
3001: exit; # clean up with dignity
3002: }
3003:
3004: sub HUPSMAN { # signal handler for SIGHUP
3005: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
3006: &status("Killing children for restart (HUP)");
3007: kill 'INT' => keys %children;
3008: &logthis("Free socket: ".shutdown($server,2)); # free up socket
3009: &logthis("<font color='red'>CRITICAL: Restarting</font>");
3010: my $execdir=$perlvar{'lonDaemons'};
3011: unlink("$execdir/logs/lond.pid");
3012: &status("Restarting self (HUP)");
3013: exec("$execdir/lond"); # here we go again
3014: }
3015:
3016: #
3017: # Kill off hashes that describe the host table prior to re-reading it.
3018: # Hashes affected are:
3019: # %hostid, %hostdom %hostip %hostdns.
3020: #
3021: sub KillHostHashes {
3022: foreach my $key (keys %hostid) {
3023: delete $hostid{$key};
3024: }
3025: foreach my $key (keys %hostdom) {
3026: delete $hostdom{$key};
3027: }
3028: foreach my $key (keys %hostip) {
3029: delete $hostip{$key};
3030: }
3031: foreach my $key (keys %hostdns) {
3032: delete $hostdns{$key};
3033: }
3034: }
3035: #
3036: # Read in the host table from file and distribute it into the various hashes:
3037: #
3038: # - %hostid - Indexed by IP, the loncapa hostname.
3039: # - %hostdom - Indexed by loncapa hostname, the domain.
3040: # - %hostip - Indexed by hostid, the Ip address of the host.
3041: sub ReadHostTable {
3042:
3043: open (CONFIG,"$perlvar{'lonTabDir'}/hosts.tab") || die "Can't read host file";
3044: my $myloncapaname = $perlvar{'lonHostID'};
3045: Debug("My loncapa name is : $myloncapaname");
3046: while (my $configline=<CONFIG>) {
3047: if (!($configline =~ /^\s*\#/)) {
3048: my ($id,$domain,$role,$name,$ip)=split(/:/,$configline);
3049: chomp($ip); $ip=~s/\D+$//;
3050: $hostid{$ip}=$id; # LonCAPA name of host by IP.
3051: $hostdom{$id}=$domain; # LonCAPA domain name of host.
3052: $hostip{$id}=$ip; # IP address of host.
3053: $hostdns{$name} = $id; # LonCAPA name of host by DNS.
3054:
3055: if ($id eq $perlvar{'lonHostID'}) {
3056: Debug("Found me in the host table: $name");
3057: $thisserver=$name;
3058: }
3059: }
3060: }
3061: close(CONFIG);
3062: }
3063: #
3064: # Reload the Apache daemon's state.
3065: # This is done by invoking /home/httpd/perl/apachereload
3066: # a setuid perl script that can be root for us to do this job.
3067: #
3068: sub ReloadApache {
3069: my $execdir = $perlvar{'lonDaemons'};
3070: my $script = $execdir."/apachereload";
3071: system($script);
3072: }
3073:
3074: #
3075: # Called in response to a USR2 signal.
3076: # - Reread hosts.tab
3077: # - All children connected to hosts that were removed from hosts.tab
3078: # are killed via SIGINT
3079: # - All children connected to previously existing hosts are sent SIGUSR1
3080: # - Our internal hosts hash is updated to reflect the new contents of
3081: # hosts.tab causing connections from hosts added to hosts.tab to
3082: # now be honored.
3083: #
3084: sub UpdateHosts {
3085: &status("Reload hosts.tab");
3086: logthis('<font color="blue"> Updating connections </font>');
3087: #
3088: # The %children hash has the set of IP's we currently have children
3089: # on. These need to be matched against records in the hosts.tab
3090: # Any ip's no longer in the table get killed off they correspond to
3091: # either dropped or changed hosts. Note that the re-read of the table
3092: # will take care of new and changed hosts as connections come into being.
3093:
3094:
3095: KillHostHashes;
3096: ReadHostTable;
3097:
3098: foreach my $child (keys %children) {
3099: my $childip = $children{$child};
3100: if(!$hostid{$childip}) {
3101: logthis('<font color="blue"> UpdateHosts killing child '
3102: ." $child for ip $childip </font>");
3103: kill('INT', $child);
3104: } else {
3105: logthis('<font color="green"> keeping child for ip '
3106: ." $childip (pid=$child) </font>");
3107: }
3108: }
3109: ReloadApache;
3110: &status("Finished reloading hosts.tab");
3111: }
3112:
3113:
3114: sub checkchildren {
3115: &status("Checking on the children (sending signals)");
3116: &initnewstatus();
3117: &logstatus();
3118: &logthis('Going to check on the children');
3119: my $docdir=$perlvar{'lonDocRoot'};
3120: foreach (sort keys %children) {
3121: sleep 1;
3122: unless (kill 'USR1' => $_) {
3123: &logthis ('Child '.$_.' is dead');
3124: &logstatus($$.' is dead');
3125: }
3126: }
3127: sleep 5;
3128: $SIG{ALRM} = sub { Debug("timeout");
3129: die "timeout"; };
3130: $SIG{__DIE__} = 'DEFAULT';
3131: &status("Checking on the children (waiting for reports)");
3132: foreach (sort keys %children) {
3133: unless (-e "$docdir/lon-status/londchld/$_.txt") {
3134: eval {
3135: alarm(300);
3136: &logthis('Child '.$_.' did not respond');
3137: kill 9 => $_;
3138: #$emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
3139: #$subj="LON: $currenthostid killed lond process $_";
3140: #my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`;
3141: #$execdir=$perlvar{'lonDaemons'};
3142: #$result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_`;
3143: alarm(0);
3144: }
3145: }
3146: }
3147: $SIG{ALRM} = 'DEFAULT';
3148: $SIG{__DIE__} = \&catchexception;
3149: &status("Finished checking children");
3150: }
3151:
3152: # --------------------------------------------------------------------- Logging
3153:
3154: sub logthis {
3155: my $message=shift;
3156: my $execdir=$perlvar{'lonDaemons'};
3157: my $fh=IO::File->new(">>$execdir/logs/lond.log");
3158: my $now=time;
3159: my $local=localtime($now);
3160: $lastlog=$local.': '.$message;
3161: print $fh "$local ($$): $message\n";
3162: }
3163:
3164: # ------------------------- Conditional log if $DEBUG true.
3165: sub Debug {
3166: my $message = shift;
3167: if($DEBUG) {
3168: &logthis($message);
3169: }
3170: }
3171:
3172: #
3173: # Sub to do replies to client.. this gives a hook for some
3174: # debug tracing too:
3175: # Parameters:
3176: # fd - File open on client.
3177: # reply - Text to send to client.
3178: # request - Original request from client.
3179: #
3180: sub Reply {
3181: my ($fd, $reply, $request) = @_;
3182: print $fd $reply;
3183: Debug("Request was $request Reply was $reply");
3184:
3185: $Transactions++;
3186:
3187:
3188: }
3189:
3190:
3191: #
3192: # Sub to report a failure.
3193: # This function:
3194: # - Increments the failure statistic counters.
3195: # - Invokes Reply to send the error message to the client.
3196: # Parameters:
3197: # fd - File descriptor open on the client
3198: # reply - Reply text to emit.
3199: # request - The original request message (used by Reply
3200: # to debug if that's enabled.
3201: # Implicit outputs:
3202: # $Failures- The number of failures is incremented.
3203: # Reply (invoked here) sends a message to the
3204: # client:
3205: #
3206: sub Failure {
3207: my $fd = shift;
3208: my $reply = shift;
3209: my $request = shift;
3210:
3211: $Failures++;
3212: Reply($fd, $reply, $request); # That's simple eh?
3213: }
3214: # ------------------------------------------------------------------ Log status
3215:
3216: sub logstatus {
3217: &status("Doing logging");
3218: my $docdir=$perlvar{'lonDocRoot'};
3219: {
3220: my $fh=IO::File->new(">>$docdir/lon-status/londstatus.txt");
3221: print $fh $$."\t".$clientname."\t".$currenthostid."\t"
3222: .$status."\t".$lastlog."\t $keymode\n";
3223: $fh->close();
3224: }
3225: &status("Finished londstatus.txt");
3226: {
3227: my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt");
3228: print $fh $status."\n".$lastlog."\n".time."\n$keymode";
3229: $fh->close();
3230: }
3231: &status("Finished logging");
3232: }
3233:
3234: sub initnewstatus {
3235: my $docdir=$perlvar{'lonDocRoot'};
3236: my $fh=IO::File->new(">$docdir/lon-status/londstatus.txt");
3237: my $now=time;
3238: my $local=localtime($now);
3239: print $fh "LOND status $local - parent $$\n\n";
3240: opendir(DIR,"$docdir/lon-status/londchld");
3241: while (my $filename=readdir(DIR)) {
3242: unlink("$docdir/lon-status/londchld/$filename");
3243: }
3244: closedir(DIR);
3245: }
3246:
3247: # -------------------------------------------------------------- Status setting
3248:
3249: sub status {
3250: my $what=shift;
3251: my $now=time;
3252: my $local=localtime($now);
3253: $status=$local.': '.$what;
3254: $0='lond: '.$what.' '.$local;
3255: }
3256:
3257: # -------------------------------------------------------- Escape Special Chars
3258:
3259: sub escape {
3260: my $str=shift;
3261: $str =~ s/(\W)/"%".unpack('H2',$1)/eg;
3262: return $str;
3263: }
3264:
3265: # ----------------------------------------------------- Un-Escape Special Chars
3266:
3267: sub unescape {
3268: my $str=shift;
3269: $str =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
3270: return $str;
3271: }
3272:
3273: # ----------------------------------------------------------- Send USR1 to lonc
3274:
3275: sub reconlonc {
3276: my $peerfile=shift;
3277: &logthis("Trying to reconnect for $peerfile");
3278: my $loncfile="$perlvar{'lonDaemons'}/logs/lonc.pid";
3279: if (my $fh=IO::File->new("$loncfile")) {
3280: my $loncpid=<$fh>;
3281: chomp($loncpid);
3282: if (kill 0 => $loncpid) {
3283: &logthis("lonc at pid $loncpid responding, sending USR1");
3284: kill USR1 => $loncpid;
3285: } else {
3286: &logthis(
3287: "<font color='red'>CRITICAL: "
3288: ."lonc at pid $loncpid not responding, giving up</font>");
3289: }
3290: } else {
3291: &logthis('<font color="red">CRITICAL: lonc not running, giving up</font>');
3292: }
3293: }
3294:
3295: # -------------------------------------------------- Non-critical communication
3296:
3297: sub subreply {
3298: my ($cmd,$server)=@_;
3299: my $peerfile="$perlvar{'lonSockDir'}/$server";
3300: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile",
3301: Type => SOCK_STREAM,
3302: Timeout => 10)
3303: or return "con_lost";
3304: print $sclient "$cmd\n";
3305: my $answer=<$sclient>;
3306: chomp($answer);
3307: if (!$answer) { $answer="con_lost"; }
3308: return $answer;
3309: }
3310:
3311: sub reply {
3312: my ($cmd,$server)=@_;
3313: my $answer;
3314: if ($server ne $currenthostid) {
3315: $answer=subreply($cmd,$server);
3316: if ($answer eq 'con_lost') {
3317: $answer=subreply("ping",$server);
3318: if ($answer ne $server) {
3319: &logthis("sub reply: answer != server answer is $answer, server is $server");
3320: &reconlonc("$perlvar{'lonSockDir'}/$server");
3321: }
3322: $answer=subreply($cmd,$server);
3323: }
3324: } else {
3325: $answer='self_reply';
3326: }
3327: return $answer;
3328: }
3329:
3330: # -------------------------------------------------------------- Talk to lonsql
3331:
3332: sub sqlreply {
3333: my ($cmd)=@_;
3334: my $answer=subsqlreply($cmd);
3335: if ($answer eq 'con_lost') { $answer=subsqlreply($cmd); }
3336: return $answer;
3337: }
3338:
3339: sub subsqlreply {
3340: my ($cmd)=@_;
3341: my $unixsock="mysqlsock";
3342: my $peerfile="$perlvar{'lonSockDir'}/$unixsock";
3343: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile",
3344: Type => SOCK_STREAM,
3345: Timeout => 10)
3346: or return "con_lost";
3347: print $sclient "$cmd\n";
3348: my $answer=<$sclient>;
3349: chomp($answer);
3350: if (!$answer) { $answer="con_lost"; }
3351: return $answer;
3352: }
3353:
3354: # -------------------------------------------- Return path to profile directory
3355:
3356: sub propath {
3357: my ($udom,$uname)=@_;
3358: $udom=~s/\W//g;
3359: $uname=~s/\W//g;
3360: my $subdir=$uname.'__';
3361: $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
3362: my $proname="$perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
3363: return $proname;
3364: }
3365:
3366: # --------------------------------------- Is this the home server of an author?
3367:
3368: sub ishome {
3369: my $author=shift;
3370: $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
3371: my ($udom,$uname)=split(/\//,$author);
3372: my $proname=propath($udom,$uname);
3373: if (-e $proname) {
3374: return 'owner';
3375: } else {
3376: return 'not_owner';
3377: }
3378: }
3379:
3380: # ======================================================= Continue main program
3381: # ---------------------------------------------------- Fork once and dissociate
3382:
3383: my $fpid=fork;
3384: exit if $fpid;
3385: die "Couldn't fork: $!" unless defined ($fpid);
3386:
3387: POSIX::setsid() or die "Can't start new session: $!";
3388:
3389: # ------------------------------------------------------- Write our PID on disk
3390:
3391: my $execdir=$perlvar{'lonDaemons'};
3392: open (PIDSAVE,">$execdir/logs/lond.pid");
3393: print PIDSAVE "$$\n";
3394: close(PIDSAVE);
3395: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
3396: &status('Starting');
3397:
3398:
3399:
3400: # ----------------------------------------------------- Install signal handlers
3401:
3402:
3403: $SIG{CHLD} = \&REAPER;
3404: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
3405: $SIG{HUP} = \&HUPSMAN;
3406: $SIG{USR1} = \&checkchildren;
3407: $SIG{USR2} = \&UpdateHosts;
3408:
3409: # Read the host hashes:
3410:
3411: ReadHostTable;
3412:
3413: # --------------------------------------------------------------
3414: # Accept connections. When a connection comes in, it is validated
3415: # and if good, a child process is created to process transactions
3416: # along the connection.
3417:
3418: while (1) {
3419: &status('Starting accept');
3420: $client = $server->accept() or next;
3421: &status('Accepted '.$client.' off to spawn');
3422: make_new_child($client);
3423: &status('Finished spawning');
3424: }
3425:
3426: sub make_new_child {
3427: my $pid;
3428: # my $cipher; # Now global
3429: my $sigset;
3430:
3431: $client = shift;
3432: &status('Starting new child '.$client);
3433: &logthis('<font color="green"> Attempting to start child ('.$client.
3434: ")</font>");
3435: # block signal for fork
3436: $sigset = POSIX::SigSet->new(SIGINT);
3437: sigprocmask(SIG_BLOCK, $sigset)
3438: or die "Can't block SIGINT for fork: $!\n";
3439:
3440: die "fork: $!" unless defined ($pid = fork);
3441:
3442: $client->sockopt(SO_KEEPALIVE, 1); # Enable monitoring of
3443: # connection liveness.
3444:
3445: #
3446: # Figure out who we're talking to so we can record the peer in
3447: # the pid hash.
3448: #
3449: my $caller = getpeername($client);
3450: my ($port,$iaddr);
3451: if (defined($caller) && length($caller) > 0) {
3452: ($port,$iaddr)=unpack_sockaddr_in($caller);
3453: } else {
3454: &logthis("Unable to determine who caller was, getpeername returned nothing");
3455: }
3456: if (defined($iaddr)) {
3457: $clientip = inet_ntoa($iaddr);
3458: Debug("Connected with $clientip");
3459: $clientdns = gethostbyaddr($iaddr, AF_INET);
3460: Debug("Connected with $clientdns by name");
3461: } else {
3462: &logthis("Unable to determine clientip");
3463: $clientip='Unavailable';
3464: }
3465:
3466: if ($pid) {
3467: # Parent records the child's birth and returns.
3468: sigprocmask(SIG_UNBLOCK, $sigset)
3469: or die "Can't unblock SIGINT for fork: $!\n";
3470: $children{$pid} = $clientip;
3471: &status('Started child '.$pid);
3472: return;
3473: } else {
3474: # Child can *not* return from this subroutine.
3475: $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before
3476: $SIG{CHLD} = 'DEFAULT'; #make this default so that pwauth returns
3477: #don't get intercepted
3478: $SIG{USR1}= \&logstatus;
3479: $SIG{ALRM}= \&timeout;
3480: $lastlog='Forked ';
3481: $status='Forked';
3482:
3483: # unblock signals
3484: sigprocmask(SIG_UNBLOCK, $sigset)
3485: or die "Can't unblock SIGINT for fork: $!\n";
3486:
3487: # my $tmpsnum=0; # Now global
3488: #---------------------------------------------------- kerberos 5 initialization
3489: &Authen::Krb5::init_context();
3490: &Authen::Krb5::init_ets();
3491:
3492: &status('Accepted connection');
3493: # =============================================================================
3494: # do something with the connection
3495: # -----------------------------------------------------------------------------
3496: # see if we know client and 'check' for spoof IP by ineffective challenge
3497:
3498: ReadManagerTable; # May also be a manager!!
3499:
3500: my $clientrec=($hostid{$clientip} ne undef);
3501: my $ismanager=($managers{$clientip} ne undef);
3502: $clientname = "[unknonwn]";
3503: if($clientrec) { # Establish client type.
3504: $ConnectionType = "client";
3505: $clientname = $hostid{$clientip};
3506: if($ismanager) {
3507: $ConnectionType = "both";
3508: }
3509: } else {
3510: $ConnectionType = "manager";
3511: $clientname = $managers{$clientip};
3512: }
3513: my $clientok;
3514:
3515: if ($clientrec || $ismanager) {
3516: &status("Waiting for init from $clientip $clientname");
3517: &logthis('<font color="yellow">INFO: Connection, '.
3518: $clientip.
3519: " ($clientname) connection type = $ConnectionType </font>" );
3520: &status("Connecting $clientip ($clientname))");
3521: my $remotereq=<$client>;
3522: chomp($remotereq);
3523: Debug("Got init: $remotereq");
3524: my $inikeyword = split(/:/, $remotereq);
3525: if ($remotereq =~ /^init/) {
3526: &sethost("sethost:$perlvar{'lonHostID'}");
3527: #
3528: # If the remote is attempting a local init... give that a try:
3529: #
3530: my ($i, $inittype) = split(/:/, $remotereq);
3531:
3532: # If the connection type is ssl, but I didn't get my
3533: # certificate files yet, then I'll drop back to
3534: # insecure (if allowed).
3535:
3536: if($inittype eq "ssl") {
3537: my ($ca, $cert) = lonssl::CertificateFile;
3538: my $kfile = lonssl::KeyFile;
3539: if((!$ca) ||
3540: (!$cert) ||
3541: (!$kfile)) {
3542: $inittype = ""; # This forces insecure attempt.
3543: &logthis("<font color=\"blue\"> Certificates not "
3544: ."installed -- trying insecure auth</font>");
3545: }
3546: else { # SSL certificates are in place so
3547: } # Leave the inittype alone.
3548: }
3549:
3550: if($inittype eq "local") {
3551: my $key = LocalConnection($client, $remotereq);
3552: if($key) {
3553: Debug("Got local key $key");
3554: $clientok = 1;
3555: my $cipherkey = pack("H32", $key);
3556: $cipher = new IDEA($cipherkey);
3557: print $client "ok:local\n";
3558: &logthis('<font color="green"'
3559: . "Successful local authentication </font>");
3560: $keymode = "local"
3561: } else {
3562: Debug("Failed to get local key");
3563: $clientok = 0;
3564: shutdown($client, 3);
3565: close $client;
3566: }
3567: } elsif ($inittype eq "ssl") {
3568: my $key = SSLConnection($client);
3569: if ($key) {
3570: $clientok = 1;
3571: my $cipherkey = pack("H32", $key);
3572: $cipher = new IDEA($cipherkey);
3573: &logthis('<font color="green">'
3574: ."Successfull ssl authentication with $clientname </font>");
3575: $keymode = "ssl";
3576:
3577: } else {
3578: $clientok = 0;
3579: close $client;
3580: }
3581:
3582: } else {
3583: my $ok = InsecureConnection($client);
3584: if($ok) {
3585: $clientok = 1;
3586: &logthis('<font color="green">'
3587: ."Successful insecure authentication with $clientname </font>");
3588: print $client "ok\n";
3589: $keymode = "insecure";
3590: } else {
3591: &logthis('<font color="yellow">'
3592: ."Attempted insecure connection disallowed </font>");
3593: close $client;
3594: $clientok = 0;
3595:
3596: }
3597: }
3598: } else {
3599: &logthis(
3600: "<font color='blue'>WARNING: "
3601: ."$clientip failed to initialize: >$remotereq< </font>");
3602: &status('No init '.$clientip);
3603: }
3604:
3605: } else {
3606: &logthis(
3607: "<font color='blue'>WARNING: Unknown client $clientip</font>");
3608: &status('Hung up on '.$clientip);
3609: }
3610:
3611: if ($clientok) {
3612: # ---------------- New known client connecting, could mean machine online again
3613:
3614: foreach my $id (keys(%hostip)) {
3615: if ($hostip{$id} ne $clientip ||
3616: $hostip{$currenthostid} eq $clientip) {
3617: # no need to try to do recon's to myself
3618: next;
3619: }
3620: &reconlonc("$perlvar{'lonSockDir'}/$id");
3621: }
3622: &logthis("<font color='green'>Established connection: $clientname</font>");
3623: &status('Will listen to '.$clientname);
3624: # ------------------------------------------------------------ Process requests
3625: my $keep_going = 1;
3626: my $user_input;
3627: while(($user_input = get_request) && $keep_going) {
3628: alarm(120);
3629: Debug("Main: Got $user_input\n");
3630: $keep_going = &process_request($user_input);
3631: alarm(0);
3632: &status('Listening to '.$clientname." ($keymode)");
3633: }
3634:
3635: # --------------------------------------------- client unknown or fishy, refuse
3636: } else {
3637: print $client "refused\n";
3638: $client->close();
3639: &logthis("<font color='blue'>WARNING: "
3640: ."Rejected client $clientip, closing connection</font>");
3641: }
3642: }
3643:
3644: # =============================================================================
3645:
3646: &logthis("<font color='red'>CRITICAL: "
3647: ."Disconnect from $clientip ($clientname)</font>");
3648:
3649:
3650: # this exit is VERY important, otherwise the child will become
3651: # a producer of more and more children, forking yourself into
3652: # process death.
3653: exit;
3654:
3655: }
3656:
3657:
3658: #
3659: # Checks to see if the input roleput request was to set
3660: # an author role. If so, invokes the lchtmldir script to set
3661: # up a correct public_html
3662: # Parameters:
3663: # request - The request sent to the rolesput subchunk.
3664: # We're looking for /domain/_au
3665: # domain - The domain in which the user is having roles doctored.
3666: # user - Name of the user for which the role is being put.
3667: # authtype - The authentication type associated with the user.
3668: #
3669: sub ManagePermissions
3670: {
3671:
3672: my ($request, $domain, $user, $authtype) = @_;
3673:
3674: # See if the request is of the form /$domain/_au
3675: if($request =~ /^(\/$domain\/_au)$/) { # It's an author rolesput...
3676: my $execdir = $perlvar{'lonDaemons'};
3677: my $userhome= "/home/$user" ;
3678: &logthis("system $execdir/lchtmldir $userhome $user $authtype");
3679: system("$execdir/lchtmldir $userhome $user $authtype");
3680: }
3681: }
3682: #
3683: # GetAuthType - Determines the authorization type of a user in a domain.
3684:
3685: # Returns the authorization type or nouser if there is no such user.
3686: #
3687: sub GetAuthType
3688: {
3689:
3690: my ($domain, $user) = @_;
3691:
3692: Debug("GetAuthType( $domain, $user ) \n");
3693: my $proname = &propath($domain, $user);
3694: my $passwdfile = "$proname/passwd";
3695: if( -e $passwdfile ) {
3696: my $pf = IO::File->new($passwdfile);
3697: my $realpassword = <$pf>;
3698: chomp($realpassword);
3699: Debug("Password info = $realpassword\n");
3700: my ($authtype, $contentpwd) = split(/:/, $realpassword);
3701: Debug("Authtype = $authtype, content = $contentpwd\n");
3702: my $availinfo = '';
3703: if($authtype eq 'krb4' or $authtype eq 'krb5') {
3704: $availinfo = $contentpwd;
3705: }
3706:
3707: return "$authtype:$availinfo";
3708: }
3709: else {
3710: Debug("Returning nouser");
3711: return "nouser";
3712: }
3713: }
3714:
3715: sub addline {
3716: my ($fname,$hostid,$ip,$newline)=@_;
3717: my $contents;
3718: my $found=0;
3719: my $expr='^'.$hostid.':'.$ip.':';
3720: $expr =~ s/\./\\\./g;
3721: my $sh;
3722: if ($sh=IO::File->new("$fname.subscription")) {
3723: while (my $subline=<$sh>) {
3724: if ($subline !~ /$expr/) {$contents.= $subline;} else {$found=1;}
3725: }
3726: $sh->close();
3727: }
3728: $sh=IO::File->new(">$fname.subscription");
3729: if ($contents) { print $sh $contents; }
3730: if ($newline) { print $sh $newline; }
3731: $sh->close();
3732: return $found;
3733: }
3734:
3735: sub getchat {
3736: my ($cdom,$cname,$udom,$uname)=@_;
3737: my %hash;
3738: my $proname=&propath($cdom,$cname);
3739: my @entries=();
3740: if (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db",
3741: &GDBM_READER(),0640)) {
3742: @entries=map { $_.':'.$hash{$_} } sort keys %hash;
3743: untie %hash;
3744: }
3745: my @participants=();
3746: my $cutoff=time-60;
3747: if (tie(%hash,'GDBM_File',"$proname/nohist_inchatroom.db",
3748: &GDBM_WRCREAT(),0640)) {
3749: $hash{$uname.':'.$udom}=time;
3750: foreach (sort keys %hash) {
3751: if ($hash{$_}>$cutoff) {
3752: $participants[$#participants+1]='active_participant:'.$_;
3753: }
3754: }
3755: untie %hash;
3756: }
3757: return (@participants,@entries);
3758: }
3759:
3760: sub chatadd {
3761: my ($cdom,$cname,$newchat)=@_;
3762: my %hash;
3763: my $proname=&propath($cdom,$cname);
3764: my @entries=();
3765: my $time=time;
3766: if (tie(%hash,'GDBM_File',"$proname/nohist_chatroom.db",
3767: &GDBM_WRCREAT(),0640)) {
3768: @entries=map { $_.':'.$hash{$_} } sort keys %hash;
3769: my ($lastid)=($entries[$#entries]=~/^(\w+)\:/);
3770: my ($thentime,$idnum)=split(/\_/,$lastid);
3771: my $newid=$time.'_000000';
3772: if ($thentime==$time) {
3773: $idnum=~s/^0+//;
3774: $idnum++;
3775: $idnum=substr('000000'.$idnum,-6,6);
3776: $newid=$time.'_'.$idnum;
3777: }
3778: $hash{$newid}=$newchat;
3779: my $expired=$time-3600;
3780: foreach (keys %hash) {
3781: my ($thistime)=($_=~/(\d+)\_/);
3782: if ($thistime<$expired) {
3783: delete $hash{$_};
3784: }
3785: }
3786: untie %hash;
3787: }
3788: {
3789: my $hfh;
3790: if ($hfh=IO::File->new(">>$proname/chatroom.log")) {
3791: print $hfh "$time:".&unescape($newchat)."\n";
3792: }
3793: }
3794: }
3795:
3796: sub unsub {
3797: my ($fname,$clientip)=@_;
3798: my $result;
3799: my $unsubs = 0; # Number of successful unsubscribes:
3800:
3801:
3802: # An old way subscriptions were handled was to have a
3803: # subscription marker file:
3804:
3805: Debug("Attempting unlink of $fname.$clientname");
3806: if (unlink("$fname.$clientname")) {
3807: $unsubs++; # Successful unsub via marker file.
3808: }
3809:
3810: # The more modern way to do it is to have a subscription list
3811: # file:
3812:
3813: if (-e "$fname.subscription") {
3814: my $found=&addline($fname,$clientname,$clientip,'');
3815: if ($found) {
3816: $unsubs++;
3817: }
3818: }
3819:
3820: # If either or both of these mechanisms succeeded in unsubscribing a
3821: # resource we can return ok:
3822:
3823: if($unsubs) {
3824: $result = "ok\n";
3825: } else {
3826: $result = "not_subscribed\n";
3827: }
3828:
3829: return $result;
3830: }
3831:
3832: sub currentversion {
3833: my $fname=shift;
3834: my $version=-1;
3835: my $ulsdir='';
3836: if ($fname=~/^(.+)\/[^\/]+$/) {
3837: $ulsdir=$1;
3838: }
3839: my ($fnamere1,$fnamere2);
3840: # remove version if already specified
3841: $fname=~s/\.\d+\.(\w+(?:\.meta)*)$/\.$1/;
3842: # get the bits that go before and after the version number
3843: if ( $fname=~/^(.*\.)(\w+(?:\.meta)*)$/ ) {
3844: $fnamere1=$1;
3845: $fnamere2='.'.$2;
3846: }
3847: if (-e $fname) { $version=1; }
3848: if (-e $ulsdir) {
3849: if(-d $ulsdir) {
3850: if (opendir(LSDIR,$ulsdir)) {
3851: my $ulsfn;
3852: while ($ulsfn=readdir(LSDIR)) {
3853: # see if this is a regular file (ignore links produced earlier)
3854: my $thisfile=$ulsdir.'/'.$ulsfn;
3855: unless (-l $thisfile) {
3856: if ($thisfile=~/\Q$fnamere1\E(\d+)\Q$fnamere2\E$/) {
3857: if ($1>$version) { $version=$1; }
3858: }
3859: }
3860: }
3861: closedir(LSDIR);
3862: $version++;
3863: }
3864: }
3865: }
3866: return $version;
3867: }
3868:
3869: sub thisversion {
3870: my $fname=shift;
3871: my $version=-1;
3872: if ($fname=~/\.(\d+)\.\w+(?:\.meta)*$/) {
3873: $version=$1;
3874: }
3875: return $version;
3876: }
3877:
3878: sub subscribe {
3879: my ($userinput,$clientip)=@_;
3880: my $result;
3881: my ($cmd,$fname)=split(/:/,$userinput);
3882: my $ownership=&ishome($fname);
3883: if ($ownership eq 'owner') {
3884: # explitly asking for the current version?
3885: unless (-e $fname) {
3886: my $currentversion=¤tversion($fname);
3887: if (&thisversion($fname)==$currentversion) {
3888: if ($fname=~/^(.+)\.\d+\.(\w+(?:\.meta)*)$/) {
3889: my $root=$1;
3890: my $extension=$2;
3891: symlink($root.'.'.$extension,
3892: $root.'.'.$currentversion.'.'.$extension);
3893: unless ($extension=~/\.meta$/) {
3894: symlink($root.'.'.$extension.'.meta',
3895: $root.'.'.$currentversion.'.'.$extension.'.meta');
3896: }
3897: }
3898: }
3899: }
3900: if (-e $fname) {
3901: if (-d $fname) {
3902: $result="directory\n";
3903: } else {
3904: if (-e "$fname.$clientname") {&unsub($fname,$clientip);}
3905: my $now=time;
3906: my $found=&addline($fname,$clientname,$clientip,
3907: "$clientname:$clientip:$now\n");
3908: if ($found) { $result="$fname\n"; }
3909: # if they were subscribed to only meta data, delete that
3910: # subscription, when you subscribe to a file you also get
3911: # the metadata
3912: unless ($fname=~/\.meta$/) { &unsub("$fname.meta",$clientip); }
3913: $fname=~s/\/home\/httpd\/html\/res/raw/;
3914: $fname="http://$thisserver/".$fname;
3915: $result="$fname\n";
3916: }
3917: } else {
3918: $result="not_found\n";
3919: }
3920: } else {
3921: $result="rejected\n";
3922: }
3923: return $result;
3924: }
3925:
3926: sub make_passwd_file {
3927: my ($uname, $umode,$npass,$passfilename)=@_;
3928: my $result="ok\n";
3929: if ($umode eq 'krb4' or $umode eq 'krb5') {
3930: {
3931: my $pf = IO::File->new(">$passfilename");
3932: print $pf "$umode:$npass\n";
3933: }
3934: } elsif ($umode eq 'internal') {
3935: my $salt=time;
3936: $salt=substr($salt,6,2);
3937: my $ncpass=crypt($npass,$salt);
3938: {
3939: &Debug("Creating internal auth");
3940: my $pf = IO::File->new(">$passfilename");
3941: print $pf "internal:$ncpass\n";
3942: }
3943: } elsif ($umode eq 'localauth') {
3944: {
3945: my $pf = IO::File->new(">$passfilename");
3946: print $pf "localauth:$npass\n";
3947: }
3948: } elsif ($umode eq 'unix') {
3949: {
3950: #
3951: # Don't allow the creation of privileged accounts!!! that would
3952: # be real bad!!!
3953: #
3954: my $uid = getpwnam($uname);
3955: if((defined $uid) && ($uid == 0)) {
3956: &logthis(">>>Attempted to create privilged account blocked");
3957: return "no_priv_account_error\n";
3958: }
3959:
3960: my $execpath="$perlvar{'lonDaemons'}/"."lcuseradd";
3961: {
3962: &Debug("Executing external: ".$execpath);
3963: &Debug("user = ".$uname.", Password =". $npass);
3964: my $se = IO::File->new("|$execpath > $perlvar{'lonDaemons'}/logs/lcuseradd.log");
3965: print $se "$uname\n";
3966: print $se "$npass\n";
3967: print $se "$npass\n";
3968: }
3969: my $useraddok = $?;
3970: if($useraddok > 0) {
3971: &logthis("Failed lcuseradd: ".&lcuseraddstrerror($useraddok));
3972: }
3973: my $pf = IO::File->new(">$passfilename");
3974: print $pf "unix:\n";
3975: }
3976: } elsif ($umode eq 'none') {
3977: {
3978: my $pf = IO::File->new(">$passfilename");
3979: print $pf "none:\n";
3980: }
3981: } else {
3982: $result="auth_mode_error\n";
3983: }
3984: return $result;
3985: }
3986:
3987: sub sethost {
3988: my ($remotereq) = @_;
3989: my (undef,$hostid)=split(/:/,$remotereq);
3990: if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; }
3991: if ($hostip{$perlvar{'lonHostID'}} eq $hostip{$hostid}) {
3992: $currenthostid =$hostid;
3993: $currentdomainid=$hostdom{$hostid};
3994: &logthis("Setting hostid to $hostid, and domain to $currentdomainid");
3995: } else {
3996: &logthis("Requested host id $hostid not an alias of ".
3997: $perlvar{'lonHostID'}." refusing connection");
3998: return 'unable_to_set';
3999: }
4000: return 'ok';
4001: }
4002:
4003: sub version {
4004: my ($userinput)=@_;
4005: $remoteVERSION=(split(/:/,$userinput))[1];
4006: return "version:$VERSION";
4007: }
4008:
4009: #There is a copy of this in lonnet.pm
4010: sub userload {
4011: my $numusers=0;
4012: {
4013: opendir(LONIDS,$perlvar{'lonIDsDir'});
4014: my $filename;
4015: my $curtime=time;
4016: while ($filename=readdir(LONIDS)) {
4017: if ($filename eq '.' || $filename eq '..') {next;}
4018: my ($mtime)=(stat($perlvar{'lonIDsDir'}.'/'.$filename))[9];
4019: if ($curtime-$mtime < 1800) { $numusers++; }
4020: }
4021: closedir(LONIDS);
4022: }
4023: my $userloadpercent=0;
4024: my $maxuserload=$perlvar{'lonUserLoadLim'};
4025: if ($maxuserload) {
4026: $userloadpercent=100*$numusers/$maxuserload;
4027: }
4028: $userloadpercent=sprintf("%.2f",$userloadpercent);
4029: return $userloadpercent;
4030: }
4031:
4032: # Routines for serializing arrays and hashes (copies from lonnet)
4033:
4034: sub array2str {
4035: my (@array) = @_;
4036: my $result=&arrayref2str(\@array);
4037: $result=~s/^__ARRAY_REF__//;
4038: $result=~s/__END_ARRAY_REF__$//;
4039: return $result;
4040: }
4041:
4042: sub arrayref2str {
4043: my ($arrayref) = @_;
4044: my $result='__ARRAY_REF__';
4045: foreach my $elem (@$arrayref) {
4046: if(ref($elem) eq 'ARRAY') {
4047: $result.=&arrayref2str($elem).'&';
4048: } elsif(ref($elem) eq 'HASH') {
4049: $result.=&hashref2str($elem).'&';
4050: } elsif(ref($elem)) {
4051: #print("Got a ref of ".(ref($elem))." skipping.");
4052: } else {
4053: $result.=&escape($elem).'&';
4054: }
4055: }
4056: $result=~s/\&$//;
4057: $result .= '__END_ARRAY_REF__';
4058: return $result;
4059: }
4060:
4061: sub hash2str {
4062: my (%hash) = @_;
4063: my $result=&hashref2str(\%hash);
4064: $result=~s/^__HASH_REF__//;
4065: $result=~s/__END_HASH_REF__$//;
4066: return $result;
4067: }
4068:
4069: sub hashref2str {
4070: my ($hashref)=@_;
4071: my $result='__HASH_REF__';
4072: foreach (sort(keys(%$hashref))) {
4073: if (ref($_) eq 'ARRAY') {
4074: $result.=&arrayref2str($_).'=';
4075: } elsif (ref($_) eq 'HASH') {
4076: $result.=&hashref2str($_).'=';
4077: } elsif (ref($_)) {
4078: $result.='=';
4079: #print("Got a ref of ".(ref($_))." skipping.");
4080: } else {
4081: if ($_) {$result.=&escape($_).'=';} else { last; }
4082: }
4083:
4084: if(ref($hashref->{$_}) eq 'ARRAY') {
4085: $result.=&arrayref2str($hashref->{$_}).'&';
4086: } elsif(ref($hashref->{$_}) eq 'HASH') {
4087: $result.=&hashref2str($hashref->{$_}).'&';
4088: } elsif(ref($hashref->{$_})) {
4089: $result.='&';
4090: #print("Got a ref of ".(ref($hashref->{$_}))." skipping.");
4091: } else {
4092: $result.=&escape($hashref->{$_}).'&';
4093: }
4094: }
4095: $result=~s/\&$//;
4096: $result .= '__END_HASH_REF__';
4097: return $result;
4098: }
4099:
4100: # ----------------------------------- POD (plain old documentation, CPAN style)
4101:
4102: =head1 NAME
4103:
4104: lond - "LON Daemon" Server (port "LOND" 5663)
4105:
4106: =head1 SYNOPSIS
4107:
4108: Usage: B<lond>
4109:
4110: Should only be run as user=www. This is a command-line script which
4111: is invoked by B<loncron>. There is no expectation that a typical user
4112: will manually start B<lond> from the command-line. (In other words,
4113: DO NOT START B<lond> YOURSELF.)
4114:
4115: =head1 DESCRIPTION
4116:
4117: There are two characteristics associated with the running of B<lond>,
4118: PROCESS MANAGEMENT (starting, stopping, handling child processes)
4119: and SERVER-SIDE ACTIVITIES (password authentication, user creation,
4120: subscriptions, etc). These are described in two large
4121: sections below.
4122:
4123: B<PROCESS MANAGEMENT>
4124:
4125: Preforker - server who forks first. Runs as a daemon. HUPs.
4126: Uses IDEA encryption
4127:
4128: B<lond> forks off children processes that correspond to the other servers
4129: in the network. Management of these processes can be done at the
4130: parent process level or the child process level.
4131:
4132: B<logs/lond.log> is the location of log messages.
4133:
4134: The process management is now explained in terms of linux shell commands,
4135: subroutines internal to this code, and signal assignments:
4136:
4137: =over 4
4138:
4139: =item *
4140:
4141: PID is stored in B<logs/lond.pid>
4142:
4143: This is the process id number of the parent B<lond> process.
4144:
4145: =item *
4146:
4147: SIGTERM and SIGINT
4148:
4149: Parent signal assignment:
4150: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
4151:
4152: Child signal assignment:
4153: $SIG{INT} = 'DEFAULT'; (and SIGTERM is DEFAULT also)
4154: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber
4155: to restart a new child.)
4156:
4157: Command-line invocations:
4158: B<kill> B<-s> SIGTERM I<PID>
4159: B<kill> B<-s> SIGINT I<PID>
4160:
4161: Subroutine B<HUNTSMAN>:
4162: This is only invoked for the B<lond> parent I<PID>.
4163: This kills all the children, and then the parent.
4164: The B<lonc.pid> file is cleared.
4165:
4166: =item *
4167:
4168: SIGHUP
4169:
4170: Current bug:
4171: This signal can only be processed the first time
4172: on the parent process. Subsequent SIGHUP signals
4173: have no effect.
4174:
4175: Parent signal assignment:
4176: $SIG{HUP} = \&HUPSMAN;
4177:
4178: Child signal assignment:
4179: none (nothing happens)
4180:
4181: Command-line invocations:
4182: B<kill> B<-s> SIGHUP I<PID>
4183:
4184: Subroutine B<HUPSMAN>:
4185: This is only invoked for the B<lond> parent I<PID>,
4186: This kills all the children, and then the parent.
4187: The B<lond.pid> file is cleared.
4188:
4189: =item *
4190:
4191: SIGUSR1
4192:
4193: Parent signal assignment:
4194: $SIG{USR1} = \&USRMAN;
4195:
4196: Child signal assignment:
4197: $SIG{USR1}= \&logstatus;
4198:
4199: Command-line invocations:
4200: B<kill> B<-s> SIGUSR1 I<PID>
4201:
4202: Subroutine B<USRMAN>:
4203: When invoked for the B<lond> parent I<PID>,
4204: SIGUSR1 is sent to all the children, and the status of
4205: each connection is logged.
4206:
4207: =item *
4208:
4209: SIGUSR2
4210:
4211: Parent Signal assignment:
4212: $SIG{USR2} = \&UpdateHosts
4213:
4214: Child signal assignment:
4215: NONE
4216:
4217:
4218: =item *
4219:
4220: SIGCHLD
4221:
4222: Parent signal assignment:
4223: $SIG{CHLD} = \&REAPER;
4224:
4225: Child signal assignment:
4226: none
4227:
4228: Command-line invocations:
4229: B<kill> B<-s> SIGCHLD I<PID>
4230:
4231: Subroutine B<REAPER>:
4232: This is only invoked for the B<lond> parent I<PID>.
4233: Information pertaining to the child is removed.
4234: The socket port is cleaned up.
4235:
4236: =back
4237:
4238: B<SERVER-SIDE ACTIVITIES>
4239:
4240: Server-side information can be accepted in an encrypted or non-encrypted
4241: method.
4242:
4243: =over 4
4244:
4245: =item ping
4246:
4247: Query a client in the hosts.tab table; "Are you there?"
4248:
4249: =item pong
4250:
4251: Respond to a ping query.
4252:
4253: =item ekey
4254:
4255: Read in encrypted key, make cipher. Respond with a buildkey.
4256:
4257: =item load
4258:
4259: Respond with CPU load based on a computation upon /proc/loadavg.
4260:
4261: =item currentauth
4262:
4263: Reply with current authentication information (only over an
4264: encrypted channel).
4265:
4266: =item auth
4267:
4268: Only over an encrypted channel, reply as to whether a user's
4269: authentication information can be validated.
4270:
4271: =item passwd
4272:
4273: Allow for a password to be set.
4274:
4275: =item makeuser
4276:
4277: Make a user.
4278:
4279: =item passwd
4280:
4281: Allow for authentication mechanism and password to be changed.
4282:
4283: =item home
4284:
4285: Respond to a question "are you the home for a given user?"
4286:
4287: =item update
4288:
4289: Update contents of a subscribed resource.
4290:
4291: =item unsubscribe
4292:
4293: The server is unsubscribing from a resource.
4294:
4295: =item subscribe
4296:
4297: The server is subscribing to a resource.
4298:
4299: =item log
4300:
4301: Place in B<logs/lond.log>
4302:
4303: =item put
4304:
4305: stores hash in namespace
4306:
4307: =item rolesput
4308:
4309: put a role into a user's environment
4310:
4311: =item get
4312:
4313: returns hash with keys from array
4314: reference filled in from namespace
4315:
4316: =item eget
4317:
4318: returns hash with keys from array
4319: reference filled in from namesp (encrypts the return communication)
4320:
4321: =item rolesget
4322:
4323: get a role from a user's environment
4324:
4325: =item del
4326:
4327: deletes keys out of array from namespace
4328:
4329: =item keys
4330:
4331: returns namespace keys
4332:
4333: =item dump
4334:
4335: dumps the complete (or key matching regexp) namespace into a hash
4336:
4337: =item store
4338:
4339: stores hash permanently
4340: for this url; hashref needs to be given and should be a \%hashname; the
4341: remaining args aren't required and if they aren't passed or are '' they will
4342: be derived from the ENV
4343:
4344: =item restore
4345:
4346: returns a hash for a given url
4347:
4348: =item querysend
4349:
4350: Tells client about the lonsql process that has been launched in response
4351: to a sent query.
4352:
4353: =item queryreply
4354:
4355: Accept information from lonsql and make appropriate storage in temporary
4356: file space.
4357:
4358: =item idput
4359:
4360: Defines usernames as corresponding to IDs. (These "IDs" are unique identifiers
4361: for each student, defined perhaps by the institutional Registrar.)
4362:
4363: =item idget
4364:
4365: Returns usernames corresponding to IDs. (These "IDs" are unique identifiers
4366: for each student, defined perhaps by the institutional Registrar.)
4367:
4368: =item tmpput
4369:
4370: Accept and store information in temporary space.
4371:
4372: =item tmpget
4373:
4374: Send along temporarily stored information.
4375:
4376: =item ls
4377:
4378: List part of a user's directory.
4379:
4380: =item pushtable
4381:
4382: Pushes a file in /home/httpd/lonTab directory. Currently limited to:
4383: hosts.tab and domain.tab. The old file is copied to *.tab.backup but
4384: must be restored manually in case of a problem with the new table file.
4385: pushtable requires that the request be encrypted and validated via
4386: ValidateManager. The form of the command is:
4387: enc:pushtable tablename <tablecontents> \n
4388: where pushtable, tablename and <tablecontents> will be encrypted, but \n is a
4389: cleartext newline.
4390:
4391: =item Hanging up (exit or init)
4392:
4393: What to do when a client tells the server that they (the client)
4394: are leaving the network.
4395:
4396: =item unknown command
4397:
4398: If B<lond> is sent an unknown command (not in the list above),
4399: it replys to the client "unknown_cmd".
4400:
4401:
4402: =item UNKNOWN CLIENT
4403:
4404: If the anti-spoofing algorithm cannot verify the client,
4405: the client is rejected (with a "refused" message sent
4406: to the client, and the connection is closed.
4407:
4408: =back
4409:
4410: =head1 PREREQUISITES
4411:
4412: IO::Socket
4413: IO::File
4414: Apache::File
4415: Symbol
4416: POSIX
4417: Crypt::IDEA
4418: LWP::UserAgent()
4419: GDBM_File
4420: Authen::Krb4
4421: Authen::Krb5
4422:
4423: =head1 COREQUISITES
4424:
4425: =head1 OSNAMES
4426:
4427: linux
4428:
4429: =head1 SCRIPT CATEGORIES
4430:
4431: Server/Process
4432:
4433: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>