Annotation of loncom/lond, revision 1.385
1.1 albertel 1: #!/usr/bin/perl
2: # The LearningOnline Network
3: # lond "LON Daemon" Server (port "LOND" 5663)
1.60 www 4: #
1.385 ! albertel 5: # $Id: lond,v 1.384 2007/10/06 04:32:23 raeburn Exp $
1.60 www 6: #
7: # Copyright Michigan State University Board of Trustees
8: #
9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
10: #
11: # LON-CAPA is free software; you can redistribute it and/or modify
12: # it under the terms of the GNU General Public License as published by
1.167 foxr 13: # the Free Software Foundation; either version 2 of the License, or
1.60 www 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
1.178 foxr 23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1.60 www 24: #
25: # /home/httpd/html/adm/gpl.txt
26: #
1.161 foxr 27:
28:
1.60 www 29: # http://www.lon-capa.org/
30: #
1.54 harris41 31:
1.134 albertel 32: use strict;
1.80 harris41 33: use lib '/home/httpd/lib/perl/';
1.325 albertel 34: use LONCAPA;
1.80 harris41 35: use LONCAPA::Configuration;
36:
1.1 albertel 37: use IO::Socket;
38: use IO::File;
1.126 albertel 39: #use Apache::File;
1.1 albertel 40: use POSIX;
41: use Crypt::IDEA;
42: use LWP::UserAgent();
1.347 raeburn 43: use Digest::MD5 qw(md5_hex);
1.3 www 44: use GDBM_File;
45: use Authen::Krb4;
1.91 albertel 46: use Authen::Krb5;
1.49 albertel 47: use localauth;
1.193 raeburn 48: use localenroll;
1.265 albertel 49: use localstudentphoto;
1.143 foxr 50: use File::Copy;
1.292 albertel 51: use File::Find;
1.200 matthew 52: use LONCAPA::lonlocal;
53: use LONCAPA::lonssl;
1.221 albertel 54: use Fcntl qw(:flock);
1.383 raeburn 55: use Apache::lonnet;
1.1 albertel 56:
1.239 foxr 57: my $DEBUG = 0; # Non zero to enable debug log entries.
1.77 foxr 58:
1.57 www 59: my $status='';
60: my $lastlog='';
61:
1.385 ! albertel 62: my $VERSION='$Revision: 1.384 $'; #' stupid emacs
1.121 albertel 63: my $remoteVERSION;
1.214 foxr 64: my $currenthostid="default";
1.115 albertel 65: my $currentdomainid;
1.134 albertel 66:
67: my $client;
1.200 matthew 68: my $clientip; # IP address of client.
69: my $clientname; # LonCAPA name of client.
1.140 foxr 70:
1.134 albertel 71: my $server;
1.200 matthew 72:
73: my $keymode;
1.198 foxr 74:
1.207 foxr 75: my $cipher; # Cipher key negotiated with client
76: my $tmpsnum = 0; # Id of tmpputs.
1.385 ! albertel 77: my $max_children = 1; # warn when exceeding this
! 78: my $max_children_enforcing = 0;
1.207 foxr 79:
1.178 foxr 80: #
81: # Connection type is:
82: # client - All client actions are allowed
83: # manager - only management functions allowed.
84: # both - Both management and client actions are allowed
85: #
1.161 foxr 86:
1.178 foxr 87: my $ConnectionType;
1.161 foxr 88:
1.178 foxr 89: my %managers; # Ip -> manager names
1.161 foxr 90:
1.178 foxr 91: my %perlvar; # Will have the apache conf defined perl vars.
1.134 albertel 92:
1.178 foxr 93: #
1.207 foxr 94: # The hash below is used for command dispatching, and is therefore keyed on the request keyword.
95: # Each element of the hash contains a reference to an array that contains:
96: # A reference to a sub that executes the request corresponding to the keyword.
97: # A flag that is true if the request must be encoded to be acceptable.
98: # A mask with bits as follows:
99: # CLIENT_OK - Set when the function is allowed by ordinary clients
100: # MANAGER_OK - Set when the function is allowed to manager clients.
101: #
102: my $CLIENT_OK = 1;
103: my $MANAGER_OK = 2;
104: my %Dispatcher;
105:
106:
107: #
1.178 foxr 108: # The array below are password error strings."
109: #
110: my $lastpwderror = 13; # Largest error number from lcpasswd.
111: my @passwderrors = ("ok",
1.287 foxr 112: "pwchange_failure - lcpasswd must be run as user 'www'",
113: "pwchange_failure - lcpasswd got incorrect number of arguments",
114: "pwchange_failure - lcpasswd did not get the right nubmer of input text lines",
115: "pwchange_failure - lcpasswd too many simultaneous pwd changes in progress",
116: "pwchange_failure - lcpasswd User does not exist.",
117: "pwchange_failure - lcpasswd Incorrect current passwd",
118: "pwchange_failure - lcpasswd Unable to su to root.",
119: "pwchange_failure - lcpasswd Cannot set new passwd.",
120: "pwchange_failure - lcpasswd Username has invalid characters",
121: "pwchange_failure - lcpasswd Invalid characters in password",
122: "pwchange_failure - lcpasswd User already exists",
123: "pwchange_failure - lcpasswd Something went wrong with user addition.",
124: "pwchange_failure - lcpasswd Password mismatch",
125: "pwchange_failure - lcpasswd Error filename is invalid");
1.97 foxr 126:
127:
1.178 foxr 128: # The array below are lcuseradd error strings.:
1.97 foxr 129:
1.178 foxr 130: my $lastadderror = 13;
131: my @adderrors = ("ok",
132: "User ID mismatch, lcuseradd must run as user www",
133: "lcuseradd Incorrect number of command line parameters must be 3",
134: "lcuseradd Incorrect number of stdinput lines, must be 3",
135: "lcuseradd Too many other simultaneous pwd changes in progress",
136: "lcuseradd User does not exist",
137: "lcuseradd Unable to make www member of users's group",
138: "lcuseradd Unable to su to root",
139: "lcuseradd Unable to set password",
1.377 albertel 140: "lcuseradd Username has invalid characters",
1.178 foxr 141: "lcuseradd Password has an invalid character",
142: "lcuseradd User already exists",
143: "lcuseradd Could not add user.",
144: "lcuseradd Password mismatch");
1.97 foxr 145:
1.96 foxr 146:
1.207 foxr 147:
148: #
149: # Statistics that are maintained and dislayed in the status line.
150: #
1.212 foxr 151: my $Transactions = 0; # Number of attempted transactions.
152: my $Failures = 0; # Number of transcations failed.
1.207 foxr 153:
154: # ResetStatistics:
155: # Resets the statistics counters:
156: #
157: sub ResetStatistics {
158: $Transactions = 0;
159: $Failures = 0;
160: }
161:
1.200 matthew 162: #------------------------------------------------------------------------
163: #
164: # LocalConnection
165: # Completes the formation of a locally authenticated connection.
166: # This function will ensure that the 'remote' client is really the
167: # local host. If not, the connection is closed, and the function fails.
168: # If so, initcmd is parsed for the name of a file containing the
169: # IDEA session key. The fie is opened, read, deleted and the session
170: # key returned to the caller.
171: #
172: # Parameters:
173: # $Socket - Socket open on client.
174: # $initcmd - The full text of the init command.
175: #
176: # Returns:
177: # IDEA session key on success.
178: # undef on failure.
179: #
180: sub LocalConnection {
181: my ($Socket, $initcmd) = @_;
1.373 albertel 182: Debug("Attempting local connection: $initcmd client: $clientip");
1.277 albertel 183: if($clientip ne "127.0.0.1") {
1.200 matthew 184: &logthis('<font color="red"> LocalConnection rejecting non local: '
1.373 albertel 185: ."$clientip ne 127.0.0.1 </font>");
1.200 matthew 186: close $Socket;
187: return undef;
1.224 foxr 188: } else {
1.200 matthew 189: chomp($initcmd); # Get rid of \n in filename.
190: my ($init, $type, $name) = split(/:/, $initcmd);
191: Debug(" Init command: $init $type $name ");
192:
193: # Require that $init = init, and $type = local: Otherwise
194: # the caller is insane:
195:
196: if(($init ne "init") && ($type ne "local")) {
197: &logthis('<font color = "red"> LocalConnection: caller is insane! '
198: ."init = $init, and type = $type </font>");
199: close($Socket);;
200: return undef;
201:
202: }
203: # Now get the key filename:
204:
205: my $IDEAKey = lonlocal::ReadKeyFile($name);
206: return $IDEAKey;
207: }
208: }
209: #------------------------------------------------------------------------------
210: #
211: # SSLConnection
212: # Completes the formation of an ssh authenticated connection. The
213: # socket is promoted to an ssl socket. If this promotion and the associated
214: # certificate exchange are successful, the IDEA key is generated and sent
215: # to the remote peer via the SSL tunnel. The IDEA key is also returned to
216: # the caller after the SSL tunnel is torn down.
217: #
218: # Parameters:
219: # Name Type Purpose
220: # $Socket IO::Socket::INET Plaintext socket.
221: #
222: # Returns:
223: # IDEA key on success.
224: # undef on failure.
225: #
226: sub SSLConnection {
227: my $Socket = shift;
228:
229: Debug("SSLConnection: ");
230: my $KeyFile = lonssl::KeyFile();
231: if(!$KeyFile) {
232: my $err = lonssl::LastError();
233: &logthis("<font color=\"red\"> CRITICAL"
234: ."Can't get key file $err </font>");
235: return undef;
236: }
237: my ($CACertificate,
238: $Certificate) = lonssl::CertificateFile();
239:
240:
241: # If any of the key, certificate or certificate authority
242: # certificate filenames are not defined, this can't work.
243:
244: if((!$Certificate) || (!$CACertificate)) {
245: my $err = lonssl::LastError();
246: &logthis("<font color=\"red\"> CRITICAL"
247: ."Can't get certificates: $err </font>");
248:
249: return undef;
250: }
251: Debug("Key: $KeyFile CA: $CACertificate Cert: $Certificate");
252:
253: # Indicate to our peer that we can procede with
254: # a transition to ssl authentication:
255:
256: print $Socket "ok:ssl\n";
257:
258: Debug("Approving promotion -> ssl");
259: # And do so:
260:
261: my $SSLSocket = lonssl::PromoteServerSocket($Socket,
262: $CACertificate,
263: $Certificate,
264: $KeyFile);
265: if(! ($SSLSocket) ) { # SSL socket promotion failed.
266: my $err = lonssl::LastError();
267: &logthis("<font color=\"red\"> CRITICAL "
268: ."SSL Socket promotion failed: $err </font>");
269: return undef;
270: }
271: Debug("SSL Promotion successful");
272:
273: #
274: # The only thing we'll use the socket for is to send the IDEA key
275: # to the peer:
276:
277: my $Key = lonlocal::CreateCipherKey();
278: print $SSLSocket "$Key\n";
279:
280: lonssl::Close($SSLSocket);
281:
282: Debug("Key exchange complete: $Key");
283:
284: return $Key;
285: }
286: #
287: # InsecureConnection:
288: # If insecure connections are allowd,
289: # exchange a challenge with the client to 'validate' the
290: # client (not really, but that's the protocol):
291: # We produce a challenge string that's sent to the client.
292: # The client must then echo the challenge verbatim to us.
293: #
294: # Parameter:
295: # Socket - Socket open on the client.
296: # Returns:
297: # 1 - success.
298: # 0 - failure (e.g.mismatch or insecure not allowed).
299: #
300: sub InsecureConnection {
301: my $Socket = shift;
302:
303: # Don't even start if insecure connections are not allowed.
304:
305: if(! $perlvar{londAllowInsecure}) { # Insecure connections not allowed.
306: return 0;
307: }
308:
309: # Fabricate a challenge string and send it..
310:
311: my $challenge = "$$".time; # pid + time.
312: print $Socket "$challenge\n";
313: &status("Waiting for challenge reply");
314:
315: my $answer = <$Socket>;
316: $answer =~s/\W//g;
317: if($challenge eq $answer) {
318: return 1;
1.224 foxr 319: } else {
1.200 matthew 320: logthis("<font color='blue'>WARNING client did not respond to challenge</font>");
321: &status("No challenge reqply");
322: return 0;
323: }
324:
325:
326: }
1.251 foxr 327: #
328: # Safely execute a command (as long as it's not a shel command and doesn
329: # not require/rely on shell escapes. The function operates by doing a
330: # a pipe based fork and capturing stdout and stderr from the pipe.
331: #
332: # Formal Parameters:
333: # $line - A line of text to be executed as a command.
334: # Returns:
335: # The output from that command. If the output is multiline the caller
336: # must know how to split up the output.
337: #
338: #
339: sub execute_command {
340: my ($line) = @_;
341: my @words = split(/\s/, $line); # Bust the command up into words.
342: my $output = "";
343:
344: my $pid = open(CHILD, "-|");
345:
346: if($pid) { # Parent process
347: Debug("In parent process for execute_command");
348: my @data = <CHILD>; # Read the child's outupt...
349: close CHILD;
350: foreach my $output_line (@data) {
351: Debug("Adding $output_line");
352: $output .= $output_line; # Presumably has a \n on it.
353: }
354:
355: } else { # Child process
356: close (STDERR);
357: open (STDERR, ">&STDOUT");# Combine stderr, and stdout...
358: exec(@words); # won't return.
359: }
360: return $output;
361: }
362:
1.200 matthew 363:
1.140 foxr 364: # GetCertificate: Given a transaction that requires a certificate,
365: # this function will extract the certificate from the transaction
366: # request. Note that at this point, the only concept of a certificate
367: # is the hostname to which we are connected.
368: #
369: # Parameter:
370: # request - The request sent by our client (this parameterization may
371: # need to change when we really use a certificate granting
372: # authority.
373: #
374: sub GetCertificate {
375: my $request = shift;
376:
377: return $clientip;
378: }
1.161 foxr 379:
1.178 foxr 380: #
381: # Return true if client is a manager.
382: #
383: sub isManager {
384: return (($ConnectionType eq "manager") || ($ConnectionType eq "both"));
385: }
386: #
387: # Return tru if client can do client functions
388: #
389: sub isClient {
390: return (($ConnectionType eq "client") || ($ConnectionType eq "both"));
391: }
1.161 foxr 392:
393:
1.156 foxr 394: #
395: # ReadManagerTable: Reads in the current manager table. For now this is
396: # done on each manager authentication because:
397: # - These authentications are not frequent
398: # - This allows dynamic changes to the manager table
399: # without the need to signal to the lond.
400: #
401: sub ReadManagerTable {
402:
403: # Clean out the old table first..
404:
1.166 foxr 405: foreach my $key (keys %managers) {
406: delete $managers{$key};
407: }
408:
409: my $tablename = $perlvar{'lonTabDir'}."/managers.tab";
410: if (!open (MANAGERS, $tablename)) {
411: logthis('<font color="red">No manager table. Nobody can manage!!</font>');
412: return;
413: }
414: while(my $host = <MANAGERS>) {
415: chomp($host);
416: if ($host =~ "^#") { # Comment line.
417: next;
418: }
1.368 albertel 419: if (!defined &Apache::lonnet::get_host_ip($host)) { # This is a non cluster member
1.161 foxr 420: # The entry is of the form:
421: # cluname:hostname
422: # cluname - A 'cluster hostname' is needed in order to negotiate
423: # the host key.
424: # hostname- The dns name of the host.
425: #
1.166 foxr 426: my($cluname, $dnsname) = split(/:/, $host);
427:
428: my $ip = gethostbyname($dnsname);
429: if(defined($ip)) { # bad names don't deserve entry.
430: my $hostip = inet_ntoa($ip);
431: $managers{$hostip} = $cluname;
432: logthis('<font color="green"> registering manager '.
433: "$dnsname as $cluname with $hostip </font>\n");
434: }
435: } else {
436: logthis('<font color="green"> existing host'." $host</font>\n");
1.368 albertel 437: $managers{&Apache::lonnet::get_host_ip($host)} = $host; # Use info from cluster tab if clumemeber
1.166 foxr 438: }
439: }
1.156 foxr 440: }
1.140 foxr 441:
442: #
443: # ValidManager: Determines if a given certificate represents a valid manager.
444: # in this primitive implementation, the 'certificate' is
445: # just the connecting loncapa client name. This is checked
446: # against a valid client list in the configuration.
447: #
448: #
449: sub ValidManager {
450: my $certificate = shift;
451:
1.163 foxr 452: return isManager;
1.140 foxr 453: }
454: #
1.143 foxr 455: # CopyFile: Called as part of the process of installing a
456: # new configuration file. This function copies an existing
457: # file to a backup file.
458: # Parameters:
459: # oldfile - Name of the file to backup.
460: # newfile - Name of the backup file.
461: # Return:
462: # 0 - Failure (errno has failure reason).
463: # 1 - Success.
464: #
465: sub CopyFile {
1.192 foxr 466:
467: my ($oldfile, $newfile) = @_;
1.143 foxr 468:
1.281 matthew 469: if (! copy($oldfile,$newfile)) {
470: return 0;
1.143 foxr 471: }
1.281 matthew 472: chmod(0660, $newfile);
473: return 1;
1.143 foxr 474: }
1.157 foxr 475: #
476: # Host files are passed out with externally visible host IPs.
477: # If, for example, we are behind a fire-wall or NAT host, our
478: # internally visible IP may be different than the externally
479: # visible IP. Therefore, we always adjust the contents of the
480: # host file so that the entry for ME is the IP that we believe
481: # we have. At present, this is defined as the entry that
482: # DNS has for us. If by some chance we are not able to get a
483: # DNS translation for us, then we assume that the host.tab file
484: # is correct.
485: # BUGBUGBUG - in the future, we really should see if we can
486: # easily query the interface(s) instead.
487: # Parameter(s):
488: # contents - The contents of the host.tab to check.
489: # Returns:
490: # newcontents - The adjusted contents.
491: #
492: #
493: sub AdjustHostContents {
494: my $contents = shift;
495: my $adjusted;
496: my $me = $perlvar{'lonHostID'};
497:
1.354 albertel 498: foreach my $line (split(/\n/,$contents)) {
1.157 foxr 499: if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/))) {
500: chomp($line);
501: my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line);
502: if ($id eq $me) {
1.354 albertel 503: my $ip = gethostbyname($name);
504: my $ipnew = inet_ntoa($ip);
505: $ip = $ipnew;
1.157 foxr 506: # Reconstruct the host line and append to adjusted:
507:
1.354 albertel 508: my $newline = "$id:$domain:$role:$name:$ip";
509: if($maxcon ne "") { # Not all hosts have loncnew tuning params
510: $newline .= ":$maxcon:$idleto:$mincon";
511: }
512: $adjusted .= $newline."\n";
1.157 foxr 513:
1.354 albertel 514: } else { # Not me, pass unmodified.
515: $adjusted .= $line."\n";
516: }
1.157 foxr 517: } else { # Blank or comment never re-written.
518: $adjusted .= $line."\n"; # Pass blanks and comments as is.
519: }
1.354 albertel 520: }
521: return $adjusted;
1.157 foxr 522: }
1.143 foxr 523: #
524: # InstallFile: Called to install an administrative file:
525: # - The file is created with <name>.tmp
526: # - The <name>.tmp file is then mv'd to <name>
527: # This lugubrious procedure is done to ensure that we are never without
528: # a valid, even if dated, version of the file regardless of who crashes
529: # and when the crash occurs.
530: #
531: # Parameters:
532: # Name of the file
533: # File Contents.
534: # Return:
535: # nonzero - success.
536: # 0 - failure and $! has an errno.
537: #
538: sub InstallFile {
1.192 foxr 539:
540: my ($Filename, $Contents) = @_;
1.143 foxr 541: my $TempFile = $Filename.".tmp";
542:
543: # Open the file for write:
544:
545: my $fh = IO::File->new("> $TempFile"); # Write to temp.
546: if(!(defined $fh)) {
547: &logthis('<font color="red"> Unable to create '.$TempFile."</font>");
548: return 0;
549: }
550: # write the contents of the file:
551:
552: print $fh ($Contents);
553: $fh->close; # In case we ever have a filesystem w. locking
554:
555: chmod(0660, $TempFile);
556:
557: # Now we can move install the file in position.
558:
559: move($TempFile, $Filename);
560:
561: return 1;
562: }
1.200 matthew 563:
564:
1.169 foxr 565: #
566: # ConfigFileFromSelector: converts a configuration file selector
567: # (one of host or domain at this point) into a
568: # configuration file pathname.
569: #
570: # Parameters:
571: # selector - Configuration file selector.
572: # Returns:
573: # Full path to the file or undef if the selector is invalid.
574: #
575: sub ConfigFileFromSelector {
576: my $selector = shift;
577: my $tablefile;
578:
579: my $tabledir = $perlvar{'lonTabDir'}.'/';
580: if ($selector eq "hosts") {
581: $tablefile = $tabledir."hosts.tab";
582: } elsif ($selector eq "domain") {
583: $tablefile = $tabledir."domain.tab";
584: } else {
585: return undef;
586: }
587: return $tablefile;
1.143 foxr 588:
1.169 foxr 589: }
1.143 foxr 590: #
1.141 foxr 591: # PushFile: Called to do an administrative push of a file.
592: # - Ensure the file being pushed is one we support.
593: # - Backup the old file to <filename.saved>
594: # - Separate the contents of the new file out from the
595: # rest of the request.
596: # - Write the new file.
597: # Parameter:
598: # Request - The entire user request. This consists of a : separated
599: # string pushfile:tablename:contents.
600: # NOTE: The contents may have :'s in it as well making things a bit
601: # more interesting... but not much.
602: # Returns:
603: # String to send to client ("ok" or "refused" if bad file).
604: #
605: sub PushFile {
606: my $request = shift;
607: my ($command, $filename, $contents) = split(":", $request, 3);
608:
609: # At this point in time, pushes for only the following tables are
610: # supported:
611: # hosts.tab ($filename eq host).
612: # domain.tab ($filename eq domain).
613: # Construct the destination filename or reject the request.
614: #
615: # lonManage is supposed to ensure this, however this session could be
616: # part of some elaborate spoof that managed somehow to authenticate.
617: #
618:
1.169 foxr 619:
620: my $tablefile = ConfigFileFromSelector($filename);
621: if(! (defined $tablefile)) {
1.141 foxr 622: return "refused";
623: }
624: #
625: # >copy< the old table to the backup table
626: # don't rename in case system crashes/reboots etc. in the time
627: # window between a rename and write.
628: #
629: my $backupfile = $tablefile;
630: $backupfile =~ s/\.tab$/.old/;
1.143 foxr 631: if(!CopyFile($tablefile, $backupfile)) {
632: &logthis('<font color="green"> CopyFile from '.$tablefile." to ".$backupfile." failed </font>");
633: return "error:$!";
634: }
1.141 foxr 635: &logthis('<font color="green"> Pushfile: backed up '
636: .$tablefile." to $backupfile</font>");
637:
1.157 foxr 638: # If the file being pushed is the host file, we adjust the entry for ourself so that the
639: # IP will be our current IP as looked up in dns. Note this is only 99% good as it's possible
640: # to conceive of conditions where we don't have a DNS entry locally. This is possible in a
641: # network sense but it doesn't make much sense in a LonCAPA sense so we ignore (for now)
642: # that possibilty.
643:
644: if($filename eq "host") {
645: $contents = AdjustHostContents($contents);
646: }
647:
1.141 foxr 648: # Install the new file:
649:
1.143 foxr 650: if(!InstallFile($tablefile, $contents)) {
651: &logthis('<font color="red"> Pushfile: unable to install '
1.145 foxr 652: .$tablefile." $! </font>");
1.143 foxr 653: return "error:$!";
1.224 foxr 654: } else {
1.143 foxr 655: &logthis('<font color="green"> Installed new '.$tablefile
656: ."</font>");
657:
658: }
659:
1.141 foxr 660:
661: # Indicate success:
662:
663: return "ok";
664:
665: }
1.145 foxr 666:
667: #
668: # Called to re-init either lonc or lond.
669: #
670: # Parameters:
671: # request - The full request by the client. This is of the form
672: # reinit:<process>
673: # where <process> is allowed to be either of
674: # lonc or lond
675: #
676: # Returns:
677: # The string to be sent back to the client either:
678: # ok - Everything worked just fine.
679: # error:why - There was a failure and why describes the reason.
680: #
681: #
682: sub ReinitProcess {
683: my $request = shift;
684:
1.146 foxr 685:
686: # separate the request (reinit) from the process identifier and
687: # validate it producing the name of the .pid file for the process.
688: #
689: #
690: my ($junk, $process) = split(":", $request);
1.147 foxr 691: my $processpidfile = $perlvar{'lonDaemons'}.'/logs/';
1.146 foxr 692: if($process eq 'lonc') {
693: $processpidfile = $processpidfile."lonc.pid";
1.147 foxr 694: if (!open(PIDFILE, "< $processpidfile")) {
695: return "error:Open failed for $processpidfile";
696: }
697: my $loncpid = <PIDFILE>;
698: close(PIDFILE);
699: logthis('<font color="red"> Reinitializing lonc pid='.$loncpid
700: ."</font>");
701: kill("USR2", $loncpid);
1.146 foxr 702: } elsif ($process eq 'lond') {
1.147 foxr 703: logthis('<font color="red"> Reinitializing self (lond) </font>');
704: &UpdateHosts; # Lond is us!!
1.146 foxr 705: } else {
706: &logthis('<font color="yellow" Invalid reinit request for '.$process
707: ."</font>");
708: return "error:Invalid process identifier $process";
709: }
1.145 foxr 710: return 'ok';
711: }
1.168 foxr 712: # Validate a line in a configuration file edit script:
713: # Validation includes:
714: # - Ensuring the command is valid.
715: # - Ensuring the command has sufficient parameters
716: # Parameters:
717: # scriptline - A line to validate (\n has been stripped for what it's worth).
1.167 foxr 718: #
1.168 foxr 719: # Return:
720: # 0 - Invalid scriptline.
721: # 1 - Valid scriptline
722: # NOTE:
723: # Only the command syntax is checked, not the executability of the
724: # command.
725: #
726: sub isValidEditCommand {
727: my $scriptline = shift;
728:
729: # Line elements are pipe separated:
730:
731: my ($command, $key, $newline) = split(/\|/, $scriptline);
732: &logthis('<font color="green"> isValideditCommand checking: '.
733: "Command = '$command', Key = '$key', Newline = '$newline' </font>\n");
734:
735: if ($command eq "delete") {
736: #
737: # key with no newline.
738: #
739: if( ($key eq "") || ($newline ne "")) {
740: return 0; # Must have key but no newline.
741: } else {
742: return 1; # Valid syntax.
743: }
1.169 foxr 744: } elsif ($command eq "replace") {
1.168 foxr 745: #
746: # key and newline:
747: #
748: if (($key eq "") || ($newline eq "")) {
749: return 0;
750: } else {
751: return 1;
752: }
1.169 foxr 753: } elsif ($command eq "append") {
754: if (($key ne "") && ($newline eq "")) {
755: return 1;
756: } else {
757: return 0;
758: }
1.168 foxr 759: } else {
760: return 0; # Invalid command.
761: }
762: return 0; # Should not get here!!!
763: }
1.169 foxr 764: #
765: # ApplyEdit - Applies an edit command to a line in a configuration
766: # file. It is the caller's responsiblity to validate the
767: # edit line.
768: # Parameters:
769: # $directive - A single edit directive to apply.
770: # Edit directives are of the form:
771: # append|newline - Appends a new line to the file.
772: # replace|key|newline - Replaces the line with key value 'key'
773: # delete|key - Deletes the line with key value 'key'.
774: # $editor - A config file editor object that contains the
775: # file being edited.
776: #
777: sub ApplyEdit {
1.192 foxr 778:
779: my ($directive, $editor) = @_;
1.169 foxr 780:
781: # Break the directive down into its command and its parameters
782: # (at most two at this point. The meaning of the parameters, if in fact
783: # they exist depends on the command).
784:
785: my ($command, $p1, $p2) = split(/\|/, $directive);
786:
787: if($command eq "append") {
788: $editor->Append($p1); # p1 - key p2 null.
789: } elsif ($command eq "replace") {
790: $editor->ReplaceLine($p1, $p2); # p1 - key p2 = newline.
791: } elsif ($command eq "delete") {
792: $editor->DeleteLine($p1); # p1 - key p2 null.
793: } else { # Should not get here!!!
794: die "Invalid command given to ApplyEdit $command"
795: }
796: }
797: #
798: # AdjustOurHost:
799: # Adjusts a host file stored in a configuration file editor object
800: # for the true IP address of this host. This is necessary for hosts
801: # that live behind a firewall.
802: # Those hosts have a publicly distributed IP of the firewall, but
803: # internally must use their actual IP. We assume that a given
804: # host only has a single IP interface for now.
805: # Formal Parameters:
806: # editor - The configuration file editor to adjust. This
807: # editor is assumed to contain a hosts.tab file.
808: # Strategy:
809: # - Figure out our hostname.
810: # - Lookup the entry for this host.
811: # - Modify the line to contain our IP
812: # - Do a replace for this host.
813: sub AdjustOurHost {
814: my $editor = shift;
815:
816: # figure out who I am.
817:
818: my $myHostName = $perlvar{'lonHostID'}; # LonCAPA hostname.
819:
820: # Get my host file entry.
821:
822: my $ConfigLine = $editor->Find($myHostName);
823: if(! (defined $ConfigLine)) {
824: die "AdjustOurHost - no entry for me in hosts file $myHostName";
825: }
826: # figure out my IP:
827: # Use the config line to get my hostname.
828: # Use gethostbyname to translate that into an IP address.
829: #
1.338 albertel 830: my ($id,$domain,$role,$name,$maxcon,$idleto,$mincon) = split(/:/,$ConfigLine);
1.169 foxr 831: #
832: # Reassemble the config line from the elements in the list.
833: # Note that if the loncnew items were not present before, they will
834: # be now even if they would be empty
835: #
836: my $newConfigLine = $id;
1.338 albertel 837: foreach my $item ($domain, $role, $name, $maxcon, $idleto, $mincon) {
1.169 foxr 838: $newConfigLine .= ":".$item;
839: }
840: # Replace the line:
841:
842: $editor->ReplaceLine($id, $newConfigLine);
843:
844: }
845: #
846: # ReplaceConfigFile:
847: # Replaces a configuration file with the contents of a
848: # configuration file editor object.
849: # This is done by:
850: # - Copying the target file to <filename>.old
851: # - Writing the new file to <filename>.tmp
852: # - Moving <filename.tmp> -> <filename>
853: # This laborious process ensures that the system is never without
854: # a configuration file that's at least valid (even if the contents
855: # may be dated).
856: # Parameters:
857: # filename - Name of the file to modify... this is a full path.
858: # editor - Editor containing the file.
859: #
860: sub ReplaceConfigFile {
1.192 foxr 861:
862: my ($filename, $editor) = @_;
1.168 foxr 863:
1.169 foxr 864: CopyFile ($filename, $filename.".old");
865:
866: my $contents = $editor->Get(); # Get the contents of the file.
867:
868: InstallFile($filename, $contents);
869: }
1.168 foxr 870: #
871: #
872: # Called to edit a configuration table file
1.167 foxr 873: # Parameters:
874: # request - The entire command/request sent by lonc or lonManage
875: # Return:
876: # The reply to send to the client.
1.168 foxr 877: #
1.167 foxr 878: sub EditFile {
879: my $request = shift;
880:
881: # Split the command into it's pieces: edit:filetype:script
882:
1.339 albertel 883: my ($cmd, $filetype, $script) = split(/:/, $request,3); # : in script
1.167 foxr 884:
885: # Check the pre-coditions for success:
886:
1.339 albertel 887: if($cmd != "edit") { # Something is amiss afoot alack.
1.167 foxr 888: return "error:edit request detected, but request != 'edit'\n";
889: }
890: if( ($filetype ne "hosts") &&
891: ($filetype ne "domain")) {
892: return "error:edit requested with invalid file specifier: $filetype \n";
893: }
894:
895: # Split the edit script and check it's validity.
1.168 foxr 896:
897: my @scriptlines = split(/\n/, $script); # one line per element.
898: my $linecount = scalar(@scriptlines);
899: for(my $i = 0; $i < $linecount; $i++) {
900: chomp($scriptlines[$i]);
901: if(!isValidEditCommand($scriptlines[$i])) {
902: return "error:edit with bad script line: '$scriptlines[$i]' \n";
903: }
904: }
1.145 foxr 905:
1.167 foxr 906: # Execute the edit operation.
1.169 foxr 907: # - Create a config file editor for the appropriate file and
908: # - execute each command in the script:
909: #
910: my $configfile = ConfigFileFromSelector($filetype);
911: if (!(defined $configfile)) {
912: return "refused\n";
913: }
914: my $editor = ConfigFileEdit->new($configfile);
1.167 foxr 915:
1.169 foxr 916: for (my $i = 0; $i < $linecount; $i++) {
917: ApplyEdit($scriptlines[$i], $editor);
918: }
919: # If the file is the host file, ensure that our host is
920: # adjusted to have our ip:
921: #
922: if($filetype eq "host") {
923: AdjustOurHost($editor);
924: }
925: # Finally replace the current file with our file.
926: #
927: ReplaceConfigFile($configfile, $editor);
1.167 foxr 928:
929: return "ok\n";
930: }
1.207 foxr 931:
1.255 foxr 932: # read_profile
933: #
934: # Returns a set of specific entries from a user's profile file.
935: # this is a utility function that is used by both get_profile_entry and
936: # get_profile_entry_encrypted.
937: #
938: # Parameters:
939: # udom - Domain in which the user exists.
940: # uname - User's account name (loncapa account)
941: # namespace - The profile namespace to open.
942: # what - A set of & separated queries.
943: # Returns:
944: # If all ok: - The string that needs to be shipped back to the user.
945: # If failure - A string that starts with error: followed by the failure
946: # reason.. note that this probabyl gets shipped back to the
947: # user as well.
948: #
949: sub read_profile {
950: my ($udom, $uname, $namespace, $what) = @_;
951:
952: my $hashref = &tie_user_hash($udom, $uname, $namespace,
953: &GDBM_READER());
954: if ($hashref) {
955: my @queries=split(/\&/,$what);
956: my $qresult='';
957:
958: for (my $i=0;$i<=$#queries;$i++) {
959: $qresult.="$hashref->{$queries[$i]}&"; # Presumably failure gives empty string.
960: }
961: $qresult=~s/\&$//; # Remove trailing & from last lookup.
1.311 albertel 962: if (&untie_user_hash($hashref)) {
1.255 foxr 963: return $qresult;
964: } else {
965: return "error: ".($!+0)." untie (GDBM) Failed";
966: }
967: } else {
968: if ($!+0 == 2) {
969: return "error:No such file or GDBM reported bad block error";
970: } else {
971: return "error: ".($!+0)." tie (GDBM) Failed";
972: }
973: }
974:
975: }
1.214 foxr 976: #--------------------- Request Handlers --------------------------------------------
977: #
1.215 foxr 978: # By convention each request handler registers itself prior to the sub
979: # declaration:
1.214 foxr 980: #
981:
1.216 foxr 982: #++
983: #
1.214 foxr 984: # Handles ping requests.
985: # Parameters:
986: # $cmd - the actual keyword that invoked us.
987: # $tail - the tail of the request that invoked us.
988: # $replyfd- File descriptor connected to the client
989: # Implicit Inputs:
990: # $currenthostid - Global variable that carries the name of the host we are
991: # known as.
992: # Returns:
993: # 1 - Ok to continue processing.
994: # 0 - Program should exit.
995: # Side effects:
996: # Reply information is sent to the client.
997: sub ping_handler {
998: my ($cmd, $tail, $client) = @_;
999: Debug("$cmd $tail $client .. $currenthostid:");
1000:
1001: Reply( $client,"$currenthostid\n","$cmd:$tail");
1002:
1003: return 1;
1004: }
1005: ®ister_handler("ping", \&ping_handler, 0, 1, 1); # Ping unencoded, client or manager.
1006:
1.216 foxr 1007: #++
1.215 foxr 1008: #
1009: # Handles pong requests. Pong replies with our current host id, and
1010: # the results of a ping sent to us via our lonc.
1011: #
1012: # Parameters:
1013: # $cmd - the actual keyword that invoked us.
1014: # $tail - the tail of the request that invoked us.
1015: # $replyfd- File descriptor connected to the client
1016: # Implicit Inputs:
1017: # $currenthostid - Global variable that carries the name of the host we are
1018: # connected to.
1019: # Returns:
1020: # 1 - Ok to continue processing.
1021: # 0 - Program should exit.
1022: # Side effects:
1023: # Reply information is sent to the client.
1024: sub pong_handler {
1025: my ($cmd, $tail, $replyfd) = @_;
1026:
1.365 albertel 1027: my $reply=&Apache::lonnet::reply("ping",$clientname);
1.215 foxr 1028: &Reply( $replyfd, "$currenthostid:$reply\n", "$cmd:$tail");
1029: return 1;
1030: }
1031: ®ister_handler("pong", \&pong_handler, 0, 1, 1); # Pong unencoded, client or manager
1032:
1.216 foxr 1033: #++
1034: # Called to establish an encrypted session key with the remote client.
1035: # Note that with secure lond, in most cases this function is never
1036: # invoked. Instead, the secure session key is established either
1037: # via a local file that's locked down tight and only lives for a short
1038: # time, or via an ssl tunnel...and is generated from a bunch-o-random
1039: # bits from /dev/urandom, rather than the predictable pattern used by
1040: # by this sub. This sub is only used in the old-style insecure
1041: # key negotiation.
1042: # Parameters:
1043: # $cmd - the actual keyword that invoked us.
1044: # $tail - the tail of the request that invoked us.
1045: # $replyfd- File descriptor connected to the client
1046: # Implicit Inputs:
1047: # $currenthostid - Global variable that carries the name of the host
1048: # known as.
1049: # $clientname - Global variable that carries the name of the hsot we're connected to.
1050: # Returns:
1051: # 1 - Ok to continue processing.
1052: # 0 - Program should exit.
1053: # Implicit Outputs:
1054: # Reply information is sent to the client.
1055: # $cipher is set with a reference to a new IDEA encryption object.
1056: #
1057: sub establish_key_handler {
1058: my ($cmd, $tail, $replyfd) = @_;
1059:
1060: my $buildkey=time.$$.int(rand 100000);
1061: $buildkey=~tr/1-6/A-F/;
1062: $buildkey=int(rand 100000).$buildkey.int(rand 100000);
1063: my $key=$currenthostid.$clientname;
1064: $key=~tr/a-z/A-Z/;
1065: $key=~tr/G-P/0-9/;
1066: $key=~tr/Q-Z/0-9/;
1067: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey;
1068: $key=substr($key,0,32);
1069: my $cipherkey=pack("H32",$key);
1070: $cipher=new IDEA $cipherkey;
1071: &Reply($replyfd, "$buildkey\n", "$cmd:$tail");
1072:
1073: return 1;
1074:
1075: }
1076: ®ister_handler("ekey", \&establish_key_handler, 0, 1,1);
1077:
1.217 foxr 1078: # Handler for the load command. Returns the current system load average
1079: # to the requestor.
1080: #
1081: # Parameters:
1082: # $cmd - the actual keyword that invoked us.
1083: # $tail - the tail of the request that invoked us.
1084: # $replyfd- File descriptor connected to the client
1085: # Implicit Inputs:
1086: # $currenthostid - Global variable that carries the name of the host
1087: # known as.
1088: # $clientname - Global variable that carries the name of the hsot we're connected to.
1089: # Returns:
1090: # 1 - Ok to continue processing.
1091: # 0 - Program should exit.
1092: # Side effects:
1093: # Reply information is sent to the client.
1094: sub load_handler {
1095: my ($cmd, $tail, $replyfd) = @_;
1096:
1097: # Get the load average from /proc/loadavg and calculate it as a percentage of
1098: # the allowed load limit as set by the perl global variable lonLoadLim
1099:
1100: my $loadavg;
1101: my $loadfile=IO::File->new('/proc/loadavg');
1102:
1103: $loadavg=<$loadfile>;
1104: $loadavg =~ s/\s.*//g; # Extract the first field only.
1105:
1106: my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'};
1107:
1108: &Reply( $replyfd, "$loadpercent\n", "$cmd:$tail");
1109:
1110: return 1;
1111: }
1.263 albertel 1112: ®ister_handler("load", \&load_handler, 0, 1, 0);
1.217 foxr 1113:
1114: #
1115: # Process the userload request. This sub returns to the client the current
1116: # user load average. It can be invoked either by clients or managers.
1117: #
1118: # Parameters:
1119: # $cmd - the actual keyword that invoked us.
1120: # $tail - the tail of the request that invoked us.
1121: # $replyfd- File descriptor connected to the client
1122: # Implicit Inputs:
1123: # $currenthostid - Global variable that carries the name of the host
1124: # known as.
1125: # $clientname - Global variable that carries the name of the hsot we're connected to.
1126: # Returns:
1127: # 1 - Ok to continue processing.
1128: # 0 - Program should exit
1129: # Implicit inputs:
1130: # whatever the userload() function requires.
1131: # Implicit outputs:
1132: # the reply is written to the client.
1133: #
1134: sub user_load_handler {
1135: my ($cmd, $tail, $replyfd) = @_;
1136:
1.365 albertel 1137: my $userloadpercent=&Apache::lonnet::userload();
1.217 foxr 1138: &Reply($replyfd, "$userloadpercent\n", "$cmd:$tail");
1139:
1140: return 1;
1141: }
1.263 albertel 1142: ®ister_handler("userload", \&user_load_handler, 0, 1, 0);
1.217 foxr 1143:
1.218 foxr 1144: # Process a request for the authorization type of a user:
1145: # (userauth).
1146: #
1147: # Parameters:
1148: # $cmd - the actual keyword that invoked us.
1149: # $tail - the tail of the request that invoked us.
1150: # $replyfd- File descriptor connected to the client
1151: # Returns:
1152: # 1 - Ok to continue processing.
1153: # 0 - Program should exit
1154: # Implicit outputs:
1155: # The user authorization type is written to the client.
1156: #
1157: sub user_authorization_type {
1158: my ($cmd, $tail, $replyfd) = @_;
1159:
1160: my $userinput = "$cmd:$tail";
1161:
1162: # Pull the domain and username out of the command tail.
1.222 foxr 1163: # and call get_auth_type to determine the authentication type.
1.218 foxr 1164:
1165: my ($udom,$uname)=split(/:/,$tail);
1.222 foxr 1166: my $result = &get_auth_type($udom, $uname);
1.218 foxr 1167: if($result eq "nouser") {
1168: &Failure( $replyfd, "unknown_user\n", $userinput);
1169: } else {
1170: #
1.222 foxr 1171: # We only want to pass the second field from get_auth_type
1.218 foxr 1172: # for ^krb.. otherwise we'll be handing out the encrypted
1173: # password for internals e.g.
1174: #
1175: my ($type,$otherinfo) = split(/:/,$result);
1176: if($type =~ /^krb/) {
1177: $type = $result;
1.269 raeburn 1178: } else {
1179: $type .= ':';
1180: }
1181: &Reply( $replyfd, "$type\n", $userinput);
1.218 foxr 1182: }
1183:
1184: return 1;
1185: }
1186: ®ister_handler("currentauth", \&user_authorization_type, 1, 1, 0);
1187:
1188: # Process a request by a manager to push a hosts or domain table
1189: # to us. We pick apart the command and pass it on to the subs
1190: # that already exist to do this.
1191: #
1192: # Parameters:
1193: # $cmd - the actual keyword that invoked us.
1194: # $tail - the tail of the request that invoked us.
1195: # $client - File descriptor connected to the client
1196: # Returns:
1197: # 1 - Ok to continue processing.
1198: # 0 - Program should exit
1199: # Implicit Output:
1200: # a reply is written to the client.
1201: sub push_file_handler {
1202: my ($cmd, $tail, $client) = @_;
1203:
1204: my $userinput = "$cmd:$tail";
1205:
1206: # At this time we only know that the IP of our partner is a valid manager
1207: # the code below is a hook to do further authentication (e.g. to resolve
1208: # spoofing).
1209:
1210: my $cert = &GetCertificate($userinput);
1211: if(&ValidManager($cert)) {
1212:
1213: # Now presumably we have the bona fides of both the peer host and the
1214: # process making the request.
1215:
1216: my $reply = &PushFile($userinput);
1217: &Reply($client, "$reply\n", $userinput);
1218:
1219: } else {
1220: &Failure( $client, "refused\n", $userinput);
1221: }
1.219 foxr 1222: return 1;
1.218 foxr 1223: }
1224: ®ister_handler("pushfile", \&push_file_handler, 1, 0, 1);
1225:
1.243 banghart 1226: #
1227: # du - list the disk usuage of a directory recursively.
1228: #
1229: # note: stolen code from the ls file handler
1230: # under construction by Rick Banghart
1231: # .
1232: # Parameters:
1233: # $cmd - The command that dispatched us (du).
1234: # $ududir - The directory path to list... I'm not sure what this
1235: # is relative as things like ls:. return e.g.
1236: # no_such_dir.
1237: # $client - Socket open on the client.
1238: # Returns:
1239: # 1 - indicating that the daemon should not disconnect.
1240: # Side Effects:
1241: # The reply is written to $client.
1242: #
1243: sub du_handler {
1244: my ($cmd, $ududir, $client) = @_;
1.339 albertel 1245: ($ududir) = split(/:/,$ududir); # Make 'telnet' testing easier.
1.251 foxr 1246: my $userinput = "$cmd:$ududir";
1247:
1.245 albertel 1248: if ($ududir=~/\.\./ || $ududir!~m|^/home/httpd/|) {
1249: &Failure($client,"refused\n","$cmd:$ududir");
1250: return 1;
1251: }
1.249 foxr 1252: # Since $ududir could have some nasties in it,
1253: # we will require that ududir is a valid
1254: # directory. Just in case someone tries to
1255: # slip us a line like .;(cd /home/httpd rm -rf*)
1256: # etc.
1257: #
1258: if (-d $ududir) {
1.292 albertel 1259: my $total_size=0;
1260: my $code=sub {
1261: if ($_=~/\.\d+\./) { return;}
1262: if ($_=~/\.meta$/) { return;}
1.362 albertel 1263: if (-d $_) { return;}
1.292 albertel 1264: $total_size+=(stat($_))[7];
1265: };
1.295 raeburn 1266: chdir($ududir);
1.292 albertel 1267: find($code,$ududir);
1268: $total_size=int($total_size/1024);
1269: &Reply($client,"$total_size\n","$cmd:$ududir");
1.249 foxr 1270: } else {
1.251 foxr 1271: &Failure($client, "bad_directory:$ududir\n","$cmd:$ududir");
1.249 foxr 1272: }
1.243 banghart 1273: return 1;
1274: }
1275: ®ister_handler("du", \&du_handler, 0, 1, 0);
1.218 foxr 1276:
1.239 foxr 1277: #
1.280 matthew 1278: # The ls_handler routine should be considered obosolete and is retained
1279: # for communication with legacy servers. Please see the ls2_handler.
1280: #
1.239 foxr 1281: # ls - list the contents of a directory. For each file in the
1282: # selected directory the filename followed by the full output of
1283: # the stat function is returned. The returned info for each
1284: # file are separated by ':'. The stat fields are separated by &'s.
1285: # Parameters:
1286: # $cmd - The command that dispatched us (ls).
1287: # $ulsdir - The directory path to list... I'm not sure what this
1288: # is relative as things like ls:. return e.g.
1289: # no_such_dir.
1290: # $client - Socket open on the client.
1291: # Returns:
1292: # 1 - indicating that the daemon should not disconnect.
1293: # Side Effects:
1294: # The reply is written to $client.
1295: #
1296: sub ls_handler {
1.280 matthew 1297: # obsoleted by ls2_handler
1.239 foxr 1298: my ($cmd, $ulsdir, $client) = @_;
1299:
1300: my $userinput = "$cmd:$ulsdir";
1301:
1302: my $obs;
1303: my $rights;
1304: my $ulsout='';
1305: my $ulsfn;
1306: if (-e $ulsdir) {
1307: if(-d $ulsdir) {
1308: if (opendir(LSDIR,$ulsdir)) {
1309: while ($ulsfn=readdir(LSDIR)) {
1.291 albertel 1310: undef($obs);
1311: undef($rights);
1.239 foxr 1312: my @ulsstats=stat($ulsdir.'/'.$ulsfn);
1313: #We do some obsolete checking here
1314: if(-e $ulsdir.'/'.$ulsfn.".meta") {
1315: open(FILE, $ulsdir.'/'.$ulsfn.".meta");
1316: my @obsolete=<FILE>;
1317: foreach my $obsolete (@obsolete) {
1.301 www 1318: if($obsolete =~ m/(<obsolete>)(on|1)/) { $obs = 1; }
1.239 foxr 1319: if($obsolete =~ m|(<copyright>)(default)|) { $rights = 1; }
1320: }
1321: }
1322: $ulsout.=$ulsfn.'&'.join('&',@ulsstats);
1323: if($obs eq '1') { $ulsout.="&1"; }
1324: else { $ulsout.="&0"; }
1325: if($rights eq '1') { $ulsout.="&1:"; }
1326: else { $ulsout.="&0:"; }
1327: }
1328: closedir(LSDIR);
1329: }
1330: } else {
1331: my @ulsstats=stat($ulsdir);
1332: $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':';
1333: }
1334: } else {
1335: $ulsout='no_such_dir';
1336: }
1337: if ($ulsout eq '') { $ulsout='empty'; }
1.249 foxr 1338: &Reply($client, "$ulsout\n", $userinput); # This supports debug logging.
1.239 foxr 1339:
1340: return 1;
1341:
1342: }
1343: ®ister_handler("ls", \&ls_handler, 0, 1, 0);
1344:
1.280 matthew 1345: #
1346: # Please also see the ls_handler, which this routine obosolets.
1347: # ls2_handler differs from ls_handler in that it escapes its return
1348: # values before concatenating them together with ':'s.
1349: #
1350: # ls2 - list the contents of a directory. For each file in the
1351: # selected directory the filename followed by the full output of
1352: # the stat function is returned. The returned info for each
1353: # file are separated by ':'. The stat fields are separated by &'s.
1354: # Parameters:
1355: # $cmd - The command that dispatched us (ls).
1356: # $ulsdir - The directory path to list... I'm not sure what this
1357: # is relative as things like ls:. return e.g.
1358: # no_such_dir.
1359: # $client - Socket open on the client.
1360: # Returns:
1361: # 1 - indicating that the daemon should not disconnect.
1362: # Side Effects:
1363: # The reply is written to $client.
1364: #
1365: sub ls2_handler {
1366: my ($cmd, $ulsdir, $client) = @_;
1367:
1368: my $userinput = "$cmd:$ulsdir";
1369:
1370: my $obs;
1371: my $rights;
1372: my $ulsout='';
1373: my $ulsfn;
1374: if (-e $ulsdir) {
1375: if(-d $ulsdir) {
1376: if (opendir(LSDIR,$ulsdir)) {
1377: while ($ulsfn=readdir(LSDIR)) {
1.291 albertel 1378: undef($obs);
1379: undef($rights);
1.280 matthew 1380: my @ulsstats=stat($ulsdir.'/'.$ulsfn);
1381: #We do some obsolete checking here
1382: if(-e $ulsdir.'/'.$ulsfn.".meta") {
1383: open(FILE, $ulsdir.'/'.$ulsfn.".meta");
1384: my @obsolete=<FILE>;
1385: foreach my $obsolete (@obsolete) {
1.301 www 1386: if($obsolete =~ m/(<obsolete>)(on|1)/) { $obs = 1; }
1.280 matthew 1387: if($obsolete =~ m|(<copyright>)(default)|) {
1388: $rights = 1;
1389: }
1390: }
1391: }
1392: my $tmp = $ulsfn.'&'.join('&',@ulsstats);
1393: if ($obs eq '1') { $tmp.="&1"; } else { $tmp.="&0"; }
1394: if ($rights eq '1') { $tmp.="&1"; } else { $tmp.="&0"; }
1395: $ulsout.= &escape($tmp).':';
1396: }
1397: closedir(LSDIR);
1398: }
1399: } else {
1400: my @ulsstats=stat($ulsdir);
1401: $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':';
1402: }
1403: } else {
1404: $ulsout='no_such_dir';
1405: }
1406: if ($ulsout eq '') { $ulsout='empty'; }
1407: &Reply($client, "$ulsout\n", $userinput); # This supports debug logging.
1408: return 1;
1409: }
1410: ®ister_handler("ls2", \&ls2_handler, 0, 1, 0);
1411:
1.218 foxr 1412: # Process a reinit request. Reinit requests that either
1413: # lonc or lond be reinitialized so that an updated
1414: # host.tab or domain.tab can be processed.
1415: #
1416: # Parameters:
1417: # $cmd - the actual keyword that invoked us.
1418: # $tail - the tail of the request that invoked us.
1419: # $client - File descriptor connected to the client
1420: # Returns:
1421: # 1 - Ok to continue processing.
1422: # 0 - Program should exit
1423: # Implicit output:
1424: # a reply is sent to the client.
1425: #
1426: sub reinit_process_handler {
1427: my ($cmd, $tail, $client) = @_;
1428:
1429: my $userinput = "$cmd:$tail";
1430:
1431: my $cert = &GetCertificate($userinput);
1432: if(&ValidManager($cert)) {
1433: chomp($userinput);
1434: my $reply = &ReinitProcess($userinput);
1435: &Reply( $client, "$reply\n", $userinput);
1436: } else {
1437: &Failure( $client, "refused\n", $userinput);
1438: }
1439: return 1;
1440: }
1441: ®ister_handler("reinit", \&reinit_process_handler, 1, 0, 1);
1442:
1443: # Process the editing script for a table edit operation.
1444: # the editing operation must be encrypted and requested by
1445: # a manager host.
1446: #
1447: # Parameters:
1448: # $cmd - the actual keyword that invoked us.
1449: # $tail - the tail of the request that invoked us.
1450: # $client - File descriptor connected to the client
1451: # Returns:
1452: # 1 - Ok to continue processing.
1453: # 0 - Program should exit
1454: # Implicit output:
1455: # a reply is sent to the client.
1456: #
1457: sub edit_table_handler {
1458: my ($command, $tail, $client) = @_;
1459:
1460: my $userinput = "$command:$tail";
1461:
1462: my $cert = &GetCertificate($userinput);
1463: if(&ValidManager($cert)) {
1464: my($filetype, $script) = split(/:/, $tail);
1465: if (($filetype eq "hosts") ||
1466: ($filetype eq "domain")) {
1467: if($script ne "") {
1468: &Reply($client, # BUGBUG - EditFile
1469: &EditFile($userinput), # could fail.
1470: $userinput);
1471: } else {
1472: &Failure($client,"refused\n",$userinput);
1473: }
1474: } else {
1475: &Failure($client,"refused\n",$userinput);
1476: }
1477: } else {
1478: &Failure($client,"refused\n",$userinput);
1479: }
1480: return 1;
1481: }
1.263 albertel 1482: ®ister_handler("edit", \&edit_table_handler, 1, 0, 1);
1.218 foxr 1483:
1.220 foxr 1484: #
1485: # Authenticate a user against the LonCAPA authentication
1486: # database. Note that there are several authentication
1487: # possibilities:
1488: # - unix - The user can be authenticated against the unix
1489: # password file.
1490: # - internal - The user can be authenticated against a purely
1491: # internal per user password file.
1492: # - kerberos - The user can be authenticated against either a kerb4 or kerb5
1493: # ticket granting authority.
1494: # - user - The person tailoring LonCAPA can supply a user authentication
1495: # mechanism that is per system.
1496: #
1497: # Parameters:
1498: # $cmd - The command that got us here.
1499: # $tail - Tail of the command (remaining parameters).
1500: # $client - File descriptor connected to client.
1501: # Returns
1502: # 0 - Requested to exit, caller should shut down.
1503: # 1 - Continue processing.
1504: # Implicit inputs:
1505: # The authentication systems describe above have their own forms of implicit
1506: # input into the authentication process that are described above.
1507: #
1508: sub authenticate_handler {
1509: my ($cmd, $tail, $client) = @_;
1510:
1511:
1512: # Regenerate the full input line
1513:
1514: my $userinput = $cmd.":".$tail;
1515:
1516: # udom - User's domain.
1517: # uname - Username.
1518: # upass - User's password.
1519:
1520: my ($udom,$uname,$upass)=split(/:/,$tail);
1521: &Debug(" Authenticate domain = $udom, user = $uname, password = $upass");
1522: chomp($upass);
1523: $upass=&unescape($upass);
1524:
1525: my $pwdcorrect = &validate_user($udom, $uname, $upass);
1526: if($pwdcorrect) {
1527: &Reply( $client, "authorized\n", $userinput);
1528: #
1529: # Bad credentials: Failed to authorize
1530: #
1531: } else {
1532: &Failure( $client, "non_authorized\n", $userinput);
1533: }
1534:
1535: return 1;
1536: }
1.263 albertel 1537: ®ister_handler("auth", \&authenticate_handler, 1, 1, 0);
1.214 foxr 1538:
1.222 foxr 1539: #
1540: # Change a user's password. Note that this function is complicated by
1541: # the fact that a user may be authenticated in more than one way:
1542: # At present, we are not able to change the password for all types of
1543: # authentication methods. Only for:
1544: # unix - unix password or shadow passoword style authentication.
1545: # local - Locally written authentication mechanism.
1546: # For now, kerb4 and kerb5 password changes are not supported and result
1547: # in an error.
1548: # FUTURE WORK:
1549: # Support kerberos passwd changes?
1550: # Parameters:
1551: # $cmd - The command that got us here.
1552: # $tail - Tail of the command (remaining parameters).
1553: # $client - File descriptor connected to client.
1554: # Returns
1555: # 0 - Requested to exit, caller should shut down.
1556: # 1 - Continue processing.
1557: # Implicit inputs:
1558: # The authentication systems describe above have their own forms of implicit
1559: # input into the authentication process that are described above.
1560: sub change_password_handler {
1561: my ($cmd, $tail, $client) = @_;
1562:
1563: my $userinput = $cmd.":".$tail; # Reconstruct client's string.
1564:
1565: #
1566: # udom - user's domain.
1567: # uname - Username.
1568: # upass - Current password.
1569: # npass - New password.
1.346 raeburn 1570: # context - Context in which this was called
1571: # (preferences or reset_by_email).
1.222 foxr 1572:
1.346 raeburn 1573: my ($udom,$uname,$upass,$npass,$context)=split(/:/,$tail);
1.222 foxr 1574:
1575: $upass=&unescape($upass);
1576: $npass=&unescape($npass);
1577: &Debug("Trying to change password for $uname");
1578:
1579: # First require that the user can be authenticated with their
1.346 raeburn 1580: # old password unless context was 'reset_by_email':
1581:
1582: my $validated;
1583: if ($context eq 'reset_by_email') {
1584: $validated = 1;
1585: } else {
1586: $validated = &validate_user($udom, $uname, $upass);
1587: }
1.222 foxr 1588: if($validated) {
1589: my $realpasswd = &get_auth_type($udom, $uname); # Defined since authd.
1590:
1591: my ($howpwd,$contentpwd)=split(/:/,$realpasswd);
1592: if ($howpwd eq 'internal') {
1593: &Debug("internal auth");
1594: my $salt=time;
1595: $salt=substr($salt,6,2);
1596: my $ncpass=crypt($npass,$salt);
1597: if(&rewrite_password_file($udom, $uname, "internal:$ncpass")) {
1598: &logthis("Result of password change for "
1599: ."$uname: pwchange_success");
1600: &Reply($client, "ok\n", $userinput);
1601: } else {
1602: &logthis("Unable to open $uname passwd "
1603: ."to change password");
1604: &Failure( $client, "non_authorized\n",$userinput);
1605: }
1.346 raeburn 1606: } elsif ($howpwd eq 'unix' && $context ne 'reset_by_email') {
1.287 foxr 1607: my $result = &change_unix_password($uname, $npass);
1.222 foxr 1608: &logthis("Result of password change for $uname: ".
1.287 foxr 1609: $result);
1.222 foxr 1610: &Reply($client, "$result\n", $userinput);
1611: } else {
1612: # this just means that the current password mode is not
1613: # one we know how to change (e.g the kerberos auth modes or
1614: # locally written auth handler).
1615: #
1616: &Failure( $client, "auth_mode_error\n", $userinput);
1617: }
1618:
1.224 foxr 1619: } else {
1.222 foxr 1620: &Failure( $client, "non_authorized\n", $userinput);
1621: }
1622:
1623: return 1;
1624: }
1.263 albertel 1625: ®ister_handler("passwd", \&change_password_handler, 1, 1, 0);
1.222 foxr 1626:
1.225 foxr 1627: #
1628: # Create a new user. User in this case means a lon-capa user.
1629: # The user must either already exist in some authentication realm
1630: # like kerberos or the /etc/passwd. If not, a user completely local to
1631: # this loncapa system is created.
1632: #
1633: # Parameters:
1634: # $cmd - The command that got us here.
1635: # $tail - Tail of the command (remaining parameters).
1636: # $client - File descriptor connected to client.
1637: # Returns
1638: # 0 - Requested to exit, caller should shut down.
1639: # 1 - Continue processing.
1640: # Implicit inputs:
1641: # The authentication systems describe above have their own forms of implicit
1642: # input into the authentication process that are described above.
1643: sub add_user_handler {
1644:
1645: my ($cmd, $tail, $client) = @_;
1646:
1647:
1648: my ($udom,$uname,$umode,$npass)=split(/:/,$tail);
1649: my $userinput = $cmd.":".$tail; # Reconstruct the full request line.
1650:
1651: &Debug("cmd =".$cmd." $udom =".$udom." uname=".$uname);
1652:
1653:
1654: if($udom eq $currentdomainid) { # Reject new users for other domains...
1655:
1656: my $oldumask=umask(0077);
1657: chomp($npass);
1658: $npass=&unescape($npass);
1659: my $passfilename = &password_path($udom, $uname);
1660: &Debug("Password file created will be:".$passfilename);
1661: if (-e $passfilename) {
1662: &Failure( $client, "already_exists\n", $userinput);
1663: } else {
1664: my $fperror='';
1.264 albertel 1665: if (!&mkpath($passfilename)) {
1666: $fperror="error: ".($!+0)." mkdir failed while attempting "
1667: ."makeuser";
1.225 foxr 1668: }
1669: unless ($fperror) {
1670: my $result=&make_passwd_file($uname, $umode,$npass, $passfilename);
1671: &Reply($client, $result, $userinput); #BUGBUG - could be fail
1672: } else {
1673: &Failure($client, "$fperror\n", $userinput);
1674: }
1675: }
1676: umask($oldumask);
1677: } else {
1678: &Failure($client, "not_right_domain\n",
1679: $userinput); # Even if we are multihomed.
1680:
1681: }
1682: return 1;
1683:
1684: }
1685: ®ister_handler("makeuser", \&add_user_handler, 1, 1, 0);
1686:
1687: #
1688: # Change the authentication method of a user. Note that this may
1689: # also implicitly change the user's password if, for example, the user is
1690: # joining an existing authentication realm. Known authentication realms at
1691: # this time are:
1692: # internal - Purely internal password file (only loncapa knows this user)
1693: # local - Institutionally written authentication module.
1694: # unix - Unix user (/etc/passwd with or without /etc/shadow).
1695: # kerb4 - kerberos version 4
1696: # kerb5 - kerberos version 5
1697: #
1698: # Parameters:
1699: # $cmd - The command that got us here.
1700: # $tail - Tail of the command (remaining parameters).
1701: # $client - File descriptor connected to client.
1702: # Returns
1703: # 0 - Requested to exit, caller should shut down.
1704: # 1 - Continue processing.
1705: # Implicit inputs:
1706: # The authentication systems describe above have their own forms of implicit
1707: # input into the authentication process that are described above.
1.287 foxr 1708: # NOTE:
1709: # This is also used to change the authentication credential values (e.g. passwd).
1710: #
1.225 foxr 1711: #
1712: sub change_authentication_handler {
1713:
1714: my ($cmd, $tail, $client) = @_;
1715:
1716: my $userinput = "$cmd:$tail"; # Reconstruct user input.
1717:
1718: my ($udom,$uname,$umode,$npass)=split(/:/,$tail);
1719: &Debug("cmd = ".$cmd." domain= ".$udom."uname =".$uname." umode= ".$umode);
1720: if ($udom ne $currentdomainid) {
1721: &Failure( $client, "not_right_domain\n", $client);
1722: } else {
1723:
1724: chomp($npass);
1725:
1726: $npass=&unescape($npass);
1.261 foxr 1727: my $oldauth = &get_auth_type($udom, $uname); # Get old auth info.
1.225 foxr 1728: my $passfilename = &password_path($udom, $uname);
1729: if ($passfilename) { # Not allowed to create a new user!!
1.287 foxr 1730: # If just changing the unix passwd. need to arrange to run
1731: # passwd since otherwise make_passwd_file will run
1732: # lcuseradd which fails if an account already exists
1733: # (to prevent an unscrupulous LONCAPA admin from stealing
1734: # an existing account by overwriting it as a LonCAPA account).
1735:
1736: if(($oldauth =~/^unix/) && ($umode eq "unix")) {
1737: my $result = &change_unix_password($uname, $npass);
1738: &logthis("Result of password change for $uname: ".$result);
1739: if ($result eq "ok") {
1740: &Reply($client, "$result\n")
1.288 albertel 1741: } else {
1.287 foxr 1742: &Failure($client, "$result\n");
1743: }
1.288 albertel 1744: } else {
1.287 foxr 1745: my $result=&make_passwd_file($uname, $umode,$npass,$passfilename);
1746: #
1747: # If the current auth mode is internal, and the old auth mode was
1748: # unix, or krb*, and the user is an author for this domain,
1749: # re-run manage_permissions for that role in order to be able
1750: # to take ownership of the construction space back to www:www
1751: #
1752:
1753:
1754: if( (($oldauth =~ /^unix/) && ($umode eq "internal")) ||
1755: (($oldauth =~ /^internal/) && ($umode eq "unix")) ) {
1756: if(&is_author($udom, $uname)) {
1757: &Debug(" Need to manage author permissions...");
1758: &manage_permissions("/$udom/_au", $udom, $uname, "$umode:");
1759: }
1.261 foxr 1760: }
1.287 foxr 1761: &Reply($client, $result, $userinput);
1.261 foxr 1762: }
1763:
1764:
1.225 foxr 1765: } else {
1.251 foxr 1766: &Failure($client, "non_authorized\n", $userinput); # Fail the user now.
1.225 foxr 1767: }
1768: }
1769: return 1;
1770: }
1771: ®ister_handler("changeuserauth", \&change_authentication_handler, 1,1, 0);
1772:
1773: #
1774: # Determines if this is the home server for a user. The home server
1775: # for a user will have his/her lon-capa passwd file. Therefore all we need
1776: # to do is determine if this file exists.
1777: #
1778: # Parameters:
1779: # $cmd - The command that got us here.
1780: # $tail - Tail of the command (remaining parameters).
1781: # $client - File descriptor connected to client.
1782: # Returns
1783: # 0 - Requested to exit, caller should shut down.
1784: # 1 - Continue processing.
1785: # Implicit inputs:
1786: # The authentication systems describe above have their own forms of implicit
1787: # input into the authentication process that are described above.
1788: #
1789: sub is_home_handler {
1790: my ($cmd, $tail, $client) = @_;
1791:
1792: my $userinput = "$cmd:$tail";
1793:
1794: my ($udom,$uname)=split(/:/,$tail);
1795: chomp($uname);
1796: my $passfile = &password_filename($udom, $uname);
1797: if($passfile) {
1798: &Reply( $client, "found\n", $userinput);
1799: } else {
1800: &Failure($client, "not_found\n", $userinput);
1801: }
1802: return 1;
1803: }
1804: ®ister_handler("home", \&is_home_handler, 0,1,0);
1805:
1806: #
1807: # Process an update request for a resource?? I think what's going on here is
1808: # that a resource has been modified that we hold a subscription to.
1809: # If the resource is not local, then we must update, or at least invalidate our
1810: # cached copy of the resource.
1811: # FUTURE WORK:
1812: # I need to look at this logic carefully. My druthers would be to follow
1813: # typical caching logic, and simple invalidate the cache, drop any subscription
1814: # an let the next fetch start the ball rolling again... however that may
1815: # actually be more difficult than it looks given the complex web of
1816: # proxy servers.
1817: # Parameters:
1818: # $cmd - The command that got us here.
1819: # $tail - Tail of the command (remaining parameters).
1820: # $client - File descriptor connected to client.
1821: # Returns
1822: # 0 - Requested to exit, caller should shut down.
1823: # 1 - Continue processing.
1824: # Implicit inputs:
1825: # The authentication systems describe above have their own forms of implicit
1826: # input into the authentication process that are described above.
1827: #
1828: sub update_resource_handler {
1829:
1830: my ($cmd, $tail, $client) = @_;
1831:
1832: my $userinput = "$cmd:$tail";
1833:
1834: my $fname= $tail; # This allows interactive testing
1835:
1836:
1837: my $ownership=ishome($fname);
1838: if ($ownership eq 'not_owner') {
1839: if (-e $fname) {
1840: my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
1841: $atime,$mtime,$ctime,$blksize,$blocks)=stat($fname);
1842: my $now=time;
1843: my $since=$now-$atime;
1844: if ($since>$perlvar{'lonExpire'}) {
1.365 albertel 1845: my $reply=&Apache::lonnet::reply("unsub:$fname","$clientname");
1.308 albertel 1846: &devalidate_meta_cache($fname);
1.225 foxr 1847: unlink("$fname");
1.334 albertel 1848: unlink("$fname.meta");
1.225 foxr 1849: } else {
1850: my $transname="$fname.in.transfer";
1.365 albertel 1851: my $remoteurl=&Apache::lonnet::reply("sub:$fname","$clientname");
1.225 foxr 1852: my $response;
1853: alarm(120);
1854: {
1855: my $ua=new LWP::UserAgent;
1856: my $request=new HTTP::Request('GET',"$remoteurl");
1857: $response=$ua->request($request,$transname);
1858: }
1859: alarm(0);
1860: if ($response->is_error()) {
1861: unlink($transname);
1862: my $message=$response->status_line;
1863: &logthis("LWP GET: $message for $fname ($remoteurl)");
1864: } else {
1865: if ($remoteurl!~/\.meta$/) {
1866: alarm(120);
1867: {
1868: my $ua=new LWP::UserAgent;
1869: my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta');
1870: my $mresponse=$ua->request($mrequest,$fname.'.meta');
1871: if ($mresponse->is_error()) {
1872: unlink($fname.'.meta');
1873: }
1874: }
1875: alarm(0);
1876: }
1877: rename($transname,$fname);
1.308 albertel 1878: &devalidate_meta_cache($fname);
1.225 foxr 1879: }
1880: }
1881: &Reply( $client, "ok\n", $userinput);
1882: } else {
1883: &Failure($client, "not_found\n", $userinput);
1884: }
1885: } else {
1886: &Failure($client, "rejected\n", $userinput);
1887: }
1888: return 1;
1889: }
1890: ®ister_handler("update", \&update_resource_handler, 0 ,1, 0);
1891:
1.308 albertel 1892: sub devalidate_meta_cache {
1893: my ($url) = @_;
1894: use Cache::Memcached;
1895: my $memcache = new Cache::Memcached({'servers'=>['127.0.0.1:11211']});
1.365 albertel 1896: $url = &Apache::lonnet::declutter($url);
1.308 albertel 1897: $url =~ s-\.meta$--;
1898: my $id = &escape('meta:'.$url);
1899: $memcache->delete($id);
1900: }
1901:
1.225 foxr 1902: #
1.226 foxr 1903: # Fetch a user file from a remote server to the user's home directory
1904: # userfiles subdir.
1.225 foxr 1905: # Parameters:
1906: # $cmd - The command that got us here.
1907: # $tail - Tail of the command (remaining parameters).
1908: # $client - File descriptor connected to client.
1909: # Returns
1910: # 0 - Requested to exit, caller should shut down.
1911: # 1 - Continue processing.
1912: #
1913: sub fetch_user_file_handler {
1914:
1915: my ($cmd, $tail, $client) = @_;
1916:
1917: my $userinput = "$cmd:$tail";
1918: my $fname = $tail;
1.232 foxr 1919: my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);
1.225 foxr 1920: my $udir=&propath($udom,$uname).'/userfiles';
1921: unless (-e $udir) {
1922: mkdir($udir,0770);
1923: }
1.232 foxr 1924: Debug("fetch user file for $fname");
1.225 foxr 1925: if (-e $udir) {
1926: $ufile=~s/^[\.\~]+//;
1.232 foxr 1927:
1928: # IF necessary, create the path right down to the file.
1929: # Note that any regular files in the way of this path are
1930: # wiped out to deal with some earlier folly of mine.
1931:
1.267 raeburn 1932: if (!&mkpath($udir.'/'.$ufile)) {
1.264 albertel 1933: &Failure($client, "unable_to_create\n", $userinput);
1.232 foxr 1934: }
1935:
1.225 foxr 1936: my $destname=$udir.'/'.$ufile;
1937: my $transname=$udir.'/'.$ufile.'.in.transit';
1938: my $remoteurl='http://'.$clientip.'/userfiles/'.$fname;
1939: my $response;
1.232 foxr 1940: Debug("Remote URL : $remoteurl Transfername $transname Destname: $destname");
1.225 foxr 1941: alarm(120);
1942: {
1943: my $ua=new LWP::UserAgent;
1944: my $request=new HTTP::Request('GET',"$remoteurl");
1945: $response=$ua->request($request,$transname);
1946: }
1947: alarm(0);
1948: if ($response->is_error()) {
1949: unlink($transname);
1950: my $message=$response->status_line;
1951: &logthis("LWP GET: $message for $fname ($remoteurl)");
1952: &Failure($client, "failed\n", $userinput);
1953: } else {
1.232 foxr 1954: Debug("Renaming $transname to $destname");
1.225 foxr 1955: if (!rename($transname,$destname)) {
1956: &logthis("Unable to move $transname to $destname");
1957: unlink($transname);
1958: &Failure($client, "failed\n", $userinput);
1959: } else {
1960: &Reply($client, "ok\n", $userinput);
1961: }
1962: }
1963: } else {
1964: &Failure($client, "not_home\n", $userinput);
1965: }
1966: return 1;
1967: }
1968: ®ister_handler("fetchuserfile", \&fetch_user_file_handler, 0, 1, 0);
1969:
1.226 foxr 1970: #
1971: # Remove a file from a user's home directory userfiles subdirectory.
1972: # Parameters:
1973: # cmd - the Lond request keyword that got us here.
1974: # tail - the part of the command past the keyword.
1975: # client- File descriptor connected with the client.
1976: #
1977: # Returns:
1978: # 1 - Continue processing.
1979: sub remove_user_file_handler {
1980: my ($cmd, $tail, $client) = @_;
1981:
1982: my ($fname) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent.
1983:
1984: my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|);
1985: if ($ufile =~m|/\.\./|) {
1986: # any files paths with /../ in them refuse
1987: # to deal with
1988: &Failure($client, "refused\n", "$cmd:$tail");
1989: } else {
1990: my $udir = &propath($udom,$uname);
1991: if (-e $udir) {
1992: my $file=$udir.'/userfiles/'.$ufile;
1993: if (-e $file) {
1.253 foxr 1994: #
1995: # If the file is a regular file unlink is fine...
1996: # However it's possible the client wants a dir.
1997: # removed, in which case rmdir is more approprate:
1998: #
1.240 banghart 1999: if (-f $file){
1.241 albertel 2000: unlink($file);
2001: } elsif(-d $file) {
2002: rmdir($file);
1.240 banghart 2003: }
1.226 foxr 2004: if (-e $file) {
1.253 foxr 2005: # File is still there after we deleted it ?!?
2006:
1.226 foxr 2007: &Failure($client, "failed\n", "$cmd:$tail");
2008: } else {
2009: &Reply($client, "ok\n", "$cmd:$tail");
2010: }
2011: } else {
2012: &Failure($client, "not_found\n", "$cmd:$tail");
2013: }
2014: } else {
2015: &Failure($client, "not_home\n", "$cmd:$tail");
2016: }
2017: }
2018: return 1;
2019: }
2020: ®ister_handler("removeuserfile", \&remove_user_file_handler, 0,1,0);
2021:
1.236 albertel 2022: #
2023: # make a directory in a user's home directory userfiles subdirectory.
2024: # Parameters:
2025: # cmd - the Lond request keyword that got us here.
2026: # tail - the part of the command past the keyword.
2027: # client- File descriptor connected with the client.
2028: #
2029: # Returns:
2030: # 1 - Continue processing.
2031: sub mkdir_user_file_handler {
2032: my ($cmd, $tail, $client) = @_;
2033:
2034: my ($dir) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent.
2035: $dir=&unescape($dir);
2036: my ($udom,$uname,$ufile) = ($dir =~ m|^([^/]+)/([^/]+)/(.+)$|);
2037: if ($ufile =~m|/\.\./|) {
2038: # any files paths with /../ in them refuse
2039: # to deal with
2040: &Failure($client, "refused\n", "$cmd:$tail");
2041: } else {
2042: my $udir = &propath($udom,$uname);
2043: if (-e $udir) {
1.264 albertel 2044: my $newdir=$udir.'/userfiles/'.$ufile.'/';
2045: if (!&mkpath($newdir)) {
2046: &Failure($client, "failed\n", "$cmd:$tail");
1.236 albertel 2047: }
1.264 albertel 2048: &Reply($client, "ok\n", "$cmd:$tail");
1.236 albertel 2049: } else {
2050: &Failure($client, "not_home\n", "$cmd:$tail");
2051: }
2052: }
2053: return 1;
2054: }
2055: ®ister_handler("mkdiruserfile", \&mkdir_user_file_handler, 0,1,0);
2056:
1.237 albertel 2057: #
2058: # rename a file in a user's home directory userfiles subdirectory.
2059: # Parameters:
2060: # cmd - the Lond request keyword that got us here.
2061: # tail - the part of the command past the keyword.
2062: # client- File descriptor connected with the client.
2063: #
2064: # Returns:
2065: # 1 - Continue processing.
2066: sub rename_user_file_handler {
2067: my ($cmd, $tail, $client) = @_;
2068:
2069: my ($udom,$uname,$old,$new) = split(/:/, $tail);
2070: $old=&unescape($old);
2071: $new=&unescape($new);
2072: if ($new =~m|/\.\./| || $old =~m|/\.\./|) {
2073: # any files paths with /../ in them refuse to deal with
2074: &Failure($client, "refused\n", "$cmd:$tail");
2075: } else {
2076: my $udir = &propath($udom,$uname);
2077: if (-e $udir) {
2078: my $oldfile=$udir.'/userfiles/'.$old;
2079: my $newfile=$udir.'/userfiles/'.$new;
2080: if (-e $newfile) {
2081: &Failure($client, "exists\n", "$cmd:$tail");
2082: } elsif (! -e $oldfile) {
2083: &Failure($client, "not_found\n", "$cmd:$tail");
2084: } else {
2085: if (!rename($oldfile,$newfile)) {
2086: &Failure($client, "failed\n", "$cmd:$tail");
2087: } else {
2088: &Reply($client, "ok\n", "$cmd:$tail");
2089: }
2090: }
2091: } else {
2092: &Failure($client, "not_home\n", "$cmd:$tail");
2093: }
2094: }
2095: return 1;
2096: }
2097: ®ister_handler("renameuserfile", \&rename_user_file_handler, 0,1,0);
2098:
1.227 foxr 2099: #
1.382 albertel 2100: # Checks if the specified user has an active session on the server
2101: # return ok if so, not_found if not
2102: #
2103: # Parameters:
2104: # cmd - The request keyword that dispatched to tus.
2105: # tail - The tail of the request (colon separated parameters).
2106: # client - Filehandle open on the client.
2107: # Return:
2108: # 1.
2109: sub user_has_session_handler {
2110: my ($cmd, $tail, $client) = @_;
2111:
2112: my ($udom, $uname) = map { &unescape($_) } (split(/:/, $tail));
2113:
2114: &logthis("Looking for $udom $uname");
2115: opendir(DIR,$perlvar{'lonIDsDir'});
2116: my $filename;
2117: while ($filename=readdir(DIR)) {
2118: last if ($filename=~/^\Q$uname\E_\d+_\Q$udom\E_/);
2119: }
2120: if ($filename) {
2121: &Reply($client, "ok\n", "$cmd:$tail");
2122: } else {
2123: &Failure($client, "not_found\n", "$cmd:$tail");
2124: }
2125: return 1;
2126:
2127: }
2128: ®ister_handler("userhassession", \&user_has_session_handler, 0,1,0);
2129:
2130: #
1.263 albertel 2131: # Authenticate access to a user file by checking that the token the user's
2132: # passed also exists in their session file
1.227 foxr 2133: #
2134: # Parameters:
2135: # cmd - The request keyword that dispatched to tus.
2136: # tail - The tail of the request (colon separated parameters).
2137: # client - Filehandle open on the client.
2138: # Return:
2139: # 1.
2140: sub token_auth_user_file_handler {
2141: my ($cmd, $tail, $client) = @_;
2142:
2143: my ($fname, $session) = split(/:/, $tail);
2144:
2145: chomp($session);
1.251 foxr 2146: my $reply="non_auth\n";
1.343 albertel 2147: my $file = $perlvar{'lonIDsDir'}.'/'.$session.'.id';
2148: if (open(ENVIN,"$file")) {
1.332 albertel 2149: flock(ENVIN,LOCK_SH);
1.343 albertel 2150: tie(my %disk_env,'GDBM_File',"$file",&GDBM_READER(),0640);
2151: if (exists($disk_env{"userfile.$fname"})) {
2152: $reply="ok\n";
2153: } else {
2154: foreach my $envname (keys(%disk_env)) {
2155: if ($envname=~ m|^userfile\.\Q$fname\E|) {
2156: $reply="ok\n";
2157: last;
2158: }
2159: }
1.227 foxr 2160: }
1.343 albertel 2161: untie(%disk_env);
1.227 foxr 2162: close(ENVIN);
1.251 foxr 2163: &Reply($client, $reply, "$cmd:$tail");
1.227 foxr 2164: } else {
2165: &Failure($client, "invalid_token\n", "$cmd:$tail");
2166: }
2167: return 1;
2168:
2169: }
2170: ®ister_handler("tokenauthuserfile", \&token_auth_user_file_handler, 0,1,0);
1.229 foxr 2171:
2172: #
2173: # Unsubscribe from a resource.
2174: #
2175: # Parameters:
2176: # $cmd - The command that got us here.
2177: # $tail - Tail of the command (remaining parameters).
2178: # $client - File descriptor connected to client.
2179: # Returns
2180: # 0 - Requested to exit, caller should shut down.
2181: # 1 - Continue processing.
2182: #
2183: sub unsubscribe_handler {
2184: my ($cmd, $tail, $client) = @_;
2185:
2186: my $userinput= "$cmd:$tail";
2187:
2188: my ($fname) = split(/:/,$tail); # Split in case there's extrs.
2189:
2190: &Debug("Unsubscribing $fname");
2191: if (-e $fname) {
2192: &Debug("Exists");
2193: &Reply($client, &unsub($fname,$clientip), $userinput);
2194: } else {
2195: &Failure($client, "not_found\n", $userinput);
2196: }
2197: return 1;
2198: }
2199: ®ister_handler("unsub", \&unsubscribe_handler, 0, 1, 0);
1.263 albertel 2200:
1.230 foxr 2201: # Subscribe to a resource
2202: #
2203: # Parameters:
2204: # $cmd - The command that got us here.
2205: # $tail - Tail of the command (remaining parameters).
2206: # $client - File descriptor connected to client.
2207: # Returns
2208: # 0 - Requested to exit, caller should shut down.
2209: # 1 - Continue processing.
2210: #
2211: sub subscribe_handler {
2212: my ($cmd, $tail, $client)= @_;
2213:
2214: my $userinput = "$cmd:$tail";
2215:
2216: &Reply( $client, &subscribe($userinput,$clientip), $userinput);
2217:
2218: return 1;
2219: }
2220: ®ister_handler("sub", \&subscribe_handler, 0, 1, 0);
2221:
2222: #
1.379 albertel 2223: # Determine the latest version of a resource (it looks for the highest
2224: # past version and then returns that +1)
1.230 foxr 2225: #
2226: # Parameters:
2227: # $cmd - The command that got us here.
2228: # $tail - Tail of the command (remaining parameters).
1.379 albertel 2229: # (Should consist of an absolute path to a file)
1.230 foxr 2230: # $client - File descriptor connected to client.
2231: # Returns
2232: # 0 - Requested to exit, caller should shut down.
2233: # 1 - Continue processing.
2234: #
2235: sub current_version_handler {
2236: my ($cmd, $tail, $client) = @_;
2237:
2238: my $userinput= "$cmd:$tail";
2239:
2240: my $fname = $tail;
2241: &Reply( $client, ¤tversion($fname)."\n", $userinput);
2242: return 1;
2243:
2244: }
2245: ®ister_handler("currentversion", \¤t_version_handler, 0, 1, 0);
2246:
2247: # Make an entry in a user's activity log.
2248: #
2249: # Parameters:
2250: # $cmd - The command that got us here.
2251: # $tail - Tail of the command (remaining parameters).
2252: # $client - File descriptor connected to client.
2253: # Returns
2254: # 0 - Requested to exit, caller should shut down.
2255: # 1 - Continue processing.
2256: #
2257: sub activity_log_handler {
2258: my ($cmd, $tail, $client) = @_;
2259:
2260:
2261: my $userinput= "$cmd:$tail";
2262:
2263: my ($udom,$uname,$what)=split(/:/,$tail);
2264: chomp($what);
2265: my $proname=&propath($udom,$uname);
2266: my $now=time;
2267: my $hfh;
2268: if ($hfh=IO::File->new(">>$proname/activity.log")) {
2269: print $hfh "$now:$clientname:$what\n";
2270: &Reply( $client, "ok\n", $userinput);
2271: } else {
2272: &Failure($client, "error: ".($!+0)." IO::File->new Failed "
2273: ."while attempting log\n",
2274: $userinput);
2275: }
2276:
2277: return 1;
2278: }
1.263 albertel 2279: ®ister_handler("log", \&activity_log_handler, 0, 1, 0);
1.230 foxr 2280:
2281: #
2282: # Put a namespace entry in a user profile hash.
2283: # My druthers would be for this to be an encrypted interaction too.
2284: # anything that might be an inadvertent covert channel about either
2285: # user authentication or user personal information....
2286: #
2287: # Parameters:
2288: # $cmd - The command that got us here.
2289: # $tail - Tail of the command (remaining parameters).
2290: # $client - File descriptor connected to client.
2291: # Returns
2292: # 0 - Requested to exit, caller should shut down.
2293: # 1 - Continue processing.
2294: #
2295: sub put_user_profile_entry {
2296: my ($cmd, $tail, $client) = @_;
1.229 foxr 2297:
1.230 foxr 2298: my $userinput = "$cmd:$tail";
2299:
1.242 raeburn 2300: my ($udom,$uname,$namespace,$what) =split(/:/,$tail,4);
1.230 foxr 2301: if ($namespace ne 'roles') {
2302: chomp($what);
2303: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2304: &GDBM_WRCREAT(),"P",$what);
2305: if($hashref) {
2306: my @pairs=split(/\&/,$what);
2307: foreach my $pair (@pairs) {
2308: my ($key,$value)=split(/=/,$pair);
2309: $hashref->{$key}=$value;
2310: }
1.311 albertel 2311: if (&untie_user_hash($hashref)) {
1.230 foxr 2312: &Reply( $client, "ok\n", $userinput);
2313: } else {
2314: &Failure($client, "error: ".($!+0)." untie(GDBM) failed ".
2315: "while attempting put\n",
2316: $userinput);
2317: }
2318: } else {
1.316 albertel 2319: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
1.230 foxr 2320: "while attempting put\n", $userinput);
2321: }
2322: } else {
2323: &Failure( $client, "refused\n", $userinput);
2324: }
2325:
2326: return 1;
2327: }
2328: ®ister_handler("put", \&put_user_profile_entry, 0, 1, 0);
2329:
1.283 albertel 2330: # Put a piece of new data in hash, returns error if entry already exists
2331: # Parameters:
2332: # $cmd - The command that got us here.
2333: # $tail - Tail of the command (remaining parameters).
2334: # $client - File descriptor connected to client.
2335: # Returns
2336: # 0 - Requested to exit, caller should shut down.
2337: # 1 - Continue processing.
2338: #
2339: sub newput_user_profile_entry {
2340: my ($cmd, $tail, $client) = @_;
2341:
2342: my $userinput = "$cmd:$tail";
2343:
2344: my ($udom,$uname,$namespace,$what) =split(/:/,$tail,4);
2345: if ($namespace eq 'roles') {
2346: &Failure( $client, "refused\n", $userinput);
2347: return 1;
2348: }
2349:
2350: chomp($what);
2351:
2352: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2353: &GDBM_WRCREAT(),"N",$what);
2354: if(!$hashref) {
1.316 albertel 2355: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
1.283 albertel 2356: "while attempting put\n", $userinput);
2357: return 1;
2358: }
2359:
2360: my @pairs=split(/\&/,$what);
2361: foreach my $pair (@pairs) {
2362: my ($key,$value)=split(/=/,$pair);
2363: if (exists($hashref->{$key})) {
2364: &Failure($client, "key_exists: ".$key."\n",$userinput);
2365: return 1;
2366: }
2367: }
2368:
2369: foreach my $pair (@pairs) {
2370: my ($key,$value)=split(/=/,$pair);
2371: $hashref->{$key}=$value;
2372: }
2373:
1.311 albertel 2374: if (&untie_user_hash($hashref)) {
1.283 albertel 2375: &Reply( $client, "ok\n", $userinput);
2376: } else {
2377: &Failure($client, "error: ".($!+0)." untie(GDBM) failed ".
2378: "while attempting put\n",
2379: $userinput);
2380: }
2381: return 1;
2382: }
2383: ®ister_handler("newput", \&newput_user_profile_entry, 0, 1, 0);
2384:
1.230 foxr 2385: #
2386: # Increment a profile entry in the user history file.
2387: # The history contains keyword value pairs. In this case,
2388: # The value itself is a pair of numbers. The first, the current value
2389: # the second an increment that this function applies to the current
2390: # value.
2391: #
2392: # Parameters:
2393: # $cmd - The command that got us here.
2394: # $tail - Tail of the command (remaining parameters).
2395: # $client - File descriptor connected to client.
2396: # Returns
2397: # 0 - Requested to exit, caller should shut down.
2398: # 1 - Continue processing.
2399: #
2400: sub increment_user_value_handler {
2401: my ($cmd, $tail, $client) = @_;
2402:
2403: my $userinput = "$cmd:$tail";
2404:
2405: my ($udom,$uname,$namespace,$what) =split(/:/,$tail);
2406: if ($namespace ne 'roles') {
2407: chomp($what);
2408: my $hashref = &tie_user_hash($udom, $uname,
2409: $namespace, &GDBM_WRCREAT(),
2410: "P",$what);
2411: if ($hashref) {
2412: my @pairs=split(/\&/,$what);
2413: foreach my $pair (@pairs) {
2414: my ($key,$value)=split(/=/,$pair);
1.284 raeburn 2415: $value = &unescape($value);
1.230 foxr 2416: # We could check that we have a number...
2417: if (! defined($value) || $value eq '') {
2418: $value = 1;
2419: }
2420: $hashref->{$key}+=$value;
1.284 raeburn 2421: if ($namespace eq 'nohist_resourcetracker') {
2422: if ($hashref->{$key} < 0) {
2423: $hashref->{$key} = 0;
2424: }
2425: }
1.230 foxr 2426: }
1.311 albertel 2427: if (&untie_user_hash($hashref)) {
1.230 foxr 2428: &Reply( $client, "ok\n", $userinput);
2429: } else {
2430: &Failure($client, "error: ".($!+0)." untie(GDBM) failed ".
2431: "while attempting inc\n", $userinput);
2432: }
2433: } else {
2434: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
2435: "while attempting inc\n", $userinput);
2436: }
2437: } else {
2438: &Failure($client, "refused\n", $userinput);
2439: }
2440:
2441: return 1;
2442: }
2443: ®ister_handler("inc", \&increment_user_value_handler, 0, 1, 0);
2444:
2445: #
2446: # Put a new role for a user. Roles are LonCAPA's packaging of permissions.
2447: # Each 'role' a user has implies a set of permissions. Adding a new role
2448: # for a person grants the permissions packaged with that role
2449: # to that user when the role is selected.
2450: #
2451: # Parameters:
2452: # $cmd - The command string (rolesput).
2453: # $tail - The remainder of the request line. For rolesput this
2454: # consists of a colon separated list that contains:
2455: # The domain and user that is granting the role (logged).
2456: # The domain and user that is getting the role.
2457: # The roles being granted as a set of & separated pairs.
2458: # each pair a key value pair.
2459: # $client - File descriptor connected to the client.
2460: # Returns:
2461: # 0 - If the daemon should exit
2462: # 1 - To continue processing.
2463: #
2464: #
2465: sub roles_put_handler {
2466: my ($cmd, $tail, $client) = @_;
2467:
2468: my $userinput = "$cmd:$tail";
2469:
2470: my ( $exedom, $exeuser, $udom, $uname, $what) = split(/:/,$tail);
2471:
2472:
2473: my $namespace='roles';
2474: chomp($what);
2475: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2476: &GDBM_WRCREAT(), "P",
2477: "$exedom:$exeuser:$what");
2478: #
2479: # Log the attempt to set a role. The {}'s here ensure that the file
2480: # handle is open for the minimal amount of time. Since the flush
2481: # is done on close this improves the chances the log will be an un-
2482: # corrupted ordered thing.
2483: if ($hashref) {
1.261 foxr 2484: my $pass_entry = &get_auth_type($udom, $uname);
2485: my ($auth_type,$pwd) = split(/:/, $pass_entry);
2486: $auth_type = $auth_type.":";
1.230 foxr 2487: my @pairs=split(/\&/,$what);
2488: foreach my $pair (@pairs) {
2489: my ($key,$value)=split(/=/,$pair);
2490: &manage_permissions($key, $udom, $uname,
1.260 foxr 2491: $auth_type);
1.230 foxr 2492: $hashref->{$key}=$value;
2493: }
1.311 albertel 2494: if (&untie_user_hash($hashref)) {
1.230 foxr 2495: &Reply($client, "ok\n", $userinput);
2496: } else {
2497: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
2498: "while attempting rolesput\n", $userinput);
2499: }
2500: } else {
2501: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
2502: "while attempting rolesput\n", $userinput);
2503: }
2504: return 1;
2505: }
2506: ®ister_handler("rolesput", \&roles_put_handler, 1,1,0); # Encoded client only.
2507:
2508: #
1.231 foxr 2509: # Deletes (removes) a role for a user. This is equivalent to removing
2510: # a permissions package associated with the role from the user's profile.
2511: #
2512: # Parameters:
2513: # $cmd - The command (rolesdel)
2514: # $tail - The remainder of the request line. This consists
2515: # of:
2516: # The domain and user requesting the change (logged)
2517: # The domain and user being changed.
2518: # The roles being revoked. These are shipped to us
2519: # as a bunch of & separated role name keywords.
2520: # $client - The file handle open on the client.
2521: # Returns:
2522: # 1 - Continue processing
2523: # 0 - Exit.
2524: #
2525: sub roles_delete_handler {
2526: my ($cmd, $tail, $client) = @_;
2527:
2528: my $userinput = "$cmd:$tail";
2529:
2530: my ($exedom,$exeuser,$udom,$uname,$what)=split(/:/,$tail);
2531: &Debug("cmd = ".$cmd." exedom= ".$exedom."user = ".$exeuser." udom=".$udom.
2532: "what = ".$what);
2533: my $namespace='roles';
2534: chomp($what);
2535: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2536: &GDBM_WRCREAT(), "D",
2537: "$exedom:$exeuser:$what");
2538:
2539: if ($hashref) {
2540: my @rolekeys=split(/\&/,$what);
2541:
2542: foreach my $key (@rolekeys) {
2543: delete $hashref->{$key};
2544: }
1.315 albertel 2545: if (&untie_user_hash($hashref)) {
1.231 foxr 2546: &Reply($client, "ok\n", $userinput);
2547: } else {
2548: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
2549: "while attempting rolesdel\n", $userinput);
2550: }
2551: } else {
2552: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
2553: "while attempting rolesdel\n", $userinput);
2554: }
2555:
2556: return 1;
2557: }
2558: ®ister_handler("rolesdel", \&roles_delete_handler, 1,1, 0); # Encoded client only
2559:
2560: # Unencrypted get from a user's profile database. See
2561: # GetProfileEntryEncrypted for a version that does end-to-end encryption.
2562: # This function retrieves a keyed item from a specific named database in the
2563: # user's directory.
2564: #
2565: # Parameters:
2566: # $cmd - Command request keyword (get).
2567: # $tail - Tail of the command. This is a colon separated list
2568: # consisting of the domain and username that uniquely
2569: # identifies the profile,
2570: # The 'namespace' which selects the gdbm file to
2571: # do the lookup in,
2572: # & separated list of keys to lookup. Note that
2573: # the values are returned as an & separated list too.
2574: # $client - File descriptor open on the client.
2575: # Returns:
2576: # 1 - Continue processing.
2577: # 0 - Exit.
2578: #
2579: sub get_profile_entry {
2580: my ($cmd, $tail, $client) = @_;
2581:
2582: my $userinput= "$cmd:$tail";
2583:
2584: my ($udom,$uname,$namespace,$what) = split(/:/,$tail);
2585: chomp($what);
1.255 foxr 2586:
2587: my $replystring = read_profile($udom, $uname, $namespace, $what);
2588: my ($first) = split(/:/,$replystring);
2589: if($first ne "error") {
2590: &Reply($client, "$replystring\n", $userinput);
1.231 foxr 2591: } else {
1.255 foxr 2592: &Failure($client, $replystring." while attempting get\n", $userinput);
1.231 foxr 2593: }
2594: return 1;
1.255 foxr 2595:
2596:
1.231 foxr 2597: }
2598: ®ister_handler("get", \&get_profile_entry, 0,1,0);
2599:
2600: #
2601: # Process the encrypted get request. Note that the request is sent
2602: # in clear, but the reply is encrypted. This is a small covert channel:
2603: # information about the sensitive keys is given to the snooper. Just not
2604: # information about the values of the sensitive key. Hmm if I wanted to
2605: # know these I'd snoop for the egets. Get the profile item names from them
2606: # and then issue a get for them since there's no enforcement of the
2607: # requirement of an encrypted get for particular profile items. If I
2608: # were re-doing this, I'd force the request to be encrypted as well as the
2609: # reply. I'd also just enforce encrypted transactions for all gets since
2610: # that would prevent any covert channel snooping.
2611: #
2612: # Parameters:
2613: # $cmd - Command keyword of request (eget).
2614: # $tail - Tail of the command. See GetProfileEntry
# for more information about this.
2615: # $client - File open on the client.
2616: # Returns:
2617: # 1 - Continue processing
2618: # 0 - server should exit.
2619: sub get_profile_entry_encrypted {
2620: my ($cmd, $tail, $client) = @_;
2621:
2622: my $userinput = "$cmd:$tail";
2623:
1.339 albertel 2624: my ($udom,$uname,$namespace,$what) = split(/:/,$tail);
1.231 foxr 2625: chomp($what);
1.255 foxr 2626: my $qresult = read_profile($udom, $uname, $namespace, $what);
2627: my ($first) = split(/:/, $qresult);
2628: if($first ne "error") {
2629:
2630: if ($cipher) {
2631: my $cmdlength=length($qresult);
2632: $qresult.=" ";
2633: my $encqresult='';
2634: for(my $encidx=0;$encidx<=$cmdlength;$encidx+=8) {
2635: $encqresult.= unpack("H16",
2636: $cipher->encrypt(substr($qresult,
2637: $encidx,
2638: 8)));
2639: }
2640: &Reply( $client, "enc:$cmdlength:$encqresult\n", $userinput);
2641: } else {
1.231 foxr 2642: &Failure( $client, "error:no_key\n", $userinput);
2643: }
2644: } else {
1.255 foxr 2645: &Failure($client, "$qresult while attempting eget\n", $userinput);
2646:
1.231 foxr 2647: }
2648:
2649: return 1;
2650: }
1.255 foxr 2651: ®ister_handler("eget", \&get_profile_entry_encrypted, 0, 1, 0);
1.263 albertel 2652:
1.231 foxr 2653: #
2654: # Deletes a key in a user profile database.
2655: #
2656: # Parameters:
2657: # $cmd - Command keyword (del).
2658: # $tail - Command tail. IN this case a colon
2659: # separated list containing:
2660: # The domain and user that identifies uniquely
2661: # the identity of the user.
2662: # The profile namespace (name of the profile
2663: # database file).
2664: # & separated list of keywords to delete.
2665: # $client - File open on client socket.
2666: # Returns:
2667: # 1 - Continue processing
2668: # 0 - Exit server.
2669: #
2670: #
2671: sub delete_profile_entry {
2672: my ($cmd, $tail, $client) = @_;
2673:
2674: my $userinput = "cmd:$tail";
2675:
2676: my ($udom,$uname,$namespace,$what) = split(/:/,$tail);
2677: chomp($what);
2678: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2679: &GDBM_WRCREAT(),
2680: "D",$what);
2681: if ($hashref) {
2682: my @keys=split(/\&/,$what);
2683: foreach my $key (@keys) {
2684: delete($hashref->{$key});
2685: }
1.315 albertel 2686: if (&untie_user_hash($hashref)) {
1.231 foxr 2687: &Reply($client, "ok\n", $userinput);
2688: } else {
2689: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
2690: "while attempting del\n", $userinput);
2691: }
2692: } else {
2693: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
2694: "while attempting del\n", $userinput);
2695: }
2696: return 1;
2697: }
2698: ®ister_handler("del", \&delete_profile_entry, 0, 1, 0);
1.263 albertel 2699:
1.231 foxr 2700: #
2701: # List the set of keys that are defined in a profile database file.
2702: # A successful reply from this will contain an & separated list of
2703: # the keys.
2704: # Parameters:
2705: # $cmd - Command request (keys).
2706: # $tail - Remainder of the request, a colon separated
2707: # list containing domain/user that identifies the
2708: # user being queried, and the database namespace
2709: # (database filename essentially).
2710: # $client - File open on the client.
2711: # Returns:
2712: # 1 - Continue processing.
2713: # 0 - Exit the server.
2714: #
2715: sub get_profile_keys {
2716: my ($cmd, $tail, $client) = @_;
2717:
2718: my $userinput = "$cmd:$tail";
2719:
2720: my ($udom,$uname,$namespace)=split(/:/,$tail);
2721: my $qresult='';
2722: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2723: &GDBM_READER());
2724: if ($hashref) {
2725: foreach my $key (keys %$hashref) {
2726: $qresult.="$key&";
2727: }
1.315 albertel 2728: if (&untie_user_hash($hashref)) {
1.231 foxr 2729: $qresult=~s/\&$//;
2730: &Reply($client, "$qresult\n", $userinput);
2731: } else {
2732: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
2733: "while attempting keys\n", $userinput);
2734: }
2735: } else {
2736: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
2737: "while attempting keys\n", $userinput);
2738: }
2739:
2740: return 1;
2741: }
2742: ®ister_handler("keys", \&get_profile_keys, 0, 1, 0);
2743:
2744: #
2745: # Dump the contents of a user profile database.
2746: # Note that this constitutes a very large covert channel too since
2747: # the dump will return sensitive information that is not encrypted.
2748: # The naive security assumption is that the session negotiation ensures
2749: # our client is trusted and I don't believe that's assured at present.
2750: # Sure want badly to go to ssl or tls. Of course if my peer isn't really
2751: # a LonCAPA node they could have negotiated an encryption key too so >sigh<.
2752: #
2753: # Parameters:
2754: # $cmd - The command request keyword (currentdump).
2755: # $tail - Remainder of the request, consisting of a colon
2756: # separated list that has the domain/username and
2757: # the namespace to dump (database file).
2758: # $client - file open on the remote client.
2759: # Returns:
2760: # 1 - Continue processing.
2761: # 0 - Exit the server.
2762: #
2763: sub dump_profile_database {
2764: my ($cmd, $tail, $client) = @_;
2765:
2766: my $userinput = "$cmd:$tail";
2767:
2768: my ($udom,$uname,$namespace) = split(/:/,$tail);
2769: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2770: &GDBM_READER());
2771: if ($hashref) {
2772: # Structure of %data:
2773: # $data{$symb}->{$parameter}=$value;
2774: # $data{$symb}->{'v.'.$parameter}=$version;
2775: # since $parameter will be unescaped, we do not
2776: # have to worry about silly parameter names...
2777:
2778: my $qresult='';
2779: my %data = (); # A hash of anonymous hashes..
2780: while (my ($key,$value) = each(%$hashref)) {
2781: my ($v,$symb,$param) = split(/:/,$key);
2782: next if ($v eq 'version' || $symb eq 'keys');
2783: next if (exists($data{$symb}) &&
2784: exists($data{$symb}->{$param}) &&
2785: $data{$symb}->{'v.'.$param} > $v);
2786: $data{$symb}->{$param}=$value;
2787: $data{$symb}->{'v.'.$param}=$v;
2788: }
1.311 albertel 2789: if (&untie_user_hash($hashref)) {
1.231 foxr 2790: while (my ($symb,$param_hash) = each(%data)) {
2791: while(my ($param,$value) = each (%$param_hash)){
2792: next if ($param =~ /^v\./); # Ignore versions...
2793: #
2794: # Just dump the symb=value pairs separated by &
2795: #
2796: $qresult.=$symb.':'.$param.'='.$value.'&';
2797: }
2798: }
2799: chop($qresult);
2800: &Reply($client , "$qresult\n", $userinput);
2801: } else {
2802: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
2803: "while attempting currentdump\n", $userinput);
2804: }
2805: } else {
2806: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
2807: "while attempting currentdump\n", $userinput);
2808: }
2809:
2810: return 1;
2811: }
2812: ®ister_handler("currentdump", \&dump_profile_database, 0, 1, 0);
2813:
2814: #
2815: # Dump a profile database with an optional regular expression
2816: # to match against the keys. In this dump, no effort is made
2817: # to separate symb from version information. Presumably the
2818: # databases that are dumped by this command are of a different
2819: # structure. Need to look at this and improve the documentation of
2820: # both this and the currentdump handler.
2821: # Parameters:
2822: # $cmd - The command keyword.
2823: # $tail - All of the characters after the $cmd:
2824: # These are expected to be a colon
2825: # separated list containing:
2826: # domain/user - identifying the user.
2827: # namespace - identifying the database.
2828: # regexp - optional regular expression
2829: # that is matched against
2830: # database keywords to do
2831: # selective dumps.
2832: # $client - Channel open on the client.
2833: # Returns:
2834: # 1 - Continue processing.
2835: # Side effects:
2836: # response is written to $client.
2837: #
2838: sub dump_with_regexp {
2839: my ($cmd, $tail, $client) = @_;
2840:
2841:
2842: my $userinput = "$cmd:$tail";
2843:
1.306 albertel 2844: my ($udom,$uname,$namespace,$regexp,$range)=split(/:/,$tail);
1.231 foxr 2845: if (defined($regexp)) {
2846: $regexp=&unescape($regexp);
2847: } else {
2848: $regexp='.';
2849: }
1.306 albertel 2850: my ($start,$end);
2851: if (defined($range)) {
2852: if ($range =~/^(\d+)\-(\d+)$/) {
2853: ($start,$end) = ($1,$2);
2854: } elsif ($range =~/^(\d+)$/) {
2855: ($start,$end) = (0,$1);
2856: } else {
2857: undef($range);
2858: }
2859: }
1.231 foxr 2860: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2861: &GDBM_READER());
2862: if ($hashref) {
2863: my $qresult='';
1.306 albertel 2864: my $count=0;
1.231 foxr 2865: while (my ($key,$value) = each(%$hashref)) {
2866: if ($regexp eq '.') {
1.306 albertel 2867: $count++;
2868: if (defined($range) && $count >= $end) { last; }
2869: if (defined($range) && $count < $start) { next; }
1.231 foxr 2870: $qresult.=$key.'='.$value.'&';
2871: } else {
2872: my $unescapeKey = &unescape($key);
2873: if (eval('$unescapeKey=~/$regexp/')) {
1.306 albertel 2874: $count++;
2875: if (defined($range) && $count >= $end) { last; }
2876: if (defined($range) && $count < $start) { next; }
1.231 foxr 2877: $qresult.="$key=$value&";
2878: }
2879: }
2880: }
1.311 albertel 2881: if (&untie_user_hash($hashref)) {
1.231 foxr 2882: chop($qresult);
2883: &Reply($client, "$qresult\n", $userinput);
2884: } else {
2885: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
2886: "while attempting dump\n", $userinput);
2887: }
2888: } else {
2889: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
2890: "while attempting dump\n", $userinput);
2891: }
2892:
2893: return 1;
2894: }
2895: ®ister_handler("dump", \&dump_with_regexp, 0, 1, 0);
2896:
2897: # Store a set of key=value pairs associated with a versioned name.
2898: #
2899: # Parameters:
2900: # $cmd - Request command keyword.
2901: # $tail - Tail of the request. This is a colon
2902: # separated list containing:
2903: # domain/user - User and authentication domain.
2904: # namespace - Name of the database being modified
2905: # rid - Resource keyword to modify.
2906: # what - new value associated with rid.
2907: #
2908: # $client - Socket open on the client.
2909: #
2910: #
2911: # Returns:
2912: # 1 (keep on processing).
2913: # Side-Effects:
2914: # Writes to the client
2915: sub store_handler {
2916: my ($cmd, $tail, $client) = @_;
2917:
2918: my $userinput = "$cmd:$tail";
2919:
2920: my ($udom,$uname,$namespace,$rid,$what) =split(/:/,$tail);
2921: if ($namespace ne 'roles') {
2922:
2923: chomp($what);
2924: my @pairs=split(/\&/,$what);
2925: my $hashref = &tie_user_hash($udom, $uname, $namespace,
1.268 albertel 2926: &GDBM_WRCREAT(), "S",
1.231 foxr 2927: "$rid:$what");
2928: if ($hashref) {
2929: my $now = time;
2930: my @previouskeys=split(/&/,$hashref->{"keys:$rid"});
2931: my $key;
2932: $hashref->{"version:$rid"}++;
2933: my $version=$hashref->{"version:$rid"};
2934: my $allkeys='';
2935: foreach my $pair (@pairs) {
2936: my ($key,$value)=split(/=/,$pair);
2937: $allkeys.=$key.':';
2938: $hashref->{"$version:$rid:$key"}=$value;
2939: }
2940: $hashref->{"$version:$rid:timestamp"}=$now;
2941: $allkeys.='timestamp';
2942: $hashref->{"$version:keys:$rid"}=$allkeys;
1.311 albertel 2943: if (&untie_user_hash($hashref)) {
1.231 foxr 2944: &Reply($client, "ok\n", $userinput);
2945: } else {
2946: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
2947: "while attempting store\n", $userinput);
2948: }
2949: } else {
2950: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
2951: "while attempting store\n", $userinput);
2952: }
2953: } else {
2954: &Failure($client, "refused\n", $userinput);
2955: }
2956:
2957: return 1;
2958: }
2959: ®ister_handler("store", \&store_handler, 0, 1, 0);
1.263 albertel 2960:
1.323 albertel 2961: # Modify a set of key=value pairs associated with a versioned name.
2962: #
2963: # Parameters:
2964: # $cmd - Request command keyword.
2965: # $tail - Tail of the request. This is a colon
2966: # separated list containing:
2967: # domain/user - User and authentication domain.
2968: # namespace - Name of the database being modified
2969: # rid - Resource keyword to modify.
2970: # v - Version item to modify
2971: # what - new value associated with rid.
2972: #
2973: # $client - Socket open on the client.
2974: #
2975: #
2976: # Returns:
2977: # 1 (keep on processing).
2978: # Side-Effects:
2979: # Writes to the client
2980: sub putstore_handler {
2981: my ($cmd, $tail, $client) = @_;
2982:
2983: my $userinput = "$cmd:$tail";
2984:
2985: my ($udom,$uname,$namespace,$rid,$v,$what) =split(/:/,$tail);
2986: if ($namespace ne 'roles') {
2987:
2988: chomp($what);
2989: my $hashref = &tie_user_hash($udom, $uname, $namespace,
2990: &GDBM_WRCREAT(), "M",
2991: "$rid:$v:$what");
2992: if ($hashref) {
2993: my $now = time;
2994: my %data = &hash_extract($what);
2995: my @allkeys;
2996: while (my($key,$value) = each(%data)) {
2997: push(@allkeys,$key);
2998: $hashref->{"$v:$rid:$key"} = $value;
2999: }
3000: my $allkeys = join(':',@allkeys);
3001: $hashref->{"$v:keys:$rid"}=$allkeys;
3002:
3003: if (&untie_user_hash($hashref)) {
3004: &Reply($client, "ok\n", $userinput);
3005: } else {
3006: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3007: "while attempting store\n", $userinput);
3008: }
3009: } else {
3010: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
3011: "while attempting store\n", $userinput);
3012: }
3013: } else {
3014: &Failure($client, "refused\n", $userinput);
3015: }
3016:
3017: return 1;
3018: }
3019: ®ister_handler("putstore", \&putstore_handler, 0, 1, 0);
3020:
3021: sub hash_extract {
3022: my ($str)=@_;
3023: my %hash;
3024: foreach my $pair (split(/\&/,$str)) {
3025: my ($key,$value)=split(/=/,$pair);
3026: $hash{$key}=$value;
3027: }
3028: return (%hash);
3029: }
3030: sub hash_to_str {
3031: my ($hash_ref)=@_;
3032: my $str;
3033: foreach my $key (keys(%$hash_ref)) {
3034: $str.=$key.'='.$hash_ref->{$key}.'&';
3035: }
3036: $str=~s/\&$//;
3037: return $str;
3038: }
3039:
1.231 foxr 3040: #
3041: # Dump out all versions of a resource that has key=value pairs associated
3042: # with it for each version. These resources are built up via the store
3043: # command.
3044: #
3045: # Parameters:
3046: # $cmd - Command keyword.
3047: # $tail - Remainder of the request which consists of:
3048: # domain/user - User and auth. domain.
3049: # namespace - name of resource database.
3050: # rid - Resource id.
3051: # $client - socket open on the client.
3052: #
3053: # Returns:
3054: # 1 indicating the caller should not yet exit.
3055: # Side-effects:
3056: # Writes a reply to the client.
3057: # The reply is a string of the following shape:
3058: # version=current&version:keys=k1:k2...&1:k1=v1&1:k2=v2...
3059: # Where the 1 above represents version 1.
3060: # this continues for all pairs of keys in all versions.
3061: #
3062: #
3063: #
3064: #
3065: sub restore_handler {
3066: my ($cmd, $tail, $client) = @_;
3067:
3068: my $userinput = "$cmd:$tail"; # Only used for logging purposes.
1.351 banghart 3069: my ($udom,$uname,$namespace,$rid) = split(/:/,$tail);
1.352 albertel 3070: $namespace=~s/\//\_/g;
1.350 albertel 3071: $namespace = &LONCAPA::clean_username($namespace);
1.349 albertel 3072:
1.231 foxr 3073: chomp($rid);
3074: my $qresult='';
1.309 albertel 3075: my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER());
3076: if ($hashref) {
3077: my $version=$hashref->{"version:$rid"};
1.231 foxr 3078: $qresult.="version=$version&";
3079: my $scope;
3080: for ($scope=1;$scope<=$version;$scope++) {
1.309 albertel 3081: my $vkeys=$hashref->{"$scope:keys:$rid"};
1.231 foxr 3082: my @keys=split(/:/,$vkeys);
3083: my $key;
3084: $qresult.="$scope:keys=$vkeys&";
3085: foreach $key (@keys) {
1.309 albertel 3086: $qresult.="$scope:$key=".$hashref->{"$scope:$rid:$key"}."&";
1.231 foxr 3087: }
3088: }
1.311 albertel 3089: if (&untie_user_hash($hashref)) {
1.231 foxr 3090: $qresult=~s/\&$//;
3091: &Reply( $client, "$qresult\n", $userinput);
3092: } else {
3093: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3094: "while attempting restore\n", $userinput);
3095: }
3096: } else {
3097: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
3098: "while attempting restore\n", $userinput);
3099: }
3100:
3101: return 1;
3102:
3103:
3104: }
3105: ®ister_handler("restore", \&restore_handler, 0,1,0);
1.234 foxr 3106:
3107: #
1.324 raeburn 3108: # Add a chat message to a synchronous discussion board.
1.234 foxr 3109: #
3110: # Parameters:
3111: # $cmd - Request keyword.
3112: # $tail - Tail of the command. A colon separated list
3113: # containing:
3114: # cdom - Domain on which the chat board lives
1.324 raeburn 3115: # cnum - Course containing the chat board.
3116: # newpost - Body of the posting.
3117: # group - Optional group, if chat board is only
3118: # accessible in a group within the course
1.234 foxr 3119: # $client - Socket open on the client.
3120: # Returns:
3121: # 1 - Indicating caller should keep on processing.
3122: #
3123: # Side-effects:
3124: # writes a reply to the client.
3125: #
3126: #
3127: sub send_chat_handler {
3128: my ($cmd, $tail, $client) = @_;
3129:
3130:
3131: my $userinput = "$cmd:$tail";
3132:
1.324 raeburn 3133: my ($cdom,$cnum,$newpost,$group)=split(/\:/,$tail);
3134: &chat_add($cdom,$cnum,$newpost,$group);
1.234 foxr 3135: &Reply($client, "ok\n", $userinput);
3136:
3137: return 1;
3138: }
3139: ®ister_handler("chatsend", \&send_chat_handler, 0, 1, 0);
1.263 albertel 3140:
1.234 foxr 3141: #
1.324 raeburn 3142: # Retrieve the set of chat messages from a discussion board.
1.234 foxr 3143: #
3144: # Parameters:
3145: # $cmd - Command keyword that initiated the request.
3146: # $tail - Remainder of the request after the command
3147: # keyword. In this case a colon separated list of
3148: # chat domain - Which discussion board.
3149: # chat id - Discussion thread(?)
3150: # domain/user - Authentication domain and username
3151: # of the requesting person.
1.324 raeburn 3152: # group - Optional course group containing
3153: # the board.
1.234 foxr 3154: # $client - Socket open on the client program.
3155: # Returns:
3156: # 1 - continue processing
3157: # Side effects:
3158: # Response is written to the client.
3159: #
3160: sub retrieve_chat_handler {
3161: my ($cmd, $tail, $client) = @_;
3162:
3163:
3164: my $userinput = "$cmd:$tail";
3165:
1.324 raeburn 3166: my ($cdom,$cnum,$udom,$uname,$group)=split(/\:/,$tail);
1.234 foxr 3167: my $reply='';
1.324 raeburn 3168: foreach (&get_chat($cdom,$cnum,$udom,$uname,$group)) {
1.234 foxr 3169: $reply.=&escape($_).':';
3170: }
3171: $reply=~s/\:$//;
3172: &Reply($client, $reply."\n", $userinput);
3173:
3174:
3175: return 1;
3176: }
3177: ®ister_handler("chatretr", \&retrieve_chat_handler, 0, 1, 0);
3178:
3179: #
3180: # Initiate a query of an sql database. SQL query repsonses get put in
3181: # a file for later retrieval. This prevents sql query results from
3182: # bottlenecking the system. Note that with loncnew, perhaps this is
3183: # less of an issue since multiple outstanding requests can be concurrently
3184: # serviced.
3185: #
3186: # Parameters:
3187: # $cmd - COmmand keyword that initiated the request.
3188: # $tail - Remainder of the command after the keyword.
3189: # For this function, this consists of a query and
3190: # 3 arguments that are self-documentingly labelled
3191: # in the original arg1, arg2, arg3.
3192: # $client - Socket open on the client.
3193: # Return:
3194: # 1 - Indicating processing should continue.
3195: # Side-effects:
3196: # a reply is written to $client.
3197: #
3198: sub send_query_handler {
3199: my ($cmd, $tail, $client) = @_;
3200:
3201:
3202: my $userinput = "$cmd:$tail";
3203:
3204: my ($query,$arg1,$arg2,$arg3)=split(/\:/,$tail);
3205: $query=~s/\n*$//g;
3206: &Reply($client, "". &sql_reply("$clientname\&$query".
3207: "\&$arg1"."\&$arg2"."\&$arg3")."\n",
3208: $userinput);
3209:
3210: return 1;
3211: }
3212: ®ister_handler("querysend", \&send_query_handler, 0, 1, 0);
3213:
3214: #
3215: # Add a reply to an sql query. SQL queries are done asyncrhonously.
3216: # The query is submitted via a "querysend" transaction.
3217: # There it is passed on to the lonsql daemon, queued and issued to
3218: # mysql.
3219: # This transaction is invoked when the sql transaction is complete
3220: # it stores the query results in flie and indicates query completion.
3221: # presumably local software then fetches this response... I'm guessing
3222: # the sequence is: lonc does a querysend, we ask lonsql to do it.
3223: # lonsql on completion of the query interacts with the lond of our
3224: # client to do a query reply storing two files:
3225: # - id - The results of the query.
3226: # - id.end - Indicating the transaction completed.
3227: # NOTE: id is a unique id assigned to the query and querysend time.
3228: # Parameters:
3229: # $cmd - Command keyword that initiated this request.
3230: # $tail - Remainder of the tail. In this case that's a colon
3231: # separated list containing the query Id and the
3232: # results of the query.
3233: # $client - Socket open on the client.
3234: # Return:
3235: # 1 - Indicating that we should continue processing.
3236: # Side effects:
3237: # ok written to the client.
3238: #
3239: sub reply_query_handler {
3240: my ($cmd, $tail, $client) = @_;
3241:
3242:
3243: my $userinput = "$cmd:$tail";
3244:
1.339 albertel 3245: my ($id,$reply)=split(/:/,$tail);
1.234 foxr 3246: my $store;
3247: my $execdir=$perlvar{'lonDaemons'};
3248: if ($store=IO::File->new(">$execdir/tmp/$id")) {
3249: $reply=~s/\&/\n/g;
3250: print $store $reply;
3251: close $store;
3252: my $store2=IO::File->new(">$execdir/tmp/$id.end");
3253: print $store2 "done\n";
3254: close $store2;
3255: &Reply($client, "ok\n", $userinput);
3256: } else {
3257: &Failure($client, "error: ".($!+0)
3258: ." IO::File->new Failed ".
3259: "while attempting queryreply\n", $userinput);
3260: }
3261:
3262:
3263: return 1;
3264: }
3265: ®ister_handler("queryreply", \&reply_query_handler, 0, 1, 0);
3266:
3267: #
3268: # Process the courseidput request. Not quite sure what this means
3269: # at the system level sense. It appears a gdbm file in the
3270: # /home/httpd/lonUsers/$domain/nohist_courseids is tied and
3271: # a set of entries made in that database.
3272: #
3273: # Parameters:
3274: # $cmd - The command keyword that initiated this request.
3275: # $tail - Tail of the command. In this case consists of a colon
3276: # separated list contaning the domain to apply this to and
3277: # an ampersand separated list of keyword=value pairs.
1.272 raeburn 3278: # Each value is a colon separated list that includes:
3279: # description, institutional code and course owner.
3280: # For backward compatibility with versions included
3281: # in LON-CAPA 1.1.X (and earlier) and 1.2.X, institutional
3282: # code and/or course owner are preserved from the existing
3283: # record when writing a new record in response to 1.1 or
3284: # 1.2 implementations of lonnet::flushcourselogs().
3285: #
1.234 foxr 3286: # $client - Socket open on the client.
3287: # Returns:
3288: # 1 - indicating that processing should continue
3289: #
3290: # Side effects:
3291: # reply is written to the client.
3292: #
3293: sub put_course_id_handler {
3294: my ($cmd, $tail, $client) = @_;
3295:
3296:
3297: my $userinput = "$cmd:$tail";
3298:
1.266 raeburn 3299: my ($udom, $what) = split(/:/, $tail,2);
1.234 foxr 3300: chomp($what);
3301: my $now=time;
3302: my @pairs=split(/\&/,$what);
3303:
3304: my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT());
3305: if ($hashref) {
3306: foreach my $pair (@pairs) {
1.271 raeburn 3307: my ($key,$courseinfo) = split(/=/,$pair,2);
3308: $courseinfo =~ s/=/:/g;
1.384 raeburn 3309: if (defined($hashref->{$key})) {
3310: my $value = &Apache::lonnet::thaw_unescape($hashref->{$key});
3311: if (ref($value) eq 'HASH') {
3312: my @items = ('description','inst_code','owner','type');
3313: my @new_items = split(/:/,$courseinfo,-1);
3314: my %storehash;
3315: for (my $i=0; $i<@new_items; $i++) {
3316: $storehash{$items[$i]} = $new_items[$i];
3317: }
3318: $hashref->{$key} =
3319: &Apache::lonnet::freeze_escape(\%storehash);
3320: my $unesc_key = &unescape($key);
3321: $hashref->{&escape('lasttime:'.$unesc_key)} = $now;
3322: next;
1.383 raeburn 3323: }
1.384 raeburn 3324: }
3325: my @current_items = split(/:/,$hashref->{$key},-1);
3326: shift(@current_items); # remove description
3327: pop(@current_items); # remove last access
3328: my $numcurrent = scalar(@current_items);
3329: if ($numcurrent > 3) {
3330: $numcurrent = 3;
3331: }
3332: my @new_items = split(/:/,$courseinfo,-1);
3333: my $numnew = scalar(@new_items);
3334: if ($numcurrent > 0) {
3335: if ($numnew <= $numcurrent) { # flushcourselogs() from pre 2.2
3336: for (my $j=$numcurrent-$numnew; $j>=0; $j--) {
3337: $courseinfo .= ':'.$current_items[$numcurrent-$j-1];
1.333 raeburn 3338: }
1.272 raeburn 3339: }
3340: }
1.384 raeburn 3341: $hashref->{$key}=$courseinfo.':'.$now;
1.234 foxr 3342: }
1.311 albertel 3343: if (&untie_domain_hash($hashref)) {
1.253 foxr 3344: &Reply( $client, "ok\n", $userinput);
1.234 foxr 3345: } else {
1.253 foxr 3346: &Failure($client, "error: ".($!+0)
1.234 foxr 3347: ." untie(GDBM) Failed ".
3348: "while attempting courseidput\n", $userinput);
3349: }
3350: } else {
1.253 foxr 3351: &Failure($client, "error: ".($!+0)
1.234 foxr 3352: ." tie(GDBM) Failed ".
3353: "while attempting courseidput\n", $userinput);
3354: }
3355:
3356: return 1;
3357: }
3358: ®ister_handler("courseidput", \&put_course_id_handler, 0, 1, 0);
3359:
1.383 raeburn 3360: sub put_course_id_hash_handler {
3361: my ($cmd, $tail, $client) = @_;
3362: my $userinput = "$cmd:$tail";
1.384 raeburn 3363: my ($udom,$mode,$what) = split(/:/, $tail,3);
1.383 raeburn 3364: chomp($what);
3365: my $now=time;
3366: my @pairs=split(/\&/,$what);
1.384 raeburn 3367: my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT());
1.383 raeburn 3368: if ($hashref) {
3369: foreach my $pair (@pairs) {
3370: my ($key,$value)=split(/=/,$pair);
1.384 raeburn 3371: my $unesc_key = &unescape($key);
3372: if ($mode ne 'timeonly') {
3373: if (!defined($hashref->{&escape('lasttime:'.$unesc_key)})) {
3374: my $curritems = &Apache::lonnet::thaw_unescape($key);
3375: if (ref($curritems) ne 'HASH') {
3376: my @current_items = split(/:/,$hashref->{$key},-1);
3377: my $lasttime = pop(@current_items);
3378: $hashref->{&escape('lasttime:'.$unesc_key)} = $lasttime;
3379: } else {
3380: $hashref->{&escape('lasttime:'.$unesc_key)} = '';
3381: }
3382: }
3383: $hashref->{$key} = $value;
3384: }
3385: if ($mode ne 'notime') {
3386: $hashref->{&escape('lasttime:'.$unesc_key)} = $now;
3387: }
1.383 raeburn 3388: }
3389: if (&untie_domain_hash($hashref)) {
3390: &Reply($client, "ok\n", $userinput);
3391: } else {
3392: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3393: "while attempting courseidputhash\n", $userinput);
3394: }
3395: } else {
3396: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
3397: "while attempting courseidputhash\n", $userinput);
3398: }
3399: return 1;
3400: }
3401: ®ister_handler("courseidputhash", \&put_course_id_hash_handler, 0, 1, 0);
3402:
1.234 foxr 3403: # Retrieves the value of a course id resource keyword pattern
3404: # defined since a starting date. Both the starting date and the
3405: # keyword pattern are optional. If the starting date is not supplied it
3406: # is treated as the beginning of time. If the pattern is not found,
3407: # it is treatred as "." matching everything.
3408: #
3409: # Parameters:
3410: # $cmd - Command keyword that resulted in us being dispatched.
3411: # $tail - The remainder of the command that, in this case, consists
3412: # of a colon separated list of:
3413: # domain - The domain in which the course database is
3414: # defined.
3415: # since - Optional parameter describing the minimum
3416: # time of definition(?) of the resources that
3417: # will match the dump.
3418: # description - regular expression that is used to filter
3419: # the dump. Only keywords matching this regexp
3420: # will be used.
1.272 raeburn 3421: # institutional code - optional supplied code to filter
3422: # the dump. Only courses with an institutional code
3423: # that match the supplied code will be returned.
1.336 raeburn 3424: # owner - optional supplied username and domain of owner to
3425: # filter the dump. Only courses for which the course
3426: # owner matches the supplied username and/or domain
3427: # will be returned. Pre-2.2.0 legacy entries from
3428: # nohist_courseiddump will only contain usernames.
1.384 raeburn 3429: # type - optional parameter for selection
3430: # regexp_ok - if true, allow the supplied institutional code
3431: # filter to behave as a regular expression.
3432: # rtn_as_hash - whether to return the information available for
3433: # each matched item as a frozen hash of all
3434: # key, value pairs in the item's hash, or as a
3435: # colon-separated list of (in order) description,
3436: # institutional code, and course owner.
3437: #
1.234 foxr 3438: # $client - The socket open on the client.
3439: # Returns:
3440: # 1 - Continue processing.
3441: # Side Effects:
3442: # a reply is written to $client.
3443: sub dump_course_id_handler {
3444: my ($cmd, $tail, $client) = @_;
3445: my $userinput = "$cmd:$tail";
3446:
1.333 raeburn 3447: my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter,
1.384 raeburn 3448: $typefilter,$regexp_ok,$rtn_as_hash) =split(/:/,$tail);
1.234 foxr 3449: if (defined($description)) {
3450: $description=&unescape($description);
3451: } else {
3452: $description='.';
3453: }
1.266 raeburn 3454: if (defined($instcodefilter)) {
3455: $instcodefilter=&unescape($instcodefilter);
3456: } else {
3457: $instcodefilter='.';
3458: }
1.336 raeburn 3459: my ($ownerunamefilter,$ownerdomfilter);
1.266 raeburn 3460: if (defined($ownerfilter)) {
3461: $ownerfilter=&unescape($ownerfilter);
1.336 raeburn 3462: if ($ownerfilter ne '.' && defined($ownerfilter)) {
3463: if ($ownerfilter =~ /^([^:]*):([^:]*)$/) {
3464: $ownerunamefilter = $1;
3465: $ownerdomfilter = $2;
3466: } else {
3467: $ownerunamefilter = $ownerfilter;
3468: $ownerdomfilter = '';
3469: }
3470: }
1.266 raeburn 3471: } else {
3472: $ownerfilter='.';
3473: }
1.336 raeburn 3474:
1.282 raeburn 3475: if (defined($coursefilter)) {
3476: $coursefilter=&unescape($coursefilter);
3477: } else {
3478: $coursefilter='.';
3479: }
1.333 raeburn 3480: if (defined($typefilter)) {
3481: $typefilter=&unescape($typefilter);
3482: } else {
3483: $typefilter='.';
3484: }
1.344 raeburn 3485: if (defined($regexp_ok)) {
3486: $regexp_ok=&unescape($regexp_ok);
3487: }
1.384 raeburn 3488: my $unpack = 1;
3489: if ($description eq '.' && $instcodefilter eq '.' && $coursefilter eq '.' &&
3490: $typefilter eq '.') {
3491: $unpack = 0;
3492: }
3493: if (!defined($since)) { $since=0; }
1.234 foxr 3494: my $qresult='';
3495: my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT());
3496: if ($hashref) {
1.384 raeburn 3497: while (my ($key,$value) = each(%$hashref)) {
3498: my ($unesc_key,$lasttime_key,$lasttime,$is_hash,%val,%unesc_val);
3499: $unesc_key = &unescape($key);
3500: if ($unesc_key =~ /^lasttime:/) {
3501: next;
3502: } else {
3503: $lasttime_key = &escape('lasttime:'.$unesc_key);
3504: }
3505: if ($hashref->{$lasttime_key} ne '') {
3506: $lasttime = $hashref->{$lasttime_key};
3507: next if ($lasttime<$since);
3508: }
3509: my $items = &Apache::lonnet::thaw_unescape($value);
3510: if (ref($items) eq 'HASH') {
3511: $is_hash = 1;
3512: if ($unpack || !$rtn_as_hash) {
3513: $unesc_val{'descr'} = $items->{'description'};
3514: $unesc_val{'inst_code'} = $items->{'inst_code'};
3515: $unesc_val{'owner'} = $items->{'owner'};
3516: $unesc_val{'type'} = $items->{'type'};
3517: }
1.383 raeburn 3518: } else {
1.384 raeburn 3519: $is_hash = 0;
3520: my @courseitems = split(/:/,&unescape($value));
1.383 raeburn 3521: $lasttime = pop(@courseitems);
1.384 raeburn 3522: next if ($lasttime<$since);
3523: ($val{'descr'},$val{'inst_code'},$val{'owner'},$val{'type'}) = @courseitems;
1.383 raeburn 3524: }
1.266 raeburn 3525: my $match = 1;
1.384 raeburn 3526: if ($description ne '.') {
3527: if (!$is_hash) {
3528: $unesc_val{'descr'} = &unescape($val{'descr'});
3529: }
3530: if (eval{$unesc_val{'descr'} !~ /\Q$description\E/i}) {
1.266 raeburn 3531: $match = 0;
1.384 raeburn 3532: }
1.266 raeburn 3533: }
1.384 raeburn 3534: if ($instcodefilter ne '.') {
3535: if (!$is_hash) {
3536: $unesc_val{'inst_code'} = &unescape($val{'inst_code'});
3537: }
1.344 raeburn 3538: if ($regexp_ok) {
1.384 raeburn 3539: if (eval{$unesc_val{'inst_code'} !~ /$instcodefilter/}) {
1.344 raeburn 3540: $match = 0;
3541: }
3542: } else {
1.384 raeburn 3543: if (eval{$unesc_val{'inst_code'} !~ /\Q$instcodefilter\E/i}) {
1.344 raeburn 3544: $match = 0;
3545: }
1.266 raeburn 3546: }
1.234 foxr 3547: }
1.384 raeburn 3548: if ($ownerfilter ne '.') {
3549: if (!$is_hash) {
3550: $unesc_val{'owner'} = &unescape($val{'owner'});
3551: }
1.336 raeburn 3552: if (($ownerunamefilter ne '') && ($ownerdomfilter ne '')) {
1.384 raeburn 3553: if ($unesc_val{'owner'} =~ /:/) {
3554: if (eval{$unesc_val{'owner'} !~
3555: /\Q$ownerunamefilter\E:\Q$ownerdomfilter\E$/i}) {
1.336 raeburn 3556: $match = 0;
3557: }
3558: } else {
1.384 raeburn 3559: if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E/i}) {
1.336 raeburn 3560: $match = 0;
3561: }
3562: }
3563: } elsif ($ownerunamefilter ne '') {
1.384 raeburn 3564: if ($unesc_val{'owner'} =~ /:/) {
3565: if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E:[^:]+$/i}) {
1.336 raeburn 3566: $match = 0;
3567: }
3568: } else {
1.384 raeburn 3569: if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E/i}) {
1.336 raeburn 3570: $match = 0;
3571: }
3572: }
3573: } elsif ($ownerdomfilter ne '') {
1.384 raeburn 3574: if ($unesc_val{'owner'} =~ /:/) {
3575: if (eval{$unesc_val{'owner'} !~ /^[^:]+:\Q$ownerdomfilter\E/}) {
1.336 raeburn 3576: $match = 0;
3577: }
3578: } else {
3579: if ($ownerdomfilter ne $udom) {
3580: $match = 0;
3581: }
3582: }
1.266 raeburn 3583: }
3584: }
1.384 raeburn 3585: if ($coursefilter ne '.') {
3586: if (eval{$unesc_key !~ /^$udom(_)\Q$coursefilter\E$/}) {
1.282 raeburn 3587: $match = 0;
3588: }
3589: }
1.384 raeburn 3590: if ($typefilter ne '.') {
3591: if (!$is_hash) {
3592: $unesc_val{'type'} = &unescape($val{'type'});
3593: }
3594: if ($unesc_val{'type'} eq '') {
1.333 raeburn 3595: if ($typefilter ne 'Course') {
3596: $match = 0;
3597: }
1.383 raeburn 3598: } else {
1.384 raeburn 3599: if (eval{$unesc_val{'type'} !~ /^\Q$typefilter\E$/}) {
1.333 raeburn 3600: $match = 0;
3601: }
3602: }
3603: }
1.266 raeburn 3604: if ($match == 1) {
1.384 raeburn 3605: if ($rtn_as_hash) {
3606: if ($is_hash) {
3607: $qresult.=$key.'='.$value.'&';
3608: } else {
3609: my %rtnhash = ( 'description' => &escape($val{'descr'}),
3610: 'inst_code' => &escape($val{'inst_code'}),
3611: 'owner' => &escape($val{'owner'}),
3612: 'type' => &escape($val{'type'}),
3613: );
3614: my $items = &Apache::lonnet::freeze_escape(\%rtnhash);
3615: $qresult.=$key.'='.$items.'&';
3616: }
1.383 raeburn 3617: } else {
1.384 raeburn 3618: if ($is_hash) {
3619: $qresult .= $key.'='.&escape($unesc_val{'descr'}).':'.
3620: &escape($unesc_val{'inst_code'}).':'.
3621: &escape($unesc_val{'owner'}).'&';
3622: } else {
3623: $qresult .= $key.'='.$val{'descr'}.':'.$val{'inst_code'}.
3624: ':'.$val{'owner'}.'&';
3625: }
1.383 raeburn 3626: }
1.266 raeburn 3627: }
1.234 foxr 3628: }
1.311 albertel 3629: if (&untie_domain_hash($hashref)) {
1.234 foxr 3630: chop($qresult);
3631: &Reply($client, "$qresult\n", $userinput);
3632: } else {
3633: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3634: "while attempting courseiddump\n", $userinput);
3635: }
3636: } else {
3637: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
3638: "while attempting courseiddump\n", $userinput);
3639: }
3640: return 1;
3641: }
3642: ®ister_handler("courseiddump", \&dump_course_id_handler, 0, 1, 0);
1.238 foxr 3643:
3644: #
1.348 raeburn 3645: # Puts an unencrypted entry in a namespace db file at the domain level
3646: #
3647: # Parameters:
3648: # $cmd - The command that got us here.
3649: # $tail - Tail of the command (remaining parameters).
3650: # $client - File descriptor connected to client.
3651: # Returns
3652: # 0 - Requested to exit, caller should shut down.
3653: # 1 - Continue processing.
3654: # Side effects:
3655: # reply is written to $client.
3656: #
3657: sub put_domain_handler {
3658: my ($cmd,$tail,$client) = @_;
3659:
3660: my $userinput = "$cmd:$tail";
3661:
3662: my ($udom,$namespace,$what) =split(/:/,$tail,3);
3663: chomp($what);
3664: my @pairs=split(/\&/,$what);
3665: my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_WRCREAT(),
3666: "P", $what);
3667: if ($hashref) {
3668: foreach my $pair (@pairs) {
3669: my ($key,$value)=split(/=/,$pair);
3670: $hashref->{$key}=$value;
3671: }
3672: if (&untie_domain_hash($hashref)) {
3673: &Reply($client, "ok\n", $userinput);
3674: } else {
3675: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3676: "while attempting putdom\n", $userinput);
3677: }
3678: } else {
3679: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
3680: "while attempting putdom\n", $userinput);
3681: }
3682:
3683: return 1;
3684: }
3685: ®ister_handler("putdom", \&put_domain_handler, 0, 1, 0);
3686:
3687: # Unencrypted get from the namespace database file at the domain level.
3688: # This function retrieves a keyed item from a specific named database in the
3689: # domain directory.
3690: #
3691: # Parameters:
3692: # $cmd - Command request keyword (get).
3693: # $tail - Tail of the command. This is a colon separated list
3694: # consisting of the domain and the 'namespace'
3695: # which selects the gdbm file to do the lookup in,
3696: # & separated list of keys to lookup. Note that
3697: # the values are returned as an & separated list too.
3698: # $client - File descriptor open on the client.
3699: # Returns:
3700: # 1 - Continue processing.
3701: # 0 - Exit.
3702: # Side effects:
3703: # reply is written to $client.
3704: #
3705:
3706: sub get_domain_handler {
3707: my ($cmd, $tail, $client) = @_;
3708:
3709: my $userinput = "$client:$tail";
3710:
3711: my ($udom,$namespace,$what)=split(/:/,$tail,3);
3712: chomp($what);
3713: my @queries=split(/\&/,$what);
3714: my $qresult='';
3715: my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_READER());
3716: if ($hashref) {
3717: for (my $i=0;$i<=$#queries;$i++) {
3718: $qresult.="$hashref->{$queries[$i]}&";
3719: }
3720: if (&untie_domain_hash($hashref)) {
3721: $qresult=~s/\&$//;
3722: &Reply($client, "$qresult\n", $userinput);
3723: } else {
3724: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
3725: "while attempting getdom\n",$userinput);
3726: }
3727: } else {
3728: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
3729: "while attempting getdom\n",$userinput);
3730: }
3731:
3732: return 1;
3733: }
1.360 raeburn 3734: ®ister_handler("getdom", \&get_domain_handler, 0, 1, 0);
1.348 raeburn 3735:
3736:
3737: #
1.238 foxr 3738: # Puts an id to a domains id database.
3739: #
3740: # Parameters:
3741: # $cmd - The command that triggered us.
3742: # $tail - Remainder of the request other than the command. This is a
3743: # colon separated list containing:
3744: # $domain - The domain for which we are writing the id.
3745: # $pairs - The id info to write... this is and & separated list
3746: # of keyword=value.
3747: # $client - Socket open on the client.
3748: # Returns:
3749: # 1 - Continue processing.
3750: # Side effects:
3751: # reply is written to $client.
3752: #
3753: sub put_id_handler {
3754: my ($cmd,$tail,$client) = @_;
3755:
3756:
3757: my $userinput = "$cmd:$tail";
3758:
3759: my ($udom,$what)=split(/:/,$tail);
3760: chomp($what);
3761: my @pairs=split(/\&/,$what);
3762: my $hashref = &tie_domain_hash($udom, "ids", &GDBM_WRCREAT(),
3763: "P", $what);
3764: if ($hashref) {
3765: foreach my $pair (@pairs) {
3766: my ($key,$value)=split(/=/,$pair);
3767: $hashref->{$key}=$value;
3768: }
1.311 albertel 3769: if (&untie_domain_hash($hashref)) {
1.238 foxr 3770: &Reply($client, "ok\n", $userinput);
3771: } else {
3772: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3773: "while attempting idput\n", $userinput);
3774: }
3775: } else {
3776: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
3777: "while attempting idput\n", $userinput);
3778: }
3779:
3780: return 1;
3781: }
1.263 albertel 3782: ®ister_handler("idput", \&put_id_handler, 0, 1, 0);
1.238 foxr 3783:
3784: #
3785: # Retrieves a set of id values from the id database.
3786: # Returns an & separated list of results, one for each requested id to the
3787: # client.
3788: #
3789: # Parameters:
3790: # $cmd - Command keyword that caused us to be dispatched.
3791: # $tail - Tail of the command. Consists of a colon separated:
3792: # domain - the domain whose id table we dump
3793: # ids Consists of an & separated list of
3794: # id keywords whose values will be fetched.
3795: # nonexisting keywords will have an empty value.
3796: # $client - Socket open on the client.
3797: #
3798: # Returns:
3799: # 1 - indicating processing should continue.
3800: # Side effects:
3801: # An & separated list of results is written to $client.
3802: #
3803: sub get_id_handler {
3804: my ($cmd, $tail, $client) = @_;
3805:
3806:
3807: my $userinput = "$client:$tail";
3808:
3809: my ($udom,$what)=split(/:/,$tail);
3810: chomp($what);
3811: my @queries=split(/\&/,$what);
3812: my $qresult='';
3813: my $hashref = &tie_domain_hash($udom, "ids", &GDBM_READER());
3814: if ($hashref) {
3815: for (my $i=0;$i<=$#queries;$i++) {
3816: $qresult.="$hashref->{$queries[$i]}&";
3817: }
1.311 albertel 3818: if (&untie_domain_hash($hashref)) {
1.238 foxr 3819: $qresult=~s/\&$//;
3820: &Reply($client, "$qresult\n", $userinput);
3821: } else {
3822: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ".
3823: "while attempting idget\n",$userinput);
3824: }
3825: } else {
3826: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
3827: "while attempting idget\n",$userinput);
3828: }
3829:
3830: return 1;
3831: }
1.263 albertel 3832: ®ister_handler("idget", \&get_id_handler, 0, 1, 0);
1.238 foxr 3833:
3834: #
1.299 raeburn 3835: # Puts broadcast e-mail sent by Domain Coordinator in nohist_dcmail database
3836: #
3837: # Parameters
3838: # $cmd - Command keyword that caused us to be dispatched.
3839: # $tail - Tail of the command. Consists of a colon separated:
3840: # domain - the domain whose dcmail we are recording
3841: # email Consists of key=value pair
3842: # where key is unique msgid
3843: # and value is message (in XML)
3844: # $client - Socket open on the client.
3845: #
3846: # Returns:
3847: # 1 - indicating processing should continue.
3848: # Side effects
3849: # reply is written to $client.
3850: #
3851: sub put_dcmail_handler {
3852: my ($cmd,$tail,$client) = @_;
3853: my $userinput = "$cmd:$tail";
3854:
3855: my ($udom,$what)=split(/:/,$tail);
3856: chomp($what);
3857: my $hashref = &tie_domain_hash($udom, "nohist_dcmail", &GDBM_WRCREAT());
3858: if ($hashref) {
3859: my ($key,$value)=split(/=/,$what);
3860: $hashref->{$key}=$value;
3861: }
1.311 albertel 3862: if (&untie_domain_hash($hashref)) {
1.299 raeburn 3863: &Reply($client, "ok\n", $userinput);
3864: } else {
3865: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3866: "while attempting dcmailput\n", $userinput);
3867: }
3868: return 1;
3869: }
3870: ®ister_handler("dcmailput", \&put_dcmail_handler, 0, 1, 0);
3871:
3872: #
3873: # Retrieves broadcast e-mail from nohist_dcmail database
3874: # Returns to client an & separated list of key=value pairs,
3875: # where key is msgid and value is message information.
3876: #
3877: # Parameters
3878: # $cmd - Command keyword that caused us to be dispatched.
3879: # $tail - Tail of the command. Consists of a colon separated:
3880: # domain - the domain whose dcmail table we dump
3881: # startfilter - beginning of time window
3882: # endfilter - end of time window
3883: # sendersfilter - & separated list of username:domain
3884: # for senders to search for.
3885: # $client - Socket open on the client.
3886: #
3887: # Returns:
3888: # 1 - indicating processing should continue.
3889: # Side effects
3890: # reply (& separated list of msgid=messageinfo pairs) is
3891: # written to $client.
3892: #
3893: sub dump_dcmail_handler {
3894: my ($cmd, $tail, $client) = @_;
3895:
3896: my $userinput = "$cmd:$tail";
3897: my ($udom,$startfilter,$endfilter,$sendersfilter) = split(/:/,$tail);
3898: chomp($sendersfilter);
3899: my @senders = ();
3900: if (defined($startfilter)) {
3901: $startfilter=&unescape($startfilter);
3902: } else {
3903: $startfilter='.';
3904: }
3905: if (defined($endfilter)) {
3906: $endfilter=&unescape($endfilter);
3907: } else {
3908: $endfilter='.';
3909: }
3910: if (defined($sendersfilter)) {
3911: $sendersfilter=&unescape($sendersfilter);
1.300 albertel 3912: @senders = map { &unescape($_) } split(/\&/,$sendersfilter);
1.299 raeburn 3913: }
3914:
3915: my $qresult='';
3916: my $hashref = &tie_domain_hash($udom, "nohist_dcmail", &GDBM_WRCREAT());
3917: if ($hashref) {
3918: while (my ($key,$value) = each(%$hashref)) {
3919: my $match = 1;
1.303 albertel 3920: my ($timestamp,$subj,$uname,$udom) =
3921: split(/:/,&unescape(&unescape($key)),5); # yes, twice really
1.299 raeburn 3922: $subj = &unescape($subj);
3923: unless ($startfilter eq '.' || !defined($startfilter)) {
3924: if ($timestamp < $startfilter) {
3925: $match = 0;
3926: }
3927: }
3928: unless ($endfilter eq '.' || !defined($endfilter)) {
3929: if ($timestamp > $endfilter) {
3930: $match = 0;
3931: }
3932: }
3933: unless (@senders < 1) {
3934: unless (grep/^$uname:$udom$/,@senders) {
3935: $match = 0;
3936: }
3937: }
3938: if ($match == 1) {
3939: $qresult.=$key.'='.$value.'&';
3940: }
3941: }
1.311 albertel 3942: if (&untie_domain_hash($hashref)) {
1.299 raeburn 3943: chop($qresult);
3944: &Reply($client, "$qresult\n", $userinput);
3945: } else {
3946: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3947: "while attempting dcmaildump\n", $userinput);
3948: }
3949: } else {
3950: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
3951: "while attempting dcmaildump\n", $userinput);
3952: }
3953: return 1;
3954: }
3955:
3956: ®ister_handler("dcmaildump", \&dump_dcmail_handler, 0, 1, 0);
3957:
3958: #
3959: # Puts domain roles in nohist_domainroles database
3960: #
3961: # Parameters
3962: # $cmd - Command keyword that caused us to be dispatched.
3963: # $tail - Tail of the command. Consists of a colon separated:
3964: # domain - the domain whose roles we are recording
3965: # role - Consists of key=value pair
3966: # where key is unique role
3967: # and value is start/end date information
3968: # $client - Socket open on the client.
3969: #
3970: # Returns:
3971: # 1 - indicating processing should continue.
3972: # Side effects
3973: # reply is written to $client.
3974: #
3975:
3976: sub put_domainroles_handler {
3977: my ($cmd,$tail,$client) = @_;
3978:
3979: my $userinput = "$cmd:$tail";
3980: my ($udom,$what)=split(/:/,$tail);
3981: chomp($what);
3982: my @pairs=split(/\&/,$what);
3983: my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT());
3984: if ($hashref) {
3985: foreach my $pair (@pairs) {
3986: my ($key,$value)=split(/=/,$pair);
3987: $hashref->{$key}=$value;
3988: }
1.311 albertel 3989: if (&untie_domain_hash($hashref)) {
1.299 raeburn 3990: &Reply($client, "ok\n", $userinput);
3991: } else {
3992: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
3993: "while attempting domroleput\n", $userinput);
3994: }
3995: } else {
3996: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ".
3997: "while attempting domroleput\n", $userinput);
3998: }
3999:
4000: return 1;
4001: }
4002:
4003: ®ister_handler("domroleput", \&put_domainroles_handler, 0, 1, 0);
4004:
4005: #
4006: # Retrieves domain roles from nohist_domainroles database
4007: # Returns to client an & separated list of key=value pairs,
4008: # where key is role and value is start and end date information.
4009: #
4010: # Parameters
4011: # $cmd - Command keyword that caused us to be dispatched.
4012: # $tail - Tail of the command. Consists of a colon separated:
4013: # domain - the domain whose domain roles table we dump
4014: # $client - Socket open on the client.
4015: #
4016: # Returns:
4017: # 1 - indicating processing should continue.
4018: # Side effects
4019: # reply (& separated list of role=start/end info pairs) is
4020: # written to $client.
4021: #
4022: sub dump_domainroles_handler {
4023: my ($cmd, $tail, $client) = @_;
4024:
4025: my $userinput = "$cmd:$tail";
4026: my ($udom,$startfilter,$endfilter,$rolesfilter) = split(/:/,$tail);
4027: chomp($rolesfilter);
4028: my @roles = ();
4029: if (defined($startfilter)) {
4030: $startfilter=&unescape($startfilter);
4031: } else {
4032: $startfilter='.';
4033: }
4034: if (defined($endfilter)) {
4035: $endfilter=&unescape($endfilter);
4036: } else {
4037: $endfilter='.';
4038: }
4039: if (defined($rolesfilter)) {
4040: $rolesfilter=&unescape($rolesfilter);
1.300 albertel 4041: @roles = split(/\&/,$rolesfilter);
1.299 raeburn 4042: }
4043:
4044: my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT());
4045: if ($hashref) {
4046: my $qresult = '';
4047: while (my ($key,$value) = each(%$hashref)) {
4048: my $match = 1;
4049: my ($start,$end) = split(/:/,&unescape($value));
4050: my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,&unescape($key));
4051: unless ($startfilter eq '.' || !defined($startfilter)) {
4052: if ($start >= $startfilter) {
4053: $match = 0;
4054: }
4055: }
4056: unless ($endfilter eq '.' || !defined($endfilter)) {
4057: if ($end <= $endfilter) {
4058: $match = 0;
4059: }
4060: }
4061: unless (@roles < 1) {
4062: unless (grep/^$trole$/,@roles) {
4063: $match = 0;
4064: }
4065: }
4066: if ($match == 1) {
4067: $qresult.=$key.'='.$value.'&';
4068: }
4069: }
1.311 albertel 4070: if (&untie_domain_hash($hashref)) {
1.299 raeburn 4071: chop($qresult);
4072: &Reply($client, "$qresult\n", $userinput);
4073: } else {
4074: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ".
4075: "while attempting domrolesdump\n", $userinput);
4076: }
4077: } else {
4078: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ".
4079: "while attempting domrolesdump\n", $userinput);
4080: }
4081: return 1;
4082: }
4083:
4084: ®ister_handler("domrolesdump", \&dump_domainroles_handler, 0, 1, 0);
4085:
4086:
1.238 foxr 4087: # Process the tmpput command I'm not sure what this does.. Seems to
4088: # create a file in the lonDaemons/tmp directory of the form $id.tmp
4089: # where Id is the client's ip concatenated with a sequence number.
4090: # The file will contain some value that is passed in. Is this e.g.
4091: # a login token?
4092: #
4093: # Parameters:
4094: # $cmd - The command that got us dispatched.
4095: # $tail - The remainder of the request following $cmd:
4096: # In this case this will be the contents of the file.
4097: # $client - Socket connected to the client.
4098: # Returns:
4099: # 1 indicating processing can continue.
4100: # Side effects:
4101: # A file is created in the local filesystem.
4102: # A reply is sent to the client.
4103: sub tmp_put_handler {
4104: my ($cmd, $what, $client) = @_;
4105:
4106: my $userinput = "$cmd:$what"; # Reconstruct for logging.
4107:
1.347 raeburn 4108: my ($record,$context) = split(/:/,$what);
4109: if ($context ne '') {
4110: chomp($context);
4111: $context = &unescape($context);
4112: }
4113: my ($id,$store);
1.238 foxr 4114: $tmpsnum++;
1.347 raeburn 4115: if ($context eq 'resetpw') {
4116: $id = &md5_hex(&md5_hex(time.{}.rand().$$));
4117: } else {
4118: $id = $$.'_'.$clientip.'_'.$tmpsnum;
4119: }
1.238 foxr 4120: $id=~s/\W/\_/g;
1.347 raeburn 4121: $record=~s/\n//g;
1.238 foxr 4122: my $execdir=$perlvar{'lonDaemons'};
4123: if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) {
1.347 raeburn 4124: print $store $record;
1.238 foxr 4125: close $store;
4126: &Reply($client, "$id\n", $userinput);
4127: } else {
4128: &Failure( $client, "error: ".($!+0)."IO::File->new Failed ".
4129: "while attempting tmpput\n", $userinput);
4130: }
4131: return 1;
4132:
4133: }
4134: ®ister_handler("tmpput", \&tmp_put_handler, 0, 1, 0);
1.263 albertel 4135:
1.238 foxr 4136: # Processes the tmpget command. This command returns the contents
4137: # of a temporary resource file(?) created via tmpput.
4138: #
4139: # Paramters:
4140: # $cmd - Command that got us dispatched.
4141: # $id - Tail of the command, contain the id of the resource
4142: # we want to fetch.
4143: # $client - socket open on the client.
4144: # Return:
4145: # 1 - Inidcating processing can continue.
4146: # Side effects:
4147: # A reply is sent to the client.
4148: #
4149: sub tmp_get_handler {
4150: my ($cmd, $id, $client) = @_;
4151:
4152: my $userinput = "$cmd:$id";
4153:
4154:
4155: $id=~s/\W/\_/g;
4156: my $store;
4157: my $execdir=$perlvar{'lonDaemons'};
4158: if ($store=IO::File->new("$execdir/tmp/$id.tmp")) {
4159: my $reply=<$store>;
4160: &Reply( $client, "$reply\n", $userinput);
4161: close $store;
4162: } else {
4163: &Failure( $client, "error: ".($!+0)."IO::File->new Failed ".
4164: "while attempting tmpget\n", $userinput);
4165: }
4166:
4167: return 1;
4168: }
4169: ®ister_handler("tmpget", \&tmp_get_handler, 0, 1, 0);
1.263 albertel 4170:
1.238 foxr 4171: #
4172: # Process the tmpdel command. This command deletes a temp resource
4173: # created by the tmpput command.
4174: #
4175: # Parameters:
4176: # $cmd - Command that got us here.
4177: # $id - Id of the temporary resource created.
4178: # $client - socket open on the client process.
4179: #
4180: # Returns:
4181: # 1 - Indicating processing should continue.
4182: # Side Effects:
4183: # A file is deleted
4184: # A reply is sent to the client.
4185: sub tmp_del_handler {
4186: my ($cmd, $id, $client) = @_;
4187:
4188: my $userinput= "$cmd:$id";
4189:
4190: chomp($id);
4191: $id=~s/\W/\_/g;
4192: my $execdir=$perlvar{'lonDaemons'};
4193: if (unlink("$execdir/tmp/$id.tmp")) {
4194: &Reply($client, "ok\n", $userinput);
4195: } else {
4196: &Failure( $client, "error: ".($!+0)."Unlink tmp Failed ".
4197: "while attempting tmpdel\n", $userinput);
4198: }
4199:
4200: return 1;
4201:
4202: }
4203: ®ister_handler("tmpdel", \&tmp_del_handler, 0, 1, 0);
1.263 albertel 4204:
1.238 foxr 4205: #
1.246 foxr 4206: # Processes the setannounce command. This command
4207: # creates a file named announce.txt in the top directory of
4208: # the documentn root and sets its contents. The announce.txt file is
4209: # printed in its entirety at the LonCAPA login page. Note:
4210: # once the announcement.txt fileis created it cannot be deleted.
4211: # However, setting the contents of the file to empty removes the
4212: # announcement from the login page of loncapa so who cares.
4213: #
4214: # Parameters:
4215: # $cmd - The command that got us dispatched.
4216: # $announcement - The text of the announcement.
4217: # $client - Socket open on the client process.
4218: # Retunrns:
4219: # 1 - Indicating request processing should continue
4220: # Side Effects:
4221: # The file {DocRoot}/announcement.txt is created.
4222: # A reply is sent to $client.
4223: #
4224: sub set_announce_handler {
4225: my ($cmd, $announcement, $client) = @_;
4226:
4227: my $userinput = "$cmd:$announcement";
4228:
4229: chomp($announcement);
4230: $announcement=&unescape($announcement);
4231: if (my $store=IO::File->new('>'.$perlvar{'lonDocRoot'}.
4232: '/announcement.txt')) {
4233: print $store $announcement;
4234: close $store;
4235: &Reply($client, "ok\n", $userinput);
4236: } else {
4237: &Failure($client, "error: ".($!+0)."\n", $userinput);
4238: }
4239:
4240: return 1;
4241: }
4242: ®ister_handler("setannounce", \&set_announce_handler, 0, 1, 0);
1.263 albertel 4243:
1.246 foxr 4244: #
4245: # Return the version of the daemon. This can be used to determine
4246: # the compatibility of cross version installations or, alternatively to
4247: # simply know who's out of date and who isn't. Note that the version
4248: # is returned concatenated with the tail.
4249: # Parameters:
4250: # $cmd - the request that dispatched to us.
4251: # $tail - Tail of the request (client's version?).
4252: # $client - Socket open on the client.
4253: #Returns:
4254: # 1 - continue processing requests.
4255: # Side Effects:
4256: # Replies with version to $client.
4257: sub get_version_handler {
4258: my ($cmd, $tail, $client) = @_;
4259:
4260: my $userinput = $cmd.$tail;
4261:
4262: &Reply($client, &version($userinput)."\n", $userinput);
4263:
4264:
4265: return 1;
4266: }
4267: ®ister_handler("version", \&get_version_handler, 0, 1, 0);
1.263 albertel 4268:
1.246 foxr 4269: # Set the current host and domain. This is used to support
4270: # multihomed systems. Each IP of the system, or even separate daemons
4271: # on the same IP can be treated as handling a separate lonCAPA virtual
4272: # machine. This command selects the virtual lonCAPA. The client always
4273: # knows the right one since it is lonc and it is selecting the domain/system
4274: # from the hosts.tab file.
4275: # Parameters:
4276: # $cmd - Command that dispatched us.
4277: # $tail - Tail of the command (domain/host requested).
4278: # $socket - Socket open on the client.
4279: #
4280: # Returns:
4281: # 1 - Indicates the program should continue to process requests.
4282: # Side-effects:
4283: # The default domain/system context is modified for this daemon.
4284: # a reply is sent to the client.
4285: #
4286: sub set_virtual_host_handler {
4287: my ($cmd, $tail, $socket) = @_;
4288:
4289: my $userinput ="$cmd:$tail";
4290:
4291: &Reply($client, &sethost($userinput)."\n", $userinput);
4292:
4293:
4294: return 1;
4295: }
1.247 albertel 4296: ®ister_handler("sethost", \&set_virtual_host_handler, 0, 1, 0);
1.246 foxr 4297:
4298: # Process a request to exit:
4299: # - "bye" is sent to the client.
4300: # - The client socket is shutdown and closed.
4301: # - We indicate to the caller that we should exit.
4302: # Formal Parameters:
4303: # $cmd - The command that got us here.
4304: # $tail - Tail of the command (empty).
4305: # $client - Socket open on the tail.
4306: # Returns:
4307: # 0 - Indicating the program should exit!!
4308: #
4309: sub exit_handler {
4310: my ($cmd, $tail, $client) = @_;
4311:
4312: my $userinput = "$cmd:$tail";
4313:
4314: &logthis("Client $clientip ($clientname) hanging up: $userinput");
4315: &Reply($client, "bye\n", $userinput);
4316: $client->shutdown(2); # shutdown the socket forcibly.
4317: $client->close();
4318:
4319: return 0;
4320: }
1.248 foxr 4321: ®ister_handler("exit", \&exit_handler, 0,1,1);
4322: ®ister_handler("init", \&exit_handler, 0,1,1);
4323: ®ister_handler("quit", \&exit_handler, 0,1,1);
4324:
4325: # Determine if auto-enrollment is enabled.
4326: # Note that the original had what I believe to be a defect.
4327: # The original returned 0 if the requestor was not a registerd client.
4328: # It should return "refused".
4329: # Formal Parameters:
4330: # $cmd - The command that invoked us.
4331: # $tail - The tail of the command (Extra command parameters.
4332: # $client - The socket open on the client that issued the request.
4333: # Returns:
4334: # 1 - Indicating processing should continue.
4335: #
4336: sub enrollment_enabled_handler {
4337: my ($cmd, $tail, $client) = @_;
4338: my $userinput = $cmd.":".$tail; # For logging purposes.
4339:
4340:
1.337 albertel 4341: my ($cdom) = split(/:/, $tail, 2); # Domain we're asking about.
4342:
1.248 foxr 4343: my $outcome = &localenroll::run($cdom);
4344: &Reply($client, "$outcome\n", $userinput);
4345:
4346: return 1;
4347: }
4348: ®ister_handler("autorun", \&enrollment_enabled_handler, 0, 1, 0);
4349:
4350: # Get the official sections for which auto-enrollment is possible.
4351: # Since the admin people won't know about 'unofficial sections'
4352: # we cannot auto-enroll on them.
4353: # Formal Parameters:
4354: # $cmd - The command request that got us dispatched here.
4355: # $tail - The remainder of the request. In our case this
4356: # will be split into:
4357: # $coursecode - The course name from the admin point of view.
4358: # $cdom - The course's domain(?).
4359: # $client - Socket open on the client.
4360: # Returns:
4361: # 1 - Indiciting processing should continue.
4362: #
4363: sub get_sections_handler {
4364: my ($cmd, $tail, $client) = @_;
4365: my $userinput = "$cmd:$tail";
4366:
4367: my ($coursecode, $cdom) = split(/:/, $tail);
4368: my @secs = &localenroll::get_sections($coursecode,$cdom);
4369: my $seclist = &escape(join(':',@secs));
4370:
4371: &Reply($client, "$seclist\n", $userinput);
4372:
4373:
4374: return 1;
4375: }
4376: ®ister_handler("autogetsections", \&get_sections_handler, 0, 1, 0);
4377:
4378: # Validate the owner of a new course section.
4379: #
4380: # Formal Parameters:
4381: # $cmd - Command that got us dispatched.
4382: # $tail - the remainder of the command. For us this consists of a
4383: # colon separated string containing:
4384: # $inst - Course Id from the institutions point of view.
4385: # $owner - Proposed owner of the course.
4386: # $cdom - Domain of the course (from the institutions
4387: # point of view?)..
4388: # $client - Socket open on the client.
4389: #
4390: # Returns:
4391: # 1 - Processing should continue.
4392: #
4393: sub validate_course_owner_handler {
4394: my ($cmd, $tail, $client) = @_;
4395: my $userinput = "$cmd:$tail";
4396: my ($inst_course_id, $owner, $cdom) = split(/:/, $tail);
4397:
1.336 raeburn 4398: $owner = &unescape($owner);
1.248 foxr 4399: my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom);
4400: &Reply($client, "$outcome\n", $userinput);
4401:
4402:
4403:
4404: return 1;
4405: }
4406: ®ister_handler("autonewcourse", \&validate_course_owner_handler, 0, 1, 0);
1.263 albertel 4407:
1.248 foxr 4408: #
4409: # Validate a course section in the official schedule of classes
4410: # from the institutions point of view (part of autoenrollment).
4411: #
4412: # Formal Parameters:
4413: # $cmd - The command request that got us dispatched.
4414: # $tail - The tail of the command. In this case,
4415: # this is a colon separated set of words that will be split
4416: # into:
4417: # $inst_course_id - The course/section id from the
4418: # institutions point of view.
4419: # $cdom - The domain from the institutions
4420: # point of view.
4421: # $client - Socket open on the client.
4422: # Returns:
4423: # 1 - Indicating processing should continue.
4424: #
4425: sub validate_course_section_handler {
4426: my ($cmd, $tail, $client) = @_;
4427: my $userinput = "$cmd:$tail";
4428: my ($inst_course_id, $cdom) = split(/:/, $tail);
4429:
4430: my $outcome=&localenroll::validate_courseID($inst_course_id,$cdom);
4431: &Reply($client, "$outcome\n", $userinput);
4432:
4433:
4434: return 1;
4435: }
4436: ®ister_handler("autovalidatecourse", \&validate_course_section_handler, 0, 1, 0);
4437:
4438: #
1.340 raeburn 4439: # Validate course owner's access to enrollment data for specific class section.
4440: #
4441: #
4442: # Formal Parameters:
4443: # $cmd - The command request that got us dispatched.
4444: # $tail - The tail of the command. In this case this is a colon separated
4445: # set of words that will be split into:
4446: # $inst_class - Institutional code for the specific class section
4447: # $courseowner - The escaped username:domain of the course owner
4448: # $cdom - The domain of the course from the institution's
4449: # point of view.
4450: # $client - The socket open on the client.
4451: # Returns:
4452: # 1 - continue processing.
4453: #
4454:
4455: sub validate_class_access_handler {
4456: my ($cmd, $tail, $client) = @_;
4457: my $userinput = "$cmd:$tail";
1.383 raeburn 4458: my ($inst_class,$ownerlist,$cdom) = split(/:/, $tail);
4459: my @owners = split(/,/,&unescape($ownerlist));
1.341 albertel 4460: my $outcome;
4461: eval {
4462: local($SIG{__DIE__})='DEFAULT';
1.383 raeburn 4463: $outcome=&localenroll::check_section($inst_class,\@owners,$cdom);
1.341 albertel 4464: };
1.340 raeburn 4465: &Reply($client,"$outcome\n", $userinput);
4466:
4467: return 1;
4468: }
4469: ®ister_handler("autovalidateclass_sec", \&validate_class_access_handler, 0, 1, 0);
4470:
4471: #
4472: # Create a password for a new LON-CAPA user added by auto-enrollment.
4473: # Only used for case where authentication method for new user is localauth
1.248 foxr 4474: #
4475: # Formal Parameters:
4476: # $cmd - The command request that got us dispatched.
4477: # $tail - The tail of the command. In this case this is a colon separated
4478: # set of words that will be split into:
1.340 raeburn 4479: # $authparam - An authentication parameter (localauth parameter).
1.248 foxr 4480: # $cdom - The domain of the course from the institution's
4481: # point of view.
4482: # $client - The socket open on the client.
4483: # Returns:
4484: # 1 - continue processing.
4485: #
4486: sub create_auto_enroll_password_handler {
4487: my ($cmd, $tail, $client) = @_;
4488: my $userinput = "$cmd:$tail";
4489:
4490: my ($authparam, $cdom) = split(/:/, $userinput);
4491:
4492: my ($create_passwd,$authchk);
4493: ($authparam,
4494: $create_passwd,
4495: $authchk) = &localenroll::create_password($authparam,$cdom);
4496:
4497: &Reply($client, &escape($authparam.':'.$create_passwd.':'.$authchk)."\n",
4498: $userinput);
4499:
4500:
4501: return 1;
4502: }
4503: ®ister_handler("autocreatepassword", \&create_auto_enroll_password_handler,
4504: 0, 1, 0);
4505:
4506: # Retrieve and remove temporary files created by/during autoenrollment.
4507: #
4508: # Formal Parameters:
4509: # $cmd - The command that got us dispatched.
4510: # $tail - The tail of the command. In our case this is a colon
4511: # separated list that will be split into:
4512: # $filename - The name of the file to remove.
4513: # The filename is given as a path relative to
4514: # the LonCAPA temp file directory.
4515: # $client - Socket open on the client.
4516: #
4517: # Returns:
4518: # 1 - Continue processing.
4519: sub retrieve_auto_file_handler {
4520: my ($cmd, $tail, $client) = @_;
4521: my $userinput = "cmd:$tail";
4522:
4523: my ($filename) = split(/:/, $tail);
4524:
4525: my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename;
4526: if ( (-e $source) && ($filename ne '') ) {
4527: my $reply = '';
4528: if (open(my $fh,$source)) {
4529: while (<$fh>) {
4530: chomp($_);
4531: $_ =~ s/^\s+//g;
4532: $_ =~ s/\s+$//g;
4533: $reply .= $_;
4534: }
4535: close($fh);
4536: &Reply($client, &escape($reply)."\n", $userinput);
4537:
4538: # Does this have to be uncommented??!? (RF).
4539: #
4540: # unlink($source);
4541: } else {
4542: &Failure($client, "error\n", $userinput);
4543: }
4544: } else {
4545: &Failure($client, "error\n", $userinput);
4546: }
4547:
4548:
4549: return 1;
4550: }
4551: ®ister_handler("autoretrieve", \&retrieve_auto_file_handler, 0,1,0);
4552:
4553: #
4554: # Read and retrieve institutional code format (for support form).
4555: # Formal Parameters:
4556: # $cmd - Command that dispatched us.
4557: # $tail - Tail of the command. In this case it conatins
4558: # the course domain and the coursename.
4559: # $client - Socket open on the client.
4560: # Returns:
4561: # 1 - Continue processing.
4562: #
4563: sub get_institutional_code_format_handler {
4564: my ($cmd, $tail, $client) = @_;
4565: my $userinput = "$cmd:$tail";
4566:
4567: my $reply;
4568: my($cdom,$course) = split(/:/,$tail);
4569: my @pairs = split/\&/,$course;
4570: my %instcodes = ();
4571: my %codes = ();
4572: my @codetitles = ();
4573: my %cat_titles = ();
4574: my %cat_order = ();
4575: foreach (@pairs) {
4576: my ($key,$value) = split/=/,$_;
4577: $instcodes{&unescape($key)} = &unescape($value);
4578: }
4579: my $formatreply = &localenroll::instcode_format($cdom,
4580: \%instcodes,
4581: \%codes,
4582: \@codetitles,
4583: \%cat_titles,
4584: \%cat_order);
4585: if ($formatreply eq 'ok') {
1.365 albertel 4586: my $codes_str = &Apache::lonnet::hash2str(%codes);
4587: my $codetitles_str = &Apache::lonnet::array2str(@codetitles);
4588: my $cat_titles_str = &Apache::lonnet::hash2str(%cat_titles);
4589: my $cat_order_str = &Apache::lonnet::hash2str(%cat_order);
1.248 foxr 4590: &Reply($client,
4591: $codes_str.':'.$codetitles_str.':'.$cat_titles_str.':'
4592: .$cat_order_str."\n",
4593: $userinput);
4594: } else {
4595: # this else branch added by RF since if not ok, lonc will
4596: # hang waiting on reply until timeout.
4597: #
4598: &Reply($client, "format_error\n", $userinput);
4599: }
4600:
4601: return 1;
4602: }
1.265 albertel 4603: ®ister_handler("autoinstcodeformat",
4604: \&get_institutional_code_format_handler,0,1,0);
1.246 foxr 4605:
1.345 raeburn 4606: sub get_institutional_defaults_handler {
4607: my ($cmd, $tail, $client) = @_;
4608: my $userinput = "$cmd:$tail";
4609:
4610: my $dom = $tail;
4611: my %defaults_hash;
4612: my @code_order;
4613: my $outcome;
4614: eval {
4615: local($SIG{__DIE__})='DEFAULT';
4616: $outcome = &localenroll::instcode_defaults($dom,\%defaults_hash,
4617: \@code_order);
4618: };
4619: if (!$@) {
4620: if ($outcome eq 'ok') {
4621: my $result='';
4622: while (my ($key,$value) = each(%defaults_hash)) {
4623: $result.=&escape($key).'='.&escape($value).'&';
4624: }
4625: $result .= 'code_order='.&escape(join('&',@code_order));
4626: &Reply($client,$result."\n",$userinput);
4627: } else {
4628: &Reply($client,"error\n", $userinput);
4629: }
4630: } else {
4631: &Failure($client,"unknown_cmd\n",$userinput);
4632: }
4633: }
4634: ®ister_handler("autoinstcodedefaults",
4635: \&get_institutional_defaults_handler,0,1,0);
4636:
1.381 raeburn 4637: sub get_institutional_user_rules {
4638: my ($cmd, $tail, $client) = @_;
4639: my $userinput = "$cmd:$tail";
4640: my $dom = &unescape($tail);
4641: my (%rules_hash,@rules_order);
4642: my $outcome;
4643: eval {
4644: local($SIG{__DIE__})='DEFAULT';
4645: $outcome = &localenroll::username_rules($dom,\%rules_hash,\@rules_order);
4646: };
4647: if (!$@) {
4648: if ($outcome eq 'ok') {
4649: my $result;
4650: foreach my $key (keys(%rules_hash)) {
4651: $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rules_hash{$key}).'&';
4652: }
4653: $result =~ s/\&$//;
4654: $result .= ':';
4655: if (@rules_order > 0) {
4656: foreach my $item (@rules_order) {
4657: $result .= &escape($item).'&';
4658: }
4659: }
4660: $result =~ s/\&$//;
4661: &Reply($client,$result."\n",$userinput);
4662: } else {
4663: &Reply($client,"error\n", $userinput);
4664: }
4665: } else {
4666: &Failure($client,"unknown_cmd\n",$userinput);
4667: }
4668: }
4669: ®ister_handler("instuserrules",\&get_institutional_user_rules,0,1,0);
4670:
4671:
4672: sub institutional_username_check {
4673: my ($cmd, $tail, $client) = @_;
4674: my $userinput = "$cmd:$tail";
4675: my %rulecheck;
4676: my $outcome;
4677: my ($udom,$uname,@rules) = split(/:/,$tail);
4678: $udom = &unescape($udom);
4679: $uname = &unescape($uname);
4680: @rules = map {&unescape($_);} (@rules);
4681: eval {
4682: local($SIG{__DIE__})='DEFAULT';
4683: $outcome = &localenroll::username_check($udom,$uname,\@rules,\%rulecheck);
4684: };
4685: if (!$@) {
4686: if ($outcome eq 'ok') {
4687: my $result='';
4688: foreach my $key (keys(%rulecheck)) {
4689: $result.=&escape($key).'='.&Apache::lonnet::freeze_escape($rulecheck{$key}).'&';
4690: }
4691: &Reply($client,$result."\n",$userinput);
4692: } else {
4693: &Reply($client,"error\n", $userinput);
4694: }
4695: } else {
4696: &Failure($client,"unknown_cmd\n",$userinput);
4697: }
4698: }
4699: ®ister_handler("instrulecheck",\&institutional_username_check,0,1,0);
4700:
1.345 raeburn 4701:
1.317 raeburn 4702: # Get domain specific conditions for import of student photographs to a course
4703: #
4704: # Retrieves information from photo_permission subroutine in localenroll.
4705: # Returns outcome (ok) if no processing errors, and whether course owner is
4706: # required to accept conditions of use (yes/no).
4707: #
4708: #
4709: sub photo_permission_handler {
4710: my ($cmd, $tail, $client) = @_;
4711: my $userinput = "$cmd:$tail";
4712: my $cdom = $tail;
4713: my ($perm_reqd,$conditions);
1.320 albertel 4714: my $outcome;
4715: eval {
4716: local($SIG{__DIE__})='DEFAULT';
4717: $outcome = &localenroll::photo_permission($cdom,\$perm_reqd,
4718: \$conditions);
4719: };
4720: if (!$@) {
4721: &Reply($client, &escape($outcome.':'.$perm_reqd.':'. $conditions)."\n",
4722: $userinput);
4723: } else {
4724: &Failure($client,"unknown_cmd\n",$userinput);
4725: }
4726: return 1;
1.317 raeburn 4727: }
4728: ®ister_handler("autophotopermission",\&photo_permission_handler,0,1,0);
4729:
4730: #
4731: # Checks if student photo is available for a user in the domain, in the user's
4732: # directory (in /userfiles/internal/studentphoto.jpg).
4733: # Uses localstudentphoto:fetch() to ensure there is an up to date copy of
4734: # the student's photo.
4735:
4736: sub photo_check_handler {
4737: my ($cmd, $tail, $client) = @_;
4738: my $userinput = "$cmd:$tail";
4739: my ($udom,$uname,$pid) = split(/:/,$tail);
4740: $udom = &unescape($udom);
4741: $uname = &unescape($uname);
4742: $pid = &unescape($pid);
4743: my $path=&propath($udom,$uname).'/userfiles/internal/';
4744: if (!-e $path) {
4745: &mkpath($path);
4746: }
4747: my $response;
4748: my $result = &localstudentphoto::fetch($udom,$uname,$pid,\$response);
4749: $result .= ':'.$response;
4750: &Reply($client, &escape($result)."\n",$userinput);
1.320 albertel 4751: return 1;
1.317 raeburn 4752: }
4753: ®ister_handler("autophotocheck",\&photo_check_handler,0,1,0);
4754:
4755: #
4756: # Retrieve information from localenroll about whether to provide a button
4757: # for users who have enbled import of student photos to initiate an
4758: # update of photo files for registered students. Also include
4759: # comment to display alongside button.
4760:
4761: sub photo_choice_handler {
4762: my ($cmd, $tail, $client) = @_;
4763: my $userinput = "$cmd:$tail";
4764: my $cdom = &unescape($tail);
1.320 albertel 4765: my ($update,$comment);
4766: eval {
4767: local($SIG{__DIE__})='DEFAULT';
4768: ($update,$comment) = &localenroll::manager_photo_update($cdom);
4769: };
4770: if (!$@) {
4771: &Reply($client,&escape($update).':'.&escape($comment)."\n",$userinput);
4772: } else {
4773: &Failure($client,"unknown_cmd\n",$userinput);
4774: }
4775: return 1;
1.317 raeburn 4776: }
4777: ®ister_handler("autophotochoice",\&photo_choice_handler,0,1,0);
4778:
1.265 albertel 4779: #
4780: # Gets a student's photo to exist (in the correct image type) in the user's
4781: # directory.
4782: # Formal Parameters:
4783: # $cmd - The command request that got us dispatched.
4784: # $tail - A colon separated set of words that will be split into:
4785: # $domain - student's domain
4786: # $uname - student username
4787: # $type - image type desired
4788: # $client - The socket open on the client.
4789: # Returns:
4790: # 1 - continue processing.
1.317 raeburn 4791:
1.265 albertel 4792: sub student_photo_handler {
4793: my ($cmd, $tail, $client) = @_;
1.317 raeburn 4794: my ($domain,$uname,$ext,$type) = split(/:/, $tail);
1.265 albertel 4795:
1.317 raeburn 4796: my $path=&propath($domain,$uname). '/userfiles/internal/';
4797: my $filename = 'studentphoto.'.$ext;
4798: if ($type eq 'thumbnail') {
4799: $filename = 'studentphoto_tn.'.$ext;
4800: }
4801: if (-e $path.$filename) {
1.265 albertel 4802: &Reply($client,"ok\n","$cmd:$tail");
4803: return 1;
4804: }
4805: &mkpath($path);
1.317 raeburn 4806: my $file;
4807: if ($type eq 'thumbnail') {
1.320 albertel 4808: eval {
4809: local($SIG{__DIE__})='DEFAULT';
4810: $file=&localstudentphoto::fetch_thumbnail($domain,$uname);
4811: };
1.317 raeburn 4812: } else {
4813: $file=&localstudentphoto::fetch($domain,$uname);
4814: }
1.265 albertel 4815: if (!$file) {
4816: &Failure($client,"unavailable\n","$cmd:$tail");
4817: return 1;
4818: }
1.317 raeburn 4819: if (!-e $path.$filename) { &convert_photo($file,$path.$filename); }
4820: if (-e $path.$filename) {
1.265 albertel 4821: &Reply($client,"ok\n","$cmd:$tail");
4822: return 1;
4823: }
4824: &Failure($client,"unable_to_convert\n","$cmd:$tail");
4825: return 1;
4826: }
4827: ®ister_handler("studentphoto", \&student_photo_handler, 0, 1, 0);
1.246 foxr 4828:
1.361 raeburn 4829: sub inst_usertypes_handler {
4830: my ($cmd, $domain, $client) = @_;
4831: my $res;
4832: my $userinput = $cmd.":".$domain; # For logging purposes.
1.370 albertel 4833: my (%typeshash,@order,$result);
4834: eval {
4835: local($SIG{__DIE__})='DEFAULT';
4836: $result=&localenroll::inst_usertypes($domain,\%typeshash,\@order);
4837: };
4838: if ($result eq 'ok') {
1.361 raeburn 4839: if (keys(%typeshash) > 0) {
4840: foreach my $key (keys(%typeshash)) {
4841: $res.=&escape($key).'='.&escape($typeshash{$key}).'&';
4842: }
4843: }
4844: $res=~s/\&$//;
4845: $res .= ':';
4846: if (@order > 0) {
4847: foreach my $item (@order) {
4848: $res .= &escape($item).'&';
4849: }
4850: }
4851: $res=~s/\&$//;
4852: }
4853: &Reply($client, "$res\n", $userinput);
4854: return 1;
4855: }
4856: ®ister_handler("inst_usertypes", \&inst_usertypes_handler, 0, 1, 0);
4857:
1.264 albertel 4858: # mkpath makes all directories for a file, expects an absolute path with a
4859: # file or a trailing / if just a dir is passed
4860: # returns 1 on success 0 on failure
4861: sub mkpath {
4862: my ($file)=@_;
4863: my @parts=split(/\//,$file,-1);
4864: my $now=$parts[0].'/'.$parts[1].'/'.$parts[2];
4865: for (my $i=3;$i<= ($#parts-1);$i++) {
1.265 albertel 4866: $now.='/'.$parts[$i];
1.264 albertel 4867: if (!-e $now) {
4868: if (!mkdir($now,0770)) { return 0; }
4869: }
4870: }
4871: return 1;
4872: }
4873:
1.207 foxr 4874: #---------------------------------------------------------------
4875: #
4876: # Getting, decoding and dispatching requests:
4877: #
4878: #
4879: # Get a Request:
4880: # Gets a Request message from the client. The transaction
4881: # is defined as a 'line' of text. We remove the new line
4882: # from the text line.
1.226 foxr 4883: #
1.211 albertel 4884: sub get_request {
1.207 foxr 4885: my $input = <$client>;
4886: chomp($input);
1.226 foxr 4887:
1.234 foxr 4888: &Debug("get_request: Request = $input\n");
1.207 foxr 4889:
4890: &status('Processing '.$clientname.':'.$input);
4891:
4892: return $input;
4893: }
1.212 foxr 4894: #---------------------------------------------------------------
4895: #
4896: # Process a request. This sub should shrink as each action
4897: # gets farmed out into a separat sub that is registered
4898: # with the dispatch hash.
4899: #
4900: # Parameters:
4901: # user_input - The request received from the client (lonc).
4902: # Returns:
4903: # true to keep processing, false if caller should exit.
4904: #
4905: sub process_request {
4906: my ($userinput) = @_; # Easier for now to break style than to
4907: # fix all the userinput -> user_input.
4908: my $wasenc = 0; # True if request was encrypted.
4909: # ------------------------------------------------------------ See if encrypted
1.322 albertel 4910: # for command
4911: # sethost:<server>
4912: # <command>:<args>
4913: # we just send it to the processor
4914: # for
4915: # sethost:<server>:<command>:<args>
4916: # we do the implict set host and then do the command
4917: if ($userinput =~ /^sethost:/) {
4918: (my $cmd,my $newid,$userinput) = split(':',$userinput,3);
4919: if (defined($userinput)) {
4920: &sethost("$cmd:$newid");
4921: } else {
4922: $userinput = "$cmd:$newid";
4923: }
4924: }
4925:
1.212 foxr 4926: if ($userinput =~ /^enc/) {
4927: $userinput = decipher($userinput);
4928: $wasenc=1;
4929: if(!$userinput) { # Cipher not defined.
1.251 foxr 4930: &Failure($client, "error: Encrypted data without negotated key\n");
1.212 foxr 4931: return 0;
4932: }
4933: }
4934: Debug("process_request: $userinput\n");
4935:
1.213 foxr 4936: #
4937: # The 'correct way' to add a command to lond is now to
4938: # write a sub to execute it and Add it to the command dispatch
4939: # hash via a call to register_handler.. The comments to that
4940: # sub should give you enough to go on to show how to do this
4941: # along with the examples that are building up as this code
4942: # is getting refactored. Until all branches of the
4943: # if/elseif monster below have been factored out into
4944: # separate procesor subs, if the dispatch hash is missing
4945: # the command keyword, we will fall through to the remainder
4946: # of the if/else chain below in order to keep this thing in
4947: # working order throughout the transmogrification.
4948:
4949: my ($command, $tail) = split(/:/, $userinput, 2);
4950: chomp($command);
4951: chomp($tail);
4952: $tail =~ s/(\r)//; # This helps people debugging with e.g. telnet.
1.214 foxr 4953: $command =~ s/(\r)//; # And this too for parameterless commands.
4954: if(!$tail) {
4955: $tail =""; # defined but blank.
4956: }
1.213 foxr 4957:
4958: &Debug("Command received: $command, encoded = $wasenc");
4959:
4960: if(defined $Dispatcher{$command}) {
4961:
4962: my $dispatch_info = $Dispatcher{$command};
4963: my $handler = $$dispatch_info[0];
4964: my $need_encode = $$dispatch_info[1];
4965: my $client_types = $$dispatch_info[2];
4966: Debug("Matched dispatch hash: mustencode: $need_encode "
4967: ."ClientType $client_types");
4968:
4969: # Validate the request:
4970:
4971: my $ok = 1;
4972: my $requesterprivs = 0;
4973: if(&isClient()) {
4974: $requesterprivs |= $CLIENT_OK;
4975: }
4976: if(&isManager()) {
4977: $requesterprivs |= $MANAGER_OK;
4978: }
4979: if($need_encode && (!$wasenc)) {
4980: Debug("Must encode but wasn't: $need_encode $wasenc");
4981: $ok = 0;
4982: }
4983: if(($client_types & $requesterprivs) == 0) {
4984: Debug("Client not privileged to do this operation");
4985: $ok = 0;
4986: }
4987:
4988: if($ok) {
4989: Debug("Dispatching to handler $command $tail");
4990: my $keep_going = &$handler($command, $tail, $client);
4991: return $keep_going;
4992: } else {
4993: Debug("Refusing to dispatch because client did not match requirements");
4994: Failure($client, "refused\n", $userinput);
4995: return 1;
4996: }
4997:
4998: }
4999:
1.262 foxr 5000: print $client "unknown_cmd\n";
1.212 foxr 5001: # -------------------------------------------------------------------- complete
5002: Debug("process_request - returning 1");
5003: return 1;
5004: }
1.207 foxr 5005: #
5006: # Decipher encoded traffic
5007: # Parameters:
5008: # input - Encoded data.
5009: # Returns:
5010: # Decoded data or undef if encryption key was not yet negotiated.
5011: # Implicit input:
5012: # cipher - This global holds the negotiated encryption key.
5013: #
1.211 albertel 5014: sub decipher {
1.207 foxr 5015: my ($input) = @_;
5016: my $output = '';
1.212 foxr 5017:
5018:
1.207 foxr 5019: if($cipher) {
5020: my($enc, $enclength, $encinput) = split(/:/, $input);
5021: for(my $encidx = 0; $encidx < length($encinput); $encidx += 16) {
5022: $output .=
5023: $cipher->decrypt(pack("H16", substr($encinput, $encidx, 16)));
5024: }
5025: return substr($output, 0, $enclength);
5026: } else {
5027: return undef;
5028: }
5029: }
5030:
5031: #
5032: # Register a command processor. This function is invoked to register a sub
5033: # to process a request. Once registered, the ProcessRequest sub can automatically
5034: # dispatch requests to an appropriate sub, and do the top level validity checking
5035: # as well:
5036: # - Is the keyword recognized.
5037: # - Is the proper client type attempting the request.
5038: # - Is the request encrypted if it has to be.
5039: # Parameters:
5040: # $request_name - Name of the request being registered.
5041: # This is the command request that will match
5042: # against the hash keywords to lookup the information
5043: # associated with the dispatch information.
5044: # $procedure - Reference to a sub to call to process the request.
5045: # All subs get called as follows:
5046: # Procedure($cmd, $tail, $replyfd, $key)
5047: # $cmd - the actual keyword that invoked us.
5048: # $tail - the tail of the request that invoked us.
5049: # $replyfd- File descriptor connected to the client
5050: # $must_encode - True if the request must be encoded to be good.
5051: # $client_ok - True if it's ok for a client to request this.
5052: # $manager_ok - True if it's ok for a manager to request this.
5053: # Side effects:
5054: # - On success, the Dispatcher hash has an entry added for the key $RequestName
5055: # - On failure, the program will die as it's a bad internal bug to try to
5056: # register a duplicate command handler.
5057: #
1.211 albertel 5058: sub register_handler {
1.212 foxr 5059: my ($request_name,$procedure,$must_encode, $client_ok,$manager_ok) = @_;
1.207 foxr 5060:
5061: # Don't allow duplication#
5062:
5063: if (defined $Dispatcher{$request_name}) {
5064: die "Attempting to define a duplicate request handler for $request_name\n";
5065: }
5066: # Build the client type mask:
5067:
5068: my $client_type_mask = 0;
5069: if($client_ok) {
5070: $client_type_mask |= $CLIENT_OK;
5071: }
5072: if($manager_ok) {
5073: $client_type_mask |= $MANAGER_OK;
5074: }
5075:
5076: # Enter the hash:
5077:
5078: my @entry = ($procedure, $must_encode, $client_type_mask);
5079:
5080: $Dispatcher{$request_name} = \@entry;
5081:
5082: }
5083:
5084:
5085: #------------------------------------------------------------------
5086:
5087:
5088:
5089:
1.141 foxr 5090: #
1.96 foxr 5091: # Convert an error return code from lcpasswd to a string value.
5092: #
5093: sub lcpasswdstrerror {
5094: my $ErrorCode = shift;
1.97 foxr 5095: if(($ErrorCode < 0) || ($ErrorCode > $lastpwderror)) {
1.96 foxr 5096: return "lcpasswd Unrecognized error return value ".$ErrorCode;
5097: } else {
1.98 foxr 5098: return $passwderrors[$ErrorCode];
1.96 foxr 5099: }
5100: }
5101:
1.97 foxr 5102: #
5103: # Convert an error return code from lcuseradd to a string value:
5104: #
5105: sub lcuseraddstrerror {
5106: my $ErrorCode = shift;
5107: if(($ErrorCode < 0) || ($ErrorCode > $lastadderror)) {
5108: return "lcuseradd - Unrecognized error code: ".$ErrorCode;
5109: } else {
1.98 foxr 5110: return $adderrors[$ErrorCode];
1.97 foxr 5111: }
5112: }
5113:
1.23 harris41 5114: # grabs exception and records it to log before exiting
5115: sub catchexception {
1.27 albertel 5116: my ($error)=@_;
1.25 www 5117: $SIG{'QUIT'}='DEFAULT';
5118: $SIG{__DIE__}='DEFAULT';
1.165 albertel 5119: &status("Catching exception");
1.190 albertel 5120: &logthis("<font color='red'>CRITICAL: "
1.373 albertel 5121: ."ABNORMAL EXIT. Child $$ for server ".$perlvar{'lonHostID'}." died through "
1.27 albertel 5122: ."a crash with this error msg->[$error]</font>");
1.57 www 5123: &logthis('Famous last words: '.$status.' - '.$lastlog);
1.27 albertel 5124: if ($client) { print $client "error: $error\n"; }
1.59 www 5125: $server->close();
1.27 albertel 5126: die($error);
1.23 harris41 5127: }
1.63 www 5128: sub timeout {
1.165 albertel 5129: &status("Handling Timeout");
1.190 albertel 5130: &logthis("<font color='red'>CRITICAL: TIME OUT ".$$."</font>");
1.63 www 5131: &catchexception('Timeout');
5132: }
1.22 harris41 5133: # -------------------------------- Set signal handlers to record abnormal exits
5134:
1.226 foxr 5135:
1.22 harris41 5136: $SIG{'QUIT'}=\&catchexception;
5137: $SIG{__DIE__}=\&catchexception;
5138:
1.81 matthew 5139: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf
1.95 harris41 5140: &status("Read loncapa.conf and loncapa_apache.conf");
5141: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf');
1.141 foxr 5142: %perlvar=%{$perlvarref};
1.80 harris41 5143: undef $perlvarref;
1.19 www 5144:
1.35 harris41 5145: # ----------------------------- Make sure this process is running from user=www
5146: my $wwwid=getpwnam('www');
5147: if ($wwwid!=$<) {
1.134 albertel 5148: my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
5149: my $subj="LON: $currenthostid User ID mismatch";
1.37 harris41 5150: system("echo 'User ID mismatch. lond must be run as user www.' |\
1.35 harris41 5151: mailto $emailto -s '$subj' > /dev/null");
5152: exit 1;
5153: }
5154:
1.19 www 5155: # --------------------------------------------- Check if other instance running
5156:
5157: my $pidfile="$perlvar{'lonDaemons'}/logs/lond.pid";
5158:
5159: if (-e $pidfile) {
5160: my $lfh=IO::File->new("$pidfile");
5161: my $pide=<$lfh>;
5162: chomp($pide);
1.29 harris41 5163: if (kill 0 => $pide) { die "already running"; }
1.19 www 5164: }
1.1 albertel 5165:
5166: # ------------------------------------------------------------- Read hosts file
5167:
5168:
5169:
5170: # establish SERVER socket, bind and listen.
5171: $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'},
5172: Type => SOCK_STREAM,
5173: Proto => 'tcp',
5174: Reuse => 1,
5175: Listen => 10 )
1.29 harris41 5176: or die "making socket: $@\n";
1.1 albertel 5177:
5178: # --------------------------------------------------------- Do global variables
5179:
5180: # global variables
5181:
1.134 albertel 5182: my %children = (); # keys are current child process IDs
1.1 albertel 5183:
1.385 ! albertel 5184: sub flip_max_children_enforcing {
! 5185: $max_children_enforcing = !$max_children_enforcing;
! 5186: &logthis("Flipped child maximum enforcement to (".
! 5187: $max_children_enforcing.")");
! 5188: }
! 5189:
1.1 albertel 5190: sub REAPER { # takes care of dead children
5191: $SIG{CHLD} = \&REAPER;
1.165 albertel 5192: &status("Handling child death");
1.178 foxr 5193: my $pid;
5194: do {
5195: $pid = waitpid(-1,&WNOHANG());
5196: if (defined($children{$pid})) {
5197: &logthis("Child $pid died");
5198: delete($children{$pid});
1.183 albertel 5199: } elsif ($pid > 0) {
1.178 foxr 5200: &logthis("Unknown Child $pid died");
5201: }
5202: } while ( $pid > 0 );
5203: foreach my $child (keys(%children)) {
5204: $pid = waitpid($child,&WNOHANG());
5205: if ($pid > 0) {
5206: &logthis("Child $child - $pid looks like we missed it's death");
5207: delete($children{$pid});
5208: }
1.176 albertel 5209: }
1.165 albertel 5210: &status("Finished Handling child death");
1.1 albertel 5211: }
5212:
5213: sub HUNTSMAN { # signal handler for SIGINT
1.165 albertel 5214: &status("Killing children (INT)");
1.1 albertel 5215: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
5216: kill 'INT' => keys %children;
1.59 www 5217: &logthis("Free socket: ".shutdown($server,2)); # free up socket
1.1 albertel 5218: my $execdir=$perlvar{'lonDaemons'};
5219: unlink("$execdir/logs/lond.pid");
1.190 albertel 5220: &logthis("<font color='red'>CRITICAL: Shutting down</font>");
1.165 albertel 5221: &status("Done killing children");
1.1 albertel 5222: exit; # clean up with dignity
5223: }
5224:
5225: sub HUPSMAN { # signal handler for SIGHUP
5226: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children
1.165 albertel 5227: &status("Killing children for restart (HUP)");
1.1 albertel 5228: kill 'INT' => keys %children;
1.59 www 5229: &logthis("Free socket: ".shutdown($server,2)); # free up socket
1.190 albertel 5230: &logthis("<font color='red'>CRITICAL: Restarting</font>");
1.134 albertel 5231: my $execdir=$perlvar{'lonDaemons'};
1.30 harris41 5232: unlink("$execdir/logs/lond.pid");
1.165 albertel 5233: &status("Restarting self (HUP)");
1.1 albertel 5234: exec("$execdir/lond"); # here we go again
5235: }
5236:
1.144 foxr 5237: #
1.148 foxr 5238: # Reload the Apache daemon's state.
1.150 foxr 5239: # This is done by invoking /home/httpd/perl/apachereload
5240: # a setuid perl script that can be root for us to do this job.
1.148 foxr 5241: #
5242: sub ReloadApache {
1.150 foxr 5243: my $execdir = $perlvar{'lonDaemons'};
5244: my $script = $execdir."/apachereload";
5245: system($script);
1.148 foxr 5246: }
5247:
5248: #
1.144 foxr 5249: # Called in response to a USR2 signal.
5250: # - Reread hosts.tab
5251: # - All children connected to hosts that were removed from hosts.tab
5252: # are killed via SIGINT
5253: # - All children connected to previously existing hosts are sent SIGUSR1
5254: # - Our internal hosts hash is updated to reflect the new contents of
5255: # hosts.tab causing connections from hosts added to hosts.tab to
5256: # now be honored.
5257: #
5258: sub UpdateHosts {
1.165 albertel 5259: &status("Reload hosts.tab");
1.147 foxr 5260: logthis('<font color="blue"> Updating connections </font>');
1.148 foxr 5261: #
5262: # The %children hash has the set of IP's we currently have children
5263: # on. These need to be matched against records in the hosts.tab
5264: # Any ip's no longer in the table get killed off they correspond to
5265: # either dropped or changed hosts. Note that the re-read of the table
5266: # will take care of new and changed hosts as connections come into being.
5267:
1.371 albertel 5268: &Apache::lonnet::reset_hosts_info();
1.148 foxr 5269:
1.368 albertel 5270: foreach my $child (keys(%children)) {
1.148 foxr 5271: my $childip = $children{$child};
1.374 albertel 5272: if ($childip ne '127.0.0.1'
5273: && !defined(&Apache::lonnet::get_hosts_from_ip($childip))) {
1.149 foxr 5274: logthis('<font color="blue"> UpdateHosts killing child '
5275: ." $child for ip $childip </font>");
1.148 foxr 5276: kill('INT', $child);
1.149 foxr 5277: } else {
5278: logthis('<font color="green"> keeping child for ip '
5279: ." $childip (pid=$child) </font>");
1.148 foxr 5280: }
5281: }
5282: ReloadApache;
1.165 albertel 5283: &status("Finished reloading hosts.tab");
1.144 foxr 5284: }
5285:
1.148 foxr 5286:
1.57 www 5287: sub checkchildren {
1.165 albertel 5288: &status("Checking on the children (sending signals)");
1.57 www 5289: &initnewstatus();
5290: &logstatus();
5291: &logthis('Going to check on the children');
1.134 albertel 5292: my $docdir=$perlvar{'lonDocRoot'};
1.61 harris41 5293: foreach (sort keys %children) {
1.221 albertel 5294: #sleep 1;
1.57 www 5295: unless (kill 'USR1' => $_) {
5296: &logthis ('Child '.$_.' is dead');
5297: &logstatus($$.' is dead');
1.221 albertel 5298: delete($children{$_});
1.57 www 5299: }
1.61 harris41 5300: }
1.63 www 5301: sleep 5;
1.212 foxr 5302: $SIG{ALRM} = sub { Debug("timeout");
5303: die "timeout"; };
1.113 albertel 5304: $SIG{__DIE__} = 'DEFAULT';
1.165 albertel 5305: &status("Checking on the children (waiting for reports)");
1.63 www 5306: foreach (sort keys %children) {
5307: unless (-e "$docdir/lon-status/londchld/$_.txt") {
1.113 albertel 5308: eval {
5309: alarm(300);
1.63 www 5310: &logthis('Child '.$_.' did not respond');
1.67 albertel 5311: kill 9 => $_;
1.131 albertel 5312: #$emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}";
5313: #$subj="LON: $currenthostid killed lond process $_";
5314: #my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`;
5315: #$execdir=$perlvar{'lonDaemons'};
5316: #$result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_`;
1.221 albertel 5317: delete($children{$_});
1.113 albertel 5318: alarm(0);
5319: }
1.63 www 5320: }
5321: }
1.113 albertel 5322: $SIG{ALRM} = 'DEFAULT';
1.155 albertel 5323: $SIG{__DIE__} = \&catchexception;
1.165 albertel 5324: &status("Finished checking children");
1.221 albertel 5325: &logthis('Finished Checking children');
1.57 www 5326: }
5327:
1.1 albertel 5328: # --------------------------------------------------------------------- Logging
5329:
5330: sub logthis {
5331: my $message=shift;
5332: my $execdir=$perlvar{'lonDaemons'};
5333: my $fh=IO::File->new(">>$execdir/logs/lond.log");
5334: my $now=time;
5335: my $local=localtime($now);
1.58 www 5336: $lastlog=$local.': '.$message;
1.1 albertel 5337: print $fh "$local ($$): $message\n";
5338: }
5339:
1.77 foxr 5340: # ------------------------- Conditional log if $DEBUG true.
5341: sub Debug {
5342: my $message = shift;
5343: if($DEBUG) {
5344: &logthis($message);
5345: }
5346: }
1.161 foxr 5347:
5348: #
5349: # Sub to do replies to client.. this gives a hook for some
5350: # debug tracing too:
5351: # Parameters:
5352: # fd - File open on client.
5353: # reply - Text to send to client.
5354: # request - Original request from client.
5355: #
5356: sub Reply {
1.192 foxr 5357: my ($fd, $reply, $request) = @_;
1.161 foxr 5358: print $fd $reply;
5359: Debug("Request was $request Reply was $reply");
5360:
1.212 foxr 5361: $Transactions++;
5362: }
5363:
5364:
5365: #
5366: # Sub to report a failure.
5367: # This function:
5368: # - Increments the failure statistic counters.
5369: # - Invokes Reply to send the error message to the client.
5370: # Parameters:
5371: # fd - File descriptor open on the client
5372: # reply - Reply text to emit.
5373: # request - The original request message (used by Reply
5374: # to debug if that's enabled.
5375: # Implicit outputs:
5376: # $Failures- The number of failures is incremented.
5377: # Reply (invoked here) sends a message to the
5378: # client:
5379: #
5380: sub Failure {
5381: my $fd = shift;
5382: my $reply = shift;
5383: my $request = shift;
5384:
5385: $Failures++;
5386: Reply($fd, $reply, $request); # That's simple eh?
1.161 foxr 5387: }
1.57 www 5388: # ------------------------------------------------------------------ Log status
5389:
5390: sub logstatus {
1.178 foxr 5391: &status("Doing logging");
5392: my $docdir=$perlvar{'lonDocRoot'};
5393: {
5394: my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt");
1.200 matthew 5395: print $fh $status."\n".$lastlog."\n".time."\n$keymode";
1.178 foxr 5396: $fh->close();
5397: }
1.221 albertel 5398: &status("Finished $$.txt");
5399: {
5400: open(LOG,">>$docdir/lon-status/londstatus.txt");
5401: flock(LOG,LOCK_EX);
5402: print LOG $$."\t".$clientname."\t".$currenthostid."\t"
5403: .$status."\t".$lastlog."\t $keymode\n";
1.275 albertel 5404: flock(LOG,LOCK_UN);
1.221 albertel 5405: close(LOG);
5406: }
1.178 foxr 5407: &status("Finished logging");
1.57 www 5408: }
5409:
5410: sub initnewstatus {
5411: my $docdir=$perlvar{'lonDocRoot'};
5412: my $fh=IO::File->new(">$docdir/lon-status/londstatus.txt");
5413: my $now=time;
5414: my $local=localtime($now);
5415: print $fh "LOND status $local - parent $$\n\n";
1.64 www 5416: opendir(DIR,"$docdir/lon-status/londchld");
1.134 albertel 5417: while (my $filename=readdir(DIR)) {
1.64 www 5418: unlink("$docdir/lon-status/londchld/$filename");
5419: }
5420: closedir(DIR);
1.57 www 5421: }
5422:
5423: # -------------------------------------------------------------- Status setting
5424:
5425: sub status {
5426: my $what=shift;
5427: my $now=time;
5428: my $local=localtime($now);
1.178 foxr 5429: $status=$local.': '.$what;
5430: $0='lond: '.$what.' '.$local;
1.57 www 5431: }
1.11 www 5432:
1.13 www 5433: # -------------------------------------------------------------- Talk to lonsql
5434:
1.234 foxr 5435: sub sql_reply {
1.12 harris41 5436: my ($cmd)=@_;
1.234 foxr 5437: my $answer=&sub_sql_reply($cmd);
5438: if ($answer eq 'con_lost') { $answer=&sub_sql_reply($cmd); }
1.12 harris41 5439: return $answer;
5440: }
5441:
1.234 foxr 5442: sub sub_sql_reply {
1.12 harris41 5443: my ($cmd)=@_;
5444: my $unixsock="mysqlsock";
5445: my $peerfile="$perlvar{'lonSockDir'}/$unixsock";
5446: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile",
5447: Type => SOCK_STREAM,
5448: Timeout => 10)
5449: or return "con_lost";
1.319 www 5450: print $sclient "$cmd:$currentdomainid\n";
1.12 harris41 5451: my $answer=<$sclient>;
5452: chomp($answer);
5453: if (!$answer) { $answer="con_lost"; }
5454: return $answer;
5455: }
5456:
1.1 albertel 5457: # --------------------------------------- Is this the home server of an author?
1.11 www 5458:
1.1 albertel 5459: sub ishome {
5460: my $author=shift;
5461: $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/;
5462: my ($udom,$uname)=split(/\//,$author);
5463: my $proname=propath($udom,$uname);
5464: if (-e $proname) {
5465: return 'owner';
5466: } else {
5467: return 'not_owner';
5468: }
5469: }
5470:
5471: # ======================================================= Continue main program
5472: # ---------------------------------------------------- Fork once and dissociate
5473:
1.134 albertel 5474: my $fpid=fork;
1.1 albertel 5475: exit if $fpid;
1.29 harris41 5476: die "Couldn't fork: $!" unless defined ($fpid);
1.1 albertel 5477:
1.29 harris41 5478: POSIX::setsid() or die "Can't start new session: $!";
1.1 albertel 5479:
5480: # ------------------------------------------------------- Write our PID on disk
5481:
1.134 albertel 5482: my $execdir=$perlvar{'lonDaemons'};
1.1 albertel 5483: open (PIDSAVE,">$execdir/logs/lond.pid");
5484: print PIDSAVE "$$\n";
5485: close(PIDSAVE);
1.190 albertel 5486: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>");
1.57 www 5487: &status('Starting');
1.1 albertel 5488:
1.106 foxr 5489:
1.1 albertel 5490:
5491: # ----------------------------------------------------- Install signal handlers
5492:
1.57 www 5493:
1.1 albertel 5494: $SIG{CHLD} = \&REAPER;
5495: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
5496: $SIG{HUP} = \&HUPSMAN;
1.57 www 5497: $SIG{USR1} = \&checkchildren;
1.144 foxr 5498: $SIG{USR2} = \&UpdateHosts;
1.385 ! albertel 5499: $SIG{SEGV} = \&flip_max_children_enforcing;
1.106 foxr 5500:
1.148 foxr 5501: # Read the host hashes:
1.368 albertel 5502: &Apache::lonnet::load_hosts_tab();
1.106 foxr 5503:
1.286 albertel 5504: my $dist=`$perlvar{'lonDaemons'}/distprobe`;
5505:
1.106 foxr 5506: # --------------------------------------------------------------
5507: # Accept connections. When a connection comes in, it is validated
5508: # and if good, a child process is created to process transactions
5509: # along the connection.
5510:
1.1 albertel 5511: while (1) {
1.165 albertel 5512: &status('Starting accept');
1.106 foxr 5513: $client = $server->accept() or next;
1.165 albertel 5514: &status('Accepted '.$client.' off to spawn');
1.385 ! albertel 5515: my $child_count = scalar(keys(%children));
! 5516: if ($child_count > $max_children) {
! 5517: &logthis("Warning too many children (".$child_count.")");
! 5518: }
! 5519: # if ($child_count > $max_children && $max_children_enforcing) {
! 5520: # &logthis(" Not creating new child ");
! 5521: # $client->close();
! 5522: # } else {
! 5523: &make_new_child($client);
! 5524: # }
! 5525: &logthis("Concurrent children at ($child_count)");
1.165 albertel 5526: &status('Finished spawning');
1.1 albertel 5527: }
5528:
1.212 foxr 5529: sub make_new_child {
5530: my $pid;
5531: # my $cipher; # Now global
5532: my $sigset;
1.178 foxr 5533:
1.212 foxr 5534: $client = shift;
5535: &status('Starting new child '.$client);
5536: &logthis('<font color="green"> Attempting to start child ('.$client.
5537: ")</font>");
5538: # block signal for fork
5539: $sigset = POSIX::SigSet->new(SIGINT);
5540: sigprocmask(SIG_BLOCK, $sigset)
5541: or die "Can't block SIGINT for fork: $!\n";
1.178 foxr 5542:
1.212 foxr 5543: die "fork: $!" unless defined ($pid = fork);
1.178 foxr 5544:
1.212 foxr 5545: $client->sockopt(SO_KEEPALIVE, 1); # Enable monitoring of
5546: # connection liveness.
1.178 foxr 5547:
1.212 foxr 5548: #
5549: # Figure out who we're talking to so we can record the peer in
5550: # the pid hash.
5551: #
5552: my $caller = getpeername($client);
5553: my ($port,$iaddr);
5554: if (defined($caller) && length($caller) > 0) {
5555: ($port,$iaddr)=unpack_sockaddr_in($caller);
5556: } else {
5557: &logthis("Unable to determine who caller was, getpeername returned nothing");
5558: }
5559: if (defined($iaddr)) {
5560: $clientip = inet_ntoa($iaddr);
5561: Debug("Connected with $clientip");
5562: } else {
5563: &logthis("Unable to determine clientip");
5564: $clientip='Unavailable';
5565: }
5566:
5567: if ($pid) {
5568: # Parent records the child's birth and returns.
5569: sigprocmask(SIG_UNBLOCK, $sigset)
5570: or die "Can't unblock SIGINT for fork: $!\n";
5571: $children{$pid} = $clientip;
5572: &status('Started child '.$pid);
5573: return;
5574: } else {
5575: # Child can *not* return from this subroutine.
5576: $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before
5577: $SIG{CHLD} = 'DEFAULT'; #make this default so that pwauth returns
5578: #don't get intercepted
5579: $SIG{USR1}= \&logstatus;
5580: $SIG{ALRM}= \&timeout;
5581: $lastlog='Forked ';
5582: $status='Forked';
1.178 foxr 5583:
1.212 foxr 5584: # unblock signals
5585: sigprocmask(SIG_UNBLOCK, $sigset)
5586: or die "Can't unblock SIGINT for fork: $!\n";
1.178 foxr 5587:
1.212 foxr 5588: # my $tmpsnum=0; # Now global
5589: #---------------------------------------------------- kerberos 5 initialization
5590: &Authen::Krb5::init_context();
1.359 raeburn 5591: unless (($dist eq 'fedora5') || ($dist eq 'fedora4') ||
5592: ($dist eq 'fedora6') || ($dist eq 'suse9.3')) {
1.286 albertel 5593: &Authen::Krb5::init_ets();
5594: }
1.209 albertel 5595:
1.212 foxr 5596: &status('Accepted connection');
5597: # =============================================================================
5598: # do something with the connection
5599: # -----------------------------------------------------------------------------
5600: # see if we know client and 'check' for spoof IP by ineffective challenge
1.178 foxr 5601:
1.278 albertel 5602: my $outsideip=$clientip;
5603: if ($clientip eq '127.0.0.1') {
1.368 albertel 5604: $outsideip=&Apache::lonnet::get_host_ip($perlvar{'lonHostID'});
1.278 albertel 5605: }
5606:
1.368 albertel 5607: my $clientrec=defined(&Apache::lonnet::get_hosts_from_ip($outsideip));
1.278 albertel 5608: my $ismanager=($managers{$outsideip} ne undef);
1.212 foxr 5609: $clientname = "[unknonwn]";
5610: if($clientrec) { # Establish client type.
5611: $ConnectionType = "client";
1.368 albertel 5612: $clientname = (&Apache::lonnet::get_hosts_from_ip($outsideip))[-1];
1.212 foxr 5613: if($ismanager) {
5614: $ConnectionType = "both";
5615: }
5616: } else {
5617: $ConnectionType = "manager";
1.278 albertel 5618: $clientname = $managers{$outsideip};
1.212 foxr 5619: }
5620: my $clientok;
1.178 foxr 5621:
1.212 foxr 5622: if ($clientrec || $ismanager) {
5623: &status("Waiting for init from $clientip $clientname");
5624: &logthis('<font color="yellow">INFO: Connection, '.
5625: $clientip.
5626: " ($clientname) connection type = $ConnectionType </font>" );
5627: &status("Connecting $clientip ($clientname))");
5628: my $remotereq=<$client>;
5629: chomp($remotereq);
5630: Debug("Got init: $remotereq");
1.337 albertel 5631:
1.212 foxr 5632: if ($remotereq =~ /^init/) {
5633: &sethost("sethost:$perlvar{'lonHostID'}");
5634: #
5635: # If the remote is attempting a local init... give that a try:
5636: #
5637: my ($i, $inittype) = split(/:/, $remotereq);
1.209 albertel 5638:
1.212 foxr 5639: # If the connection type is ssl, but I didn't get my
5640: # certificate files yet, then I'll drop back to
5641: # insecure (if allowed).
5642:
5643: if($inittype eq "ssl") {
5644: my ($ca, $cert) = lonssl::CertificateFile;
5645: my $kfile = lonssl::KeyFile;
5646: if((!$ca) ||
5647: (!$cert) ||
5648: (!$kfile)) {
5649: $inittype = ""; # This forces insecure attempt.
5650: &logthis("<font color=\"blue\"> Certificates not "
5651: ."installed -- trying insecure auth</font>");
1.224 foxr 5652: } else { # SSL certificates are in place so
1.212 foxr 5653: } # Leave the inittype alone.
5654: }
5655:
5656: if($inittype eq "local") {
5657: my $key = LocalConnection($client, $remotereq);
5658: if($key) {
5659: Debug("Got local key $key");
5660: $clientok = 1;
5661: my $cipherkey = pack("H32", $key);
5662: $cipher = new IDEA($cipherkey);
5663: print $client "ok:local\n";
5664: &logthis('<font color="green"'
5665: . "Successful local authentication </font>");
5666: $keymode = "local"
1.178 foxr 5667: } else {
1.212 foxr 5668: Debug("Failed to get local key");
5669: $clientok = 0;
5670: shutdown($client, 3);
5671: close $client;
1.178 foxr 5672: }
1.212 foxr 5673: } elsif ($inittype eq "ssl") {
5674: my $key = SSLConnection($client);
5675: if ($key) {
5676: $clientok = 1;
5677: my $cipherkey = pack("H32", $key);
5678: $cipher = new IDEA($cipherkey);
5679: &logthis('<font color="green">'
5680: ."Successfull ssl authentication with $clientname </font>");
5681: $keymode = "ssl";
5682:
1.178 foxr 5683: } else {
1.212 foxr 5684: $clientok = 0;
5685: close $client;
1.178 foxr 5686: }
1.212 foxr 5687:
5688: } else {
5689: my $ok = InsecureConnection($client);
5690: if($ok) {
5691: $clientok = 1;
5692: &logthis('<font color="green">'
5693: ."Successful insecure authentication with $clientname </font>");
5694: print $client "ok\n";
5695: $keymode = "insecure";
1.178 foxr 5696: } else {
1.212 foxr 5697: &logthis('<font color="yellow">'
5698: ."Attempted insecure connection disallowed </font>");
5699: close $client;
5700: $clientok = 0;
1.178 foxr 5701:
5702: }
5703: }
1.212 foxr 5704: } else {
5705: &logthis(
5706: "<font color='blue'>WARNING: "
5707: ."$clientip failed to initialize: >$remotereq< </font>");
5708: &status('No init '.$clientip);
5709: }
5710:
5711: } else {
5712: &logthis(
5713: "<font color='blue'>WARNING: Unknown client $clientip</font>");
5714: &status('Hung up on '.$clientip);
5715: }
5716:
5717: if ($clientok) {
5718: # ---------------- New known client connecting, could mean machine online again
1.368 albertel 5719: if (&Apache::lonnet::get_host_ip($currenthostid) ne $clientip
1.367 albertel 5720: && $clientip ne '127.0.0.1') {
1.375 albertel 5721: &Apache::lonnet::reconlonc($clientname);
1.212 foxr 5722: }
5723: &logthis("<font color='green'>Established connection: $clientname</font>");
5724: &status('Will listen to '.$clientname);
5725: # ------------------------------------------------------------ Process requests
5726: my $keep_going = 1;
5727: my $user_input;
1.385 ! albertel 5728: my $max_size = (split("\n",`ps -o vsz $$`))[-1];
1.212 foxr 5729: while(($user_input = get_request) && $keep_going) {
5730: alarm(120);
5731: Debug("Main: Got $user_input\n");
5732: $keep_going = &process_request($user_input);
1.385 ! albertel 5733: if (!$max_children_enforcing) {
! 5734: my $new_size = (split("\n",`ps -o vsz $$`))[-1];
! 5735: if ($new_size > $max_size) {
! 5736: &logthis("size increase of ".($new_size-$max_size)." ($new_size) while processing (".length($user_input).")\n".substr($user_input,0,80));
! 5737: $max_size = $new_size;
! 5738: }
! 5739: }
1.178 foxr 5740: alarm(0);
1.212 foxr 5741: &status('Listening to '.$clientname." ($keymode)");
1.161 foxr 5742: }
1.212 foxr 5743:
1.59 www 5744: # --------------------------------------------- client unknown or fishy, refuse
1.212 foxr 5745: } else {
1.161 foxr 5746: print $client "refused\n";
5747: $client->close();
1.190 albertel 5748: &logthis("<font color='blue'>WARNING: "
1.161 foxr 5749: ."Rejected client $clientip, closing connection</font>");
5750: }
1.212 foxr 5751: }
1.161 foxr 5752:
1.1 albertel 5753: # =============================================================================
1.161 foxr 5754:
1.190 albertel 5755: &logthis("<font color='red'>CRITICAL: "
1.161 foxr 5756: ."Disconnect from $clientip ($clientname)</font>");
5757:
5758:
5759: # this exit is VERY important, otherwise the child will become
5760: # a producer of more and more children, forking yourself into
5761: # process death.
5762: exit;
1.106 foxr 5763:
1.78 foxr 5764: }
1.261 foxr 5765: #
5766: # Determine if a user is an author for the indicated domain.
5767: #
5768: # Parameters:
5769: # domain - domain to check in .
5770: # user - Name of user to check.
5771: #
5772: # Return:
5773: # 1 - User is an author for domain.
5774: # 0 - User is not an author for domain.
5775: sub is_author {
5776: my ($domain, $user) = @_;
5777:
5778: &Debug("is_author: $user @ $domain");
5779:
5780: my $hashref = &tie_user_hash($domain, $user, "roles",
5781: &GDBM_READER());
5782:
5783: # Author role should show up as a key /domain/_au
1.78 foxr 5784:
1.321 albertel 5785: my $key = "/$domain/_au";
5786: my $value;
5787: if (defined($hashref)) {
5788: $value = $hashref->{$key};
5789: }
1.78 foxr 5790:
1.261 foxr 5791: if(defined($value)) {
5792: &Debug("$user @ $domain is an author");
5793: }
5794:
5795: return defined($value);
5796: }
1.78 foxr 5797: #
5798: # Checks to see if the input roleput request was to set
5799: # an author role. If so, invokes the lchtmldir script to set
5800: # up a correct public_html
5801: # Parameters:
5802: # request - The request sent to the rolesput subchunk.
5803: # We're looking for /domain/_au
5804: # domain - The domain in which the user is having roles doctored.
5805: # user - Name of the user for which the role is being put.
5806: # authtype - The authentication type associated with the user.
5807: #
1.289 albertel 5808: sub manage_permissions {
1.192 foxr 5809: my ($request, $domain, $user, $authtype) = @_;
1.78 foxr 5810:
1.261 foxr 5811: &Debug("manage_permissions: $request $domain $user $authtype");
5812:
1.78 foxr 5813: # See if the request is of the form /$domain/_au
1.289 albertel 5814: if($request =~ /^(\/\Q$domain\E\/_au)$/) { # It's an author rolesput...
1.78 foxr 5815: my $execdir = $perlvar{'lonDaemons'};
5816: my $userhome= "/home/$user" ;
1.134 albertel 5817: &logthis("system $execdir/lchtmldir $userhome $user $authtype");
1.261 foxr 5818: &Debug("Setting homedir permissions for $userhome");
1.78 foxr 5819: system("$execdir/lchtmldir $userhome $user $authtype");
5820: }
5821: }
1.222 foxr 5822:
5823:
5824: #
5825: # Return the full path of a user password file, whether it exists or not.
5826: # Parameters:
5827: # domain - Domain in which the password file lives.
5828: # user - name of the user.
5829: # Returns:
5830: # Full passwd path:
5831: #
5832: sub password_path {
5833: my ($domain, $user) = @_;
1.264 albertel 5834: return &propath($domain, $user).'/passwd';
1.222 foxr 5835: }
5836:
5837: # Password Filename
5838: # Returns the path to a passwd file given domain and user... only if
5839: # it exists.
5840: # Parameters:
5841: # domain - Domain in which to search.
5842: # user - username.
5843: # Returns:
5844: # - If the password file exists returns its path.
5845: # - If the password file does not exist, returns undefined.
5846: #
5847: sub password_filename {
5848: my ($domain, $user) = @_;
5849:
5850: Debug ("PasswordFilename called: dom = $domain user = $user");
5851:
5852: my $path = &password_path($domain, $user);
5853: Debug("PasswordFilename got path: $path");
5854: if(-e $path) {
5855: return $path;
5856: } else {
5857: return undef;
5858: }
5859: }
5860:
5861: #
5862: # Rewrite the contents of the user's passwd file.
5863: # Parameters:
5864: # domain - domain of the user.
5865: # name - User's name.
5866: # contents - New contents of the file.
5867: # Returns:
5868: # 0 - Failed.
5869: # 1 - Success.
5870: #
5871: sub rewrite_password_file {
5872: my ($domain, $user, $contents) = @_;
5873:
5874: my $file = &password_filename($domain, $user);
5875: if (defined $file) {
5876: my $pf = IO::File->new(">$file");
5877: if($pf) {
5878: print $pf "$contents\n";
5879: return 1;
5880: } else {
5881: return 0;
5882: }
5883: } else {
5884: return 0;
5885: }
5886:
5887: }
5888:
1.78 foxr 5889: #
1.222 foxr 5890: # get_auth_type - Determines the authorization type of a user in a domain.
1.78 foxr 5891:
5892: # Returns the authorization type or nouser if there is no such user.
5893: #
1.222 foxr 5894: sub get_auth_type
1.78 foxr 5895: {
1.192 foxr 5896:
5897: my ($domain, $user) = @_;
1.78 foxr 5898:
1.222 foxr 5899: Debug("get_auth_type( $domain, $user ) \n");
1.78 foxr 5900: my $proname = &propath($domain, $user);
5901: my $passwdfile = "$proname/passwd";
5902: if( -e $passwdfile ) {
5903: my $pf = IO::File->new($passwdfile);
5904: my $realpassword = <$pf>;
5905: chomp($realpassword);
1.79 foxr 5906: Debug("Password info = $realpassword\n");
1.78 foxr 5907: my ($authtype, $contentpwd) = split(/:/, $realpassword);
1.79 foxr 5908: Debug("Authtype = $authtype, content = $contentpwd\n");
1.259 raeburn 5909: return "$authtype:$contentpwd";
1.224 foxr 5910: } else {
1.79 foxr 5911: Debug("Returning nouser");
1.78 foxr 5912: return "nouser";
5913: }
1.1 albertel 5914: }
5915:
1.220 foxr 5916: #
5917: # Validate a user given their domain, name and password. This utility
5918: # function is used by both AuthenticateHandler and ChangePasswordHandler
5919: # to validate the login credentials of a user.
5920: # Parameters:
5921: # $domain - The domain being logged into (this is required due to
5922: # the capability for multihomed systems.
5923: # $user - The name of the user being validated.
5924: # $password - The user's propoposed password.
5925: #
5926: # Returns:
5927: # 1 - The domain,user,pasword triplet corresponds to a valid
5928: # user.
5929: # 0 - The domain,user,password triplet is not a valid user.
5930: #
5931: sub validate_user {
5932: my ($domain, $user, $password) = @_;
5933:
5934:
5935: # Why negative ~pi you may well ask? Well this function is about
5936: # authentication, and therefore very important to get right.
5937: # I've initialized the flag that determines whether or not I've
5938: # validated correctly to a value it's not supposed to get.
5939: # At the end of this function. I'll ensure that it's not still that
5940: # value so we don't just wind up returning some accidental value
5941: # as a result of executing an unforseen code path that
1.249 foxr 5942: # did not set $validated. At the end of valid execution paths,
5943: # validated shoule be 1 for success or 0 for failuer.
1.220 foxr 5944:
5945: my $validated = -3.14159;
5946:
5947: # How we authenticate is determined by the type of authentication
5948: # the user has been assigned. If the authentication type is
5949: # "nouser", the user does not exist so we will return 0.
5950:
1.222 foxr 5951: my $contents = &get_auth_type($domain, $user);
1.220 foxr 5952: my ($howpwd, $contentpwd) = split(/:/, $contents);
5953:
5954: my $null = pack("C",0); # Used by kerberos auth types.
5955:
5956: if ($howpwd ne 'nouser') {
5957:
5958: if($howpwd eq "internal") { # Encrypted is in local password file.
5959: $validated = (crypt($password, $contentpwd) eq $contentpwd);
5960: }
5961: elsif ($howpwd eq "unix") { # User is a normal unix user.
5962: $contentpwd = (getpwnam($user))[1];
5963: if($contentpwd) {
5964: if($contentpwd eq 'x') { # Shadow password file...
5965: my $pwauth_path = "/usr/local/sbin/pwauth";
5966: open PWAUTH, "|$pwauth_path" or
5967: die "Cannot invoke authentication";
5968: print PWAUTH "$user\n$password\n";
5969: close PWAUTH;
5970: $validated = ! $?;
5971:
5972: } else { # Passwords in /etc/passwd.
5973: $validated = (crypt($password,
5974: $contentpwd) eq $contentpwd);
5975: }
5976: } else {
5977: $validated = 0;
5978: }
5979: }
5980: elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain.
5981: if(! ($password =~ /$null/) ) {
5982: my $k4error = &Authen::Krb4::get_pw_in_tkt($user,
5983: "",
5984: $contentpwd,,
5985: 'krbtgt',
5986: $contentpwd,
5987: 1,
5988: $password);
5989: if(!$k4error) {
5990: $validated = 1;
1.224 foxr 5991: } else {
1.220 foxr 5992: $validated = 0;
5993: &logthis('krb4: '.$user.', '.$contentpwd.', '.
5994: &Authen::Krb4::get_err_txt($Authen::Krb4::error));
5995: }
1.224 foxr 5996: } else {
1.220 foxr 5997: $validated = 0; # Password has a match with null.
5998: }
1.224 foxr 5999: } elsif ($howpwd eq "krb5") { # User is in kerberos 5 auth. domain.
1.220 foxr 6000: if(!($password =~ /$null/)) { # Null password not allowed.
6001: my $krbclient = &Authen::Krb5::parse_name($user.'@'
6002: .$contentpwd);
6003: my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd;
6004: my $krbserver = &Authen::Krb5::parse_name($krbservice);
6005: my $credentials= &Authen::Krb5::cc_default();
1.359 raeburn 6006: $credentials->initialize(&Authen::Krb5::parse_name($user.'@'
6007: .$contentpwd));
1.270 matthew 6008: my $krbreturn = &Authen::Krb5::get_in_tkt_with_password($krbclient,
1.220 foxr 6009: $krbserver,
6010: $password,
6011: $credentials);
6012: $validated = ($krbreturn == 1);
1.380 albertel 6013: if (!$validated) {
6014: &logthis('krb5: '.$user.', '.$contentpwd.', '.
6015: &Authen::Krb5::error());
6016: }
1.224 foxr 6017: } else {
1.220 foxr 6018: $validated = 0;
6019: }
1.224 foxr 6020: } elsif ($howpwd eq "localauth") {
1.220 foxr 6021: # Authenticate via installation specific authentcation method:
6022: $validated = &localauth::localauth($user,
6023: $password,
1.353 albertel 6024: $contentpwd,
6025: $domain);
1.358 albertel 6026: if ($validated < 0) {
1.357 albertel 6027: &logthis("localauth for $contentpwd $user:$domain returned a $validated");
6028: $validated = 0;
6029: }
1.224 foxr 6030: } else { # Unrecognized auth is also bad.
1.220 foxr 6031: $validated = 0;
6032: }
6033: } else {
6034: $validated = 0;
6035: }
6036: #
6037: # $validated has the correct stat of the authentication:
6038: #
6039:
6040: unless ($validated != -3.14159) {
1.249 foxr 6041: # I >really really< want to know if this happens.
6042: # since it indicates that user authentication is badly
6043: # broken in some code path.
6044: #
6045: die "ValidateUser - failed to set the value of validated $domain, $user $password";
1.220 foxr 6046: }
6047: return $validated;
6048: }
6049:
6050:
1.84 albertel 6051: sub addline {
6052: my ($fname,$hostid,$ip,$newline)=@_;
6053: my $contents;
6054: my $found=0;
1.355 albertel 6055: my $expr='^'.quotemeta($hostid).':'.quotemeta($ip).':';
1.134 albertel 6056: my $sh;
1.84 albertel 6057: if ($sh=IO::File->new("$fname.subscription")) {
6058: while (my $subline=<$sh>) {
6059: if ($subline !~ /$expr/) {$contents.= $subline;} else {$found=1;}
6060: }
6061: $sh->close();
6062: }
6063: $sh=IO::File->new(">$fname.subscription");
6064: if ($contents) { print $sh $contents; }
6065: if ($newline) { print $sh $newline; }
6066: $sh->close();
6067: return $found;
1.86 www 6068: }
6069:
1.234 foxr 6070: sub get_chat {
1.324 raeburn 6071: my ($cdom,$cname,$udom,$uname,$group)=@_;
1.310 albertel 6072:
1.87 www 6073: my @entries=();
1.324 raeburn 6074: my $namespace = 'nohist_chatroom';
6075: my $namespace_inroom = 'nohist_inchatroom';
1.335 albertel 6076: if ($group ne '') {
1.324 raeburn 6077: $namespace .= '_'.$group;
6078: $namespace_inroom .= '_'.$group;
6079: }
6080: my $hashref = &tie_user_hash($cdom, $cname, $namespace,
1.310 albertel 6081: &GDBM_READER());
6082: if ($hashref) {
6083: @entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref));
1.311 albertel 6084: &untie_user_hash($hashref);
1.123 www 6085: }
1.124 www 6086: my @participants=();
1.134 albertel 6087: my $cutoff=time-60;
1.324 raeburn 6088: $hashref = &tie_user_hash($cdom, $cname, $namespace_inroom,
1.310 albertel 6089: &GDBM_WRCREAT());
6090: if ($hashref) {
6091: $hashref->{$uname.':'.$udom}=time;
6092: foreach my $user (sort(keys(%$hashref))) {
6093: if ($hashref->{$user}>$cutoff) {
6094: push(@participants, 'active_participant:'.$user);
1.123 www 6095: }
6096: }
1.311 albertel 6097: &untie_user_hash($hashref);
1.86 www 6098: }
1.124 www 6099: return (@participants,@entries);
1.86 www 6100: }
6101:
1.234 foxr 6102: sub chat_add {
1.324 raeburn 6103: my ($cdom,$cname,$newchat,$group)=@_;
1.88 albertel 6104: my @entries=();
1.142 www 6105: my $time=time;
1.324 raeburn 6106: my $namespace = 'nohist_chatroom';
6107: my $logfile = 'chatroom.log';
1.335 albertel 6108: if ($group ne '') {
1.324 raeburn 6109: $namespace .= '_'.$group;
6110: $logfile = 'chatroom_'.$group.'.log';
6111: }
6112: my $hashref = &tie_user_hash($cdom, $cname, $namespace,
1.310 albertel 6113: &GDBM_WRCREAT());
6114: if ($hashref) {
6115: @entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref));
1.88 albertel 6116: my ($lastid)=($entries[$#entries]=~/^(\w+)\:/);
6117: my ($thentime,$idnum)=split(/\_/,$lastid);
6118: my $newid=$time.'_000000';
6119: if ($thentime==$time) {
6120: $idnum=~s/^0+//;
6121: $idnum++;
6122: $idnum=substr('000000'.$idnum,-6,6);
6123: $newid=$time.'_'.$idnum;
6124: }
1.310 albertel 6125: $hashref->{$newid}=$newchat;
1.88 albertel 6126: my $expired=$time-3600;
1.310 albertel 6127: foreach my $comment (keys(%$hashref)) {
6128: my ($thistime) = ($comment=~/(\d+)\_/);
1.88 albertel 6129: if ($thistime<$expired) {
1.310 albertel 6130: delete $hashref->{$comment};
1.88 albertel 6131: }
6132: }
1.310 albertel 6133: {
6134: my $proname=&propath($cdom,$cname);
1.324 raeburn 6135: if (open(CHATLOG,">>$proname/$logfile")) {
1.310 albertel 6136: print CHATLOG ("$time:".&unescape($newchat)."\n");
6137: }
6138: close(CHATLOG);
1.142 www 6139: }
1.311 albertel 6140: &untie_user_hash($hashref);
1.86 www 6141: }
1.84 albertel 6142: }
6143:
6144: sub unsub {
6145: my ($fname,$clientip)=@_;
6146: my $result;
1.188 foxr 6147: my $unsubs = 0; # Number of successful unsubscribes:
6148:
6149:
6150: # An old way subscriptions were handled was to have a
6151: # subscription marker file:
6152:
6153: Debug("Attempting unlink of $fname.$clientname");
1.161 foxr 6154: if (unlink("$fname.$clientname")) {
1.188 foxr 6155: $unsubs++; # Successful unsub via marker file.
6156: }
6157:
6158: # The more modern way to do it is to have a subscription list
6159: # file:
6160:
1.84 albertel 6161: if (-e "$fname.subscription") {
1.161 foxr 6162: my $found=&addline($fname,$clientname,$clientip,'');
1.188 foxr 6163: if ($found) {
6164: $unsubs++;
6165: }
6166: }
6167:
6168: # If either or both of these mechanisms succeeded in unsubscribing a
6169: # resource we can return ok:
6170:
6171: if($unsubs) {
6172: $result = "ok\n";
1.84 albertel 6173: } else {
1.188 foxr 6174: $result = "not_subscribed\n";
1.84 albertel 6175: }
1.188 foxr 6176:
1.84 albertel 6177: return $result;
6178: }
6179:
1.101 www 6180: sub currentversion {
6181: my $fname=shift;
6182: my $version=-1;
6183: my $ulsdir='';
6184: if ($fname=~/^(.+)\/[^\/]+$/) {
6185: $ulsdir=$1;
6186: }
1.114 albertel 6187: my ($fnamere1,$fnamere2);
6188: # remove version if already specified
1.101 www 6189: $fname=~s/\.\d+\.(\w+(?:\.meta)*)$/\.$1/;
1.114 albertel 6190: # get the bits that go before and after the version number
6191: if ( $fname=~/^(.*\.)(\w+(?:\.meta)*)$/ ) {
6192: $fnamere1=$1;
6193: $fnamere2='.'.$2;
6194: }
1.101 www 6195: if (-e $fname) { $version=1; }
6196: if (-e $ulsdir) {
1.134 albertel 6197: if(-d $ulsdir) {
6198: if (opendir(LSDIR,$ulsdir)) {
6199: my $ulsfn;
6200: while ($ulsfn=readdir(LSDIR)) {
1.101 www 6201: # see if this is a regular file (ignore links produced earlier)
1.134 albertel 6202: my $thisfile=$ulsdir.'/'.$ulsfn;
6203: unless (-l $thisfile) {
1.160 www 6204: if ($thisfile=~/\Q$fnamere1\E(\d+)\Q$fnamere2\E$/) {
1.134 albertel 6205: if ($1>$version) { $version=$1; }
6206: }
6207: }
6208: }
6209: closedir(LSDIR);
6210: $version++;
6211: }
6212: }
6213: }
6214: return $version;
1.101 www 6215: }
6216:
6217: sub thisversion {
6218: my $fname=shift;
6219: my $version=-1;
6220: if ($fname=~/\.(\d+)\.\w+(?:\.meta)*$/) {
6221: $version=$1;
6222: }
6223: return $version;
6224: }
6225:
1.84 albertel 6226: sub subscribe {
6227: my ($userinput,$clientip)=@_;
6228: my $result;
1.293 albertel 6229: my ($cmd,$fname)=split(/:/,$userinput,2);
1.84 albertel 6230: my $ownership=&ishome($fname);
6231: if ($ownership eq 'owner') {
1.101 www 6232: # explitly asking for the current version?
6233: unless (-e $fname) {
6234: my $currentversion=¤tversion($fname);
6235: if (&thisversion($fname)==$currentversion) {
6236: if ($fname=~/^(.+)\.\d+\.(\w+(?:\.meta)*)$/) {
6237: my $root=$1;
6238: my $extension=$2;
6239: symlink($root.'.'.$extension,
6240: $root.'.'.$currentversion.'.'.$extension);
1.102 www 6241: unless ($extension=~/\.meta$/) {
6242: symlink($root.'.'.$extension.'.meta',
6243: $root.'.'.$currentversion.'.'.$extension.'.meta');
6244: }
1.101 www 6245: }
6246: }
6247: }
1.84 albertel 6248: if (-e $fname) {
6249: if (-d $fname) {
6250: $result="directory\n";
6251: } else {
1.161 foxr 6252: if (-e "$fname.$clientname") {&unsub($fname,$clientip);}
1.134 albertel 6253: my $now=time;
1.161 foxr 6254: my $found=&addline($fname,$clientname,$clientip,
6255: "$clientname:$clientip:$now\n");
1.84 albertel 6256: if ($found) { $result="$fname\n"; }
6257: # if they were subscribed to only meta data, delete that
6258: # subscription, when you subscribe to a file you also get
6259: # the metadata
6260: unless ($fname=~/\.meta$/) { &unsub("$fname.meta",$clientip); }
6261: $fname=~s/\/home\/httpd\/html\/res/raw/;
1.373 albertel 6262: $fname="http://".&Apache::lonnet::hostname($perlvar{'lonHostID'})."/".$fname;
1.84 albertel 6263: $result="$fname\n";
6264: }
6265: } else {
6266: $result="not_found\n";
6267: }
6268: } else {
6269: $result="rejected\n";
6270: }
6271: return $result;
6272: }
1.287 foxr 6273: # Change the passwd of a unix user. The caller must have
6274: # first verified that the user is a loncapa user.
6275: #
6276: # Parameters:
6277: # user - Unix user name to change.
6278: # pass - New password for the user.
6279: # Returns:
6280: # ok - if success
6281: # other - Some meaningfule error message string.
6282: # NOTE:
6283: # invokes a setuid script to change the passwd.
6284: sub change_unix_password {
6285: my ($user, $pass) = @_;
6286:
6287: &Debug("change_unix_password");
6288: my $execdir=$perlvar{'lonDaemons'};
6289: &Debug("Opening lcpasswd pipeline");
6290: my $pf = IO::File->new("|$execdir/lcpasswd > "
6291: ."$perlvar{'lonDaemons'}"
6292: ."/logs/lcpasswd.log");
6293: print $pf "$user\n$pass\n$pass\n";
6294: close $pf;
6295: my $err = $?;
6296: return ($err < @passwderrors) ? $passwderrors[$err] :
6297: "pwchange_falure - unknown error";
6298:
6299:
6300: }
6301:
1.91 albertel 6302:
6303: sub make_passwd_file {
1.98 foxr 6304: my ($uname, $umode,$npass,$passfilename)=@_;
1.91 albertel 6305: my $result="ok\n";
6306: if ($umode eq 'krb4' or $umode eq 'krb5') {
6307: {
6308: my $pf = IO::File->new(">$passfilename");
1.261 foxr 6309: if ($pf) {
6310: print $pf "$umode:$npass\n";
6311: } else {
6312: $result = "pass_file_failed_error";
6313: }
1.91 albertel 6314: }
6315: } elsif ($umode eq 'internal') {
6316: my $salt=time;
6317: $salt=substr($salt,6,2);
6318: my $ncpass=crypt($npass,$salt);
6319: {
6320: &Debug("Creating internal auth");
6321: my $pf = IO::File->new(">$passfilename");
1.261 foxr 6322: if($pf) {
6323: print $pf "internal:$ncpass\n";
6324: } else {
6325: $result = "pass_file_failed_error";
6326: }
1.91 albertel 6327: }
6328: } elsif ($umode eq 'localauth') {
6329: {
6330: my $pf = IO::File->new(">$passfilename");
1.261 foxr 6331: if($pf) {
6332: print $pf "localauth:$npass\n";
6333: } else {
6334: $result = "pass_file_failed_error";
6335: }
1.91 albertel 6336: }
6337: } elsif ($umode eq 'unix') {
6338: {
1.186 foxr 6339: #
6340: # Don't allow the creation of privileged accounts!!! that would
6341: # be real bad!!!
6342: #
6343: my $uid = getpwnam($uname);
6344: if((defined $uid) && ($uid == 0)) {
6345: &logthis(">>>Attempted to create privilged account blocked");
6346: return "no_priv_account_error\n";
6347: }
6348:
1.223 foxr 6349: my $execpath ="$perlvar{'lonDaemons'}/"."lcuseradd";
1.224 foxr 6350:
6351: my $lc_error_file = $execdir."/tmp/lcuseradd".$$.".status";
1.91 albertel 6352: {
6353: &Debug("Executing external: ".$execpath);
1.98 foxr 6354: &Debug("user = ".$uname.", Password =". $npass);
1.132 matthew 6355: my $se = IO::File->new("|$execpath > $perlvar{'lonDaemons'}/logs/lcuseradd.log");
1.91 albertel 6356: print $se "$uname\n";
6357: print $se "$npass\n";
6358: print $se "$npass\n";
1.223 foxr 6359: print $se "$lc_error_file\n"; # Status -> unique file.
1.97 foxr 6360: }
1.285 foxr 6361: if (-r $lc_error_file) {
6362: &Debug("Opening error file: $lc_error_file");
6363: my $error = IO::File->new("< $lc_error_file");
6364: my $useraddok = <$error>;
6365: $error->close;
6366: unlink($lc_error_file);
6367:
6368: chomp $useraddok;
6369:
6370: if($useraddok > 0) {
6371: my $error_text = &lcuseraddstrerror($useraddok);
6372: &logthis("Failed lcuseradd: $error_text");
6373: $result = "lcuseradd_failed:$error_text\n";
6374: } else {
6375: my $pf = IO::File->new(">$passfilename");
6376: if($pf) {
6377: print $pf "unix:\n";
6378: } else {
6379: $result = "pass_file_failed_error";
6380: }
6381: }
1.224 foxr 6382: } else {
1.285 foxr 6383: &Debug("Could not locate lcuseradd error: $lc_error_file");
6384: $result="bug_lcuseradd_no_output_file";
1.91 albertel 6385: }
6386: }
6387: } elsif ($umode eq 'none') {
6388: {
1.223 foxr 6389: my $pf = IO::File->new("> $passfilename");
1.261 foxr 6390: if($pf) {
6391: print $pf "none:\n";
6392: } else {
6393: $result = "pass_file_failed_error";
6394: }
1.91 albertel 6395: }
6396: } else {
6397: $result="auth_mode_error\n";
6398: }
6399: return $result;
1.121 albertel 6400: }
6401:
1.265 albertel 6402: sub convert_photo {
6403: my ($start,$dest)=@_;
6404: system("convert $start $dest");
6405: }
6406:
1.121 albertel 6407: sub sethost {
6408: my ($remotereq) = @_;
6409: my (undef,$hostid)=split(/:/,$remotereq);
1.322 albertel 6410: # ignore sethost if we are already correct
6411: if ($hostid eq $currenthostid) {
6412: return 'ok';
6413: }
6414:
1.121 albertel 6415: if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; }
1.368 albertel 6416: if (&Apache::lonnet::get_host_ip($perlvar{'lonHostID'})
6417: eq &Apache::lonnet::get_host_ip($hostid)) {
1.200 matthew 6418: $currenthostid =$hostid;
1.369 albertel 6419: $currentdomainid=&Apache::lonnet::host_domain($hostid);
1.385 ! albertel 6420: #&logthis("Setting hostid to $hostid, and domain to $currentdomainid");
1.121 albertel 6421: } else {
6422: &logthis("Requested host id $hostid not an alias of ".
6423: $perlvar{'lonHostID'}." refusing connection");
6424: return 'unable_to_set';
6425: }
6426: return 'ok';
6427: }
6428:
6429: sub version {
6430: my ($userinput)=@_;
6431: $remoteVERSION=(split(/:/,$userinput))[1];
6432: return "version:$VERSION";
1.127 albertel 6433: }
1.178 foxr 6434:
1.200 matthew 6435:
1.61 harris41 6436: # ----------------------------------- POD (plain old documentation, CPAN style)
6437:
6438: =head1 NAME
6439:
6440: lond - "LON Daemon" Server (port "LOND" 5663)
6441:
6442: =head1 SYNOPSIS
6443:
1.74 harris41 6444: Usage: B<lond>
6445:
6446: Should only be run as user=www. This is a command-line script which
6447: is invoked by B<loncron>. There is no expectation that a typical user
6448: will manually start B<lond> from the command-line. (In other words,
6449: DO NOT START B<lond> YOURSELF.)
1.61 harris41 6450:
6451: =head1 DESCRIPTION
6452:
1.74 harris41 6453: There are two characteristics associated with the running of B<lond>,
6454: PROCESS MANAGEMENT (starting, stopping, handling child processes)
6455: and SERVER-SIDE ACTIVITIES (password authentication, user creation,
6456: subscriptions, etc). These are described in two large
6457: sections below.
6458:
6459: B<PROCESS MANAGEMENT>
6460:
1.61 harris41 6461: Preforker - server who forks first. Runs as a daemon. HUPs.
6462: Uses IDEA encryption
6463:
1.74 harris41 6464: B<lond> forks off children processes that correspond to the other servers
6465: in the network. Management of these processes can be done at the
6466: parent process level or the child process level.
6467:
6468: B<logs/lond.log> is the location of log messages.
6469:
6470: The process management is now explained in terms of linux shell commands,
6471: subroutines internal to this code, and signal assignments:
6472:
6473: =over 4
6474:
6475: =item *
6476:
6477: PID is stored in B<logs/lond.pid>
6478:
6479: This is the process id number of the parent B<lond> process.
6480:
6481: =item *
6482:
6483: SIGTERM and SIGINT
6484:
6485: Parent signal assignment:
6486: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN;
6487:
6488: Child signal assignment:
6489: $SIG{INT} = 'DEFAULT'; (and SIGTERM is DEFAULT also)
6490: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber
6491: to restart a new child.)
6492:
6493: Command-line invocations:
6494: B<kill> B<-s> SIGTERM I<PID>
6495: B<kill> B<-s> SIGINT I<PID>
6496:
6497: Subroutine B<HUNTSMAN>:
6498: This is only invoked for the B<lond> parent I<PID>.
6499: This kills all the children, and then the parent.
6500: The B<lonc.pid> file is cleared.
6501:
6502: =item *
6503:
6504: SIGHUP
6505:
6506: Current bug:
6507: This signal can only be processed the first time
6508: on the parent process. Subsequent SIGHUP signals
6509: have no effect.
6510:
6511: Parent signal assignment:
6512: $SIG{HUP} = \&HUPSMAN;
6513:
6514: Child signal assignment:
6515: none (nothing happens)
6516:
6517: Command-line invocations:
6518: B<kill> B<-s> SIGHUP I<PID>
6519:
6520: Subroutine B<HUPSMAN>:
6521: This is only invoked for the B<lond> parent I<PID>,
6522: This kills all the children, and then the parent.
6523: The B<lond.pid> file is cleared.
6524:
6525: =item *
6526:
6527: SIGUSR1
6528:
6529: Parent signal assignment:
6530: $SIG{USR1} = \&USRMAN;
6531:
6532: Child signal assignment:
6533: $SIG{USR1}= \&logstatus;
6534:
6535: Command-line invocations:
6536: B<kill> B<-s> SIGUSR1 I<PID>
6537:
6538: Subroutine B<USRMAN>:
6539: When invoked for the B<lond> parent I<PID>,
6540: SIGUSR1 is sent to all the children, and the status of
6541: each connection is logged.
1.144 foxr 6542:
6543: =item *
6544:
6545: SIGUSR2
6546:
6547: Parent Signal assignment:
6548: $SIG{USR2} = \&UpdateHosts
6549:
6550: Child signal assignment:
6551: NONE
6552:
1.74 harris41 6553:
6554: =item *
6555:
6556: SIGCHLD
6557:
6558: Parent signal assignment:
6559: $SIG{CHLD} = \&REAPER;
6560:
6561: Child signal assignment:
6562: none
6563:
6564: Command-line invocations:
6565: B<kill> B<-s> SIGCHLD I<PID>
6566:
6567: Subroutine B<REAPER>:
6568: This is only invoked for the B<lond> parent I<PID>.
6569: Information pertaining to the child is removed.
6570: The socket port is cleaned up.
6571:
6572: =back
6573:
6574: B<SERVER-SIDE ACTIVITIES>
6575:
6576: Server-side information can be accepted in an encrypted or non-encrypted
6577: method.
6578:
6579: =over 4
6580:
6581: =item ping
6582:
6583: Query a client in the hosts.tab table; "Are you there?"
6584:
6585: =item pong
6586:
6587: Respond to a ping query.
6588:
6589: =item ekey
6590:
6591: Read in encrypted key, make cipher. Respond with a buildkey.
6592:
6593: =item load
6594:
6595: Respond with CPU load based on a computation upon /proc/loadavg.
6596:
6597: =item currentauth
6598:
6599: Reply with current authentication information (only over an
6600: encrypted channel).
6601:
6602: =item auth
6603:
6604: Only over an encrypted channel, reply as to whether a user's
6605: authentication information can be validated.
6606:
6607: =item passwd
6608:
6609: Allow for a password to be set.
6610:
6611: =item makeuser
6612:
6613: Make a user.
6614:
6615: =item passwd
6616:
6617: Allow for authentication mechanism and password to be changed.
6618:
6619: =item home
1.61 harris41 6620:
1.74 harris41 6621: Respond to a question "are you the home for a given user?"
6622:
6623: =item update
6624:
6625: Update contents of a subscribed resource.
6626:
6627: =item unsubscribe
6628:
6629: The server is unsubscribing from a resource.
6630:
6631: =item subscribe
6632:
6633: The server is subscribing to a resource.
6634:
6635: =item log
6636:
6637: Place in B<logs/lond.log>
6638:
6639: =item put
6640:
6641: stores hash in namespace
6642:
1.230 foxr 6643: =item rolesputy
1.74 harris41 6644:
6645: put a role into a user's environment
6646:
6647: =item get
6648:
6649: returns hash with keys from array
6650: reference filled in from namespace
6651:
6652: =item eget
6653:
6654: returns hash with keys from array
6655: reference filled in from namesp (encrypts the return communication)
6656:
6657: =item rolesget
6658:
6659: get a role from a user's environment
6660:
6661: =item del
6662:
6663: deletes keys out of array from namespace
6664:
6665: =item keys
6666:
6667: returns namespace keys
6668:
6669: =item dump
6670:
6671: dumps the complete (or key matching regexp) namespace into a hash
6672:
6673: =item store
6674:
6675: stores hash permanently
6676: for this url; hashref needs to be given and should be a \%hashname; the
6677: remaining args aren't required and if they aren't passed or are '' they will
6678: be derived from the ENV
6679:
6680: =item restore
6681:
6682: returns a hash for a given url
6683:
6684: =item querysend
6685:
6686: Tells client about the lonsql process that has been launched in response
6687: to a sent query.
6688:
6689: =item queryreply
6690:
6691: Accept information from lonsql and make appropriate storage in temporary
6692: file space.
6693:
6694: =item idput
6695:
6696: Defines usernames as corresponding to IDs. (These "IDs" are unique identifiers
6697: for each student, defined perhaps by the institutional Registrar.)
6698:
6699: =item idget
6700:
6701: Returns usernames corresponding to IDs. (These "IDs" are unique identifiers
6702: for each student, defined perhaps by the institutional Registrar.)
6703:
6704: =item tmpput
6705:
6706: Accept and store information in temporary space.
6707:
6708: =item tmpget
6709:
6710: Send along temporarily stored information.
6711:
6712: =item ls
6713:
6714: List part of a user's directory.
6715:
1.135 foxr 6716: =item pushtable
6717:
6718: Pushes a file in /home/httpd/lonTab directory. Currently limited to:
6719: hosts.tab and domain.tab. The old file is copied to *.tab.backup but
6720: must be restored manually in case of a problem with the new table file.
6721: pushtable requires that the request be encrypted and validated via
6722: ValidateManager. The form of the command is:
6723: enc:pushtable tablename <tablecontents> \n
6724: where pushtable, tablename and <tablecontents> will be encrypted, but \n is a
6725: cleartext newline.
6726:
1.74 harris41 6727: =item Hanging up (exit or init)
6728:
6729: What to do when a client tells the server that they (the client)
6730: are leaving the network.
6731:
6732: =item unknown command
6733:
6734: If B<lond> is sent an unknown command (not in the list above),
6735: it replys to the client "unknown_cmd".
1.135 foxr 6736:
1.74 harris41 6737:
6738: =item UNKNOWN CLIENT
6739:
6740: If the anti-spoofing algorithm cannot verify the client,
6741: the client is rejected (with a "refused" message sent
6742: to the client, and the connection is closed.
6743:
6744: =back
1.61 harris41 6745:
6746: =head1 PREREQUISITES
6747:
6748: IO::Socket
6749: IO::File
6750: Apache::File
6751: POSIX
6752: Crypt::IDEA
6753: LWP::UserAgent()
6754: GDBM_File
6755: Authen::Krb4
1.91 albertel 6756: Authen::Krb5
1.61 harris41 6757:
6758: =head1 COREQUISITES
6759:
6760: =head1 OSNAMES
6761:
6762: linux
6763:
6764: =head1 SCRIPT CATEGORIES
6765:
6766: Server/Process
6767:
6768: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>