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