![]() ![]() | ![]() |
- For 2.11. - Backport 1.495, 1.496, 1.497.
1: #!/usr/bin/perl 2: # The LearningOnline Network 3: # lond "LON Daemon" Server (port "LOND" 5663) 4: # 5: # $Id: lond,v 1.489.2.2 2012/10/06 14:44:50 raeburn Exp $ 6: # 7: # Copyright Michigan State University Board of Trustees 8: # 9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA). 10: # 11: # LON-CAPA is free software; you can redistribute it and/or modify 12: # it under the terms of the GNU General Public License as published by 13: # the Free Software Foundation; either version 2 of the License, or 14: # (at your option) any later version. 15: # 16: # LON-CAPA is distributed in the hope that it will be useful, 17: # but WITHOUT ANY WARRANTY; without even the implied warranty of 18: 19: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20: # GNU General Public License for more details. 21: # 22: # You should have received a copy of the GNU General Public License 23: # along with LON-CAPA; if not, write to the Free Software 24: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 25: # 26: # /home/httpd/html/adm/gpl.txt 27: # 28: 29: 30: # http://www.lon-capa.org/ 31: # 32: 33: use strict; 34: use lib '/home/httpd/lib/perl/'; 35: use LONCAPA; 36: use LONCAPA::Configuration; 37: 38: use IO::Socket; 39: use IO::File; 40: #use Apache::File; 41: use POSIX; 42: use Crypt::IDEA; 43: use LWP::UserAgent(); 44: use Digest::MD5 qw(md5_hex); 45: use GDBM_File; 46: use Authen::Krb5; 47: use localauth; 48: use localenroll; 49: use localstudentphoto; 50: use File::Copy; 51: use File::Find; 52: use LONCAPA::lonlocal; 53: use LONCAPA::lonssl; 54: use Fcntl qw(:flock); 55: use Apache::lonnet; 56: use Mail::Send; 57: 58: my $DEBUG = 0; # Non zero to enable debug log entries. 59: 60: my $status=''; 61: my $lastlog=''; 62: 63: my $VERSION='$Revision: 1.489.2.2 $'; #' stupid emacs 64: my $remoteVERSION; 65: my $currenthostid="default"; 66: my $currentdomainid; 67: 68: my $client; 69: my $clientip; # IP address of client. 70: my $clientname; # LonCAPA name of client. 71: my $clientversion; # LonCAPA version running on client. 72: my $clienthomedom; # LonCAPA domain of homeID for client. 73: # primary library server. 74: 75: my $server; 76: 77: my $keymode; 78: 79: my $cipher; # Cipher key negotiated with client 80: my $tmpsnum = 0; # Id of tmpputs. 81: 82: # 83: # Connection type is: 84: # client - All client actions are allowed 85: # manager - only management functions allowed. 86: # both - Both management and client actions are allowed 87: # 88: 89: my $ConnectionType; 90: 91: my %managers; # Ip -> manager names 92: 93: my %perlvar; # Will have the apache conf defined perl vars. 94: 95: my $dist; 96: 97: # 98: # The hash below is used for command dispatching, and is therefore keyed on the request keyword. 99: # Each element of the hash contains a reference to an array that contains: 100: # A reference to a sub that executes the request corresponding to the keyword. 101: # A flag that is true if the request must be encoded to be acceptable. 102: # A mask with bits as follows: 103: # CLIENT_OK - Set when the function is allowed by ordinary clients 104: # MANAGER_OK - Set when the function is allowed to manager clients. 105: # 106: my $CLIENT_OK = 1; 107: my $MANAGER_OK = 2; 108: my %Dispatcher; 109: 110: 111: # 112: # The array below are password error strings." 113: # 114: my $lastpwderror = 13; # Largest error number from lcpasswd. 115: my @passwderrors = ("ok", 116: "pwchange_failure - lcpasswd must be run as user 'www'", 117: "pwchange_failure - lcpasswd got incorrect number of arguments", 118: "pwchange_failure - lcpasswd did not get the right nubmer of input text lines", 119: "pwchange_failure - lcpasswd too many simultaneous pwd changes in progress", 120: "pwchange_failure - lcpasswd User does not exist.", 121: "pwchange_failure - lcpasswd Incorrect current passwd", 122: "pwchange_failure - lcpasswd Unable to su to root.", 123: "pwchange_failure - lcpasswd Cannot set new passwd.", 124: "pwchange_failure - lcpasswd Username has invalid characters", 125: "pwchange_failure - lcpasswd Invalid characters in password", 126: "pwchange_failure - lcpasswd User already exists", 127: "pwchange_failure - lcpasswd Something went wrong with user addition.", 128: "pwchange_failure - lcpasswd Password mismatch", 129: "pwchange_failure - lcpasswd Error filename is invalid"); 130: 131: 132: # The array below are lcuseradd error strings.: 133: 134: my $lastadderror = 13; 135: my @adderrors = ("ok", 136: "User ID mismatch, lcuseradd must run as user www", 137: "lcuseradd Incorrect number of command line parameters must be 3", 138: "lcuseradd Incorrect number of stdinput lines, must be 3", 139: "lcuseradd Too many other simultaneous pwd changes in progress", 140: "lcuseradd User does not exist", 141: "lcuseradd Unable to make www member of users's group", 142: "lcuseradd Unable to su to root", 143: "lcuseradd Unable to set password", 144: "lcuseradd Username has invalid characters", 145: "lcuseradd Password has an invalid character", 146: "lcuseradd User already exists", 147: "lcuseradd Could not add user.", 148: "lcuseradd Password mismatch"); 149: 150: 151: # This array are the errors from lcinstallfile: 152: 153: my @installerrors = ("ok", 154: "Initial user id of client not that of www", 155: "Usage error, not enough command line arguments", 156: "Source file name does not exist", 157: "Destination file name does not exist", 158: "Some file operation failed", 159: "Invalid table filename." 160: ); 161: 162: # 163: # Statistics that are maintained and dislayed in the status line. 164: # 165: my $Transactions = 0; # Number of attempted transactions. 166: my $Failures = 0; # Number of transcations failed. 167: 168: # ResetStatistics: 169: # Resets the statistics counters: 170: # 171: sub ResetStatistics { 172: $Transactions = 0; 173: $Failures = 0; 174: } 175: 176: #------------------------------------------------------------------------ 177: # 178: # LocalConnection 179: # Completes the formation of a locally authenticated connection. 180: # This function will ensure that the 'remote' client is really the 181: # local host. If not, the connection is closed, and the function fails. 182: # If so, initcmd is parsed for the name of a file containing the 183: # IDEA session key. The fie is opened, read, deleted and the session 184: # key returned to the caller. 185: # 186: # Parameters: 187: # $Socket - Socket open on client. 188: # $initcmd - The full text of the init command. 189: # 190: # Returns: 191: # IDEA session key on success. 192: # undef on failure. 193: # 194: sub LocalConnection { 195: my ($Socket, $initcmd) = @_; 196: Debug("Attempting local connection: $initcmd client: $clientip"); 197: if($clientip ne "127.0.0.1") { 198: &logthis('<font color="red"> LocalConnection rejecting non local: ' 199: ."$clientip ne 127.0.0.1 </font>"); 200: close $Socket; 201: return undef; 202: } else { 203: chomp($initcmd); # Get rid of \n in filename. 204: my ($init, $type, $name) = split(/:/, $initcmd); 205: Debug(" Init command: $init $type $name "); 206: 207: # Require that $init = init, and $type = local: Otherwise 208: # the caller is insane: 209: 210: if(($init ne "init") && ($type ne "local")) { 211: &logthis('<font color = "red"> LocalConnection: caller is insane! ' 212: ."init = $init, and type = $type </font>"); 213: close($Socket);; 214: return undef; 215: 216: } 217: # Now get the key filename: 218: 219: my $IDEAKey = lonlocal::ReadKeyFile($name); 220: return $IDEAKey; 221: } 222: } 223: #------------------------------------------------------------------------------ 224: # 225: # SSLConnection 226: # Completes the formation of an ssh authenticated connection. The 227: # socket is promoted to an ssl socket. If this promotion and the associated 228: # certificate exchange are successful, the IDEA key is generated and sent 229: # to the remote peer via the SSL tunnel. The IDEA key is also returned to 230: # the caller after the SSL tunnel is torn down. 231: # 232: # Parameters: 233: # Name Type Purpose 234: # $Socket IO::Socket::INET Plaintext socket. 235: # 236: # Returns: 237: # IDEA key on success. 238: # undef on failure. 239: # 240: sub SSLConnection { 241: my $Socket = shift; 242: 243: Debug("SSLConnection: "); 244: my $KeyFile = lonssl::KeyFile(); 245: if(!$KeyFile) { 246: my $err = lonssl::LastError(); 247: &logthis("<font color=\"red\"> CRITICAL" 248: ."Can't get key file $err </font>"); 249: return undef; 250: } 251: my ($CACertificate, 252: $Certificate) = lonssl::CertificateFile(); 253: 254: 255: # If any of the key, certificate or certificate authority 256: # certificate filenames are not defined, this can't work. 257: 258: if((!$Certificate) || (!$CACertificate)) { 259: my $err = lonssl::LastError(); 260: &logthis("<font color=\"red\"> CRITICAL" 261: ."Can't get certificates: $err </font>"); 262: 263: return undef; 264: } 265: Debug("Key: $KeyFile CA: $CACertificate Cert: $Certificate"); 266: 267: # Indicate to our peer that we can procede with 268: # a transition to ssl authentication: 269: 270: print $Socket "ok:ssl\n"; 271: 272: Debug("Approving promotion -> ssl"); 273: # And do so: 274: 275: my $SSLSocket = lonssl::PromoteServerSocket($Socket, 276: $CACertificate, 277: $Certificate, 278: $KeyFile); 279: if(! ($SSLSocket) ) { # SSL socket promotion failed. 280: my $err = lonssl::LastError(); 281: &logthis("<font color=\"red\"> CRITICAL " 282: ."SSL Socket promotion failed: $err </font>"); 283: return undef; 284: } 285: Debug("SSL Promotion successful"); 286: 287: # 288: # The only thing we'll use the socket for is to send the IDEA key 289: # to the peer: 290: 291: my $Key = lonlocal::CreateCipherKey(); 292: print $SSLSocket "$Key\n"; 293: 294: lonssl::Close($SSLSocket); 295: 296: Debug("Key exchange complete: $Key"); 297: 298: return $Key; 299: } 300: # 301: # InsecureConnection: 302: # If insecure connections are allowd, 303: # exchange a challenge with the client to 'validate' the 304: # client (not really, but that's the protocol): 305: # We produce a challenge string that's sent to the client. 306: # The client must then echo the challenge verbatim to us. 307: # 308: # Parameter: 309: # Socket - Socket open on the client. 310: # Returns: 311: # 1 - success. 312: # 0 - failure (e.g.mismatch or insecure not allowed). 313: # 314: sub InsecureConnection { 315: my $Socket = shift; 316: 317: # Don't even start if insecure connections are not allowed. 318: 319: if(! $perlvar{londAllowInsecure}) { # Insecure connections not allowed. 320: return 0; 321: } 322: 323: # Fabricate a challenge string and send it.. 324: 325: my $challenge = "$$".time; # pid + time. 326: print $Socket "$challenge\n"; 327: &status("Waiting for challenge reply"); 328: 329: my $answer = <$Socket>; 330: $answer =~s/\W//g; 331: if($challenge eq $answer) { 332: return 1; 333: } else { 334: logthis("<font color='blue'>WARNING client did not respond to challenge</font>"); 335: &status("No challenge reqply"); 336: return 0; 337: } 338: 339: 340: } 341: # 342: # Safely execute a command (as long as it's not a shel command and doesn 343: # not require/rely on shell escapes. The function operates by doing a 344: # a pipe based fork and capturing stdout and stderr from the pipe. 345: # 346: # Formal Parameters: 347: # $line - A line of text to be executed as a command. 348: # Returns: 349: # The output from that command. If the output is multiline the caller 350: # must know how to split up the output. 351: # 352: # 353: sub execute_command { 354: my ($line) = @_; 355: my @words = split(/\s/, $line); # Bust the command up into words. 356: my $output = ""; 357: 358: my $pid = open(CHILD, "-|"); 359: 360: if($pid) { # Parent process 361: Debug("In parent process for execute_command"); 362: my @data = <CHILD>; # Read the child's outupt... 363: close CHILD; 364: foreach my $output_line (@data) { 365: Debug("Adding $output_line"); 366: $output .= $output_line; # Presumably has a \n on it. 367: } 368: 369: } else { # Child process 370: close (STDERR); 371: open (STDERR, ">&STDOUT");# Combine stderr, and stdout... 372: exec(@words); # won't return. 373: } 374: return $output; 375: } 376: 377: 378: # GetCertificate: Given a transaction that requires a certificate, 379: # this function will extract the certificate from the transaction 380: # request. Note that at this point, the only concept of a certificate 381: # is the hostname to which we are connected. 382: # 383: # Parameter: 384: # request - The request sent by our client (this parameterization may 385: # need to change when we really use a certificate granting 386: # authority. 387: # 388: sub GetCertificate { 389: my $request = shift; 390: 391: return $clientip; 392: } 393: 394: # 395: # Return true if client is a manager. 396: # 397: sub isManager { 398: return (($ConnectionType eq "manager") || ($ConnectionType eq "both")); 399: } 400: # 401: # Return tru if client can do client functions 402: # 403: sub isClient { 404: return (($ConnectionType eq "client") || ($ConnectionType eq "both")); 405: } 406: 407: 408: # 409: # ReadManagerTable: Reads in the current manager table. For now this is 410: # done on each manager authentication because: 411: # - These authentications are not frequent 412: # - This allows dynamic changes to the manager table 413: # without the need to signal to the lond. 414: # 415: sub ReadManagerTable { 416: 417: &Debug("Reading manager table"); 418: # Clean out the old table first.. 419: 420: foreach my $key (keys %managers) { 421: delete $managers{$key}; 422: } 423: 424: my $tablename = $perlvar{'lonTabDir'}."/managers.tab"; 425: if (!open (MANAGERS, $tablename)) { 426: my $hostname = &Apache::lonnet::hostname($perlvar{'lonHostID'}); 427: if (&Apache::lonnet::is_LC_dns($hostname)) { 428: &logthis('<font color="red">No manager table. Nobody can manage!!</font>'); 429: } 430: return; 431: } 432: while(my $host = <MANAGERS>) { 433: chomp($host); 434: if ($host =~ "^#") { # Comment line. 435: next; 436: } 437: if (!defined &Apache::lonnet::get_host_ip($host)) { # This is a non cluster member 438: # The entry is of the form: 439: # cluname:hostname 440: # cluname - A 'cluster hostname' is needed in order to negotiate 441: # the host key. 442: # hostname- The dns name of the host. 443: # 444: my($cluname, $dnsname) = split(/:/, $host); 445: 446: my $ip = gethostbyname($dnsname); 447: if(defined($ip)) { # bad names don't deserve entry. 448: my $hostip = inet_ntoa($ip); 449: $managers{$hostip} = $cluname; 450: logthis('<font color="green"> registering manager '. 451: "$dnsname as $cluname with $hostip </font>\n"); 452: } 453: } else { 454: logthis('<font color="green"> existing host'." $host</font>\n"); 455: $managers{&Apache::lonnet::get_host_ip($host)} = $host; # Use info from cluster tab if cluster memeber 456: } 457: } 458: } 459: 460: # 461: # ValidManager: Determines if a given certificate represents a valid manager. 462: # in this primitive implementation, the 'certificate' is 463: # just the connecting loncapa client name. This is checked 464: # against a valid client list in the configuration. 465: # 466: # 467: sub ValidManager { 468: my $certificate = shift; 469: 470: return isManager; 471: } 472: # 473: # CopyFile: Called as part of the process of installing a 474: # new configuration file. This function copies an existing 475: # file to a backup file. 476: # Parameters: 477: # oldfile - Name of the file to backup. 478: # newfile - Name of the backup file. 479: # Return: 480: # 0 - Failure (errno has failure reason). 481: # 1 - Success. 482: # 483: sub CopyFile { 484: 485: my ($oldfile, $newfile) = @_; 486: 487: if (! copy($oldfile,$newfile)) { 488: return 0; 489: } 490: chmod(0660, $newfile); 491: return 1; 492: } 493: # 494: # Host files are passed out with externally visible host IPs. 495: # If, for example, we are behind a fire-wall or NAT host, our 496: # internally visible IP may be different than the externally 497: # visible IP. Therefore, we always adjust the contents of the 498: # host file so that the entry for ME is the IP that we believe 499: # we have. At present, this is defined as the entry that 500: # DNS has for us. If by some chance we are not able to get a 501: # DNS translation for us, then we assume that the host.tab file 502: # is correct. 503: # BUGBUGBUG - in the future, we really should see if we can 504: # easily query the interface(s) instead. 505: # Parameter(s): 506: # contents - The contents of the host.tab to check. 507: # Returns: 508: # newcontents - The adjusted contents. 509: # 510: # 511: sub AdjustHostContents { 512: my $contents = shift; 513: my $adjusted; 514: my $me = $perlvar{'lonHostID'}; 515: 516: foreach my $line (split(/\n/,$contents)) { 517: if(!(($line eq "") || ($line =~ /^ *\#/) || ($line =~ /^ *$/) || 518: ($line =~ /^\s*\^/))) { 519: chomp($line); 520: my ($id,$domain,$role,$name,$ip,$maxcon,$idleto,$mincon)=split(/:/,$line); 521: if ($id eq $me) { 522: my $ip = gethostbyname($name); 523: my $ipnew = inet_ntoa($ip); 524: $ip = $ipnew; 525: # Reconstruct the host line and append to adjusted: 526: 527: my $newline = "$id:$domain:$role:$name:$ip"; 528: if($maxcon ne "") { # Not all hosts have loncnew tuning params 529: $newline .= ":$maxcon:$idleto:$mincon"; 530: } 531: $adjusted .= $newline."\n"; 532: 533: } else { # Not me, pass unmodified. 534: $adjusted .= $line."\n"; 535: } 536: } else { # Blank or comment never re-written. 537: $adjusted .= $line."\n"; # Pass blanks and comments as is. 538: } 539: } 540: return $adjusted; 541: } 542: # 543: # InstallFile: Called to install an administrative file: 544: # - The file is created int a temp directory called <name>.tmp 545: # - lcinstall file is called to install the file. 546: # since the web app has no direct write access to the table directory 547: # 548: # Parameters: 549: # Name of the file 550: # File Contents. 551: # Return: 552: # nonzero - success. 553: # 0 - failure and $! has an errno. 554: # Assumptions: 555: # File installtion is a relatively infrequent 556: # 557: sub InstallFile { 558: 559: my ($Filename, $Contents) = @_; 560: # my $TempFile = $Filename.".tmp"; 561: my $exedir = $perlvar{'lonDaemons'}; 562: my $tmpdir = $exedir.'/tmp/'; 563: my $TempFile = $tmpdir."TempTableFile.tmp"; 564: 565: # Open the file for write: 566: 567: my $fh = IO::File->new("> $TempFile"); # Write to temp. 568: if(!(defined $fh)) { 569: &logthis('<font color="red"> Unable to create '.$TempFile."</font>"); 570: return 0; 571: } 572: # write the contents of the file: 573: 574: print $fh ($Contents); 575: $fh->close; # In case we ever have a filesystem w. locking 576: 577: chmod(0664, $TempFile); # Everyone can write it. 578: 579: # Use lcinstall file to put the file in the table directory... 580: 581: &Debug("Opening pipe to $exedir/lcinstallfile $TempFile $Filename"); 582: my $pf = IO::File->new("| $exedir/lcinstallfile $TempFile $Filename > $exedir/logs/lcinstallfile.log"); 583: close $pf; 584: my $err = $?; 585: &Debug("Status is $err"); 586: if ($err != 0) { 587: my $msg = $err; 588: if ($err < @installerrors) { 589: $msg = $installerrors[$err]; 590: } 591: &logthis("Install failed for table file $Filename : $msg"); 592: return 0; 593: } 594: 595: # Remove the temp file: 596: 597: unlink($TempFile); 598: 599: return 1; 600: } 601: 602: 603: # 604: # ConfigFileFromSelector: converts a configuration file selector 605: # into a configuration file pathname. 606: # Supports the following file selectors: 607: # hosts, domain, dns_hosts, dns_domain 608: # 609: # 610: # Parameters: 611: # selector - Configuration file selector. 612: # Returns: 613: # Full path to the file or undef if the selector is invalid. 614: # 615: sub ConfigFileFromSelector { 616: my $selector = shift; 617: my $tablefile; 618: 619: my $tabledir = $perlvar{'lonTabDir'}.'/'; 620: if (($selector eq "hosts") || ($selector eq "domain") || 621: ($selector eq "dns_hosts") || ($selector eq "dns_domain")) { 622: $tablefile = $tabledir.$selector.'.tab'; 623: } 624: return $tablefile; 625: } 626: # 627: # PushFile: Called to do an administrative push of a file. 628: # - Ensure the file being pushed is one we support. 629: # - Backup the old file to <filename.saved> 630: # - Separate the contents of the new file out from the 631: # rest of the request. 632: # - Write the new file. 633: # Parameter: 634: # Request - The entire user request. This consists of a : separated 635: # string pushfile:tablename:contents. 636: # NOTE: The contents may have :'s in it as well making things a bit 637: # more interesting... but not much. 638: # Returns: 639: # String to send to client ("ok" or "refused" if bad file). 640: # 641: sub PushFile { 642: my $request = shift; 643: my ($command, $filename, $contents) = split(":", $request, 3); 644: &Debug("PushFile"); 645: 646: # At this point in time, pushes for only the following tables are 647: # supported: 648: # hosts.tab ($filename eq host). 649: # domain.tab ($filename eq domain). 650: # dns_hosts.tab ($filename eq dns_host). 651: # dns_domain.tab ($filename eq dns_domain). 652: # Construct the destination filename or reject the request. 653: # 654: # lonManage is supposed to ensure this, however this session could be 655: # part of some elaborate spoof that managed somehow to authenticate. 656: # 657: 658: 659: my $tablefile = ConfigFileFromSelector($filename); 660: if(! (defined $tablefile)) { 661: return "refused"; 662: } 663: 664: # If the file being pushed is the host file, we adjust the entry for ourself so that the 665: # IP will be our current IP as looked up in dns. Note this is only 99% good as it's possible 666: # to conceive of conditions where we don't have a DNS entry locally. This is possible in a 667: # network sense but it doesn't make much sense in a LonCAPA sense so we ignore (for now) 668: # that possibilty. 669: 670: if($filename eq "host") { 671: $contents = AdjustHostContents($contents); 672: } 673: 674: # Install the new file: 675: 676: &logthis("Installing new $tablefile contents:\n$contents"); 677: if(!InstallFile($tablefile, $contents)) { 678: &logthis('<font color="red"> Pushfile: unable to install ' 679: .$tablefile." $! </font>"); 680: return "error:$!"; 681: } else { 682: &logthis('<font color="green"> Installed new '.$tablefile 683: ." - transaction by: $clientname ($clientip)</font>"); 684: my $adminmail = $perlvar{'lonAdmEMail'}; 685: my $admindom = &Apache::lonnet::host_domain($perlvar{'lonHostID'}); 686: if ($admindom ne '') { 687: my %domconfig = 688: &Apache::lonnet::get_dom('configuration',['contacts'],$admindom); 689: if (ref($domconfig{'contacts'}) eq 'HASH') { 690: if ($domconfig{'contacts'}{'adminemail'} ne '') { 691: $adminmail = $domconfig{'contacts'}{'adminemail'}; 692: } 693: } 694: } 695: if ($adminmail =~ /^[^\@]+\@[^\@]+$/) { 696: my $msg = new Mail::Send; 697: $msg->to($adminmail); 698: $msg->subject('LON-CAPA DNS update on '.$perlvar{'lonHostID'}); 699: $msg->add('Content-type','text/plain; charset=UTF-8'); 700: if (my $fh = $msg->open()) { 701: print $fh 'Update to '.$tablefile.' from Cluster Manager '. 702: "$clientname ($clientip)\n"; 703: $fh->close; 704: } 705: } 706: } 707: 708: # Indicate success: 709: 710: return "ok"; 711: 712: } 713: 714: # 715: # Called to re-init either lonc or lond. 716: # 717: # Parameters: 718: # request - The full request by the client. This is of the form 719: # reinit:<process> 720: # where <process> is allowed to be either of 721: # lonc or lond 722: # 723: # Returns: 724: # The string to be sent back to the client either: 725: # ok - Everything worked just fine. 726: # error:why - There was a failure and why describes the reason. 727: # 728: # 729: sub ReinitProcess { 730: my $request = shift; 731: 732: 733: # separate the request (reinit) from the process identifier and 734: # validate it producing the name of the .pid file for the process. 735: # 736: # 737: my ($junk, $process) = split(":", $request); 738: my $processpidfile = $perlvar{'lonDaemons'}.'/logs/'; 739: if($process eq 'lonc') { 740: $processpidfile = $processpidfile."lonc.pid"; 741: if (!open(PIDFILE, "< $processpidfile")) { 742: return "error:Open failed for $processpidfile"; 743: } 744: my $loncpid = <PIDFILE>; 745: close(PIDFILE); 746: logthis('<font color="red"> Reinitializing lonc pid='.$loncpid 747: ."</font>"); 748: kill("USR2", $loncpid); 749: } elsif ($process eq 'lond') { 750: logthis('<font color="red"> Reinitializing self (lond) </font>'); 751: &UpdateHosts; # Lond is us!! 752: } else { 753: &logthis('<font color="yellow" Invalid reinit request for '.$process 754: ."</font>"); 755: return "error:Invalid process identifier $process"; 756: } 757: return 'ok'; 758: } 759: # Validate a line in a configuration file edit script: 760: # Validation includes: 761: # - Ensuring the command is valid. 762: # - Ensuring the command has sufficient parameters 763: # Parameters: 764: # scriptline - A line to validate (\n has been stripped for what it's worth). 765: # 766: # Return: 767: # 0 - Invalid scriptline. 768: # 1 - Valid scriptline 769: # NOTE: 770: # Only the command syntax is checked, not the executability of the 771: # command. 772: # 773: sub isValidEditCommand { 774: my $scriptline = shift; 775: 776: # Line elements are pipe separated: 777: 778: my ($command, $key, $newline) = split(/\|/, $scriptline); 779: &logthis('<font color="green"> isValideditCommand checking: '. 780: "Command = '$command', Key = '$key', Newline = '$newline' </font>\n"); 781: 782: if ($command eq "delete") { 783: # 784: # key with no newline. 785: # 786: if( ($key eq "") || ($newline ne "")) { 787: return 0; # Must have key but no newline. 788: } else { 789: return 1; # Valid syntax. 790: } 791: } elsif ($command eq "replace") { 792: # 793: # key and newline: 794: # 795: if (($key eq "") || ($newline eq "")) { 796: return 0; 797: } else { 798: return 1; 799: } 800: } elsif ($command eq "append") { 801: if (($key ne "") && ($newline eq "")) { 802: return 1; 803: } else { 804: return 0; 805: } 806: } else { 807: return 0; # Invalid command. 808: } 809: return 0; # Should not get here!!! 810: } 811: # 812: # ApplyEdit - Applies an edit command to a line in a configuration 813: # file. It is the caller's responsiblity to validate the 814: # edit line. 815: # Parameters: 816: # $directive - A single edit directive to apply. 817: # Edit directives are of the form: 818: # append|newline - Appends a new line to the file. 819: # replace|key|newline - Replaces the line with key value 'key' 820: # delete|key - Deletes the line with key value 'key'. 821: # $editor - A config file editor object that contains the 822: # file being edited. 823: # 824: sub ApplyEdit { 825: 826: my ($directive, $editor) = @_; 827: 828: # Break the directive down into its command and its parameters 829: # (at most two at this point. The meaning of the parameters, if in fact 830: # they exist depends on the command). 831: 832: my ($command, $p1, $p2) = split(/\|/, $directive); 833: 834: if($command eq "append") { 835: $editor->Append($p1); # p1 - key p2 null. 836: } elsif ($command eq "replace") { 837: $editor->ReplaceLine($p1, $p2); # p1 - key p2 = newline. 838: } elsif ($command eq "delete") { 839: $editor->DeleteLine($p1); # p1 - key p2 null. 840: } else { # Should not get here!!! 841: die "Invalid command given to ApplyEdit $command" 842: } 843: } 844: # 845: # AdjustOurHost: 846: # Adjusts a host file stored in a configuration file editor object 847: # for the true IP address of this host. This is necessary for hosts 848: # that live behind a firewall. 849: # Those hosts have a publicly distributed IP of the firewall, but 850: # internally must use their actual IP. We assume that a given 851: # host only has a single IP interface for now. 852: # Formal Parameters: 853: # editor - The configuration file editor to adjust. This 854: # editor is assumed to contain a hosts.tab file. 855: # Strategy: 856: # - Figure out our hostname. 857: # - Lookup the entry for this host. 858: # - Modify the line to contain our IP 859: # - Do a replace for this host. 860: sub AdjustOurHost { 861: my $editor = shift; 862: 863: # figure out who I am. 864: 865: my $myHostName = $perlvar{'lonHostID'}; # LonCAPA hostname. 866: 867: # Get my host file entry. 868: 869: my $ConfigLine = $editor->Find($myHostName); 870: if(! (defined $ConfigLine)) { 871: die "AdjustOurHost - no entry for me in hosts file $myHostName"; 872: } 873: # figure out my IP: 874: # Use the config line to get my hostname. 875: # Use gethostbyname to translate that into an IP address. 876: # 877: my ($id,$domain,$role,$name,$maxcon,$idleto,$mincon) = split(/:/,$ConfigLine); 878: # 879: # Reassemble the config line from the elements in the list. 880: # Note that if the loncnew items were not present before, they will 881: # be now even if they would be empty 882: # 883: my $newConfigLine = $id; 884: foreach my $item ($domain, $role, $name, $maxcon, $idleto, $mincon) { 885: $newConfigLine .= ":".$item; 886: } 887: # Replace the line: 888: 889: $editor->ReplaceLine($id, $newConfigLine); 890: 891: } 892: # 893: # ReplaceConfigFile: 894: # Replaces a configuration file with the contents of a 895: # configuration file editor object. 896: # This is done by: 897: # - Copying the target file to <filename>.old 898: # - Writing the new file to <filename>.tmp 899: # - Moving <filename.tmp> -> <filename> 900: # This laborious process ensures that the system is never without 901: # a configuration file that's at least valid (even if the contents 902: # may be dated). 903: # Parameters: 904: # filename - Name of the file to modify... this is a full path. 905: # editor - Editor containing the file. 906: # 907: sub ReplaceConfigFile { 908: 909: my ($filename, $editor) = @_; 910: 911: CopyFile ($filename, $filename.".old"); 912: 913: my $contents = $editor->Get(); # Get the contents of the file. 914: 915: InstallFile($filename, $contents); 916: } 917: # 918: # 919: # Called to edit a configuration table file 920: # Parameters: 921: # request - The entire command/request sent by lonc or lonManage 922: # Return: 923: # The reply to send to the client. 924: # 925: sub EditFile { 926: my $request = shift; 927: 928: # Split the command into it's pieces: edit:filetype:script 929: 930: my ($cmd, $filetype, $script) = split(/:/, $request,3); # : in script 931: 932: # Check the pre-coditions for success: 933: 934: if($cmd != "edit") { # Something is amiss afoot alack. 935: return "error:edit request detected, but request != 'edit'\n"; 936: } 937: if( ($filetype ne "hosts") && 938: ($filetype ne "domain")) { 939: return "error:edit requested with invalid file specifier: $filetype \n"; 940: } 941: 942: # Split the edit script and check it's validity. 943: 944: my @scriptlines = split(/\n/, $script); # one line per element. 945: my $linecount = scalar(@scriptlines); 946: for(my $i = 0; $i < $linecount; $i++) { 947: chomp($scriptlines[$i]); 948: if(!isValidEditCommand($scriptlines[$i])) { 949: return "error:edit with bad script line: '$scriptlines[$i]' \n"; 950: } 951: } 952: 953: # Execute the edit operation. 954: # - Create a config file editor for the appropriate file and 955: # - execute each command in the script: 956: # 957: my $configfile = ConfigFileFromSelector($filetype); 958: if (!(defined $configfile)) { 959: return "refused\n"; 960: } 961: my $editor = ConfigFileEdit->new($configfile); 962: 963: for (my $i = 0; $i < $linecount; $i++) { 964: ApplyEdit($scriptlines[$i], $editor); 965: } 966: # If the file is the host file, ensure that our host is 967: # adjusted to have our ip: 968: # 969: if($filetype eq "host") { 970: AdjustOurHost($editor); 971: } 972: # Finally replace the current file with our file. 973: # 974: ReplaceConfigFile($configfile, $editor); 975: 976: return "ok\n"; 977: } 978: 979: # read_profile 980: # 981: # Returns a set of specific entries from a user's profile file. 982: # this is a utility function that is used by both get_profile_entry and 983: # get_profile_entry_encrypted. 984: # 985: # Parameters: 986: # udom - Domain in which the user exists. 987: # uname - User's account name (loncapa account) 988: # namespace - The profile namespace to open. 989: # what - A set of & separated queries. 990: # Returns: 991: # If all ok: - The string that needs to be shipped back to the user. 992: # If failure - A string that starts with error: followed by the failure 993: # reason.. note that this probabyl gets shipped back to the 994: # user as well. 995: # 996: sub read_profile { 997: my ($udom, $uname, $namespace, $what) = @_; 998: 999: my $hashref = &tie_user_hash($udom, $uname, $namespace, 1000: &GDBM_READER()); 1001: if ($hashref) { 1002: my @queries=split(/\&/,$what); 1003: if ($namespace eq 'roles') { 1004: @queries = map { &unescape($_); } @queries; 1005: } 1006: my $qresult=''; 1007: 1008: for (my $i=0;$i<=$#queries;$i++) { 1009: $qresult.="$hashref->{$queries[$i]}&"; # Presumably failure gives empty string. 1010: } 1011: $qresult=~s/\&$//; # Remove trailing & from last lookup. 1012: if (&untie_user_hash($hashref)) { 1013: return $qresult; 1014: } else { 1015: return "error: ".($!+0)." untie (GDBM) Failed"; 1016: } 1017: } else { 1018: if ($!+0 == 2) { 1019: return "error:No such file or GDBM reported bad block error"; 1020: } else { 1021: return "error: ".($!+0)." tie (GDBM) Failed"; 1022: } 1023: } 1024: 1025: } 1026: #--------------------- Request Handlers -------------------------------------------- 1027: # 1028: # By convention each request handler registers itself prior to the sub 1029: # declaration: 1030: # 1031: 1032: #++ 1033: # 1034: # Handles ping requests. 1035: # Parameters: 1036: # $cmd - the actual keyword that invoked us. 1037: # $tail - the tail of the request that invoked us. 1038: # $replyfd- File descriptor connected to the client 1039: # Implicit Inputs: 1040: # $currenthostid - Global variable that carries the name of the host we are 1041: # known as. 1042: # Returns: 1043: # 1 - Ok to continue processing. 1044: # 0 - Program should exit. 1045: # Side effects: 1046: # Reply information is sent to the client. 1047: sub ping_handler { 1048: my ($cmd, $tail, $client) = @_; 1049: Debug("$cmd $tail $client .. $currenthostid:"); 1050: 1051: Reply( $client,\$currenthostid,"$cmd:$tail"); 1052: 1053: return 1; 1054: } 1055: ®ister_handler("ping", \&ping_handler, 0, 1, 1); # Ping unencoded, client or manager. 1056: 1057: #++ 1058: # 1059: # Handles pong requests. Pong replies with our current host id, and 1060: # the results of a ping sent to us via our lonc. 1061: # 1062: # Parameters: 1063: # $cmd - the actual keyword that invoked us. 1064: # $tail - the tail of the request that invoked us. 1065: # $replyfd- File descriptor connected to the client 1066: # Implicit Inputs: 1067: # $currenthostid - Global variable that carries the name of the host we are 1068: # connected to. 1069: # Returns: 1070: # 1 - Ok to continue processing. 1071: # 0 - Program should exit. 1072: # Side effects: 1073: # Reply information is sent to the client. 1074: sub pong_handler { 1075: my ($cmd, $tail, $replyfd) = @_; 1076: 1077: my $reply=&Apache::lonnet::reply("ping",$clientname); 1078: &Reply( $replyfd, "$currenthostid:$reply\n", "$cmd:$tail"); 1079: return 1; 1080: } 1081: ®ister_handler("pong", \&pong_handler, 0, 1, 1); # Pong unencoded, client or manager 1082: 1083: #++ 1084: # Called to establish an encrypted session key with the remote client. 1085: # Note that with secure lond, in most cases this function is never 1086: # invoked. Instead, the secure session key is established either 1087: # via a local file that's locked down tight and only lives for a short 1088: # time, or via an ssl tunnel...and is generated from a bunch-o-random 1089: # bits from /dev/urandom, rather than the predictable pattern used by 1090: # by this sub. This sub is only used in the old-style insecure 1091: # key negotiation. 1092: # Parameters: 1093: # $cmd - the actual keyword that invoked us. 1094: # $tail - the tail of the request that invoked us. 1095: # $replyfd- File descriptor connected to the client 1096: # Implicit Inputs: 1097: # $currenthostid - Global variable that carries the name of the host 1098: # known as. 1099: # $clientname - Global variable that carries the name of the host we're connected to. 1100: # Returns: 1101: # 1 - Ok to continue processing. 1102: # 0 - Program should exit. 1103: # Implicit Outputs: 1104: # Reply information is sent to the client. 1105: # $cipher is set with a reference to a new IDEA encryption object. 1106: # 1107: sub establish_key_handler { 1108: my ($cmd, $tail, $replyfd) = @_; 1109: 1110: my $buildkey=time.$$.int(rand 100000); 1111: $buildkey=~tr/1-6/A-F/; 1112: $buildkey=int(rand 100000).$buildkey.int(rand 100000); 1113: my $key=$currenthostid.$clientname; 1114: $key=~tr/a-z/A-Z/; 1115: $key=~tr/G-P/0-9/; 1116: $key=~tr/Q-Z/0-9/; 1117: $key=$key.$buildkey.$key.$buildkey.$key.$buildkey; 1118: $key=substr($key,0,32); 1119: my $cipherkey=pack("H32",$key); 1120: $cipher=new IDEA $cipherkey; 1121: &Reply($replyfd, \$buildkey, "$cmd:$tail"); 1122: 1123: return 1; 1124: 1125: } 1126: ®ister_handler("ekey", \&establish_key_handler, 0, 1,1); 1127: 1128: # Handler for the load command. Returns the current system load average 1129: # to the requestor. 1130: # 1131: # Parameters: 1132: # $cmd - the actual keyword that invoked us. 1133: # $tail - the tail of the request that invoked us. 1134: # $replyfd- File descriptor connected to the client 1135: # Implicit Inputs: 1136: # $currenthostid - Global variable that carries the name of the host 1137: # known as. 1138: # $clientname - Global variable that carries the name of the host we're connected to. 1139: # Returns: 1140: # 1 - Ok to continue processing. 1141: # 0 - Program should exit. 1142: # Side effects: 1143: # Reply information is sent to the client. 1144: sub load_handler { 1145: my ($cmd, $tail, $replyfd) = @_; 1146: 1147: 1148: 1149: # Get the load average from /proc/loadavg and calculate it as a percentage of 1150: # the allowed load limit as set by the perl global variable lonLoadLim 1151: 1152: my $loadavg; 1153: my $loadfile=IO::File->new('/proc/loadavg'); 1154: 1155: $loadavg=<$loadfile>; 1156: $loadavg =~ s/\s.*//g; # Extract the first field only. 1157: 1158: my $loadpercent=100*$loadavg/$perlvar{'lonLoadLim'}; 1159: 1160: &Reply( $replyfd, \$loadpercent, "$cmd:$tail"); 1161: 1162: return 1; 1163: } 1164: ®ister_handler("load", \&load_handler, 0, 1, 0); 1165: 1166: # 1167: # Process the userload request. This sub returns to the client the current 1168: # user load average. It can be invoked either by clients or managers. 1169: # 1170: # Parameters: 1171: # $cmd - the actual keyword that invoked us. 1172: # $tail - the tail of the request that invoked us. 1173: # $replyfd- File descriptor connected to the client 1174: # Implicit Inputs: 1175: # $currenthostid - Global variable that carries the name of the host 1176: # known as. 1177: # $clientname - Global variable that carries the name of the host we're connected to. 1178: # Returns: 1179: # 1 - Ok to continue processing. 1180: # 0 - Program should exit 1181: # Implicit inputs: 1182: # whatever the userload() function requires. 1183: # Implicit outputs: 1184: # the reply is written to the client. 1185: # 1186: sub user_load_handler { 1187: my ($cmd, $tail, $replyfd) = @_; 1188: 1189: my $userloadpercent=&Apache::lonnet::userload(); 1190: &Reply($replyfd, \$userloadpercent, "$cmd:$tail"); 1191: 1192: return 1; 1193: } 1194: ®ister_handler("userload", \&user_load_handler, 0, 1, 0); 1195: 1196: # Process a request for the authorization type of a user: 1197: # (userauth). 1198: # 1199: # Parameters: 1200: # $cmd - the actual keyword that invoked us. 1201: # $tail - the tail of the request that invoked us. 1202: # $replyfd- File descriptor connected to the client 1203: # Returns: 1204: # 1 - Ok to continue processing. 1205: # 0 - Program should exit 1206: # Implicit outputs: 1207: # The user authorization type is written to the client. 1208: # 1209: sub user_authorization_type { 1210: my ($cmd, $tail, $replyfd) = @_; 1211: 1212: my $userinput = "$cmd:$tail"; 1213: 1214: # Pull the domain and username out of the command tail. 1215: # and call get_auth_type to determine the authentication type. 1216: 1217: my ($udom,$uname)=split(/:/,$tail); 1218: my $result = &get_auth_type($udom, $uname); 1219: if($result eq "nouser") { 1220: &Failure( $replyfd, "unknown_user\n", $userinput); 1221: } else { 1222: # 1223: # We only want to pass the second field from get_auth_type 1224: # for ^krb.. otherwise we'll be handing out the encrypted 1225: # password for internals e.g. 1226: # 1227: my ($type,$otherinfo) = split(/:/,$result); 1228: if($type =~ /^krb/) { 1229: $type = $result; 1230: } else { 1231: $type .= ':'; 1232: } 1233: &Reply( $replyfd, \$type, $userinput); 1234: } 1235: 1236: return 1; 1237: } 1238: ®ister_handler("currentauth", \&user_authorization_type, 1, 1, 0); 1239: 1240: # Process a request by a manager to push a hosts or domain table 1241: # to us. We pick apart the command and pass it on to the subs 1242: # that already exist to do this. 1243: # 1244: # Parameters: 1245: # $cmd - the actual keyword that invoked us. 1246: # $tail - the tail of the request that invoked us. 1247: # $client - File descriptor connected to the client 1248: # Returns: 1249: # 1 - Ok to continue processing. 1250: # 0 - Program should exit 1251: # Implicit Output: 1252: # a reply is written to the client. 1253: sub push_file_handler { 1254: my ($cmd, $tail, $client) = @_; 1255: &Debug("In push file handler"); 1256: my $userinput = "$cmd:$tail"; 1257: 1258: # At this time we only know that the IP of our partner is a valid manager 1259: # the code below is a hook to do further authentication (e.g. to resolve 1260: # spoofing). 1261: 1262: my $cert = &GetCertificate($userinput); 1263: if(&ValidManager($cert)) { 1264: &Debug("Valid manager: $client"); 1265: 1266: # Now presumably we have the bona fides of both the peer host and the 1267: # process making the request. 1268: 1269: my $reply = &PushFile($userinput); 1270: &Reply($client, \$reply, $userinput); 1271: 1272: } else { 1273: &logthis("push_file_handler $client is not valid"); 1274: &Failure( $client, "refused\n", $userinput); 1275: } 1276: return 1; 1277: } 1278: ®ister_handler("pushfile", \&push_file_handler, 1, 0, 1); 1279: 1280: # The du_handler routine should be considered obsolete and is retained 1281: # for communication with legacy servers. Please see the du2_handler. 1282: # 1283: # du - list the disk usage of a directory recursively. 1284: # 1285: # note: stolen code from the ls file handler 1286: # under construction by Rick Banghart 1287: # . 1288: # Parameters: 1289: # $cmd - The command that dispatched us (du). 1290: # $ududir - The directory path to list... I'm not sure what this 1291: # is relative as things like ls:. return e.g. 1292: # no_such_dir. 1293: # $client - Socket open on the client. 1294: # Returns: 1295: # 1 - indicating that the daemon should not disconnect. 1296: # Side Effects: 1297: # The reply is written to $client. 1298: # 1299: sub du_handler { 1300: my ($cmd, $ududir, $client) = @_; 1301: ($ududir) = split(/:/,$ududir); # Make 'telnet' testing easier. 1302: my $userinput = "$cmd:$ududir"; 1303: 1304: if ($ududir=~/\.\./ || $ududir!~m|^/home/httpd/|) { 1305: &Failure($client,"refused\n","$cmd:$ududir"); 1306: return 1; 1307: } 1308: # Since $ududir could have some nasties in it, 1309: # we will require that ududir is a valid 1310: # directory. Just in case someone tries to 1311: # slip us a line like .;(cd /home/httpd rm -rf*) 1312: # etc. 1313: # 1314: if (-d $ududir) { 1315: my $total_size=0; 1316: my $code=sub { 1317: if ($_=~/\.\d+\./) { return;} 1318: if ($_=~/\.meta$/) { return;} 1319: if (-d $_) { return;} 1320: $total_size+=(stat($_))[7]; 1321: }; 1322: chdir($ududir); 1323: find($code,$ududir); 1324: $total_size=int($total_size/1024); 1325: &Reply($client,\$total_size,"$cmd:$ududir"); 1326: } else { 1327: &Failure($client, "bad_directory:$ududir\n","$cmd:$ududir"); 1328: } 1329: return 1; 1330: } 1331: ®ister_handler("du", \&du_handler, 0, 1, 0); 1332: 1333: # Please also see the du_handler, which is obsoleted by du2. 1334: # du2_handler differs from du_handler in that required path to directory 1335: # provided by &propath() is prepended in the handler instead of on the 1336: # client side. 1337: # 1338: # du2 - list the disk usage of a directory recursively. 1339: # 1340: # Parameters: 1341: # $cmd - The command that dispatched us (du). 1342: # $tail - The tail of the request that invoked us. 1343: # $tail is a : separated list of the following: 1344: # - $ududir - directory path to list (before prepending) 1345: # - $getpropath = 1 if &propath() should prepend 1346: # - $uname - username to use for &propath or user dir 1347: # - $udom - domain to use for &propath or user dir 1348: # All are escaped. 1349: # $client - Socket open on the client. 1350: # Returns: 1351: # 1 - indicating that the daemon should not disconnect. 1352: # Side Effects: 1353: # The reply is written to $client. 1354: # 1355: 1356: sub du2_handler { 1357: my ($cmd, $tail, $client) = @_; 1358: my ($ududir,$getpropath,$uname,$udom) = map { &unescape($_) } (split(/:/, $tail)); 1359: my $userinput = "$cmd:$tail"; 1360: if (($ududir=~/\.\./) || (($ududir!~m|^/home/httpd/|) && (!$getpropath))) { 1361: &Failure($client,"refused\n","$cmd:$tail"); 1362: return 1; 1363: } 1364: if ($getpropath) { 1365: if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) { 1366: $ududir = &propath($udom,$uname).'/'.$ududir; 1367: } else { 1368: &Failure($client,"refused\n","$cmd:$tail"); 1369: return 1; 1370: } 1371: } 1372: # Since $ududir could have some nasties in it, 1373: # we will require that ududir is a valid 1374: # directory. Just in case someone tries to 1375: # slip us a line like .;(cd /home/httpd rm -rf*) 1376: # etc. 1377: # 1378: if (-d $ududir) { 1379: my $total_size=0; 1380: my $code=sub { 1381: if ($_=~/\.\d+\./) { return;} 1382: if ($_=~/\.meta$/) { return;} 1383: if (-d $_) { return;} 1384: $total_size+=(stat($_))[7]; 1385: }; 1386: chdir($ududir); 1387: find($code,$ududir); 1388: $total_size=int($total_size/1024); 1389: &Reply($client,\$total_size,"$cmd:$ududir"); 1390: } else { 1391: &Failure($client, "bad_directory:$ududir\n","$cmd:$tail"); 1392: } 1393: return 1; 1394: } 1395: ®ister_handler("du2", \&du2_handler, 0, 1, 0); 1396: 1397: # 1398: # The ls_handler routine should be considered obsolete and is retained 1399: # for communication with legacy servers. Please see the ls3_handler. 1400: # 1401: # ls - list the contents of a directory. For each file in the 1402: # selected directory the filename followed by the full output of 1403: # the stat function is returned. The returned info for each 1404: # file are separated by ':'. The stat fields are separated by &'s. 1405: # Parameters: 1406: # $cmd - The command that dispatched us (ls). 1407: # $ulsdir - The directory path to list... I'm not sure what this 1408: # is relative as things like ls:. return e.g. 1409: # no_such_dir. 1410: # $client - Socket open on the client. 1411: # Returns: 1412: # 1 - indicating that the daemon should not disconnect. 1413: # Side Effects: 1414: # The reply is written to $client. 1415: # 1416: sub ls_handler { 1417: # obsoleted by ls2_handler 1418: my ($cmd, $ulsdir, $client) = @_; 1419: 1420: my $userinput = "$cmd:$ulsdir"; 1421: 1422: my $obs; 1423: my $rights; 1424: my $ulsout=''; 1425: my $ulsfn; 1426: if (-e $ulsdir) { 1427: if(-d $ulsdir) { 1428: if (opendir(LSDIR,$ulsdir)) { 1429: while ($ulsfn=readdir(LSDIR)) { 1430: undef($obs); 1431: undef($rights); 1432: my @ulsstats=stat($ulsdir.'/'.$ulsfn); 1433: #We do some obsolete checking here 1434: if(-e $ulsdir.'/'.$ulsfn.".meta") { 1435: open(FILE, $ulsdir.'/'.$ulsfn.".meta"); 1436: my @obsolete=<FILE>; 1437: foreach my $obsolete (@obsolete) { 1438: if($obsolete =~ m/(<obsolete>)(on|1)/) { $obs = 1; } 1439: if($obsolete =~ m|(<copyright>)(default)|) { $rights = 1; } 1440: } 1441: } 1442: $ulsout.=$ulsfn.'&'.join('&',@ulsstats); 1443: if($obs eq '1') { $ulsout.="&1"; } 1444: else { $ulsout.="&0"; } 1445: if($rights eq '1') { $ulsout.="&1:"; } 1446: else { $ulsout.="&0:"; } 1447: } 1448: closedir(LSDIR); 1449: } 1450: } else { 1451: my @ulsstats=stat($ulsdir); 1452: $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; 1453: } 1454: } else { 1455: $ulsout='no_such_dir'; 1456: } 1457: if ($ulsout eq '') { $ulsout='empty'; } 1458: &Reply($client, \$ulsout, $userinput); # This supports debug logging. 1459: 1460: return 1; 1461: 1462: } 1463: ®ister_handler("ls", \&ls_handler, 0, 1, 0); 1464: 1465: # The ls2_handler routine should be considered obsolete and is retained 1466: # for communication with legacy servers. Please see the ls3_handler. 1467: # Please also see the ls_handler, which was itself obsoleted by ls2. 1468: # ls2_handler differs from ls_handler in that it escapes its return 1469: # values before concatenating them together with ':'s. 1470: # 1471: # ls2 - list the contents of a directory. For each file in the 1472: # selected directory the filename followed by the full output of 1473: # the stat function is returned. The returned info for each 1474: # file are separated by ':'. The stat fields are separated by &'s. 1475: # Parameters: 1476: # $cmd - The command that dispatched us (ls). 1477: # $ulsdir - The directory path to list... I'm not sure what this 1478: # is relative as things like ls:. return e.g. 1479: # no_such_dir. 1480: # $client - Socket open on the client. 1481: # Returns: 1482: # 1 - indicating that the daemon should not disconnect. 1483: # Side Effects: 1484: # The reply is written to $client. 1485: # 1486: sub ls2_handler { 1487: my ($cmd, $ulsdir, $client) = @_; 1488: 1489: my $userinput = "$cmd:$ulsdir"; 1490: 1491: my $obs; 1492: my $rights; 1493: my $ulsout=''; 1494: my $ulsfn; 1495: if (-e $ulsdir) { 1496: if(-d $ulsdir) { 1497: if (opendir(LSDIR,$ulsdir)) { 1498: while ($ulsfn=readdir(LSDIR)) { 1499: undef($obs); 1500: undef($rights); 1501: my @ulsstats=stat($ulsdir.'/'.$ulsfn); 1502: #We do some obsolete checking here 1503: if(-e $ulsdir.'/'.$ulsfn.".meta") { 1504: open(FILE, $ulsdir.'/'.$ulsfn.".meta"); 1505: my @obsolete=<FILE>; 1506: foreach my $obsolete (@obsolete) { 1507: if($obsolete =~ m/(<obsolete>)(on|1)/) { $obs = 1; } 1508: if($obsolete =~ m|(<copyright>)(default)|) { 1509: $rights = 1; 1510: } 1511: } 1512: } 1513: my $tmp = $ulsfn.'&'.join('&',@ulsstats); 1514: if ($obs eq '1') { $tmp.="&1"; } else { $tmp.="&0"; } 1515: if ($rights eq '1') { $tmp.="&1"; } else { $tmp.="&0"; } 1516: $ulsout.= &escape($tmp).':'; 1517: } 1518: closedir(LSDIR); 1519: } 1520: } else { 1521: my @ulsstats=stat($ulsdir); 1522: $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; 1523: } 1524: } else { 1525: $ulsout='no_such_dir'; 1526: } 1527: if ($ulsout eq '') { $ulsout='empty'; } 1528: &Reply($client, \$ulsout, $userinput); # This supports debug logging. 1529: return 1; 1530: } 1531: ®ister_handler("ls2", \&ls2_handler, 0, 1, 0); 1532: # 1533: # ls3 - list the contents of a directory. For each file in the 1534: # selected directory the filename followed by the full output of 1535: # the stat function is returned. The returned info for each 1536: # file are separated by ':'. The stat fields are separated by &'s. 1537: # Parameters: 1538: # $cmd - The command that dispatched us (ls). 1539: # $tail - The tail of the request that invoked us. 1540: # $tail is a : separated list of the following: 1541: # - $ulsdir - directory path to list (before prepending) 1542: # - $getpropath = 1 if &propath() should prepend 1543: # - $getuserdir = 1 if path to user dir in lonUsers should 1544: # prepend 1545: # - $alternate_root - path to prepend 1546: # - $uname - username to use for &propath or user dir 1547: # - $udom - domain to use for &propath or user dir 1548: # All of these except $getpropath and &getuserdir are escaped. 1549: # no_such_dir. 1550: # $client - Socket open on the client. 1551: # Returns: 1552: # 1 - indicating that the daemon should not disconnect. 1553: # Side Effects: 1554: # The reply is written to $client. 1555: # 1556: 1557: sub ls3_handler { 1558: my ($cmd, $tail, $client) = @_; 1559: my $userinput = "$cmd:$tail"; 1560: my ($ulsdir,$getpropath,$getuserdir,$alternate_root,$uname,$udom) = 1561: split(/:/,$tail); 1562: if (defined($ulsdir)) { 1563: $ulsdir = &unescape($ulsdir); 1564: } 1565: if (defined($alternate_root)) { 1566: $alternate_root = &unescape($alternate_root); 1567: } 1568: if (defined($uname)) { 1569: $uname = &unescape($uname); 1570: } 1571: if (defined($udom)) { 1572: $udom = &unescape($udom); 1573: } 1574: 1575: my $dir_root = $perlvar{'lonDocRoot'}; 1576: if ($getpropath) { 1577: if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) { 1578: $dir_root = &propath($udom,$uname); 1579: $dir_root =~ s/\/$//; 1580: } else { 1581: &Failure($client,"refused\n","$cmd:$tail"); 1582: return 1; 1583: } 1584: } elsif ($getuserdir) { 1585: if (($uname =~ /^$LONCAPA::match_name$/) && ($udom =~ /^$LONCAPA::match_domain$/)) { 1586: my $subdir=$uname.'__'; 1587: $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/; 1588: $dir_root = $Apache::lonnet::perlvar{'lonUsersDir'} 1589: ."/$udom/$subdir/$uname"; 1590: } else { 1591: &Failure($client,"refused\n","$cmd:$tail"); 1592: return 1; 1593: } 1594: } elsif ($alternate_root ne '') { 1595: $dir_root = $alternate_root; 1596: } 1597: if (($dir_root ne '') && ($dir_root ne '/')) { 1598: if ($ulsdir =~ /^\//) { 1599: $ulsdir = $dir_root.$ulsdir; 1600: } else { 1601: $ulsdir = $dir_root.'/'.$ulsdir; 1602: } 1603: } 1604: my $obs; 1605: my $rights; 1606: my $ulsout=''; 1607: my $ulsfn; 1608: if (-e $ulsdir) { 1609: if(-d $ulsdir) { 1610: if (opendir(LSDIR,$ulsdir)) { 1611: while ($ulsfn=readdir(LSDIR)) { 1612: undef($obs); 1613: undef($rights); 1614: my @ulsstats=stat($ulsdir.'/'.$ulsfn); 1615: #We do some obsolete checking here 1616: if(-e $ulsdir.'/'.$ulsfn.".meta") { 1617: open(FILE, $ulsdir.'/'.$ulsfn.".meta"); 1618: my @obsolete=<FILE>; 1619: foreach my $obsolete (@obsolete) { 1620: if($obsolete =~ m/(<obsolete>)(on|1)/) { $obs = 1; } 1621: if($obsolete =~ m|(<copyright>)(default)|) { 1622: $rights = 1; 1623: } 1624: } 1625: } 1626: my $tmp = $ulsfn.'&'.join('&',@ulsstats); 1627: if ($obs eq '1') { $tmp.="&1"; } else { $tmp.="&0"; } 1628: if ($rights eq '1') { $tmp.="&1"; } else { $tmp.="&0"; } 1629: $ulsout.= &escape($tmp).':'; 1630: } 1631: closedir(LSDIR); 1632: } 1633: } else { 1634: my @ulsstats=stat($ulsdir); 1635: $ulsout.=$ulsfn.'&'.join('&',@ulsstats).':'; 1636: } 1637: } else { 1638: $ulsout='no_such_dir'; 1639: } 1640: if ($ulsout eq '') { $ulsout='empty'; } 1641: &Reply($client, \$ulsout, $userinput); # This supports debug logging. 1642: return 1; 1643: } 1644: ®ister_handler("ls3", \&ls3_handler, 0, 1, 0); 1645: 1646: sub read_lonnet_global { 1647: my ($cmd,$tail,$client) = @_; 1648: my $userinput = "$cmd:$tail"; 1649: my $requested = &Apache::lonnet::thaw_unescape($tail); 1650: my $result; 1651: my %packagevars = ( 1652: spareid => \%Apache::lonnet::spareid, 1653: perlvar => \%Apache::lonnet::perlvar, 1654: ); 1655: my %limit_to = ( 1656: perlvar => { 1657: lonOtherAuthen => 1, 1658: lonBalancer => 1, 1659: lonVersion => 1, 1660: lonSysEMail => 1, 1661: lonHostID => 1, 1662: lonRole => 1, 1663: lonDefDomain => 1, 1664: lonLoadLim => 1, 1665: lonUserLoadLim => 1, 1666: } 1667: ); 1668: if (ref($requested) eq 'HASH') { 1669: foreach my $what (keys(%{$requested})) { 1670: my $response; 1671: my $items = {}; 1672: if (exists($packagevars{$what})) { 1673: if (ref($limit_to{$what}) eq 'HASH') { 1674: foreach my $varname (keys(%{$packagevars{$what}})) { 1675: if ($limit_to{$what}{$varname}) { 1676: $items->{$varname} = $packagevars{$what}{$varname}; 1677: } 1678: } 1679: } else { 1680: $items = $packagevars{$what}; 1681: } 1682: if ($what eq 'perlvar') { 1683: if (!exists($packagevars{$what}{'lonBalancer'})) { 1684: if ($dist =~ /^(centos|rhes|fedora|scientific)/) { 1685: my $othervarref=LONCAPA::Configuration::read_conf('httpd.conf'); 1686: if (ref($othervarref) eq 'HASH') { 1687: $items->{'lonBalancer'} = $othervarref->{'lonBalancer'}; 1688: } 1689: } 1690: } 1691: } 1692: $response = &Apache::lonnet::freeze_escape($items); 1693: } 1694: $result .= &escape($what).'='.$response.'&'; 1695: } 1696: } 1697: $result =~ s/\&$//; 1698: &Reply($client,\$result,$userinput); 1699: return 1; 1700: } 1701: ®ister_handler("readlonnetglobal", \&read_lonnet_global, 0, 1, 0); 1702: 1703: sub server_devalidatecache_handler { 1704: my ($cmd,$tail,$client) = @_; 1705: my $userinput = "$cmd:$tail"; 1706: my ($name,$id) = map { &unescape($_); } split(/:/,$tail); 1707: &Apache::lonnet::devalidate_cache_new($name,$id); 1708: my $result = 'ok'; 1709: &Reply($client,\$result,$userinput); 1710: return 1; 1711: } 1712: ®ister_handler("devalidatecache", \&server_devalidatecache_handler, 0, 1, 0); 1713: 1714: sub server_timezone_handler { 1715: my ($cmd,$tail,$client) = @_; 1716: my $userinput = "$cmd:$tail"; 1717: my $timezone; 1718: my $clockfile = '/etc/sysconfig/clock'; # Fedora/CentOS/SuSE 1719: my $tzfile = '/etc/timezone'; # Debian/Ubuntu 1720: if (-e $clockfile) { 1721: if (open(my $fh,"<$clockfile")) { 1722: while (<$fh>) { 1723: next if (/^[\#\s]/); 1724: if (/^(?:TIME)?ZONE\s*=\s*['"]?\s*([\w\/]+)/) { 1725: $timezone = $1; 1726: last; 1727: } 1728: } 1729: close($fh); 1730: } 1731: } elsif (-e $tzfile) { 1732: if (open(my $fh,"<$tzfile")) { 1733: $timezone = <$fh>; 1734: close($fh); 1735: chomp($timezone); 1736: if ($timezone =~ m{^Etc/(\w+)$}) { 1737: $timezone = $1; 1738: } 1739: } 1740: } 1741: &Reply($client,\$timezone,$userinput); # This supports debug logging. 1742: return 1; 1743: } 1744: ®ister_handler("servertimezone", \&server_timezone_handler, 0, 1, 0); 1745: 1746: sub server_loncaparev_handler { 1747: my ($cmd,$tail,$client) = @_; 1748: my $userinput = "$cmd:$tail"; 1749: &Reply($client,\$perlvar{'lonVersion'},$userinput); 1750: return 1; 1751: } 1752: ®ister_handler("serverloncaparev", \&server_loncaparev_handler, 0, 1, 0); 1753: 1754: sub server_homeID_handler { 1755: my ($cmd,$tail,$client) = @_; 1756: my $userinput = "$cmd:$tail"; 1757: &Reply($client,\$perlvar{'lonHostID'},$userinput); 1758: return 1; 1759: } 1760: ®ister_handler("serverhomeID", \&server_homeID_handler, 0, 1, 0); 1761: 1762: sub server_distarch_handler { 1763: my ($cmd,$tail,$client) = @_; 1764: my $userinput = "$cmd:$tail"; 1765: my $reply = &distro_and_arch(); 1766: &Reply($client,\$reply,$userinput); 1767: return 1; 1768: } 1769: ®ister_handler("serverdistarch", \&server_distarch_handler, 0, 1, 0); 1770: 1771: # Process a reinit request. Reinit requests that either 1772: # lonc or lond be reinitialized so that an updated 1773: # host.tab or domain.tab can be processed. 1774: # 1775: # Parameters: 1776: # $cmd - the actual keyword that invoked us. 1777: # $tail - the tail of the request that invoked us. 1778: # $client - File descriptor connected to the client 1779: # Returns: 1780: # 1 - Ok to continue processing. 1781: # 0 - Program should exit 1782: # Implicit output: 1783: # a reply is sent to the client. 1784: # 1785: sub reinit_process_handler { 1786: my ($cmd, $tail, $client) = @_; 1787: 1788: my $userinput = "$cmd:$tail"; 1789: 1790: my $cert = &GetCertificate($userinput); 1791: if(&ValidManager($cert)) { 1792: chomp($userinput); 1793: my $reply = &ReinitProcess($userinput); 1794: &Reply( $client, \$reply, $userinput); 1795: } else { 1796: &Failure( $client, "refused\n", $userinput); 1797: } 1798: return 1; 1799: } 1800: ®ister_handler("reinit", \&reinit_process_handler, 1, 0, 1); 1801: 1802: # Process the editing script for a table edit operation. 1803: # the editing operation must be encrypted and requested by 1804: # a manager host. 1805: # 1806: # Parameters: 1807: # $cmd - the actual keyword that invoked us. 1808: # $tail - the tail of the request that invoked us. 1809: # $client - File descriptor connected to the client 1810: # Returns: 1811: # 1 - Ok to continue processing. 1812: # 0 - Program should exit 1813: # Implicit output: 1814: # a reply is sent to the client. 1815: # 1816: sub edit_table_handler { 1817: my ($command, $tail, $client) = @_; 1818: 1819: my $userinput = "$command:$tail"; 1820: 1821: my $cert = &GetCertificate($userinput); 1822: if(&ValidManager($cert)) { 1823: my($filetype, $script) = split(/:/, $tail); 1824: if (($filetype eq "hosts") || 1825: ($filetype eq "domain")) { 1826: if($script ne "") { 1827: &Reply($client, # BUGBUG - EditFile 1828: &EditFile($userinput), # could fail. 1829: $userinput); 1830: } else { 1831: &Failure($client,"refused\n",$userinput); 1832: } 1833: } else { 1834: &Failure($client,"refused\n",$userinput); 1835: } 1836: } else { 1837: &Failure($client,"refused\n",$userinput); 1838: } 1839: return 1; 1840: } 1841: ®ister_handler("edit", \&edit_table_handler, 1, 0, 1); 1842: 1843: # 1844: # Authenticate a user against the LonCAPA authentication 1845: # database. Note that there are several authentication 1846: # possibilities: 1847: # - unix - The user can be authenticated against the unix 1848: # password file. 1849: # - internal - The user can be authenticated against a purely 1850: # internal per user password file. 1851: # - kerberos - The user can be authenticated against either a kerb4 or kerb5 1852: # ticket granting authority. 1853: # - user - The person tailoring LonCAPA can supply a user authentication 1854: # mechanism that is per system. 1855: # 1856: # Parameters: 1857: # $cmd - The command that got us here. 1858: # $tail - Tail of the command (remaining parameters). 1859: # $client - File descriptor connected to client. 1860: # Returns 1861: # 0 - Requested to exit, caller should shut down. 1862: # 1 - Continue processing. 1863: # Implicit inputs: 1864: # The authentication systems describe above have their own forms of implicit 1865: # input into the authentication process that are described above. 1866: # 1867: sub authenticate_handler { 1868: my ($cmd, $tail, $client) = @_; 1869: 1870: 1871: # Regenerate the full input line 1872: 1873: my $userinput = $cmd.":".$tail; 1874: 1875: # udom - User's domain. 1876: # uname - Username. 1877: # upass - User's password. 1878: # checkdefauth - Pass to validate_user() to try authentication 1879: # with default auth type(s) if no user account. 1880: # clientcancheckhost - Passed by clients with functionality in lonauth.pm 1881: # to check if session can be hosted. 1882: 1883: my ($udom, $uname, $upass, $checkdefauth, $clientcancheckhost)=split(/:/,$tail); 1884: &Debug(" Authenticate domain = $udom, user = $uname, password = $upass, checkdefauth = $checkdefauth"); 1885: chomp($upass); 1886: $upass=&unescape($upass); 1887: 1888: my $pwdcorrect = &validate_user($udom,$uname,$upass,$checkdefauth); 1889: if($pwdcorrect) { 1890: my $canhost = 1; 1891: unless ($clientcancheckhost) { 1892: my $uprimary_id = &Apache::lonnet::domain($udom,'primary'); 1893: my $uint_dom = &Apache::lonnet::internet_dom($uprimary_id); 1894: my @intdoms; 1895: my $internet_names = &Apache::lonnet::get_internet_names($clientname); 1896: if (ref($internet_names) eq 'ARRAY') { 1897: @intdoms = @{$internet_names}; 1898: } 1899: unless ($uint_dom ne '' && grep(/^\Q$uint_dom\E$/,@intdoms)) { 1900: my ($remote,$hosted); 1901: my $remotesession = &get_usersession_config($udom,'remotesession'); 1902: if (ref($remotesession) eq 'HASH') { 1903: $remote = $remotesession->{'remote'} 1904: } 1905: my $hostedsession = &get_usersession_config($clienthomedom,'hostedsession'); 1906: if (ref($hostedsession) eq 'HASH') { 1907: $hosted = $hostedsession->{'hosted'}; 1908: } 1909: my $loncaparev = $clientversion; 1910: if ($loncaparev eq '') { 1911: $loncaparev = $Apache::lonnet::loncaparevs{$clientname}; 1912: } 1913: $canhost = &Apache::lonnet::can_host_session($udom,$clientname, 1914: $loncaparev, 1915: $remote,$hosted); 1916: } 1917: } 1918: if ($canhost) { 1919: &Reply( $client, "authorized\n", $userinput); 1920: } else { 1921: &Reply( $client, "not_allowed_to_host\n", $userinput); 1922: } 1923: # 1924: # Bad credentials: Failed to authorize 1925: # 1926: } else { 1927: &Failure( $client, "non_authorized\n", $userinput); 1928: } 1929: 1930: return 1; 1931: } 1932: ®ister_handler("auth", \&authenticate_handler, 1, 1, 0); 1933: 1934: # 1935: # Change a user's password. Note that this function is complicated by 1936: # the fact that a user may be authenticated in more than one way: 1937: # At present, we are not able to change the password for all types of 1938: # authentication methods. Only for: 1939: # unix - unix password or shadow passoword style authentication. 1940: # local - Locally written authentication mechanism. 1941: # For now, kerb4 and kerb5 password changes are not supported and result 1942: # in an error. 1943: # FUTURE WORK: 1944: # Support kerberos passwd changes? 1945: # Parameters: 1946: # $cmd - The command that got us here. 1947: # $tail - Tail of the command (remaining parameters). 1948: # $client - File descriptor connected to client. 1949: # Returns 1950: # 0 - Requested to exit, caller should shut down. 1951: # 1 - Continue processing. 1952: # Implicit inputs: 1953: # The authentication systems describe above have their own forms of implicit 1954: # input into the authentication process that are described above. 1955: sub change_password_handler { 1956: my ($cmd, $tail, $client) = @_; 1957: 1958: my $userinput = $cmd.":".$tail; # Reconstruct client's string. 1959: 1960: # 1961: # udom - user's domain. 1962: # uname - Username. 1963: # upass - Current password. 1964: # npass - New password. 1965: # context - Context in which this was called 1966: # (preferences or reset_by_email). 1967: # lonhost - HostID of server where request originated 1968: 1969: my ($udom,$uname,$upass,$npass,$context,$lonhost)=split(/:/,$tail); 1970: 1971: $upass=&unescape($upass); 1972: $npass=&unescape($npass); 1973: &Debug("Trying to change password for $uname"); 1974: 1975: # First require that the user can be authenticated with their 1976: # old password unless context was 'reset_by_email': 1977: 1978: my ($validated,$failure); 1979: if ($context eq 'reset_by_email') { 1980: if ($lonhost eq '') { 1981: $failure = 'invalid_client'; 1982: } else { 1983: $validated = 1; 1984: } 1985: } else { 1986: $validated = &validate_user($udom, $uname, $upass); 1987: } 1988: if($validated) { 1989: my $realpasswd = &get_auth_type($udom, $uname); # Defined since authd. 1990: 1991: my ($howpwd,$contentpwd)=split(/:/,$realpasswd); 1992: if ($howpwd eq 'internal') { 1993: &Debug("internal auth"); 1994: my $salt=time; 1995: $salt=substr($salt,6,2); 1996: my $ncpass=crypt($npass,$salt); 1997: if(&rewrite_password_file($udom, $uname, "internal:$ncpass")) { 1998: my $msg="Result of password change for $uname: pwchange_success"; 1999: if ($lonhost) { 2000: $msg .= " - request originated from: $lonhost"; 2001: } 2002: &logthis($msg); 2003: &Reply($client, "ok\n", $userinput); 2004: } else { 2005: &logthis("Unable to open $uname passwd " 2006: ."to change password"); 2007: &Failure( $client, "non_authorized\n",$userinput); 2008: } 2009: } elsif ($howpwd eq 'unix' && $context ne 'reset_by_email') { 2010: my $result = &change_unix_password($uname, $npass); 2011: &logthis("Result of password change for $uname: ". 2012: $result); 2013: &Reply($client, \$result, $userinput); 2014: } else { 2015: # this just means that the current password mode is not 2016: # one we know how to change (e.g the kerberos auth modes or 2017: # locally written auth handler). 2018: # 2019: &Failure( $client, "auth_mode_error\n", $userinput); 2020: } 2021: 2022: } else { 2023: if ($failure eq '') { 2024: $failure = 'non_authorized'; 2025: } 2026: &Failure( $client, "$failure\n", $userinput); 2027: } 2028: 2029: return 1; 2030: } 2031: ®ister_handler("passwd", \&change_password_handler, 1, 1, 0); 2032: 2033: # 2034: # Create a new user. User in this case means a lon-capa user. 2035: # The user must either already exist in some authentication realm 2036: # like kerberos or the /etc/passwd. If not, a user completely local to 2037: # this loncapa system is created. 2038: # 2039: # Parameters: 2040: # $cmd - The command that got us here. 2041: # $tail - Tail of the command (remaining parameters). 2042: # $client - File descriptor connected to client. 2043: # Returns 2044: # 0 - Requested to exit, caller should shut down. 2045: # 1 - Continue processing. 2046: # Implicit inputs: 2047: # The authentication systems describe above have their own forms of implicit 2048: # input into the authentication process that are described above. 2049: sub add_user_handler { 2050: 2051: my ($cmd, $tail, $client) = @_; 2052: 2053: 2054: my ($udom,$uname,$umode,$npass)=split(/:/,$tail); 2055: my $userinput = $cmd.":".$tail; # Reconstruct the full request line. 2056: 2057: &Debug("cmd =".$cmd." $udom =".$udom." uname=".$uname); 2058: 2059: 2060: if($udom eq $currentdomainid) { # Reject new users for other domains... 2061: 2062: my $oldumask=umask(0077); 2063: chomp($npass); 2064: $npass=&unescape($npass); 2065: my $passfilename = &password_path($udom, $uname); 2066: &Debug("Password file created will be:".$passfilename); 2067: if (-e $passfilename) { 2068: &Failure( $client, "already_exists\n", $userinput); 2069: } else { 2070: my $fperror=''; 2071: if (!&mkpath($passfilename)) { 2072: $fperror="error: ".($!+0)." mkdir failed while attempting " 2073: ."makeuser"; 2074: } 2075: unless ($fperror) { 2076: my $result=&make_passwd_file($uname,$udom,$umode,$npass, $passfilename); 2077: &Reply($client,\$result, $userinput); #BUGBUG - could be fail 2078: } else { 2079: &Failure($client, \$fperror, $userinput); 2080: } 2081: } 2082: umask($oldumask); 2083: } else { 2084: &Failure($client, "not_right_domain\n", 2085: $userinput); # Even if we are multihomed. 2086: 2087: } 2088: return 1; 2089: 2090: } 2091: ®ister_handler("makeuser", \&add_user_handler, 1, 1, 0); 2092: 2093: # 2094: # Change the authentication method of a user. Note that this may 2095: # also implicitly change the user's password if, for example, the user is 2096: # joining an existing authentication realm. Known authentication realms at 2097: # this time are: 2098: # internal - Purely internal password file (only loncapa knows this user) 2099: # local - Institutionally written authentication module. 2100: # unix - Unix user (/etc/passwd with or without /etc/shadow). 2101: # kerb4 - kerberos version 4 2102: # kerb5 - kerberos version 5 2103: # 2104: # Parameters: 2105: # $cmd - The command that got us here. 2106: # $tail - Tail of the command (remaining parameters). 2107: # $client - File descriptor connected to client. 2108: # Returns 2109: # 0 - Requested to exit, caller should shut down. 2110: # 1 - Continue processing. 2111: # Implicit inputs: 2112: # The authentication systems describe above have their own forms of implicit 2113: # input into the authentication process that are described above. 2114: # NOTE: 2115: # This is also used to change the authentication credential values (e.g. passwd). 2116: # 2117: # 2118: sub change_authentication_handler { 2119: 2120: my ($cmd, $tail, $client) = @_; 2121: 2122: my $userinput = "$cmd:$tail"; # Reconstruct user input. 2123: 2124: my ($udom,$uname,$umode,$npass)=split(/:/,$tail); 2125: &Debug("cmd = ".$cmd." domain= ".$udom."uname =".$uname." umode= ".$umode); 2126: if ($udom ne $currentdomainid) { 2127: &Failure( $client, "not_right_domain\n", $client); 2128: } else { 2129: 2130: chomp($npass); 2131: 2132: $npass=&unescape($npass); 2133: my $oldauth = &get_auth_type($udom, $uname); # Get old auth info. 2134: my $passfilename = &password_path($udom, $uname); 2135: if ($passfilename) { # Not allowed to create a new user!! 2136: # If just changing the unix passwd. need to arrange to run 2137: # passwd since otherwise make_passwd_file will run 2138: # lcuseradd which fails if an account already exists 2139: # (to prevent an unscrupulous LONCAPA admin from stealing 2140: # an existing account by overwriting it as a LonCAPA account). 2141: 2142: if(($oldauth =~/^unix/) && ($umode eq "unix")) { 2143: my $result = &change_unix_password($uname, $npass); 2144: &logthis("Result of password change for $uname: ".$result); 2145: if ($result eq "ok") { 2146: &Reply($client, \$result); 2147: } else { 2148: &Failure($client, \$result); 2149: } 2150: } else { 2151: my $result=&make_passwd_file($uname,$udom,$umode,$npass,$passfilename); 2152: # 2153: # If the current auth mode is internal, and the old auth mode was 2154: # unix, or krb*, and the user is an author for this domain, 2155: # re-run manage_permissions for that role in order to be able 2156: # to take ownership of the construction space back to www:www 2157: # 2158: 2159: 2160: if( (($oldauth =~ /^unix/) && ($umode eq "internal")) || 2161: (($oldauth =~ /^internal/) && ($umode eq "unix")) ) { 2162: if(&is_author($udom, $uname)) { 2163: &Debug(" Need to manage author permissions..."); 2164: &manage_permissions("/$udom/_au", $udom, $uname, "$umode:"); 2165: } 2166: } 2167: &Reply($client, \$result, $userinput); 2168: } 2169: 2170: 2171: } else { 2172: &Failure($client, "non_authorized\n", $userinput); # Fail the user now. 2173: } 2174: } 2175: return 1; 2176: } 2177: ®ister_handler("changeuserauth", \&change_authentication_handler, 1,1, 0); 2178: 2179: # 2180: # Determines if this is the home server for a user. The home server 2181: # for a user will have his/her lon-capa passwd file. Therefore all we need 2182: # to do is determine if this file exists. 2183: # 2184: # Parameters: 2185: # $cmd - The command that got us here. 2186: # $tail - Tail of the command (remaining parameters). 2187: # $client - File descriptor connected to client. 2188: # Returns 2189: # 0 - Requested to exit, caller should shut down. 2190: # 1 - Continue processing. 2191: # Implicit inputs: 2192: # The authentication systems describe above have their own forms of implicit 2193: # input into the authentication process that are described above. 2194: # 2195: sub is_home_handler { 2196: my ($cmd, $tail, $client) = @_; 2197: 2198: my $userinput = "$cmd:$tail"; 2199: 2200: my ($udom,$uname)=split(/:/,$tail); 2201: chomp($uname); 2202: my $passfile = &password_filename($udom, $uname); 2203: if($passfile) { 2204: &Reply( $client, "found\n", $userinput); 2205: } else { 2206: &Failure($client, "not_found\n", $userinput); 2207: } 2208: return 1; 2209: } 2210: ®ister_handler("home", \&is_home_handler, 0,1,0); 2211: 2212: # 2213: # Process an update request for a resource. 2214: # A resource has been modified that we hold a subscription to. 2215: # If the resource is not local, then we must update, or at least invalidate our 2216: # cached copy of the resource. 2217: # Parameters: 2218: # $cmd - The command that got us here. 2219: # $tail - Tail of the command (remaining parameters). 2220: # $client - File descriptor connected to client. 2221: # Returns 2222: # 0 - Requested to exit, caller should shut down. 2223: # 1 - Continue processing. 2224: # Implicit inputs: 2225: # The authentication systems describe above have their own forms of implicit 2226: # input into the authentication process that are described above. 2227: # 2228: sub update_resource_handler { 2229: 2230: my ($cmd, $tail, $client) = @_; 2231: 2232: my $userinput = "$cmd:$tail"; 2233: 2234: my $fname= $tail; # This allows interactive testing 2235: 2236: 2237: my $ownership=ishome($fname); 2238: if ($ownership eq 'not_owner') { 2239: if (-e $fname) { 2240: # Delete preview file, if exists 2241: unlink("$fname.tmp"); 2242: # Get usage stats 2243: my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, 2244: $atime,$mtime,$ctime,$blksize,$blocks)=stat($fname); 2245: my $now=time; 2246: my $since=$now-$atime; 2247: # If the file has not been used within lonExpire seconds, 2248: # unsubscribe from it and delete local copy 2249: if ($since>$perlvar{'lonExpire'}) { 2250: my $reply=&Apache::lonnet::reply("unsub:$fname","$clientname"); 2251: &devalidate_meta_cache($fname); 2252: unlink("$fname"); 2253: unlink("$fname.meta"); 2254: } else { 2255: # Yes, this is in active use. Get a fresh copy. Since it might be in 2256: # very active use and huge (like a movie), copy it to "in.transfer" filename first. 2257: my $transname="$fname.in.transfer"; 2258: my $remoteurl=&Apache::lonnet::reply("sub:$fname","$clientname"); 2259: my $response; 2260: # FIXME: cannot replicate files that take more than two minutes to transfer? 2261: # alarm(120); 2262: # FIXME: this should use the LWP mechanism, not internal alarms. 2263: alarm(1200); 2264: { 2265: my $ua=new LWP::UserAgent; 2266: my $request=new HTTP::Request('GET',"$remoteurl"); 2267: $response=$ua->request($request,$transname); 2268: } 2269: alarm(0); 2270: if ($response->is_error()) { 2271: # FIXME: we should probably clean up here instead of just whine 2272: unlink($transname); 2273: my $message=$response->status_line; 2274: &logthis("LWP GET: $message for $fname ($remoteurl)"); 2275: } else { 2276: if ($remoteurl!~/\.meta$/) { 2277: # FIXME: isn't there an internal LWP mechanism for this? 2278: alarm(120); 2279: { 2280: my $ua=new LWP::UserAgent; 2281: my $mrequest=new HTTP::Request('GET',$remoteurl.'.meta'); 2282: my $mresponse=$ua->request($mrequest,$fname.'.meta'); 2283: if ($mresponse->is_error()) { 2284: unlink($fname.'.meta'); 2285: } 2286: } 2287: alarm(0); 2288: } 2289: # we successfully transfered, copy file over to real name 2290: rename($transname,$fname); 2291: &devalidate_meta_cache($fname); 2292: } 2293: } 2294: &Reply( $client, "ok\n", $userinput); 2295: } else { 2296: &Failure($client, "not_found\n", $userinput); 2297: } 2298: } else { 2299: &Failure($client, "rejected\n", $userinput); 2300: } 2301: return 1; 2302: } 2303: ®ister_handler("update", \&update_resource_handler, 0 ,1, 0); 2304: 2305: sub devalidate_meta_cache { 2306: my ($url) = @_; 2307: use Cache::Memcached; 2308: my $memcache = new Cache::Memcached({'servers'=>['127.0.0.1:11211']}); 2309: $url = &Apache::lonnet::declutter($url); 2310: $url =~ s-\.meta$--; 2311: my $id = &escape('meta:'.$url); 2312: $memcache->delete($id); 2313: } 2314: 2315: # 2316: # Fetch a user file from a remote server to the user's home directory 2317: # userfiles subdir. 2318: # Parameters: 2319: # $cmd - The command that got us here. 2320: # $tail - Tail of the command (remaining parameters). 2321: # $client - File descriptor connected to client. 2322: # Returns 2323: # 0 - Requested to exit, caller should shut down. 2324: # 1 - Continue processing. 2325: # 2326: sub fetch_user_file_handler { 2327: 2328: my ($cmd, $tail, $client) = @_; 2329: 2330: my $userinput = "$cmd:$tail"; 2331: my $fname = $tail; 2332: my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|); 2333: my $udir=&propath($udom,$uname).'/userfiles'; 2334: unless (-e $udir) { 2335: mkdir($udir,0770); 2336: } 2337: Debug("fetch user file for $fname"); 2338: if (-e $udir) { 2339: $ufile=~s/^[\.\~]+//; 2340: 2341: # IF necessary, create the path right down to the file. 2342: # Note that any regular files in the way of this path are 2343: # wiped out to deal with some earlier folly of mine. 2344: 2345: if (!&mkpath($udir.'/'.$ufile)) { 2346: &Failure($client, "unable_to_create\n", $userinput); 2347: } 2348: 2349: my $destname=$udir.'/'.$ufile; 2350: my $transname=$udir.'/'.$ufile.'.in.transit'; 2351: my $clientprotocol=$Apache::lonnet::protocol{$clientname}; 2352: $clientprotocol = 'http' if ($clientprotocol ne 'https'); 2353: my $clienthost = &Apache::lonnet::hostname($clientname); 2354: my $remoteurl=$clientprotocol.'://'.$clienthost.'/userfiles/'.$fname; 2355: my $response; 2356: Debug("Remote URL : $remoteurl Transfername $transname Destname: $destname"); 2357: alarm(120); 2358: { 2359: my $ua=new LWP::UserAgent; 2360: my $request=new HTTP::Request('GET',"$remoteurl"); 2361: $response=$ua->request($request,$transname); 2362: } 2363: alarm(0); 2364: if ($response->is_error()) { 2365: unlink($transname); 2366: my $message=$response->status_line; 2367: &logthis("LWP GET: $message for $fname ($remoteurl)"); 2368: &Failure($client, "failed\n", $userinput); 2369: } else { 2370: Debug("Renaming $transname to $destname"); 2371: if (!rename($transname,$destname)) { 2372: &logthis("Unable to move $transname to $destname"); 2373: unlink($transname); 2374: &Failure($client, "failed\n", $userinput); 2375: } else { 2376: if ($fname =~ /^default.+\.(page|sequence)$/) { 2377: my ($major,$minor) = split(/\./,$clientversion); 2378: if (($major < 2) || ($major == 2 && $minor < 11)) { 2379: my $now = time; 2380: &Apache::lonnet::do_cache_new('crschange',$udom.'_'.$uname,$now,600); 2381: my $key = &escape('internal.contentchange'); 2382: my $what = "$key=$now"; 2383: my $hashref = &tie_user_hash($udom,$uname,'environment', 2384: &GDBM_WRCREAT(),"P",$what); 2385: if ($hashref) { 2386: $hashref->{$key}=$now; 2387: if (!&untie_user_hash($hashref)) { 2388: &logthis("error: ".($!+0)." untie (GDBM) failed ". 2389: "when updating internal.contentchange"); 2390: } 2391: } 2392: } 2393: } 2394: &Reply($client, "ok\n", $userinput); 2395: } 2396: } 2397: } else { 2398: &Failure($client, "not_home\n", $userinput); 2399: } 2400: return 1; 2401: } 2402: ®ister_handler("fetchuserfile", \&fetch_user_file_handler, 0, 1, 0); 2403: 2404: # 2405: # Remove a file from a user's home directory userfiles subdirectory. 2406: # Parameters: 2407: # cmd - the Lond request keyword that got us here. 2408: # tail - the part of the command past the keyword. 2409: # client- File descriptor connected with the client. 2410: # 2411: # Returns: 2412: # 1 - Continue processing. 2413: sub remove_user_file_handler { 2414: my ($cmd, $tail, $client) = @_; 2415: 2416: my ($fname) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent. 2417: 2418: my ($udom,$uname,$ufile) = ($fname =~ m|^([^/]+)/([^/]+)/(.+)$|); 2419: if ($ufile =~m|/\.\./|) { 2420: # any files paths with /../ in them refuse 2421: # to deal with 2422: &Failure($client, "refused\n", "$cmd:$tail"); 2423: } else { 2424: my $udir = &propath($udom,$uname); 2425: if (-e $udir) { 2426: my $file=$udir.'/userfiles/'.$ufile; 2427: if (-e $file) { 2428: # 2429: # If the file is a regular file unlink is fine... 2430: # However it's possible the client wants a dir. 2431: # removed, in which case rmdir is more approprate: 2432: # 2433: if (-f $file){ 2434: unlink($file); 2435: } elsif(-d $file) { 2436: rmdir($file); 2437: } 2438: if (-e $file) { 2439: # File is still there after we deleted it ?!? 2440: 2441: &Failure($client, "failed\n", "$cmd:$tail"); 2442: } else { 2443: &Reply($client, "ok\n", "$cmd:$tail"); 2444: } 2445: } else { 2446: &Failure($client, "not_found\n", "$cmd:$tail"); 2447: } 2448: } else { 2449: &Failure($client, "not_home\n", "$cmd:$tail"); 2450: } 2451: } 2452: return 1; 2453: } 2454: ®ister_handler("removeuserfile", \&remove_user_file_handler, 0,1,0); 2455: 2456: # 2457: # make a directory in a user's home directory userfiles subdirectory. 2458: # Parameters: 2459: # cmd - the Lond request keyword that got us here. 2460: # tail - the part of the command past the keyword. 2461: # client- File descriptor connected with the client. 2462: # 2463: # Returns: 2464: # 1 - Continue processing. 2465: sub mkdir_user_file_handler { 2466: my ($cmd, $tail, $client) = @_; 2467: 2468: my ($dir) = split(/:/, $tail); # Get rid of any tailing :'s lonc may have sent. 2469: $dir=&unescape($dir); 2470: my ($udom,$uname,$ufile) = ($dir =~ m|^([^/]+)/([^/]+)/(.+)$|); 2471: if ($ufile =~m|/\.\./|) { 2472: # any files paths with /../ in them refuse 2473: # to deal with 2474: &Failure($client, "refused\n", "$cmd:$tail"); 2475: } else { 2476: my $udir = &propath($udom,$uname); 2477: if (-e $udir) { 2478: my $newdir=$udir.'/userfiles/'.$ufile.'/'; 2479: if (!&mkpath($newdir)) { 2480: &Failure($client, "failed\n", "$cmd:$tail"); 2481: } 2482: &Reply($client, "ok\n", "$cmd:$tail"); 2483: } else { 2484: &Failure($client, "not_home\n", "$cmd:$tail"); 2485: } 2486: } 2487: return 1; 2488: } 2489: ®ister_handler("mkdiruserfile", \&mkdir_user_file_handler, 0,1,0); 2490: 2491: # 2492: # rename a file in a user's home directory userfiles subdirectory. 2493: # Parameters: 2494: # cmd - the Lond request keyword that got us here. 2495: # tail - the part of the command past the keyword. 2496: # client- File descriptor connected with the client. 2497: # 2498: # Returns: 2499: # 1 - Continue processing. 2500: sub rename_user_file_handler { 2501: my ($cmd, $tail, $client) = @_; 2502: 2503: my ($udom,$uname,$old,$new) = split(/:/, $tail); 2504: $old=&unescape($old); 2505: $new=&unescape($new); 2506: if ($new =~m|/\.\./| || $old =~m|/\.\./|) { 2507: # any files paths with /../ in them refuse to deal with 2508: &Failure($client, "refused\n", "$cmd:$tail"); 2509: } else { 2510: my $udir = &propath($udom,$uname); 2511: if (-e $udir) { 2512: my $oldfile=$udir.'/userfiles/'.$old; 2513: my $newfile=$udir.'/userfiles/'.$new; 2514: if (-e $newfile) { 2515: &Failure($client, "exists\n", "$cmd:$tail"); 2516: } elsif (! -e $oldfile) { 2517: &Failure($client, "not_found\n", "$cmd:$tail"); 2518: } else { 2519: if (!rename($oldfile,$newfile)) { 2520: &Failure($client, "failed\n", "$cmd:$tail"); 2521: } else { 2522: &Reply($client, "ok\n", "$cmd:$tail"); 2523: } 2524: } 2525: } else { 2526: &Failure($client, "not_home\n", "$cmd:$tail"); 2527: } 2528: } 2529: return 1; 2530: } 2531: ®ister_handler("renameuserfile", \&rename_user_file_handler, 0,1,0); 2532: 2533: # 2534: # Checks if the specified user has an active session on the server 2535: # return ok if so, not_found if not 2536: # 2537: # Parameters: 2538: # cmd - The request keyword that dispatched to tus. 2539: # tail - The tail of the request (colon separated parameters). 2540: # client - Filehandle open on the client. 2541: # Return: 2542: # 1. 2543: sub user_has_session_handler { 2544: my ($cmd, $tail, $client) = @_; 2545: 2546: my ($udom, $uname) = map { &unescape($_) } (split(/:/, $tail)); 2547: 2548: opendir(DIR,$perlvar{'lonIDsDir'}); 2549: my $filename; 2550: while ($filename=readdir(DIR)) { 2551: last if ($filename=~/^\Q$uname\E_\d+_\Q$udom\E_/); 2552: } 2553: if ($filename) { 2554: &Reply($client, "ok\n", "$cmd:$tail"); 2555: } else { 2556: &Failure($client, "not_found\n", "$cmd:$tail"); 2557: } 2558: return 1; 2559: 2560: } 2561: ®ister_handler("userhassession", \&user_has_session_handler, 0,1,0); 2562: 2563: # 2564: # Authenticate access to a user file by checking that the token the user's 2565: # passed also exists in their session file 2566: # 2567: # Parameters: 2568: # cmd - The request keyword that dispatched to tus. 2569: # tail - The tail of the request (colon separated parameters). 2570: # client - Filehandle open on the client. 2571: # Return: 2572: # 1. 2573: sub token_auth_user_file_handler { 2574: my ($cmd, $tail, $client) = @_; 2575: 2576: my ($fname, $session) = split(/:/, $tail); 2577: 2578: chomp($session); 2579: my $reply="non_auth"; 2580: my $file = $perlvar{'lonIDsDir'}.'/'.$session.'.id'; 2581: if (open(ENVIN,"$file")) { 2582: flock(ENVIN,LOCK_SH); 2583: tie(my %disk_env,'GDBM_File',"$file",&GDBM_READER(),0640); 2584: if (exists($disk_env{"userfile.$fname"})) { 2585: $reply="ok"; 2586: } else { 2587: foreach my $envname (keys(%disk_env)) { 2588: if ($envname=~ m|^userfile\.\Q$fname\E|) { 2589: $reply="ok"; 2590: last; 2591: } 2592: } 2593: } 2594: untie(%disk_env); 2595: close(ENVIN); 2596: &Reply($client, \$reply, "$cmd:$tail"); 2597: } else { 2598: &Failure($client, "invalid_token\n", "$cmd:$tail"); 2599: } 2600: return 1; 2601: 2602: } 2603: ®ister_handler("tokenauthuserfile", \&token_auth_user_file_handler, 0,1,0); 2604: 2605: # 2606: # Unsubscribe from a resource. 2607: # 2608: # Parameters: 2609: # $cmd - The command that got us here. 2610: # $tail - Tail of the command (remaining parameters). 2611: # $client - File descriptor connected to client. 2612: # Returns 2613: # 0 - Requested to exit, caller should shut down. 2614: # 1 - Continue processing. 2615: # 2616: sub unsubscribe_handler { 2617: my ($cmd, $tail, $client) = @_; 2618: 2619: my $userinput= "$cmd:$tail"; 2620: 2621: my ($fname) = split(/:/,$tail); # Split in case there's extrs. 2622: 2623: &Debug("Unsubscribing $fname"); 2624: if (-e $fname) { 2625: &Debug("Exists"); 2626: &Reply($client, &unsub($fname,$clientip), $userinput); 2627: } else { 2628: &Failure($client, "not_found\n", $userinput); 2629: } 2630: return 1; 2631: } 2632: ®ister_handler("unsub", \&unsubscribe_handler, 0, 1, 0); 2633: 2634: # Subscribe to a resource 2635: # 2636: # Parameters: 2637: # $cmd - The command that got us here. 2638: # $tail - Tail of the command (remaining parameters). 2639: # $client - File descriptor connected to client. 2640: # Returns 2641: # 0 - Requested to exit, caller should shut down. 2642: # 1 - Continue processing. 2643: # 2644: sub subscribe_handler { 2645: my ($cmd, $tail, $client)= @_; 2646: 2647: my $userinput = "$cmd:$tail"; 2648: 2649: &Reply( $client, &subscribe($userinput,$clientip), $userinput); 2650: 2651: return 1; 2652: } 2653: ®ister_handler("sub", \&subscribe_handler, 0, 1, 0); 2654: 2655: # 2656: # Determine the latest version of a resource (it looks for the highest 2657: # past version and then returns that +1) 2658: # 2659: # Parameters: 2660: # $cmd - The command that got us here. 2661: # $tail - Tail of the command (remaining parameters). 2662: # (Should consist of an absolute path to a file) 2663: # $client - File descriptor connected to client. 2664: # Returns 2665: # 0 - Requested to exit, caller should shut down. 2666: # 1 - Continue processing. 2667: # 2668: sub current_version_handler { 2669: my ($cmd, $tail, $client) = @_; 2670: 2671: my $userinput= "$cmd:$tail"; 2672: 2673: my $fname = $tail; 2674: &Reply( $client, ¤tversion($fname)."\n", $userinput); 2675: return 1; 2676: 2677: } 2678: ®ister_handler("currentversion", \¤t_version_handler, 0, 1, 0); 2679: 2680: # Make an entry in a user's activity log. 2681: # 2682: # Parameters: 2683: # $cmd - The command that got us here. 2684: # $tail - Tail of the command (remaining parameters). 2685: # $client - File descriptor connected to client. 2686: # Returns 2687: # 0 - Requested to exit, caller should shut down. 2688: # 1 - Continue processing. 2689: # 2690: sub activity_log_handler { 2691: my ($cmd, $tail, $client) = @_; 2692: 2693: 2694: my $userinput= "$cmd:$tail"; 2695: 2696: my ($udom,$uname,$what)=split(/:/,$tail); 2697: chomp($what); 2698: my $proname=&propath($udom,$uname); 2699: my $now=time; 2700: my $hfh; 2701: if ($hfh=IO::File->new(">>$proname/activity.log")) { 2702: print $hfh "$now:$clientname:$what\n"; 2703: &Reply( $client, "ok\n", $userinput); 2704: } else { 2705: &Failure($client, "error: ".($!+0)." IO::File->new Failed " 2706: ."while attempting log\n", 2707: $userinput); 2708: } 2709: 2710: return 1; 2711: } 2712: ®ister_handler("log", \&activity_log_handler, 0, 1, 0); 2713: 2714: # 2715: # Put a namespace entry in a user profile hash. 2716: # My druthers would be for this to be an encrypted interaction too. 2717: # anything that might be an inadvertent covert channel about either 2718: # user authentication or user personal information.... 2719: # 2720: # Parameters: 2721: # $cmd - The command that got us here. 2722: # $tail - Tail of the command (remaining parameters). 2723: # $client - File descriptor connected to client. 2724: # Returns 2725: # 0 - Requested to exit, caller should shut down. 2726: # 1 - Continue processing. 2727: # 2728: sub put_user_profile_entry { 2729: my ($cmd, $tail, $client) = @_; 2730: 2731: my $userinput = "$cmd:$tail"; 2732: 2733: my ($udom,$uname,$namespace,$what) =split(/:/,$tail,4); 2734: if ($namespace ne 'roles') { 2735: chomp($what); 2736: my $hashref = &tie_user_hash($udom, $uname, $namespace, 2737: &GDBM_WRCREAT(),"P",$what); 2738: if($hashref) { 2739: my @pairs=split(/\&/,$what); 2740: foreach my $pair (@pairs) { 2741: my ($key,$value)=split(/=/,$pair); 2742: $hashref->{$key}=$value; 2743: } 2744: if (&untie_user_hash($hashref)) { 2745: &Reply( $client, "ok\n", $userinput); 2746: } else { 2747: &Failure($client, "error: ".($!+0)." untie(GDBM) failed ". 2748: "while attempting put\n", 2749: $userinput); 2750: } 2751: } else { 2752: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 2753: "while attempting put\n", $userinput); 2754: } 2755: } else { 2756: &Failure( $client, "refused\n", $userinput); 2757: } 2758: 2759: return 1; 2760: } 2761: ®ister_handler("put", \&put_user_profile_entry, 0, 1, 0); 2762: 2763: # Put a piece of new data in hash, returns error if entry already exists 2764: # Parameters: 2765: # $cmd - The command that got us here. 2766: # $tail - Tail of the command (remaining parameters). 2767: # $client - File descriptor connected to client. 2768: # Returns 2769: # 0 - Requested to exit, caller should shut down. 2770: # 1 - Continue processing. 2771: # 2772: sub newput_user_profile_entry { 2773: my ($cmd, $tail, $client) = @_; 2774: 2775: my $userinput = "$cmd:$tail"; 2776: 2777: my ($udom,$uname,$namespace,$what) =split(/:/,$tail,4); 2778: if ($namespace eq 'roles') { 2779: &Failure( $client, "refused\n", $userinput); 2780: return 1; 2781: } 2782: 2783: chomp($what); 2784: 2785: my $hashref = &tie_user_hash($udom, $uname, $namespace, 2786: &GDBM_WRCREAT(),"N",$what); 2787: if(!$hashref) { 2788: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 2789: "while attempting put\n", $userinput); 2790: return 1; 2791: } 2792: 2793: my @pairs=split(/\&/,$what); 2794: foreach my $pair (@pairs) { 2795: my ($key,$value)=split(/=/,$pair); 2796: if (exists($hashref->{$key})) { 2797: &Failure($client, "key_exists: ".$key."\n",$userinput); 2798: return 1; 2799: } 2800: } 2801: 2802: foreach my $pair (@pairs) { 2803: my ($key,$value)=split(/=/,$pair); 2804: $hashref->{$key}=$value; 2805: } 2806: 2807: if (&untie_user_hash($hashref)) { 2808: &Reply( $client, "ok\n", $userinput); 2809: } else { 2810: &Failure($client, "error: ".($!+0)." untie(GDBM) failed ". 2811: "while attempting put\n", 2812: $userinput); 2813: } 2814: return 1; 2815: } 2816: ®ister_handler("newput", \&newput_user_profile_entry, 0, 1, 0); 2817: 2818: # 2819: # Increment a profile entry in the user history file. 2820: # The history contains keyword value pairs. In this case, 2821: # The value itself is a pair of numbers. The first, the current value 2822: # the second an increment that this function applies to the current 2823: # value. 2824: # 2825: # Parameters: 2826: # $cmd - The command that got us here. 2827: # $tail - Tail of the command (remaining parameters). 2828: # $client - File descriptor connected to client. 2829: # Returns 2830: # 0 - Requested to exit, caller should shut down. 2831: # 1 - Continue processing. 2832: # 2833: sub increment_user_value_handler { 2834: my ($cmd, $tail, $client) = @_; 2835: 2836: my $userinput = "$cmd:$tail"; 2837: 2838: my ($udom,$uname,$namespace,$what) =split(/:/,$tail); 2839: if ($namespace ne 'roles') { 2840: chomp($what); 2841: my $hashref = &tie_user_hash($udom, $uname, 2842: $namespace, &GDBM_WRCREAT(), 2843: "P",$what); 2844: if ($hashref) { 2845: my @pairs=split(/\&/,$what); 2846: foreach my $pair (@pairs) { 2847: my ($key,$value)=split(/=/,$pair); 2848: $value = &unescape($value); 2849: # We could check that we have a number... 2850: if (! defined($value) || $value eq '') { 2851: $value = 1; 2852: } 2853: $hashref->{$key}+=$value; 2854: if ($namespace eq 'nohist_resourcetracker') { 2855: if ($hashref->{$key} < 0) { 2856: $hashref->{$key} = 0; 2857: } 2858: } 2859: } 2860: if (&untie_user_hash($hashref)) { 2861: &Reply( $client, "ok\n", $userinput); 2862: } else { 2863: &Failure($client, "error: ".($!+0)." untie(GDBM) failed ". 2864: "while attempting inc\n", $userinput); 2865: } 2866: } else { 2867: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 2868: "while attempting inc\n", $userinput); 2869: } 2870: } else { 2871: &Failure($client, "refused\n", $userinput); 2872: } 2873: 2874: return 1; 2875: } 2876: ®ister_handler("inc", \&increment_user_value_handler, 0, 1, 0); 2877: 2878: # 2879: # Put a new role for a user. Roles are LonCAPA's packaging of permissions. 2880: # Each 'role' a user has implies a set of permissions. Adding a new role 2881: # for a person grants the permissions packaged with that role 2882: # to that user when the role is selected. 2883: # 2884: # Parameters: 2885: # $cmd - The command string (rolesput). 2886: # $tail - The remainder of the request line. For rolesput this 2887: # consists of a colon separated list that contains: 2888: # The domain and user that is granting the role (logged). 2889: # The domain and user that is getting the role. 2890: # The roles being granted as a set of & separated pairs. 2891: # each pair a key value pair. 2892: # $client - File descriptor connected to the client. 2893: # Returns: 2894: # 0 - If the daemon should exit 2895: # 1 - To continue processing. 2896: # 2897: # 2898: sub roles_put_handler { 2899: my ($cmd, $tail, $client) = @_; 2900: 2901: my $userinput = "$cmd:$tail"; 2902: 2903: my ( $exedom, $exeuser, $udom, $uname, $what) = split(/:/,$tail); 2904: 2905: 2906: my $namespace='roles'; 2907: chomp($what); 2908: my $hashref = &tie_user_hash($udom, $uname, $namespace, 2909: &GDBM_WRCREAT(), "P", 2910: "$exedom:$exeuser:$what"); 2911: # 2912: # Log the attempt to set a role. The {}'s here ensure that the file 2913: # handle is open for the minimal amount of time. Since the flush 2914: # is done on close this improves the chances the log will be an un- 2915: # corrupted ordered thing. 2916: if ($hashref) { 2917: my $pass_entry = &get_auth_type($udom, $uname); 2918: my ($auth_type,$pwd) = split(/:/, $pass_entry); 2919: $auth_type = $auth_type.":"; 2920: my @pairs=split(/\&/,$what); 2921: foreach my $pair (@pairs) { 2922: my ($key,$value)=split(/=/,$pair); 2923: &manage_permissions($key, $udom, $uname, 2924: $auth_type); 2925: $hashref->{$key}=$value; 2926: } 2927: if (&untie_user_hash($hashref)) { 2928: &Reply($client, "ok\n", $userinput); 2929: } else { 2930: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". 2931: "while attempting rolesput\n", $userinput); 2932: } 2933: } else { 2934: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 2935: "while attempting rolesput\n", $userinput); 2936: } 2937: return 1; 2938: } 2939: ®ister_handler("rolesput", \&roles_put_handler, 1,1,0); # Encoded client only. 2940: 2941: # 2942: # Deletes (removes) a role for a user. This is equivalent to removing 2943: # a permissions package associated with the role from the user's profile. 2944: # 2945: # Parameters: 2946: # $cmd - The command (rolesdel) 2947: # $tail - The remainder of the request line. This consists 2948: # of: 2949: # The domain and user requesting the change (logged) 2950: # The domain and user being changed. 2951: # The roles being revoked. These are shipped to us 2952: # as a bunch of & separated role name keywords. 2953: # $client - The file handle open on the client. 2954: # Returns: 2955: # 1 - Continue processing 2956: # 0 - Exit. 2957: # 2958: sub roles_delete_handler { 2959: my ($cmd, $tail, $client) = @_; 2960: 2961: my $userinput = "$cmd:$tail"; 2962: 2963: my ($exedom,$exeuser,$udom,$uname,$what)=split(/:/,$tail); 2964: &Debug("cmd = ".$cmd." exedom= ".$exedom."user = ".$exeuser." udom=".$udom. 2965: "what = ".$what); 2966: my $namespace='roles'; 2967: chomp($what); 2968: my $hashref = &tie_user_hash($udom, $uname, $namespace, 2969: &GDBM_WRCREAT(), "D", 2970: "$exedom:$exeuser:$what"); 2971: 2972: if ($hashref) { 2973: my @rolekeys=split(/\&/,$what); 2974: 2975: foreach my $key (@rolekeys) { 2976: delete $hashref->{$key}; 2977: } 2978: if (&untie_user_hash($hashref)) { 2979: &Reply($client, "ok\n", $userinput); 2980: } else { 2981: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". 2982: "while attempting rolesdel\n", $userinput); 2983: } 2984: } else { 2985: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 2986: "while attempting rolesdel\n", $userinput); 2987: } 2988: 2989: return 1; 2990: } 2991: ®ister_handler("rolesdel", \&roles_delete_handler, 1,1, 0); # Encoded client only 2992: 2993: # Unencrypted get from a user's profile database. See 2994: # GetProfileEntryEncrypted for a version that does end-to-end encryption. 2995: # This function retrieves a keyed item from a specific named database in the 2996: # user's directory. 2997: # 2998: # Parameters: 2999: # $cmd - Command request keyword (get). 3000: # $tail - Tail of the command. This is a colon separated list 3001: # consisting of the domain and username that uniquely 3002: # identifies the profile, 3003: # The 'namespace' which selects the gdbm file to 3004: # do the lookup in, 3005: # & separated list of keys to lookup. Note that 3006: # the values are returned as an & separated list too. 3007: # $client - File descriptor open on the client. 3008: # Returns: 3009: # 1 - Continue processing. 3010: # 0 - Exit. 3011: # 3012: sub get_profile_entry { 3013: my ($cmd, $tail, $client) = @_; 3014: 3015: my $userinput= "$cmd:$tail"; 3016: 3017: my ($udom,$uname,$namespace,$what) = split(/:/,$tail); 3018: chomp($what); 3019: 3020: 3021: my $replystring = read_profile($udom, $uname, $namespace, $what); 3022: my ($first) = split(/:/,$replystring); 3023: if($first ne "error") { 3024: &Reply($client, \$replystring, $userinput); 3025: } else { 3026: &Failure($client, $replystring." while attempting get\n", $userinput); 3027: } 3028: return 1; 3029: 3030: 3031: } 3032: ®ister_handler("get", \&get_profile_entry, 0,1,0); 3033: 3034: # 3035: # Process the encrypted get request. Note that the request is sent 3036: # in clear, but the reply is encrypted. This is a small covert channel: 3037: # information about the sensitive keys is given to the snooper. Just not 3038: # information about the values of the sensitive key. Hmm if I wanted to 3039: # know these I'd snoop for the egets. Get the profile item names from them 3040: # and then issue a get for them since there's no enforcement of the 3041: # requirement of an encrypted get for particular profile items. If I 3042: # were re-doing this, I'd force the request to be encrypted as well as the 3043: # reply. I'd also just enforce encrypted transactions for all gets since 3044: # that would prevent any covert channel snooping. 3045: # 3046: # Parameters: 3047: # $cmd - Command keyword of request (eget). 3048: # $tail - Tail of the command. See GetProfileEntry # for more information about this. 3049: # $client - File open on the client. 3050: # Returns: 3051: # 1 - Continue processing 3052: # 0 - server should exit. 3053: sub get_profile_entry_encrypted { 3054: my ($cmd, $tail, $client) = @_; 3055: 3056: my $userinput = "$cmd:$tail"; 3057: 3058: my ($udom,$uname,$namespace,$what) = split(/:/,$tail); 3059: chomp($what); 3060: my $qresult = read_profile($udom, $uname, $namespace, $what); 3061: my ($first) = split(/:/, $qresult); 3062: if($first ne "error") { 3063: 3064: if ($cipher) { 3065: my $cmdlength=length($qresult); 3066: $qresult.=" "; 3067: my $encqresult=''; 3068: for(my $encidx=0;$encidx<=$cmdlength;$encidx+=8) { 3069: $encqresult.= unpack("H16", 3070: $cipher->encrypt(substr($qresult, 3071: $encidx, 3072: 8))); 3073: } 3074: &Reply( $client, "enc:$cmdlength:$encqresult\n", $userinput); 3075: } else { 3076: &Failure( $client, "error:no_key\n", $userinput); 3077: } 3078: } else { 3079: &Failure($client, "$qresult while attempting eget\n", $userinput); 3080: 3081: } 3082: 3083: return 1; 3084: } 3085: ®ister_handler("eget", \&get_profile_entry_encrypted, 0, 1, 0); 3086: 3087: # 3088: # Deletes a key in a user profile database. 3089: # 3090: # Parameters: 3091: # $cmd - Command keyword (del). 3092: # $tail - Command tail. IN this case a colon 3093: # separated list containing: 3094: # The domain and user that identifies uniquely 3095: # the identity of the user. 3096: # The profile namespace (name of the profile 3097: # database file). 3098: # & separated list of keywords to delete. 3099: # $client - File open on client socket. 3100: # Returns: 3101: # 1 - Continue processing 3102: # 0 - Exit server. 3103: # 3104: # 3105: sub delete_profile_entry { 3106: my ($cmd, $tail, $client) = @_; 3107: 3108: my $userinput = "cmd:$tail"; 3109: 3110: my ($udom,$uname,$namespace,$what) = split(/:/,$tail); 3111: chomp($what); 3112: my $hashref = &tie_user_hash($udom, $uname, $namespace, 3113: &GDBM_WRCREAT(), 3114: "D",$what); 3115: if ($hashref) { 3116: my @keys=split(/\&/,$what); 3117: foreach my $key (@keys) { 3118: delete($hashref->{$key}); 3119: } 3120: if (&untie_user_hash($hashref)) { 3121: &Reply($client, "ok\n", $userinput); 3122: } else { 3123: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 3124: "while attempting del\n", $userinput); 3125: } 3126: } else { 3127: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 3128: "while attempting del\n", $userinput); 3129: } 3130: return 1; 3131: } 3132: ®ister_handler("del", \&delete_profile_entry, 0, 1, 0); 3133: 3134: # 3135: # List the set of keys that are defined in a profile database file. 3136: # A successful reply from this will contain an & separated list of 3137: # the keys. 3138: # Parameters: 3139: # $cmd - Command request (keys). 3140: # $tail - Remainder of the request, a colon separated 3141: # list containing domain/user that identifies the 3142: # user being queried, and the database namespace 3143: # (database filename essentially). 3144: # $client - File open on the client. 3145: # Returns: 3146: # 1 - Continue processing. 3147: # 0 - Exit the server. 3148: # 3149: sub get_profile_keys { 3150: my ($cmd, $tail, $client) = @_; 3151: 3152: my $userinput = "$cmd:$tail"; 3153: 3154: my ($udom,$uname,$namespace)=split(/:/,$tail); 3155: my $qresult=''; 3156: my $hashref = &tie_user_hash($udom, $uname, $namespace, 3157: &GDBM_READER()); 3158: if ($hashref) { 3159: foreach my $key (keys %$hashref) { 3160: $qresult.="$key&"; 3161: } 3162: if (&untie_user_hash($hashref)) { 3163: $qresult=~s/\&$//; 3164: &Reply($client, \$qresult, $userinput); 3165: } else { 3166: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 3167: "while attempting keys\n", $userinput); 3168: } 3169: } else { 3170: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 3171: "while attempting keys\n", $userinput); 3172: } 3173: 3174: return 1; 3175: } 3176: ®ister_handler("keys", \&get_profile_keys, 0, 1, 0); 3177: 3178: # 3179: # Dump the contents of a user profile database. 3180: # Note that this constitutes a very large covert channel too since 3181: # the dump will return sensitive information that is not encrypted. 3182: # The naive security assumption is that the session negotiation ensures 3183: # our client is trusted and I don't believe that's assured at present. 3184: # Sure want badly to go to ssl or tls. Of course if my peer isn't really 3185: # a LonCAPA node they could have negotiated an encryption key too so >sigh<. 3186: # 3187: # Parameters: 3188: # $cmd - The command request keyword (currentdump). 3189: # $tail - Remainder of the request, consisting of a colon 3190: # separated list that has the domain/username and 3191: # the namespace to dump (database file). 3192: # $client - file open on the remote client. 3193: # Returns: 3194: # 1 - Continue processing. 3195: # 0 - Exit the server. 3196: # 3197: sub dump_profile_database { 3198: my ($cmd, $tail, $client) = @_; 3199: 3200: my $userinput = "$cmd:$tail"; 3201: 3202: my ($udom,$uname,$namespace) = split(/:/,$tail); 3203: my $hashref = &tie_user_hash($udom, $uname, $namespace, 3204: &GDBM_READER()); 3205: if ($hashref) { 3206: # Structure of %data: 3207: # $data{$symb}->{$parameter}=$value; 3208: # $data{$symb}->{'v.'.$parameter}=$version; 3209: # since $parameter will be unescaped, we do not 3210: # have to worry about silly parameter names... 3211: 3212: my $qresult=''; 3213: my %data = (); # A hash of anonymous hashes.. 3214: while (my ($key,$value) = each(%$hashref)) { 3215: my ($v,$symb,$param) = split(/:/,$key); 3216: next if ($v eq 'version' || $symb eq 'keys'); 3217: next if (exists($data{$symb}) && 3218: exists($data{$symb}->{$param}) && 3219: $data{$symb}->{'v.'.$param} > $v); 3220: $data{$symb}->{$param}=$value; 3221: $data{$symb}->{'v.'.$param}=$v; 3222: } 3223: if (&untie_user_hash($hashref)) { 3224: while (my ($symb,$param_hash) = each(%data)) { 3225: while(my ($param,$value) = each (%$param_hash)){ 3226: next if ($param =~ /^v\./); # Ignore versions... 3227: # 3228: # Just dump the symb=value pairs separated by & 3229: # 3230: $qresult.=$symb.':'.$param.'='.$value.'&'; 3231: } 3232: } 3233: chop($qresult); 3234: &Reply($client , \$qresult, $userinput); 3235: } else { 3236: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". 3237: "while attempting currentdump\n", $userinput); 3238: } 3239: } else { 3240: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 3241: "while attempting currentdump\n", $userinput); 3242: } 3243: 3244: return 1; 3245: } 3246: ®ister_handler("currentdump", \&dump_profile_database, 0, 1, 0); 3247: 3248: # 3249: # Dump a profile database with an optional regular expression 3250: # to match against the keys. In this dump, no effort is made 3251: # to separate symb from version information. Presumably the 3252: # databases that are dumped by this command are of a different 3253: # structure. Need to look at this and improve the documentation of 3254: # both this and the currentdump handler. 3255: # Parameters: 3256: # $cmd - The command keyword. 3257: # $tail - All of the characters after the $cmd: 3258: # These are expected to be a colon 3259: # separated list containing: 3260: # domain/user - identifying the user. 3261: # namespace - identifying the database. 3262: # regexp - optional regular expression 3263: # that is matched against 3264: # database keywords to do 3265: # selective dumps. 3266: # range - optional range of entries 3267: # e.g., 10-20 would return the 3268: # 10th to 19th items, etc. 3269: # $client - Channel open on the client. 3270: # Returns: 3271: # 1 - Continue processing. 3272: # Side effects: 3273: # response is written to $client. 3274: # 3275: sub dump_with_regexp { 3276: my ($cmd, $tail, $client) = @_; 3277: 3278: 3279: my $userinput = "$cmd:$tail"; 3280: 3281: my ($udom,$uname,$namespace,$regexp,$range)=split(/:/,$tail); 3282: if (defined($regexp)) { 3283: $regexp=&unescape($regexp); 3284: } else { 3285: $regexp='.'; 3286: } 3287: my ($start,$end); 3288: if (defined($range)) { 3289: if ($range =~/^(\d+)\-(\d+)$/) { 3290: ($start,$end) = ($1,$2); 3291: } elsif ($range =~/^(\d+)$/) { 3292: ($start,$end) = (0,$1); 3293: } else { 3294: undef($range); 3295: } 3296: } 3297: my $hashref = &tie_user_hash($udom, $uname, $namespace, 3298: &GDBM_READER()); 3299: if ($hashref) { 3300: my $qresult=''; 3301: my $count=0; 3302: # 3303: # When dump is for roles.db, determine if LON-CAPA version checking is needed. 3304: # Sessions on 2.10 and later do not require version checking, as that occurs 3305: # on the server hosting the user session, when constructing the roles/courses 3306: # screen). 3307: # 3308: my $skipcheck; 3309: my @ids = &Apache::lonnet::current_machine_ids(); 3310: my (%homecourses,$major,$minor,$now); 3311: # 3312: # If dump is for roles.db from a pre-2.10 server, determine the LON-CAPA 3313: # version on the server which requested the data. For LON-CAPA 2.9, the 3314: # client session will have sent its LON-CAPA version when initiating the 3315: # connection. For LON-CAPA 2.8 and older, the version is retrieved from 3316: # the global %loncaparevs in lonnet.pm. 3317: # 3318: # 3319: if ($namespace eq 'roles') { 3320: my $loncaparev = $clientversion; 3321: if ($loncaparev eq '') { 3322: $loncaparev = $Apache::lonnet::loncaparevs{$clientname}; 3323: } 3324: if ($loncaparev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?/) { 3325: $major = $1; 3326: $minor = $2; 3327: } 3328: if (($major > 2) || (($major == 2) && ($minor > 9))) { 3329: $skipcheck = 1; 3330: } 3331: $now = time; 3332: } 3333: while (my ($key,$value) = each(%$hashref)) { 3334: if (($namespace eq 'roles') && (!$skipcheck)) { 3335: if ($key =~ m{^/($LONCAPA::match_domain)/($LONCAPA::match_courseid)(/?[^_]*)_(cc|co|in|ta|ep|ad|st|cr)$}) { 3336: my $cdom = $1; 3337: my $cnum = $2; 3338: my ($role,$roleend,$rolestart) = split(/\_/,$value); 3339: if (!$roleend || $roleend > $now) { 3340: # 3341: # For active course roles, check that requesting server is running a LON-CAPA 3342: # version which meets any version requirements for the course. Do not include 3343: # the role amongst the results returned if the requesting server's version is 3344: # too old. 3345: # 3346: # This determination is handled differently depending on whether the course's 3347: # homeserver is the current server, or whether it is a different server. 3348: # In both cases, the course's version requirement needs to be retrieved. 3349: # 3350: next unless (&releasereqd_check($cnum,$cdom,$key,$value,$major, 3351: $minor,\%homecourses,\@ids)); 3352: } 3353: } 3354: } 3355: if ($regexp eq '.') { 3356: $count++; 3357: if (defined($range) && $count >= $end) { last; } 3358: if (defined($range) && $count < $start) { next; } 3359: $qresult.=$key.'='.$value.'&'; 3360: } else { 3361: my $unescapeKey = &unescape($key); 3362: if (eval('$unescapeKey=~/$regexp/')) { 3363: $count++; 3364: if (defined($range) && $count >= $end) { last; } 3365: if (defined($range) && $count < $start) { next; } 3366: $qresult.="$key=$value&"; 3367: } 3368: } 3369: } 3370: if (&untie_user_hash($hashref)) { 3371: # 3372: # If dump is for roles.db from a pre-2.10 server, check if the LON-CAPA 3373: # version requirements for courses for which the current server is the home 3374: # server permit course roles to be usable on the client server hosting the 3375: # user's session. If so, include those role results in the data returned to 3376: # the client server. 3377: # 3378: if (($namespace eq 'roles') && (!$skipcheck)) { 3379: if (keys(%homecourses) > 0) { 3380: $qresult .= &check_homecourses(\%homecourses,$regexp,$count, 3381: $range,$start,$end,$major,$minor); 3382: } 3383: } 3384: chop($qresult); 3385: &Reply($client, \$qresult, $userinput); 3386: } else { 3387: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". 3388: "while attempting dump\n", $userinput); 3389: } 3390: } else { 3391: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 3392: "while attempting dump\n", $userinput); 3393: } 3394: 3395: return 1; 3396: } 3397: ®ister_handler("dump", \&dump_with_regexp, 0, 1, 0); 3398: 3399: # Store a set of key=value pairs associated with a versioned name. 3400: # 3401: # Parameters: 3402: # $cmd - Request command keyword. 3403: # $tail - Tail of the request. This is a colon 3404: # separated list containing: 3405: # domain/user - User and authentication domain. 3406: # namespace - Name of the database being modified 3407: # rid - Resource keyword to modify. 3408: # what - new value associated with rid. 3409: # 3410: # $client - Socket open on the client. 3411: # 3412: # 3413: # Returns: 3414: # 1 (keep on processing). 3415: # Side-Effects: 3416: # Writes to the client 3417: sub store_handler { 3418: my ($cmd, $tail, $client) = @_; 3419: 3420: my $userinput = "$cmd:$tail"; 3421: 3422: my ($udom,$uname,$namespace,$rid,$what) =split(/:/,$tail); 3423: if ($namespace ne 'roles') { 3424: 3425: chomp($what); 3426: my @pairs=split(/\&/,$what); 3427: my $hashref = &tie_user_hash($udom, $uname, $namespace, 3428: &GDBM_WRCREAT(), "S", 3429: "$rid:$what"); 3430: if ($hashref) { 3431: my $now = time; 3432: my @previouskeys=split(/&/,$hashref->{"keys:$rid"}); 3433: my $key; 3434: $hashref->{"version:$rid"}++; 3435: my $version=$hashref->{"version:$rid"}; 3436: my $allkeys=''; 3437: foreach my $pair (@pairs) { 3438: my ($key,$value)=split(/=/,$pair); 3439: $allkeys.=$key.':'; 3440: $hashref->{"$version:$rid:$key"}=$value; 3441: } 3442: $hashref->{"$version:$rid:timestamp"}=$now; 3443: $allkeys.='timestamp'; 3444: $hashref->{"$version:keys:$rid"}=$allkeys; 3445: if (&untie_user_hash($hashref)) { 3446: &Reply($client, "ok\n", $userinput); 3447: } else { 3448: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 3449: "while attempting store\n", $userinput); 3450: } 3451: } else { 3452: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 3453: "while attempting store\n", $userinput); 3454: } 3455: } else { 3456: &Failure($client, "refused\n", $userinput); 3457: } 3458: 3459: return 1; 3460: } 3461: ®ister_handler("store", \&store_handler, 0, 1, 0); 3462: 3463: # Modify a set of key=value pairs associated with a versioned name. 3464: # 3465: # Parameters: 3466: # $cmd - Request command keyword. 3467: # $tail - Tail of the request. This is a colon 3468: # separated list containing: 3469: # domain/user - User and authentication domain. 3470: # namespace - Name of the database being modified 3471: # rid - Resource keyword to modify. 3472: # v - Version item to modify 3473: # what - new value associated with rid. 3474: # 3475: # $client - Socket open on the client. 3476: # 3477: # 3478: # Returns: 3479: # 1 (keep on processing). 3480: # Side-Effects: 3481: # Writes to the client 3482: sub putstore_handler { 3483: my ($cmd, $tail, $client) = @_; 3484: 3485: my $userinput = "$cmd:$tail"; 3486: 3487: my ($udom,$uname,$namespace,$rid,$v,$what) =split(/:/,$tail); 3488: if ($namespace ne 'roles') { 3489: 3490: chomp($what); 3491: my $hashref = &tie_user_hash($udom, $uname, $namespace, 3492: &GDBM_WRCREAT(), "M", 3493: "$rid:$v:$what"); 3494: if ($hashref) { 3495: my $now = time; 3496: my %data = &hash_extract($what); 3497: my @allkeys; 3498: while (my($key,$value) = each(%data)) { 3499: push(@allkeys,$key); 3500: $hashref->{"$v:$rid:$key"} = $value; 3501: } 3502: my $allkeys = join(':',@allkeys); 3503: $hashref->{"$v:keys:$rid"}=$allkeys; 3504: 3505: if (&untie_user_hash($hashref)) { 3506: &Reply($client, "ok\n", $userinput); 3507: } else { 3508: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 3509: "while attempting store\n", $userinput); 3510: } 3511: } else { 3512: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 3513: "while attempting store\n", $userinput); 3514: } 3515: } else { 3516: &Failure($client, "refused\n", $userinput); 3517: } 3518: 3519: return 1; 3520: } 3521: ®ister_handler("putstore", \&putstore_handler, 0, 1, 0); 3522: 3523: sub hash_extract { 3524: my ($str)=@_; 3525: my %hash; 3526: foreach my $pair (split(/\&/,$str)) { 3527: my ($key,$value)=split(/=/,$pair); 3528: $hash{$key}=$value; 3529: } 3530: return (%hash); 3531: } 3532: sub hash_to_str { 3533: my ($hash_ref)=@_; 3534: my $str; 3535: foreach my $key (keys(%$hash_ref)) { 3536: $str.=$key.'='.$hash_ref->{$key}.'&'; 3537: } 3538: $str=~s/\&$//; 3539: return $str; 3540: } 3541: 3542: # 3543: # Dump out all versions of a resource that has key=value pairs associated 3544: # with it for each version. These resources are built up via the store 3545: # command. 3546: # 3547: # Parameters: 3548: # $cmd - Command keyword. 3549: # $tail - Remainder of the request which consists of: 3550: # domain/user - User and auth. domain. 3551: # namespace - name of resource database. 3552: # rid - Resource id. 3553: # $client - socket open on the client. 3554: # 3555: # Returns: 3556: # 1 indicating the caller should not yet exit. 3557: # Side-effects: 3558: # Writes a reply to the client. 3559: # The reply is a string of the following shape: 3560: # version=current&version:keys=k1:k2...&1:k1=v1&1:k2=v2... 3561: # Where the 1 above represents version 1. 3562: # this continues for all pairs of keys in all versions. 3563: # 3564: # 3565: # 3566: # 3567: sub restore_handler { 3568: my ($cmd, $tail, $client) = @_; 3569: 3570: my $userinput = "$cmd:$tail"; # Only used for logging purposes. 3571: my ($udom,$uname,$namespace,$rid) = split(/:/,$tail); 3572: $namespace=~s/\//\_/g; 3573: $namespace = &LONCAPA::clean_username($namespace); 3574: 3575: chomp($rid); 3576: my $qresult=''; 3577: my $hashref = &tie_user_hash($udom, $uname, $namespace, &GDBM_READER()); 3578: if ($hashref) { 3579: my $version=$hashref->{"version:$rid"}; 3580: $qresult.="version=$version&"; 3581: my $scope; 3582: for ($scope=1;$scope<=$version;$scope++) { 3583: my $vkeys=$hashref->{"$scope:keys:$rid"}; 3584: my @keys=split(/:/,$vkeys); 3585: my $key; 3586: $qresult.="$scope:keys=$vkeys&"; 3587: foreach $key (@keys) { 3588: $qresult.="$scope:$key=".$hashref->{"$scope:$rid:$key"}."&"; 3589: } 3590: } 3591: if (&untie_user_hash($hashref)) { 3592: $qresult=~s/\&$//; 3593: &Reply( $client, \$qresult, $userinput); 3594: } else { 3595: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 3596: "while attempting restore\n", $userinput); 3597: } 3598: } else { 3599: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 3600: "while attempting restore\n", $userinput); 3601: } 3602: 3603: return 1; 3604: 3605: 3606: } 3607: ®ister_handler("restore", \&restore_handler, 0,1,0); 3608: 3609: # 3610: # Add a chat message to a synchronous discussion board. 3611: # 3612: # Parameters: 3613: # $cmd - Request keyword. 3614: # $tail - Tail of the command. A colon separated list 3615: # containing: 3616: # cdom - Domain on which the chat board lives 3617: # cnum - Course containing the chat board. 3618: # newpost - Body of the posting. 3619: # group - Optional group, if chat board is only 3620: # accessible in a group within the course 3621: # $client - Socket open on the client. 3622: # Returns: 3623: # 1 - Indicating caller should keep on processing. 3624: # 3625: # Side-effects: 3626: # writes a reply to the client. 3627: # 3628: # 3629: sub send_chat_handler { 3630: my ($cmd, $tail, $client) = @_; 3631: 3632: 3633: my $userinput = "$cmd:$tail"; 3634: 3635: my ($cdom,$cnum,$newpost,$group)=split(/\:/,$tail); 3636: &chat_add($cdom,$cnum,$newpost,$group); 3637: &Reply($client, "ok\n", $userinput); 3638: 3639: return 1; 3640: } 3641: ®ister_handler("chatsend", \&send_chat_handler, 0, 1, 0); 3642: 3643: # 3644: # Retrieve the set of chat messages from a discussion board. 3645: # 3646: # Parameters: 3647: # $cmd - Command keyword that initiated the request. 3648: # $tail - Remainder of the request after the command 3649: # keyword. In this case a colon separated list of 3650: # chat domain - Which discussion board. 3651: # chat id - Discussion thread(?) 3652: # domain/user - Authentication domain and username 3653: # of the requesting person. 3654: # group - Optional course group containing 3655: # the board. 3656: # $client - Socket open on the client program. 3657: # Returns: 3658: # 1 - continue processing 3659: # Side effects: 3660: # Response is written to the client. 3661: # 3662: sub retrieve_chat_handler { 3663: my ($cmd, $tail, $client) = @_; 3664: 3665: 3666: my $userinput = "$cmd:$tail"; 3667: 3668: my ($cdom,$cnum,$udom,$uname,$group)=split(/\:/,$tail); 3669: my $reply=''; 3670: foreach (&get_chat($cdom,$cnum,$udom,$uname,$group)) { 3671: $reply.=&escape($_).':'; 3672: } 3673: $reply=~s/\:$//; 3674: &Reply($client, \$reply, $userinput); 3675: 3676: 3677: return 1; 3678: } 3679: ®ister_handler("chatretr", \&retrieve_chat_handler, 0, 1, 0); 3680: 3681: # 3682: # Initiate a query of an sql database. SQL query repsonses get put in 3683: # a file for later retrieval. This prevents sql query results from 3684: # bottlenecking the system. Note that with loncnew, perhaps this is 3685: # less of an issue since multiple outstanding requests can be concurrently 3686: # serviced. 3687: # 3688: # Parameters: 3689: # $cmd - COmmand keyword that initiated the request. 3690: # $tail - Remainder of the command after the keyword. 3691: # For this function, this consists of a query and 3692: # 3 arguments that are self-documentingly labelled 3693: # in the original arg1, arg2, arg3. 3694: # $client - Socket open on the client. 3695: # Return: 3696: # 1 - Indicating processing should continue. 3697: # Side-effects: 3698: # a reply is written to $client. 3699: # 3700: sub send_query_handler { 3701: my ($cmd, $tail, $client) = @_; 3702: 3703: 3704: my $userinput = "$cmd:$tail"; 3705: 3706: my ($query,$arg1,$arg2,$arg3)=split(/\:/,$tail); 3707: $query=~s/\n*$//g; 3708: &Reply($client, "". &sql_reply("$clientname\&$query". 3709: "\&$arg1"."\&$arg2"."\&$arg3")."\n", 3710: $userinput); 3711: 3712: return 1; 3713: } 3714: ®ister_handler("querysend", \&send_query_handler, 0, 1, 0); 3715: 3716: # 3717: # Add a reply to an sql query. SQL queries are done asyncrhonously. 3718: # The query is submitted via a "querysend" transaction. 3719: # There it is passed on to the lonsql daemon, queued and issued to 3720: # mysql. 3721: # This transaction is invoked when the sql transaction is complete 3722: # it stores the query results in flie and indicates query completion. 3723: # presumably local software then fetches this response... I'm guessing 3724: # the sequence is: lonc does a querysend, we ask lonsql to do it. 3725: # lonsql on completion of the query interacts with the lond of our 3726: # client to do a query reply storing two files: 3727: # - id - The results of the query. 3728: # - id.end - Indicating the transaction completed. 3729: # NOTE: id is a unique id assigned to the query and querysend time. 3730: # Parameters: 3731: # $cmd - Command keyword that initiated this request. 3732: # $tail - Remainder of the tail. In this case that's a colon 3733: # separated list containing the query Id and the 3734: # results of the query. 3735: # $client - Socket open on the client. 3736: # Return: 3737: # 1 - Indicating that we should continue processing. 3738: # Side effects: 3739: # ok written to the client. 3740: # 3741: sub reply_query_handler { 3742: my ($cmd, $tail, $client) = @_; 3743: 3744: 3745: my $userinput = "$cmd:$tail"; 3746: 3747: my ($id,$reply)=split(/:/,$tail); 3748: my $store; 3749: my $execdir=$perlvar{'lonDaemons'}; 3750: if ($store=IO::File->new(">$execdir/tmp/$id")) { 3751: $reply=~s/\&/\n/g; 3752: print $store $reply; 3753: close $store; 3754: my $store2=IO::File->new(">$execdir/tmp/$id.end"); 3755: print $store2 "done\n"; 3756: close $store2; 3757: &Reply($client, "ok\n", $userinput); 3758: } else { 3759: &Failure($client, "error: ".($!+0) 3760: ." IO::File->new Failed ". 3761: "while attempting queryreply\n", $userinput); 3762: } 3763: 3764: 3765: return 1; 3766: } 3767: ®ister_handler("queryreply", \&reply_query_handler, 0, 1, 0); 3768: 3769: # 3770: # Process the courseidput request. Not quite sure what this means 3771: # at the system level sense. It appears a gdbm file in the 3772: # /home/httpd/lonUsers/$domain/nohist_courseids is tied and 3773: # a set of entries made in that database. 3774: # 3775: # Parameters: 3776: # $cmd - The command keyword that initiated this request. 3777: # $tail - Tail of the command. In this case consists of a colon 3778: # separated list contaning the domain to apply this to and 3779: # an ampersand separated list of keyword=value pairs. 3780: # Each value is a colon separated list that includes: 3781: # description, institutional code and course owner. 3782: # For backward compatibility with versions included 3783: # in LON-CAPA 1.1.X (and earlier) and 1.2.X, institutional 3784: # code and/or course owner are preserved from the existing 3785: # record when writing a new record in response to 1.1 or 3786: # 1.2 implementations of lonnet::flushcourselogs(). 3787: # 3788: # $client - Socket open on the client. 3789: # Returns: 3790: # 1 - indicating that processing should continue 3791: # 3792: # Side effects: 3793: # reply is written to the client. 3794: # 3795: sub put_course_id_handler { 3796: my ($cmd, $tail, $client) = @_; 3797: 3798: 3799: my $userinput = "$cmd:$tail"; 3800: 3801: my ($udom, $what) = split(/:/, $tail,2); 3802: chomp($what); 3803: my $now=time; 3804: my @pairs=split(/\&/,$what); 3805: 3806: my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT()); 3807: if ($hashref) { 3808: foreach my $pair (@pairs) { 3809: my ($key,$courseinfo) = split(/=/,$pair,2); 3810: $courseinfo =~ s/=/:/g; 3811: if (defined($hashref->{$key})) { 3812: my $value = &Apache::lonnet::thaw_unescape($hashref->{$key}); 3813: if (ref($value) eq 'HASH') { 3814: my @items = ('description','inst_code','owner','type'); 3815: my @new_items = split(/:/,$courseinfo,-1); 3816: my %storehash; 3817: for (my $i=0; $i<@new_items; $i++) { 3818: $storehash{$items[$i]} = &unescape($new_items[$i]); 3819: } 3820: $hashref->{$key} = 3821: &Apache::lonnet::freeze_escape(\%storehash); 3822: my $unesc_key = &unescape($key); 3823: $hashref->{&escape('lasttime:'.$unesc_key)} = $now; 3824: next; 3825: } 3826: } 3827: my @current_items = split(/:/,$hashref->{$key},-1); 3828: shift(@current_items); # remove description 3829: pop(@current_items); # remove last access 3830: my $numcurrent = scalar(@current_items); 3831: if ($numcurrent > 3) { 3832: $numcurrent = 3; 3833: } 3834: my @new_items = split(/:/,$courseinfo,-1); 3835: my $numnew = scalar(@new_items); 3836: if ($numcurrent > 0) { 3837: if ($numnew <= $numcurrent) { # flushcourselogs() from pre 2.2 3838: for (my $j=$numcurrent-$numnew; $j>=0; $j--) { 3839: $courseinfo .= ':'.$current_items[$numcurrent-$j-1]; 3840: } 3841: } 3842: } 3843: $hashref->{$key}=$courseinfo.':'.$now; 3844: } 3845: if (&untie_domain_hash($hashref)) { 3846: &Reply( $client, "ok\n", $userinput); 3847: } else { 3848: &Failure($client, "error: ".($!+0) 3849: ." untie(GDBM) Failed ". 3850: "while attempting courseidput\n", $userinput); 3851: } 3852: } else { 3853: &Failure($client, "error: ".($!+0) 3854: ." tie(GDBM) Failed ". 3855: "while attempting courseidput\n", $userinput); 3856: } 3857: 3858: return 1; 3859: } 3860: ®ister_handler("courseidput", \&put_course_id_handler, 0, 1, 0); 3861: 3862: sub put_course_id_hash_handler { 3863: my ($cmd, $tail, $client) = @_; 3864: my $userinput = "$cmd:$tail"; 3865: my ($udom,$mode,$what) = split(/:/, $tail,3); 3866: chomp($what); 3867: my $now=time; 3868: my @pairs=split(/\&/,$what); 3869: my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT()); 3870: if ($hashref) { 3871: foreach my $pair (@pairs) { 3872: my ($key,$value)=split(/=/,$pair); 3873: my $unesc_key = &unescape($key); 3874: if ($mode ne 'timeonly') { 3875: if (!defined($hashref->{&escape('lasttime:'.$unesc_key)})) { 3876: my $curritems = &Apache::lonnet::thaw_unescape($key); 3877: if (ref($curritems) ne 'HASH') { 3878: my @current_items = split(/:/,$hashref->{$key},-1); 3879: my $lasttime = pop(@current_items); 3880: $hashref->{&escape('lasttime:'.$unesc_key)} = $lasttime; 3881: } else { 3882: $hashref->{&escape('lasttime:'.$unesc_key)} = ''; 3883: } 3884: } 3885: $hashref->{$key} = $value; 3886: } 3887: if ($mode ne 'notime') { 3888: $hashref->{&escape('lasttime:'.$unesc_key)} = $now; 3889: } 3890: } 3891: if (&untie_domain_hash($hashref)) { 3892: &Reply($client, "ok\n", $userinput); 3893: } else { 3894: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 3895: "while attempting courseidputhash\n", $userinput); 3896: } 3897: } else { 3898: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 3899: "while attempting courseidputhash\n", $userinput); 3900: } 3901: return 1; 3902: } 3903: ®ister_handler("courseidputhash", \&put_course_id_hash_handler, 0, 1, 0); 3904: 3905: # Retrieves the value of a course id resource keyword pattern 3906: # defined since a starting date. Both the starting date and the 3907: # keyword pattern are optional. If the starting date is not supplied it 3908: # is treated as the beginning of time. If the pattern is not found, 3909: # it is treatred as "." matching everything. 3910: # 3911: # Parameters: 3912: # $cmd - Command keyword that resulted in us being dispatched. 3913: # $tail - The remainder of the command that, in this case, consists 3914: # of a colon separated list of: 3915: # domain - The domain in which the course database is 3916: # defined. 3917: # since - Optional parameter describing the minimum 3918: # time of definition(?) of the resources that 3919: # will match the dump. 3920: # description - regular expression that is used to filter 3921: # the dump. Only keywords matching this regexp 3922: # will be used. 3923: # institutional code - optional supplied code to filter 3924: # the dump. Only courses with an institutional code 3925: # that match the supplied code will be returned. 3926: # owner - optional supplied username and domain of owner to 3927: # filter the dump. Only courses for which the course 3928: # owner matches the supplied username and/or domain 3929: # will be returned. Pre-2.2.0 legacy entries from 3930: # nohist_courseiddump will only contain usernames. 3931: # type - optional parameter for selection 3932: # regexp_ok - if 1 or -1 allow the supplied institutional code 3933: # filter to behave as a regular expression: 3934: # 1 will not exclude the course if the instcode matches the RE 3935: # -1 will exclude the course if the instcode matches the RE 3936: # rtn_as_hash - whether to return the information available for 3937: # each matched item as a frozen hash of all 3938: # key, value pairs in the item's hash, or as a 3939: # colon-separated list of (in order) description, 3940: # institutional code, and course owner. 3941: # selfenrollonly - filter by courses allowing self-enrollment 3942: # now or in the future (selfenrollonly = 1). 3943: # catfilter - filter by course category, assigned to a course 3944: # using manually defined categories (i.e., not 3945: # self-cataloging based on on institutional code). 3946: # showhidden - include course in results even if course 3947: # was set to be excluded from course catalog (DC only). 3948: # caller - if set to 'coursecatalog', courses set to be hidden 3949: # from course catalog will be excluded from results (unless 3950: # overridden by "showhidden". 3951: # cloner - escaped username:domain of course cloner (if picking course to 3952: # clone). 3953: # cc_clone_list - escaped comma separated list of courses for which 3954: # course cloner has active CC role (and so can clone 3955: # automatically). 3956: # cloneonly - filter by courses for which cloner has rights to clone. 3957: # createdbefore - include courses for which creation date preceeded this date. 3958: # createdafter - include courses for which creation date followed this date. 3959: # creationcontext - include courses created in specified context 3960: # 3961: # domcloner - flag to indicate if user can create CCs in course's domain. 3962: # If so, ability to clone course is automatic. 3963: # 3964: # $client - The socket open on the client. 3965: # Returns: 3966: # 1 - Continue processing. 3967: # Side Effects: 3968: # a reply is written to $client. 3969: sub dump_course_id_handler { 3970: my ($cmd, $tail, $client) = @_; 3971: my $userinput = "$cmd:$tail"; 3972: 3973: my ($udom,$since,$description,$instcodefilter,$ownerfilter,$coursefilter, 3974: $typefilter,$regexp_ok,$rtn_as_hash,$selfenrollonly,$catfilter,$showhidden, 3975: $caller,$cloner,$cc_clone_list,$cloneonly,$createdbefore,$createdafter, 3976: $creationcontext,$domcloner) =split(/:/,$tail); 3977: my $now = time; 3978: my ($cloneruname,$clonerudom,%cc_clone); 3979: if (defined($description)) { 3980: $description=&unescape($description); 3981: } else { 3982: $description='.'; 3983: } 3984: if (defined($instcodefilter)) { 3985: $instcodefilter=&unescape($instcodefilter); 3986: } else { 3987: $instcodefilter='.'; 3988: } 3989: my ($ownerunamefilter,$ownerdomfilter); 3990: if (defined($ownerfilter)) { 3991: $ownerfilter=&unescape($ownerfilter); 3992: if ($ownerfilter ne '.' && defined($ownerfilter)) { 3993: if ($ownerfilter =~ /^([^:]*):([^:]*)$/) { 3994: $ownerunamefilter = $1; 3995: $ownerdomfilter = $2; 3996: } else { 3997: $ownerunamefilter = $ownerfilter; 3998: $ownerdomfilter = ''; 3999: } 4000: } 4001: } else { 4002: $ownerfilter='.'; 4003: } 4004: 4005: if (defined($coursefilter)) { 4006: $coursefilter=&unescape($coursefilter); 4007: } else { 4008: $coursefilter='.'; 4009: } 4010: if (defined($typefilter)) { 4011: $typefilter=&unescape($typefilter); 4012: } else { 4013: $typefilter='.'; 4014: } 4015: if (defined($regexp_ok)) { 4016: $regexp_ok=&unescape($regexp_ok); 4017: } 4018: if (defined($catfilter)) { 4019: $catfilter=&unescape($catfilter); 4020: } 4021: if (defined($cloner)) { 4022: $cloner = &unescape($cloner); 4023: ($cloneruname,$clonerudom) = ($cloner =~ /^($LONCAPA::match_username):($LONCAPA::match_domain)$/); 4024: } 4025: if (defined($cc_clone_list)) { 4026: $cc_clone_list = &unescape($cc_clone_list); 4027: my @cc_cloners = split('&',$cc_clone_list); 4028: foreach my $cid (@cc_cloners) { 4029: my ($clonedom,$clonenum) = split(':',$cid); 4030: next if ($clonedom ne $udom); 4031: $cc_clone{$clonedom.'_'.$clonenum} = 1; 4032: } 4033: } 4034: if ($createdbefore ne '') { 4035: $createdbefore = &unescape($createdbefore); 4036: } else { 4037: $createdbefore = 0; 4038: } 4039: if ($createdafter ne '') { 4040: $createdafter = &unescape($createdafter); 4041: } else { 4042: $createdafter = 0; 4043: } 4044: if ($creationcontext ne '') { 4045: $creationcontext = &unescape($creationcontext); 4046: } else { 4047: $creationcontext = '.'; 4048: } 4049: my $unpack = 1; 4050: if ($description eq '.' && $instcodefilter eq '.' && $ownerfilter eq '.' && 4051: $typefilter eq '.') { 4052: $unpack = 0; 4053: } 4054: if (!defined($since)) { $since=0; } 4055: my $qresult=''; 4056: my $hashref = &tie_domain_hash($udom, "nohist_courseids", &GDBM_WRCREAT()); 4057: if ($hashref) { 4058: while (my ($key,$value) = each(%$hashref)) { 4059: my ($unesc_key,$lasttime_key,$lasttime,$is_hash,%val, 4060: %unesc_val,$selfenroll_end,$selfenroll_types,$created, 4061: $context); 4062: $unesc_key = &unescape($key); 4063: if ($unesc_key =~ /^lasttime:/) { 4064: next; 4065: } else { 4066: $lasttime_key = &escape('lasttime:'.$unesc_key); 4067: } 4068: if ($hashref->{$lasttime_key} ne '') { 4069: $lasttime = $hashref->{$lasttime_key}; 4070: next if ($lasttime<$since); 4071: } 4072: my ($canclone,$valchange); 4073: my $items = &Apache::lonnet::thaw_unescape($value); 4074: if (ref($items) eq 'HASH') { 4075: if ($hashref->{$lasttime_key} eq '') { 4076: next if ($since > 1); 4077: } 4078: $is_hash = 1; 4079: if ($domcloner) { 4080: $canclone = 1; 4081: } elsif (defined($clonerudom)) { 4082: if ($items->{'cloners'}) { 4083: my @cloneable = split(',',$items->{'cloners'}); 4084: if (@cloneable) { 4085: if (grep(/^\*$/,@cloneable)) { 4086: $canclone = 1; 4087: } elsif (grep(/^\*:\Q$clonerudom\E$/,@cloneable)) { 4088: $canclone = 1; 4089: } elsif (grep(/^\Q$cloneruname\E:\Q$clonerudom\E$/,@cloneable)) { 4090: $canclone = 1; 4091: } 4092: } 4093: unless ($canclone) { 4094: if ($cloneruname ne '' && $clonerudom ne '') { 4095: if ($cc_clone{$unesc_key}) { 4096: $canclone = 1; 4097: $items->{'cloners'} .= ','.$cloneruname.':'. 4098: $clonerudom; 4099: $valchange = 1; 4100: } 4101: } 4102: } 4103: } elsif (defined($cloneruname)) { 4104: if ($cc_clone{$unesc_key}) { 4105: $canclone = 1; 4106: $items->{'cloners'} = $cloneruname.':'.$clonerudom; 4107: $valchange = 1; 4108: } 4109: unless ($canclone) { 4110: if ($items->{'owner'} =~ /:/) { 4111: if ($items->{'owner'} eq $cloner) { 4112: $canclone = 1; 4113: } 4114: } elsif ($cloner eq $items->{'owner'}.':'.$udom) { 4115: $canclone = 1; 4116: } 4117: if ($canclone) { 4118: $items->{'cloners'} = $cloneruname.':'.$clonerudom; 4119: $valchange = 1; 4120: } 4121: } 4122: } 4123: } 4124: if ($unpack || !$rtn_as_hash) { 4125: $unesc_val{'descr'} = $items->{'description'}; 4126: $unesc_val{'inst_code'} = $items->{'inst_code'}; 4127: $unesc_val{'owner'} = $items->{'owner'}; 4128: $unesc_val{'type'} = $items->{'type'}; 4129: $unesc_val{'cloners'} = $items->{'cloners'}; 4130: $unesc_val{'created'} = $items->{'created'}; 4131: $unesc_val{'context'} = $items->{'context'}; 4132: } 4133: $selfenroll_types = $items->{'selfenroll_types'}; 4134: $selfenroll_end = $items->{'selfenroll_end_date'}; 4135: $created = $items->{'created'}; 4136: $context = $items->{'context'}; 4137: if ($selfenrollonly) { 4138: next if (!$selfenroll_types); 4139: if (($selfenroll_end > 0) && ($selfenroll_end <= $now)) { 4140: next; 4141: } 4142: } 4143: if ($creationcontext ne '.') { 4144: next if (($context ne '') && ($context ne $creationcontext)); 4145: } 4146: if ($createdbefore > 0) { 4147: next if (($created eq '') || ($created > $createdbefore)); 4148: } 4149: if ($createdafter > 0) { 4150: next if (($created eq '') || ($created <= $createdafter)); 4151: } 4152: if ($catfilter ne '') { 4153: next if ($items->{'categories'} eq ''); 4154: my @categories = split('&',$items->{'categories'}); 4155: next if (@categories == 0); 4156: my @subcats = split('&',$catfilter); 4157: my $matchcat = 0; 4158: foreach my $cat (@categories) { 4159: if (grep(/^\Q$cat\E$/,@subcats)) { 4160: $matchcat = 1; 4161: last; 4162: } 4163: } 4164: next if (!$matchcat); 4165: } 4166: if ($caller eq 'coursecatalog') { 4167: if ($items->{'hidefromcat'} eq 'yes') { 4168: next if !$showhidden; 4169: } 4170: } 4171: } else { 4172: next if ($catfilter ne ''); 4173: next if ($selfenrollonly); 4174: next if ($createdbefore || $createdafter); 4175: next if ($creationcontext ne '.'); 4176: if ((defined($clonerudom)) && (defined($cloneruname))) { 4177: if ($cc_clone{$unesc_key}) { 4178: $canclone = 1; 4179: $val{'cloners'} = &escape($cloneruname.':'.$clonerudom); 4180: } 4181: } 4182: $is_hash = 0; 4183: my @courseitems = split(/:/,$value); 4184: $lasttime = pop(@courseitems); 4185: if ($hashref->{$lasttime_key} eq '') { 4186: next if ($lasttime<$since); 4187: } 4188: ($val{'descr'},$val{'inst_code'},$val{'owner'},$val{'type'}) = @courseitems; 4189: } 4190: if ($cloneonly) { 4191: next unless ($canclone); 4192: } 4193: my $match = 1; 4194: if ($description ne '.') { 4195: if (!$is_hash) { 4196: $unesc_val{'descr'} = &unescape($val{'descr'}); 4197: } 4198: if (eval{$unesc_val{'descr'} !~ /\Q$description\E/i}) { 4199: $match = 0; 4200: } 4201: } 4202: if ($instcodefilter ne '.') { 4203: if (!$is_hash) { 4204: $unesc_val{'inst_code'} = &unescape($val{'inst_code'}); 4205: } 4206: if ($regexp_ok == 1) { 4207: if (eval{$unesc_val{'inst_code'} !~ /$instcodefilter/}) { 4208: $match = 0; 4209: } 4210: } elsif ($regexp_ok == -1) { 4211: if (eval{$unesc_val{'inst_code'} =~ /$instcodefilter/}) { 4212: $match = 0; 4213: } 4214: } else { 4215: if (eval{$unesc_val{'inst_code'} !~ /\Q$instcodefilter\E/i}) { 4216: $match = 0; 4217: } 4218: } 4219: } 4220: if ($ownerfilter ne '.') { 4221: if (!$is_hash) { 4222: $unesc_val{'owner'} = &unescape($val{'owner'}); 4223: } 4224: if (($ownerunamefilter ne '') && ($ownerdomfilter ne '')) { 4225: if ($unesc_val{'owner'} =~ /:/) { 4226: if (eval{$unesc_val{'owner'} !~ 4227: /\Q$ownerunamefilter\E:\Q$ownerdomfilter\E$/i}) { 4228: $match = 0; 4229: } 4230: } else { 4231: if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E/i}) { 4232: $match = 0; 4233: } 4234: } 4235: } elsif ($ownerunamefilter ne '') { 4236: if ($unesc_val{'owner'} =~ /:/) { 4237: if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E:[^:]+$/i}) { 4238: $match = 0; 4239: } 4240: } else { 4241: if (eval{$unesc_val{'owner'} !~ /\Q$ownerunamefilter\E/i}) { 4242: $match = 0; 4243: } 4244: } 4245: } elsif ($ownerdomfilter ne '') { 4246: if ($unesc_val{'owner'} =~ /:/) { 4247: if (eval{$unesc_val{'owner'} !~ /^[^:]+:\Q$ownerdomfilter\E/}) { 4248: $match = 0; 4249: } 4250: } else { 4251: if ($ownerdomfilter ne $udom) { 4252: $match = 0; 4253: } 4254: } 4255: } 4256: } 4257: if ($coursefilter ne '.') { 4258: if (eval{$unesc_key !~ /^$udom(_)\Q$coursefilter\E$/}) { 4259: $match = 0; 4260: } 4261: } 4262: if ($typefilter ne '.') { 4263: if (!$is_hash) { 4264: $unesc_val{'type'} = &unescape($val{'type'}); 4265: } 4266: if ($unesc_val{'type'} eq '') { 4267: if ($typefilter ne 'Course') { 4268: $match = 0; 4269: } 4270: } else { 4271: if (eval{$unesc_val{'type'} !~ /^\Q$typefilter\E$/}) { 4272: $match = 0; 4273: } 4274: } 4275: } 4276: if ($match == 1) { 4277: if ($rtn_as_hash) { 4278: if ($is_hash) { 4279: if ($valchange) { 4280: my $newvalue = &Apache::lonnet::freeze_escape($items); 4281: $qresult.=$key.'='.$newvalue.'&'; 4282: } else { 4283: $qresult.=$key.'='.$value.'&'; 4284: } 4285: } else { 4286: my %rtnhash = ( 'description' => &unescape($val{'descr'}), 4287: 'inst_code' => &unescape($val{'inst_code'}), 4288: 'owner' => &unescape($val{'owner'}), 4289: 'type' => &unescape($val{'type'}), 4290: 'cloners' => &unescape($val{'cloners'}), 4291: ); 4292: my $items = &Apache::lonnet::freeze_escape(\%rtnhash); 4293: $qresult.=$key.'='.$items.'&'; 4294: } 4295: } else { 4296: if ($is_hash) { 4297: $qresult .= $key.'='.&escape($unesc_val{'descr'}).':'. 4298: &escape($unesc_val{'inst_code'}).':'. 4299: &escape($unesc_val{'owner'}).'&'; 4300: } else { 4301: $qresult .= $key.'='.$val{'descr'}.':'.$val{'inst_code'}. 4302: ':'.$val{'owner'}.'&'; 4303: } 4304: } 4305: } 4306: } 4307: if (&untie_domain_hash($hashref)) { 4308: chop($qresult); 4309: &Reply($client, \$qresult, $userinput); 4310: } else { 4311: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 4312: "while attempting courseiddump\n", $userinput); 4313: } 4314: } else { 4315: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 4316: "while attempting courseiddump\n", $userinput); 4317: } 4318: return 1; 4319: } 4320: ®ister_handler("courseiddump", \&dump_course_id_handler, 0, 1, 0); 4321: 4322: sub course_lastaccess_handler { 4323: my ($cmd, $tail, $client) = @_; 4324: my $userinput = "$cmd:$tail"; 4325: my ($cdom,$cnum) = split(':',$tail); 4326: my (%lastaccess,$qresult); 4327: my $hashref = &tie_domain_hash($cdom, "nohist_courseids", &GDBM_WRCREAT()); 4328: if ($hashref) { 4329: while (my ($key,$value) = each(%$hashref)) { 4330: my ($unesc_key,$lasttime); 4331: $unesc_key = &unescape($key); 4332: if ($cnum) { 4333: next unless ($unesc_key =~ /\Q$cdom\E_\Q$cnum\E$/); 4334: } 4335: if ($unesc_key =~ /^lasttime:($LONCAPA::match_domain\_$LONCAPA::match_courseid)/) { 4336: $lastaccess{$1} = $value; 4337: } else { 4338: my $items = &Apache::lonnet::thaw_unescape($value); 4339: if (ref($items) eq 'HASH') { 4340: unless ($lastaccess{$unesc_key}) { 4341: $lastaccess{$unesc_key} = ''; 4342: } 4343: } else { 4344: my @courseitems = split(':',$value); 4345: $lastaccess{$unesc_key} = pop(@courseitems); 4346: } 4347: } 4348: } 4349: foreach my $cid (sort(keys(%lastaccess))) { 4350: $qresult.=&escape($cid).'='.$lastaccess{$cid}.'&'; 4351: } 4352: if (&untie_domain_hash($hashref)) { 4353: if ($qresult) { 4354: chop($qresult); 4355: } 4356: &Reply($client, \$qresult, $userinput); 4357: } else { 4358: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 4359: "while attempting lastacourseaccess\n", $userinput); 4360: } 4361: } else { 4362: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 4363: "while attempting lastcourseaccess\n", $userinput); 4364: } 4365: return 1; 4366: } 4367: ®ister_handler("courselastaccess",\&course_lastaccess_handler, 0, 1, 0); 4368: 4369: # 4370: # Puts an unencrypted entry in a namespace db file at the domain level 4371: # 4372: # Parameters: 4373: # $cmd - The command that got us here. 4374: # $tail - Tail of the command (remaining parameters). 4375: # $client - File descriptor connected to client. 4376: # Returns 4377: # 0 - Requested to exit, caller should shut down. 4378: # 1 - Continue processing. 4379: # Side effects: 4380: # reply is written to $client. 4381: # 4382: sub put_domain_handler { 4383: my ($cmd,$tail,$client) = @_; 4384: 4385: my $userinput = "$cmd:$tail"; 4386: 4387: my ($udom,$namespace,$what) =split(/:/,$tail,3); 4388: chomp($what); 4389: my @pairs=split(/\&/,$what); 4390: my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_WRCREAT(), 4391: "P", $what); 4392: if ($hashref) { 4393: foreach my $pair (@pairs) { 4394: my ($key,$value)=split(/=/,$pair); 4395: $hashref->{$key}=$value; 4396: } 4397: if (&untie_domain_hash($hashref)) { 4398: &Reply($client, "ok\n", $userinput); 4399: } else { 4400: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 4401: "while attempting putdom\n", $userinput); 4402: } 4403: } else { 4404: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 4405: "while attempting putdom\n", $userinput); 4406: } 4407: 4408: return 1; 4409: } 4410: ®ister_handler("putdom", \&put_domain_handler, 0, 1, 0); 4411: 4412: # Unencrypted get from the namespace database file at the domain level. 4413: # This function retrieves a keyed item from a specific named database in the 4414: # domain directory. 4415: # 4416: # Parameters: 4417: # $cmd - Command request keyword (get). 4418: # $tail - Tail of the command. This is a colon separated list 4419: # consisting of the domain and the 'namespace' 4420: # which selects the gdbm file to do the lookup in, 4421: # & separated list of keys to lookup. Note that 4422: # the values are returned as an & separated list too. 4423: # $client - File descriptor open on the client. 4424: # Returns: 4425: # 1 - Continue processing. 4426: # 0 - Exit. 4427: # Side effects: 4428: # reply is written to $client. 4429: # 4430: 4431: sub get_domain_handler { 4432: my ($cmd, $tail, $client) = @_; 4433: 4434: 4435: my $userinput = "$client:$tail"; 4436: 4437: my ($udom,$namespace,$what)=split(/:/,$tail,3); 4438: chomp($what); 4439: my @queries=split(/\&/,$what); 4440: my $qresult=''; 4441: my $hashref = &tie_domain_hash($udom, "$namespace", &GDBM_READER()); 4442: if ($hashref) { 4443: for (my $i=0;$i<=$#queries;$i++) { 4444: $qresult.="$hashref->{$queries[$i]}&"; 4445: } 4446: if (&untie_domain_hash($hashref)) { 4447: $qresult=~s/\&$//; 4448: &Reply($client, \$qresult, $userinput); 4449: } else { 4450: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". 4451: "while attempting getdom\n",$userinput); 4452: } 4453: } else { 4454: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 4455: "while attempting getdom\n",$userinput); 4456: } 4457: 4458: return 1; 4459: } 4460: ®ister_handler("getdom", \&get_domain_handler, 0, 1, 0); 4461: 4462: # 4463: # Puts an id to a domains id database. 4464: # 4465: # Parameters: 4466: # $cmd - The command that triggered us. 4467: # $tail - Remainder of the request other than the command. This is a 4468: # colon separated list containing: 4469: # $domain - The domain for which we are writing the id. 4470: # $pairs - The id info to write... this is and & separated list 4471: # of keyword=value. 4472: # $client - Socket open on the client. 4473: # Returns: 4474: # 1 - Continue processing. 4475: # Side effects: 4476: # reply is written to $client. 4477: # 4478: sub put_id_handler { 4479: my ($cmd,$tail,$client) = @_; 4480: 4481: 4482: my $userinput = "$cmd:$tail"; 4483: 4484: my ($udom,$what)=split(/:/,$tail); 4485: chomp($what); 4486: my @pairs=split(/\&/,$what); 4487: my $hashref = &tie_domain_hash($udom, "ids", &GDBM_WRCREAT(), 4488: "P", $what); 4489: if ($hashref) { 4490: foreach my $pair (@pairs) { 4491: my ($key,$value)=split(/=/,$pair); 4492: $hashref->{$key}=$value; 4493: } 4494: if (&untie_domain_hash($hashref)) { 4495: &Reply($client, "ok\n", $userinput); 4496: } else { 4497: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 4498: "while attempting idput\n", $userinput); 4499: } 4500: } else { 4501: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 4502: "while attempting idput\n", $userinput); 4503: } 4504: 4505: return 1; 4506: } 4507: ®ister_handler("idput", \&put_id_handler, 0, 1, 0); 4508: 4509: # 4510: # Retrieves a set of id values from the id database. 4511: # Returns an & separated list of results, one for each requested id to the 4512: # client. 4513: # 4514: # Parameters: 4515: # $cmd - Command keyword that caused us to be dispatched. 4516: # $tail - Tail of the command. Consists of a colon separated: 4517: # domain - the domain whose id table we dump 4518: # ids Consists of an & separated list of 4519: # id keywords whose values will be fetched. 4520: # nonexisting keywords will have an empty value. 4521: # $client - Socket open on the client. 4522: # 4523: # Returns: 4524: # 1 - indicating processing should continue. 4525: # Side effects: 4526: # An & separated list of results is written to $client. 4527: # 4528: sub get_id_handler { 4529: my ($cmd, $tail, $client) = @_; 4530: 4531: 4532: my $userinput = "$client:$tail"; 4533: 4534: my ($udom,$what)=split(/:/,$tail); 4535: chomp($what); 4536: my @queries=split(/\&/,$what); 4537: my $qresult=''; 4538: my $hashref = &tie_domain_hash($udom, "ids", &GDBM_READER()); 4539: if ($hashref) { 4540: for (my $i=0;$i<=$#queries;$i++) { 4541: $qresult.="$hashref->{$queries[$i]}&"; 4542: } 4543: if (&untie_domain_hash($hashref)) { 4544: $qresult=~s/\&$//; 4545: &Reply($client, \$qresult, $userinput); 4546: } else { 4547: &Failure( $client, "error: ".($!+0)." untie(GDBM) Failed ". 4548: "while attempting idget\n",$userinput); 4549: } 4550: } else { 4551: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 4552: "while attempting idget\n",$userinput); 4553: } 4554: 4555: return 1; 4556: } 4557: ®ister_handler("idget", \&get_id_handler, 0, 1, 0); 4558: 4559: # 4560: # Puts broadcast e-mail sent by Domain Coordinator in nohist_dcmail database 4561: # 4562: # Parameters 4563: # $cmd - Command keyword that caused us to be dispatched. 4564: # $tail - Tail of the command. Consists of a colon separated: 4565: # domain - the domain whose dcmail we are recording 4566: # email Consists of key=value pair 4567: # where key is unique msgid 4568: # and value is message (in XML) 4569: # $client - Socket open on the client. 4570: # 4571: # Returns: 4572: # 1 - indicating processing should continue. 4573: # Side effects 4574: # reply is written to $client. 4575: # 4576: sub put_dcmail_handler { 4577: my ($cmd,$tail,$client) = @_; 4578: my $userinput = "$cmd:$tail"; 4579: 4580: 4581: my ($udom,$what)=split(/:/,$tail); 4582: chomp($what); 4583: my $hashref = &tie_domain_hash($udom, "nohist_dcmail", &GDBM_WRCREAT()); 4584: if ($hashref) { 4585: my ($key,$value)=split(/=/,$what); 4586: $hashref->{$key}=$value; 4587: } 4588: if (&untie_domain_hash($hashref)) { 4589: &Reply($client, "ok\n", $userinput); 4590: } else { 4591: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 4592: "while attempting dcmailput\n", $userinput); 4593: } 4594: return 1; 4595: } 4596: ®ister_handler("dcmailput", \&put_dcmail_handler, 0, 1, 0); 4597: 4598: # 4599: # Retrieves broadcast e-mail from nohist_dcmail database 4600: # Returns to client an & separated list of key=value pairs, 4601: # where key is msgid and value is message information. 4602: # 4603: # Parameters 4604: # $cmd - Command keyword that caused us to be dispatched. 4605: # $tail - Tail of the command. Consists of a colon separated: 4606: # domain - the domain whose dcmail table we dump 4607: # startfilter - beginning of time window 4608: # endfilter - end of time window 4609: # sendersfilter - & separated list of username:domain 4610: # for senders to search for. 4611: # $client - Socket open on the client. 4612: # 4613: # Returns: 4614: # 1 - indicating processing should continue. 4615: # Side effects 4616: # reply (& separated list of msgid=messageinfo pairs) is 4617: # written to $client. 4618: # 4619: sub dump_dcmail_handler { 4620: my ($cmd, $tail, $client) = @_; 4621: 4622: my $userinput = "$cmd:$tail"; 4623: my ($udom,$startfilter,$endfilter,$sendersfilter) = split(/:/,$tail); 4624: chomp($sendersfilter); 4625: my @senders = (); 4626: if (defined($startfilter)) { 4627: $startfilter=&unescape($startfilter); 4628: } else { 4629: $startfilter='.'; 4630: } 4631: if (defined($endfilter)) { 4632: $endfilter=&unescape($endfilter); 4633: } else { 4634: $endfilter='.'; 4635: } 4636: if (defined($sendersfilter)) { 4637: $sendersfilter=&unescape($sendersfilter); 4638: @senders = map { &unescape($_) } split(/\&/,$sendersfilter); 4639: } 4640: 4641: my $qresult=''; 4642: my $hashref = &tie_domain_hash($udom, "nohist_dcmail", &GDBM_WRCREAT()); 4643: if ($hashref) { 4644: while (my ($key,$value) = each(%$hashref)) { 4645: my $match = 1; 4646: my ($timestamp,$subj,$uname,$udom) = 4647: split(/:/,&unescape(&unescape($key)),5); # yes, twice really 4648: $subj = &unescape($subj); 4649: unless ($startfilter eq '.' || !defined($startfilter)) { 4650: if ($timestamp < $startfilter) { 4651: $match = 0; 4652: } 4653: } 4654: unless ($endfilter eq '.' || !defined($endfilter)) { 4655: if ($timestamp > $endfilter) { 4656: $match = 0; 4657: } 4658: } 4659: unless (@senders < 1) { 4660: unless (grep/^$uname:$udom$/,@senders) { 4661: $match = 0; 4662: } 4663: } 4664: if ($match == 1) { 4665: $qresult.=$key.'='.$value.'&'; 4666: } 4667: } 4668: if (&untie_domain_hash($hashref)) { 4669: chop($qresult); 4670: &Reply($client, \$qresult, $userinput); 4671: } else { 4672: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 4673: "while attempting dcmaildump\n", $userinput); 4674: } 4675: } else { 4676: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 4677: "while attempting dcmaildump\n", $userinput); 4678: } 4679: return 1; 4680: } 4681: 4682: ®ister_handler("dcmaildump", \&dump_dcmail_handler, 0, 1, 0); 4683: 4684: # 4685: # Puts domain roles in nohist_domainroles database 4686: # 4687: # Parameters 4688: # $cmd - Command keyword that caused us to be dispatched. 4689: # $tail - Tail of the command. Consists of a colon separated: 4690: # domain - the domain whose roles we are recording 4691: # role - Consists of key=value pair 4692: # where key is unique role 4693: # and value is start/end date information 4694: # $client - Socket open on the client. 4695: # 4696: # Returns: 4697: # 1 - indicating processing should continue. 4698: # Side effects 4699: # reply is written to $client. 4700: # 4701: 4702: sub put_domainroles_handler { 4703: my ($cmd,$tail,$client) = @_; 4704: 4705: my $userinput = "$cmd:$tail"; 4706: my ($udom,$what)=split(/:/,$tail); 4707: chomp($what); 4708: my @pairs=split(/\&/,$what); 4709: my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT()); 4710: if ($hashref) { 4711: foreach my $pair (@pairs) { 4712: my ($key,$value)=split(/=/,$pair); 4713: $hashref->{$key}=$value; 4714: } 4715: if (&untie_domain_hash($hashref)) { 4716: &Reply($client, "ok\n", $userinput); 4717: } else { 4718: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 4719: "while attempting domroleput\n", $userinput); 4720: } 4721: } else { 4722: &Failure( $client, "error: ".($!+0)." tie(GDBM) Failed ". 4723: "while attempting domroleput\n", $userinput); 4724: } 4725: 4726: return 1; 4727: } 4728: 4729: ®ister_handler("domroleput", \&put_domainroles_handler, 0, 1, 0); 4730: 4731: # 4732: # Retrieves domain roles from nohist_domainroles database 4733: # Returns to client an & separated list of key=value pairs, 4734: # where key is role and value is start and end date information. 4735: # 4736: # Parameters 4737: # $cmd - Command keyword that caused us to be dispatched. 4738: # $tail - Tail of the command. Consists of a colon separated: 4739: # domain - the domain whose domain roles table we dump 4740: # $client - Socket open on the client. 4741: # 4742: # Returns: 4743: # 1 - indicating processing should continue. 4744: # Side effects 4745: # reply (& separated list of role=start/end info pairs) is 4746: # written to $client. 4747: # 4748: sub dump_domainroles_handler { 4749: my ($cmd, $tail, $client) = @_; 4750: 4751: my $userinput = "$cmd:$tail"; 4752: my ($udom,$startfilter,$endfilter,$rolesfilter) = split(/:/,$tail); 4753: chomp($rolesfilter); 4754: my @roles = (); 4755: if (defined($startfilter)) { 4756: $startfilter=&unescape($startfilter); 4757: } else { 4758: $startfilter='.'; 4759: } 4760: if (defined($endfilter)) { 4761: $endfilter=&unescape($endfilter); 4762: } else { 4763: $endfilter='.'; 4764: } 4765: if (defined($rolesfilter)) { 4766: $rolesfilter=&unescape($rolesfilter); 4767: @roles = split(/\&/,$rolesfilter); 4768: } 4769: 4770: my $hashref = &tie_domain_hash($udom, "nohist_domainroles", &GDBM_WRCREAT()); 4771: if ($hashref) { 4772: my $qresult = ''; 4773: while (my ($key,$value) = each(%$hashref)) { 4774: my $match = 1; 4775: my ($end,$start) = split(/:/,&unescape($value)); 4776: my ($trole,$uname,$udom,$runame,$rudom,$rsec) = split(/:/,&unescape($key)); 4777: unless (@roles < 1) { 4778: unless (grep/^\Q$trole\E$/,@roles) { 4779: $match = 0; 4780: next; 4781: } 4782: } 4783: unless ($startfilter eq '.' || !defined($startfilter)) { 4784: if ((defined($start)) && ($start >= $startfilter)) { 4785: $match = 0; 4786: next; 4787: } 4788: } 4789: unless ($endfilter eq '.' || !defined($endfilter)) { 4790: if ((defined($end)) && (($end > 0) && ($end <= $endfilter))) { 4791: $match = 0; 4792: next; 4793: } 4794: } 4795: if ($match == 1) { 4796: $qresult.=$key.'='.$value.'&'; 4797: } 4798: } 4799: if (&untie_domain_hash($hashref)) { 4800: chop($qresult); 4801: &Reply($client, \$qresult, $userinput); 4802: } else { 4803: &Failure($client, "error: ".($!+0)." untie(GDBM) Failed ". 4804: "while attempting domrolesdump\n", $userinput); 4805: } 4806: } else { 4807: &Failure($client, "error: ".($!+0)." tie(GDBM) Failed ". 4808: "while attempting domrolesdump\n", $userinput); 4809: } 4810: return 1; 4811: } 4812: 4813: ®ister_handler("domrolesdump", \&dump_domainroles_handler, 0, 1, 0); 4814: 4815: 4816: # Process the tmpput command I'm not sure what this does.. Seems to 4817: # create a file in the lonDaemons/tmp directory of the form $id.tmp 4818: # where Id is the client's ip concatenated with a sequence number. 4819: # The file will contain some value that is passed in. Is this e.g. 4820: # a login token? 4821: # 4822: # Parameters: 4823: # $cmd - The command that got us dispatched. 4824: # $tail - The remainder of the request following $cmd: 4825: # In this case this will be the contents of the file. 4826: # $client - Socket connected to the client. 4827: # Returns: 4828: # 1 indicating processing can continue. 4829: # Side effects: 4830: # A file is created in the local filesystem. 4831: # A reply is sent to the client. 4832: sub tmp_put_handler { 4833: my ($cmd, $what, $client) = @_; 4834: 4835: my $userinput = "$cmd:$what"; # Reconstruct for logging. 4836: 4837: my ($record,$context) = split(/:/,$what); 4838: if ($context ne '') { 4839: chomp($context); 4840: $context = &unescape($context); 4841: } 4842: my ($id,$store); 4843: $tmpsnum++; 4844: if (($context eq 'resetpw') || ($context eq 'createaccount')) { 4845: $id = &md5_hex(&md5_hex(time.{}.rand().$$)); 4846: } else { 4847: $id = $$.'_'.$clientip.'_'.$tmpsnum; 4848: } 4849: $id=~s/\W/\_/g; 4850: $record=~s/\n//g; 4851: my $execdir=$perlvar{'lonDaemons'}; 4852: if ($store=IO::File->new(">$execdir/tmp/$id.tmp")) { 4853: print $store $record; 4854: close $store; 4855: &Reply($client, \$id, $userinput); 4856: } else { 4857: &Failure( $client, "error: ".($!+0)."IO::File->new Failed ". 4858: "while attempting tmpput\n", $userinput); 4859: } 4860: return 1; 4861: 4862: } 4863: ®ister_handler("tmpput", \&tmp_put_handler, 0, 1, 0); 4864: 4865: # Processes the tmpget command. This command returns the contents 4866: # of a temporary resource file(?) created via tmpput. 4867: # 4868: # Paramters: 4869: # $cmd - Command that got us dispatched. 4870: # $id - Tail of the command, contain the id of the resource 4871: # we want to fetch. 4872: # $client - socket open on the client. 4873: # Return: 4874: # 1 - Inidcating processing can continue. 4875: # Side effects: 4876: # A reply is sent to the client. 4877: # 4878: sub tmp_get_handler { 4879: my ($cmd, $id, $client) = @_; 4880: 4881: my $userinput = "$cmd:$id"; 4882: 4883: 4884: $id=~s/\W/\_/g; 4885: my $store; 4886: my $execdir=$perlvar{'lonDaemons'}; 4887: if ($store=IO::File->new("$execdir/tmp/$id.tmp")) { 4888: my $reply=<$store>; 4889: &Reply( $client, \$reply, $userinput); 4890: close $store; 4891: } else { 4892: &Failure( $client, "error: ".($!+0)."IO::File->new Failed ". 4893: "while attempting tmpget\n", $userinput); 4894: } 4895: 4896: return 1; 4897: } 4898: ®ister_handler("tmpget", \&tmp_get_handler, 0, 1, 0); 4899: 4900: # 4901: # Process the tmpdel command. This command deletes a temp resource 4902: # created by the tmpput command. 4903: # 4904: # Parameters: 4905: # $cmd - Command that got us here. 4906: # $id - Id of the temporary resource created. 4907: # $client - socket open on the client process. 4908: # 4909: # Returns: 4910: # 1 - Indicating processing should continue. 4911: # Side Effects: 4912: # A file is deleted 4913: # A reply is sent to the client. 4914: sub tmp_del_handler { 4915: my ($cmd, $id, $client) = @_; 4916: 4917: my $userinput= "$cmd:$id"; 4918: 4919: chomp($id); 4920: $id=~s/\W/\_/g; 4921: my $execdir=$perlvar{'lonDaemons'}; 4922: if (unlink("$execdir/tmp/$id.tmp")) { 4923: &Reply($client, "ok\n", $userinput); 4924: } else { 4925: &Failure( $client, "error: ".($!+0)."Unlink tmp Failed ". 4926: "while attempting tmpdel\n", $userinput); 4927: } 4928: 4929: return 1; 4930: 4931: } 4932: ®ister_handler("tmpdel", \&tmp_del_handler, 0, 1, 0); 4933: 4934: # 4935: # Processes the setannounce command. This command 4936: # creates a file named announce.txt in the top directory of 4937: # the documentn root and sets its contents. The announce.txt file is 4938: # printed in its entirety at the LonCAPA login page. Note: 4939: # once the announcement.txt fileis created it cannot be deleted. 4940: # However, setting the contents of the file to empty removes the 4941: # announcement from the login page of loncapa so who cares. 4942: # 4943: # Parameters: 4944: # $cmd - The command that got us dispatched. 4945: # $announcement - The text of the announcement. 4946: # $client - Socket open on the client process. 4947: # Retunrns: 4948: # 1 - Indicating request processing should continue 4949: # Side Effects: 4950: # The file {DocRoot}/announcement.txt is created. 4951: # A reply is sent to $client. 4952: # 4953: sub set_announce_handler { 4954: my ($cmd, $announcement, $client) = @_; 4955: 4956: my $userinput = "$cmd:$announcement"; 4957: 4958: chomp($announcement); 4959: $announcement=&unescape($announcement); 4960: if (my $store=IO::File->new('>'.$perlvar{'lonDocRoot'}. 4961: '/announcement.txt')) { 4962: print $store $announcement; 4963: close $store; 4964: &Reply($client, "ok\n", $userinput); 4965: } else { 4966: &Failure($client, "error: ".($!+0)."\n", $userinput); 4967: } 4968: 4969: return 1; 4970: } 4971: ®ister_handler("setannounce", \&set_announce_handler, 0, 1, 0); 4972: 4973: # 4974: # Return the version of the daemon. This can be used to determine 4975: # the compatibility of cross version installations or, alternatively to 4976: # simply know who's out of date and who isn't. Note that the version 4977: # is returned concatenated with the tail. 4978: # Parameters: 4979: # $cmd - the request that dispatched to us. 4980: # $tail - Tail of the request (client's version?). 4981: # $client - Socket open on the client. 4982: #Returns: 4983: # 1 - continue processing requests. 4984: # Side Effects: 4985: # Replies with version to $client. 4986: sub get_version_handler { 4987: my ($cmd, $tail, $client) = @_; 4988: 4989: my $userinput = $cmd.$tail; 4990: 4991: &Reply($client, &version($userinput)."\n", $userinput); 4992: 4993: 4994: return 1; 4995: } 4996: ®ister_handler("version", \&get_version_handler, 0, 1, 0); 4997: 4998: # Set the current host and domain. This is used to support 4999: # multihomed systems. Each IP of the system, or even separate daemons 5000: # on the same IP can be treated as handling a separate lonCAPA virtual 5001: # machine. This command selects the virtual lonCAPA. The client always 5002: # knows the right one since it is lonc and it is selecting the domain/system 5003: # from the hosts.tab file. 5004: # Parameters: 5005: # $cmd - Command that dispatched us. 5006: # $tail - Tail of the command (domain/host requested). 5007: # $socket - Socket open on the client. 5008: # 5009: # Returns: 5010: # 1 - Indicates the program should continue to process requests. 5011: # Side-effects: 5012: # The default domain/system context is modified for this daemon. 5013: # a reply is sent to the client. 5014: # 5015: sub set_virtual_host_handler { 5016: my ($cmd, $tail, $socket) = @_; 5017: 5018: my $userinput ="$cmd:$tail"; 5019: 5020: &Reply($client, &sethost($userinput)."\n", $userinput); 5021: 5022: 5023: return 1; 5024: } 5025: ®ister_handler("sethost", \&set_virtual_host_handler, 0, 1, 0); 5026: 5027: # Process a request to exit: 5028: # - "bye" is sent to the client. 5029: # - The client socket is shutdown and closed. 5030: # - We indicate to the caller that we should exit. 5031: # Formal Parameters: 5032: # $cmd - The command that got us here. 5033: # $tail - Tail of the command (empty). 5034: # $client - Socket open on the tail. 5035: # Returns: 5036: # 0 - Indicating the program should exit!! 5037: # 5038: sub exit_handler { 5039: my ($cmd, $tail, $client) = @_; 5040: 5041: my $userinput = "$cmd:$tail"; 5042: 5043: &logthis("Client $clientip ($clientname) hanging up: $userinput"); 5044: &Reply($client, "bye\n", $userinput); 5045: $client->shutdown(2); # shutdown the socket forcibly. 5046: $client->close(); 5047: 5048: return 0; 5049: } 5050: ®ister_handler("exit", \&exit_handler, 0,1,1); 5051: ®ister_handler("init", \&exit_handler, 0,1,1); 5052: ®ister_handler("quit", \&exit_handler, 0,1,1); 5053: 5054: # Determine if auto-enrollment is enabled. 5055: # Note that the original had what I believe to be a defect. 5056: # The original returned 0 if the requestor was not a registerd client. 5057: # It should return "refused". 5058: # Formal Parameters: 5059: # $cmd - The command that invoked us. 5060: # $tail - The tail of the command (Extra command parameters. 5061: # $client - The socket open on the client that issued the request. 5062: # Returns: 5063: # 1 - Indicating processing should continue. 5064: # 5065: sub enrollment_enabled_handler { 5066: my ($cmd, $tail, $client) = @_; 5067: my $userinput = $cmd.":".$tail; # For logging purposes. 5068: 5069: 5070: my ($cdom) = split(/:/, $tail, 2); # Domain we're asking about. 5071: 5072: my $outcome = &localenroll::run($cdom); 5073: &Reply($client, \$outcome, $userinput); 5074: 5075: return 1; 5076: } 5077: ®ister_handler("autorun", \&enrollment_enabled_handler, 0, 1, 0); 5078: 5079: # 5080: # Validate an institutional code used for a LON-CAPA course. 5081: # 5082: # Formal Parameters: 5083: # $cmd - The command request that got us dispatched. 5084: # $tail - The tail of the command. In this case, 5085: # this is a colon separated set of words that will be split 5086: # into: 5087: # $dom - The domain for which the check of 5088: # institutional course code will occur. 5089: # 5090: # $instcode - The institutional code for the course 5091: # being requested, or validated for rights 5092: # to request. 5093: # 5094: # $owner - The course requestor (who will be the 5095: # course owner, in the form username:domain 5096: # 5097: # $client - Socket open on the client. 5098: # Returns: 5099: # 1 - Indicating processing should continue. 5100: # 5101: sub validate_instcode_handler { 5102: my ($cmd, $tail, $client) = @_; 5103: my $userinput = "$cmd:$tail"; 5104: my ($dom,$instcode,$owner) = split(/:/, $tail); 5105: $instcode = &unescape($instcode); 5106: $owner = &unescape($owner); 5107: my ($outcome,$description) = 5108: &localenroll::validate_instcode($dom,$instcode,$owner); 5109: my $result = &escape($outcome).'&'.&escape($description); 5110: &Reply($client, \$result, $userinput); 5111: 5112: return 1; 5113: } 5114: ®ister_handler("autovalidateinstcode", \&validate_instcode_handler, 0, 1, 0); 5115: 5116: # Get the official sections for which auto-enrollment is possible. 5117: # Since the admin people won't know about 'unofficial sections' 5118: # we cannot auto-enroll on them. 5119: # Formal Parameters: 5120: # $cmd - The command request that got us dispatched here. 5121: # $tail - The remainder of the request. In our case this 5122: # will be split into: 5123: # $coursecode - The course name from the admin point of view. 5124: # $cdom - The course's domain(?). 5125: # $client - Socket open on the client. 5126: # Returns: 5127: # 1 - Indiciting processing should continue. 5128: # 5129: sub get_sections_handler { 5130: my ($cmd, $tail, $client) = @_; 5131: my $userinput = "$cmd:$tail"; 5132: 5133: my ($coursecode, $cdom) = split(/:/, $tail); 5134: my @secs = &localenroll::get_sections($coursecode,$cdom); 5135: my $seclist = &escape(join(':',@secs)); 5136: 5137: &Reply($client, \$seclist, $userinput); 5138: 5139: 5140: return 1; 5141: } 5142: ®ister_handler("autogetsections", \&get_sections_handler, 0, 1, 0); 5143: 5144: # Validate the owner of a new course section. 5145: # 5146: # Formal Parameters: 5147: # $cmd - Command that got us dispatched. 5148: # $tail - the remainder of the command. For us this consists of a 5149: # colon separated string containing: 5150: # $inst - Course Id from the institutions point of view. 5151: # $owner - Proposed owner of the course. 5152: # $cdom - Domain of the course (from the institutions 5153: # point of view?).. 5154: # $client - Socket open on the client. 5155: # 5156: # Returns: 5157: # 1 - Processing should continue. 5158: # 5159: sub validate_course_owner_handler { 5160: my ($cmd, $tail, $client) = @_; 5161: my $userinput = "$cmd:$tail"; 5162: my ($inst_course_id, $owner, $cdom, $coowners) = split(/:/, $tail); 5163: 5164: $owner = &unescape($owner); 5165: $coowners = &unescape($coowners); 5166: my $outcome = &localenroll::new_course($inst_course_id,$owner,$cdom,$coowners); 5167: &Reply($client, \$outcome, $userinput); 5168: 5169: 5170: 5171: return 1; 5172: } 5173: ®ister_handler("autonewcourse", \&validate_course_owner_handler, 0, 1, 0); 5174: 5175: # 5176: # Validate a course section in the official schedule of classes 5177: # from the institutions point of view (part of autoenrollment). 5178: # 5179: # Formal Parameters: 5180: # $cmd - The command request that got us dispatched. 5181: # $tail - The tail of the command. In this case, 5182: # this is a colon separated set of words that will be split 5183: # into: 5184: # $inst_course_id - The course/section id from the 5185: # institutions point of view. 5186: # $cdom - The domain from the institutions 5187: # point of view. 5188: # $client - Socket open on the client. 5189: # Returns: 5190: # 1 - Indicating processing should continue. 5191: # 5192: sub validate_course_section_handler { 5193: my ($cmd, $tail, $client) = @_; 5194: my $userinput = "$cmd:$tail"; 5195: my ($inst_course_id, $cdom) = split(/:/, $tail); 5196: 5197: my $outcome=&localenroll::validate_courseID($inst_course_id,$cdom); 5198: &Reply($client, \$outcome, $userinput); 5199: 5200: 5201: return 1; 5202: } 5203: ®ister_handler("autovalidatecourse", \&validate_course_section_handler, 0, 1, 0); 5204: 5205: # 5206: # Validate course owner's access to enrollment data for specific class section. 5207: # 5208: # 5209: # Formal Parameters: 5210: # $cmd - The command request that got us dispatched. 5211: # $tail - The tail of the command. In this case this is a colon separated 5212: # set of words that will be split into: 5213: # $inst_class - Institutional code for the specific class section 5214: # $courseowner - The escaped username:domain of the course owner 5215: # $cdom - The domain of the course from the institution's 5216: # point of view. 5217: # $client - The socket open on the client. 5218: # Returns: 5219: # 1 - continue processing. 5220: # 5221: 5222: sub validate_class_access_handler { 5223: my ($cmd, $tail, $client) = @_; 5224: my $userinput = "$cmd:$tail"; 5225: my ($inst_class,$ownerlist,$cdom) = split(/:/, $tail); 5226: my $owners = &unescape($ownerlist); 5227: my $outcome; 5228: eval { 5229: local($SIG{__DIE__})='DEFAULT'; 5230: $outcome=&localenroll::check_section($inst_class,$owners,$cdom); 5231: }; 5232: &Reply($client,\$outcome, $userinput); 5233: 5234: return 1; 5235: } 5236: ®ister_handler("autovalidateclass_sec", \&validate_class_access_handler, 0, 1, 0); 5237: 5238: # 5239: # Create a password for a new LON-CAPA user added by auto-enrollment. 5240: # Only used for case where authentication method for new user is localauth 5241: # 5242: # Formal Parameters: 5243: # $cmd - The command request that got us dispatched. 5244: # $tail - The tail of the command. In this case this is a colon separated 5245: # set of words that will be split into: 5246: # $authparam - An authentication parameter (localauth parameter). 5247: # $cdom - The domain of the course from the institution's 5248: # point of view. 5249: # $client - The socket open on the client. 5250: # Returns: 5251: # 1 - continue processing. 5252: # 5253: sub create_auto_enroll_password_handler { 5254: my ($cmd, $tail, $client) = @_; 5255: my $userinput = "$cmd:$tail"; 5256: 5257: my ($authparam, $cdom) = split(/:/, $userinput); 5258: 5259: my ($create_passwd,$authchk); 5260: ($authparam, 5261: $create_passwd, 5262: $authchk) = &localenroll::create_password($authparam,$cdom); 5263: 5264: &Reply($client, &escape($authparam.':'.$create_passwd.':'.$authchk)."\n", 5265: $userinput); 5266: 5267: 5268: return 1; 5269: } 5270: ®ister_handler("autocreatepassword", \&create_auto_enroll_password_handler, 5271: 0, 1, 0); 5272: 5273: # Retrieve and remove temporary files created by/during autoenrollment. 5274: # 5275: # Formal Parameters: 5276: # $cmd - The command that got us dispatched. 5277: # $tail - The tail of the command. In our case this is a colon 5278: # separated list that will be split into: 5279: # $filename - The name of the file to remove. 5280: # The filename is given as a path relative to 5281: # the LonCAPA temp file directory. 5282: # $client - Socket open on the client. 5283: # 5284: # Returns: 5285: # 1 - Continue processing. 5286: sub retrieve_auto_file_handler { 5287: my ($cmd, $tail, $client) = @_; 5288: my $userinput = "cmd:$tail"; 5289: 5290: my ($filename) = split(/:/, $tail); 5291: 5292: my $source = $perlvar{'lonDaemons'}.'/tmp/'.$filename; 5293: if ( (-e $source) && ($filename ne '') ) { 5294: my $reply = ''; 5295: if (open(my $fh,$source)) { 5296: while (<$fh>) { 5297: chomp($_); 5298: $_ =~ s/^\s+//g; 5299: $_ =~ s/\s+$//g; 5300: $reply .= $_; 5301: } 5302: close($fh); 5303: &Reply($client, &escape($reply)."\n", $userinput); 5304: 5305: # Does this have to be uncommented??!? (RF). 5306: # 5307: # unlink($source); 5308: } else { 5309: &Failure($client, "error\n", $userinput); 5310: } 5311: } else { 5312: &Failure($client, "error\n", $userinput); 5313: } 5314: 5315: 5316: return 1; 5317: } 5318: ®ister_handler("autoretrieve", \&retrieve_auto_file_handler, 0,1,0); 5319: 5320: sub crsreq_checks_handler { 5321: my ($cmd, $tail, $client) = @_; 5322: my $userinput = "$cmd:$tail"; 5323: my $dom = $tail; 5324: my $result; 5325: my @reqtypes = ('official','unofficial','community'); 5326: eval { 5327: local($SIG{__DIE__})='DEFAULT'; 5328: my %validations; 5329: my $response = &localenroll::crsreq_checks($dom,\@reqtypes, 5330: \%validations); 5331: if ($response eq 'ok') { 5332: foreach my $key (keys(%validations)) { 5333: $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($validations{$key}).'&'; 5334: } 5335: $result =~ s/\&$//; 5336: } else { 5337: $result = 'error'; 5338: } 5339: }; 5340: if (!$@) { 5341: &Reply($client, \$result, $userinput); 5342: } else { 5343: &Failure($client,"unknown_cmd\n",$userinput); 5344: } 5345: return 1; 5346: } 5347: ®ister_handler("autocrsreqchecks", \&crsreq_checks_handler, 0, 1, 0); 5348: 5349: sub validate_crsreq_handler { 5350: my ($cmd, $tail, $client) = @_; 5351: my $userinput = "$cmd:$tail"; 5352: my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist) = split(/:/, $tail); 5353: $instcode = &unescape($instcode); 5354: $owner = &unescape($owner); 5355: $crstype = &unescape($crstype); 5356: $inststatuslist = &unescape($inststatuslist); 5357: $instcode = &unescape($instcode); 5358: $instseclist = &unescape($instseclist); 5359: my $outcome; 5360: eval { 5361: local($SIG{__DIE__})='DEFAULT'; 5362: $outcome = &localenroll::validate_crsreq($dom,$owner,$crstype, 5363: $inststatuslist,$instcode, 5364: $instseclist); 5365: }; 5366: if (!$@) { 5367: &Reply($client, \$outcome, $userinput); 5368: } else { 5369: &Failure($client,"unknown_cmd\n",$userinput); 5370: } 5371: return 1; 5372: } 5373: ®ister_handler("autocrsreqvalidation", \&validate_crsreq_handler, 0, 1, 0); 5374: 5375: # 5376: # Read and retrieve institutional code format (for support form). 5377: # Formal Parameters: 5378: # $cmd - Command that dispatched us. 5379: # $tail - Tail of the command. In this case it conatins 5380: # the course domain and the coursename. 5381: # $client - Socket open on the client. 5382: # Returns: 5383: # 1 - Continue processing. 5384: # 5385: sub get_institutional_code_format_handler { 5386: my ($cmd, $tail, $client) = @_; 5387: my $userinput = "$cmd:$tail"; 5388: 5389: my $reply; 5390: my($cdom,$course) = split(/:/,$tail); 5391: my @pairs = split/\&/,$course; 5392: my %instcodes = (); 5393: my %codes = (); 5394: my @codetitles = (); 5395: my %cat_titles = (); 5396: my %cat_order = (); 5397: foreach (@pairs) { 5398: my ($key,$value) = split/=/,$_; 5399: $instcodes{&unescape($key)} = &unescape($value); 5400: } 5401: my $formatreply = &localenroll::instcode_format($cdom, 5402: \%instcodes, 5403: \%codes, 5404: \@codetitles, 5405: \%cat_titles, 5406: \%cat_order); 5407: if ($formatreply eq 'ok') { 5408: my $codes_str = &Apache::lonnet::hash2str(%codes); 5409: my $codetitles_str = &Apache::lonnet::array2str(@codetitles); 5410: my $cat_titles_str = &Apache::lonnet::hash2str(%cat_titles); 5411: my $cat_order_str = &Apache::lonnet::hash2str(%cat_order); 5412: &Reply($client, 5413: $codes_str.':'.$codetitles_str.':'.$cat_titles_str.':' 5414: .$cat_order_str."\n", 5415: $userinput); 5416: } else { 5417: # this else branch added by RF since if not ok, lonc will 5418: # hang waiting on reply until timeout. 5419: # 5420: &Reply($client, "format_error\n", $userinput); 5421: } 5422: 5423: return 1; 5424: } 5425: ®ister_handler("autoinstcodeformat", 5426: \&get_institutional_code_format_handler,0,1,0); 5427: 5428: sub get_institutional_defaults_handler { 5429: my ($cmd, $tail, $client) = @_; 5430: my $userinput = "$cmd:$tail"; 5431: 5432: my $dom = $tail; 5433: my %defaults_hash; 5434: my @code_order; 5435: my $outcome; 5436: eval { 5437: local($SIG{__DIE__})='DEFAULT'; 5438: $outcome = &localenroll::instcode_defaults($dom,\%defaults_hash, 5439: \@code_order); 5440: }; 5441: if (!$@) { 5442: if ($outcome eq 'ok') { 5443: my $result=''; 5444: while (my ($key,$value) = each(%defaults_hash)) { 5445: $result.=&escape($key).'='.&escape($value).'&'; 5446: } 5447: $result .= 'code_order='.&escape(join('&',@code_order)); 5448: &Reply($client,\$result,$userinput); 5449: } else { 5450: &Reply($client,"error\n", $userinput); 5451: } 5452: } else { 5453: &Failure($client,"unknown_cmd\n",$userinput); 5454: } 5455: } 5456: ®ister_handler("autoinstcodedefaults", 5457: \&get_institutional_defaults_handler,0,1,0); 5458: 5459: sub get_possible_instcodes_handler { 5460: my ($cmd, $tail, $client) = @_; 5461: my $userinput = "$cmd:$tail"; 5462: 5463: my $reply; 5464: my $cdom = $tail; 5465: my (@codetitles,%cat_titles,%cat_order,@code_order); 5466: my $formatreply = &localenroll::possible_instcodes($cdom, 5467: \@codetitles, 5468: \%cat_titles, 5469: \%cat_order, 5470: \@code_order); 5471: if ($formatreply eq 'ok') { 5472: my $result = join('&',map {&escape($_);} (@codetitles)).':'; 5473: $result .= join('&',map {&escape($_);} (@code_order)).':'; 5474: foreach my $key (keys(%cat_titles)) { 5475: $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($cat_titles{$key}).'&'; 5476: } 5477: $result =~ s/\&$//; 5478: $result .= ':'; 5479: foreach my $key (keys(%cat_order)) { 5480: $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($cat_order{$key}).'&'; 5481: } 5482: $result =~ s/\&$//; 5483: &Reply($client,\$result,$userinput); 5484: } else { 5485: &Reply($client, "format_error\n", $userinput); 5486: } 5487: return 1; 5488: } 5489: ®ister_handler("autopossibleinstcodes", 5490: \&get_possible_instcodes_handler,0,1,0); 5491: 5492: sub get_institutional_user_rules { 5493: my ($cmd, $tail, $client) = @_; 5494: my $userinput = "$cmd:$tail"; 5495: my $dom = &unescape($tail); 5496: my (%rules_hash,@rules_order); 5497: my $outcome; 5498: eval { 5499: local($SIG{__DIE__})='DEFAULT'; 5500: $outcome = &localenroll::username_rules($dom,\%rules_hash,\@rules_order); 5501: }; 5502: if (!$@) { 5503: if ($outcome eq 'ok') { 5504: my $result; 5505: foreach my $key (keys(%rules_hash)) { 5506: $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rules_hash{$key}).'&'; 5507: } 5508: $result =~ s/\&$//; 5509: $result .= ':'; 5510: if (@rules_order > 0) { 5511: foreach my $item (@rules_order) { 5512: $result .= &escape($item).'&'; 5513: } 5514: } 5515: $result =~ s/\&$//; 5516: &Reply($client,\$result,$userinput); 5517: } else { 5518: &Reply($client,"error\n", $userinput); 5519: } 5520: } else { 5521: &Failure($client,"unknown_cmd\n",$userinput); 5522: } 5523: } 5524: ®ister_handler("instuserrules",\&get_institutional_user_rules,0,1,0); 5525: 5526: sub get_institutional_id_rules { 5527: my ($cmd, $tail, $client) = @_; 5528: my $userinput = "$cmd:$tail"; 5529: my $dom = &unescape($tail); 5530: my (%rules_hash,@rules_order); 5531: my $outcome; 5532: eval { 5533: local($SIG{__DIE__})='DEFAULT'; 5534: $outcome = &localenroll::id_rules($dom,\%rules_hash,\@rules_order); 5535: }; 5536: if (!$@) { 5537: if ($outcome eq 'ok') { 5538: my $result; 5539: foreach my $key (keys(%rules_hash)) { 5540: $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rules_hash{$key}).'&'; 5541: } 5542: $result =~ s/\&$//; 5543: $result .= ':'; 5544: if (@rules_order > 0) { 5545: foreach my $item (@rules_order) { 5546: $result .= &escape($item).'&'; 5547: } 5548: } 5549: $result =~ s/\&$//; 5550: &Reply($client,\$result,$userinput); 5551: } else { 5552: &Reply($client,"error\n", $userinput); 5553: } 5554: } else { 5555: &Failure($client,"unknown_cmd\n",$userinput); 5556: } 5557: } 5558: ®ister_handler("instidrules",\&get_institutional_id_rules,0,1,0); 5559: 5560: sub get_institutional_selfcreate_rules { 5561: my ($cmd, $tail, $client) = @_; 5562: my $userinput = "$cmd:$tail"; 5563: my $dom = &unescape($tail); 5564: my (%rules_hash,@rules_order); 5565: my $outcome; 5566: eval { 5567: local($SIG{__DIE__})='DEFAULT'; 5568: $outcome = &localenroll::selfcreate_rules($dom,\%rules_hash,\@rules_order); 5569: }; 5570: if (!$@) { 5571: if ($outcome eq 'ok') { 5572: my $result; 5573: foreach my $key (keys(%rules_hash)) { 5574: $result .= &escape($key).'='.&Apache::lonnet::freeze_escape($rules_hash{$key}).'&'; 5575: } 5576: $result =~ s/\&$//; 5577: $result .= ':'; 5578: if (@rules_order > 0) { 5579: foreach my $item (@rules_order) { 5580: $result .= &escape($item).'&'; 5581: } 5582: } 5583: $result =~ s/\&$//; 5584: &Reply($client,\$result,$userinput); 5585: } else { 5586: &Reply($client,"error\n", $userinput); 5587: } 5588: } else { 5589: &Failure($client,"unknown_cmd\n",$userinput); 5590: } 5591: } 5592: ®ister_handler("instemailrules",\&get_institutional_selfcreate_rules,0,1,0); 5593: 5594: 5595: sub institutional_username_check { 5596: my ($cmd, $tail, $client) = @_; 5597: my $userinput = "$cmd:$tail"; 5598: my %rulecheck; 5599: my $outcome; 5600: my ($udom,$uname,@rules) = split(/:/,$tail); 5601: $udom = &unescape($udom); 5602: $uname = &unescape($uname); 5603: @rules = map {&unescape($_);} (@rules); 5604: eval { 5605: local($SIG{__DIE__})='DEFAULT'; 5606: $outcome = &localenroll::username_check($udom,$uname,\@rules,\%rulecheck); 5607: }; 5608: if (!$@) { 5609: if ($outcome eq 'ok') { 5610: my $result=''; 5611: foreach my $key (keys(%rulecheck)) { 5612: $result.=&escape($key).'='.&Apache::lonnet::freeze_escape($rulecheck{$key}).'&'; 5613: } 5614: &Reply($client,\$result,$userinput); 5615: } else { 5616: &Reply($client,"error\n", $userinput); 5617: } 5618: } else { 5619: &Failure($client,"unknown_cmd\n",$userinput); 5620: } 5621: } 5622: ®ister_handler("instrulecheck",\&institutional_username_check,0,1,0); 5623: 5624: sub institutional_id_check { 5625: my ($cmd, $tail, $client) = @_; 5626: my $userinput = "$cmd:$tail"; 5627: my %rulecheck; 5628: my $outcome; 5629: my ($udom,$id,@rules) = split(/:/,$tail); 5630: $udom = &unescape($udom); 5631: $id = &unescape($id); 5632: @rules = map {&unescape($_);} (@rules); 5633: eval { 5634: local($SIG{__DIE__})='DEFAULT'; 5635: $outcome = &localenroll::id_check($udom,$id,\@rules,\%rulecheck); 5636: }; 5637: if (!$@) { 5638: if ($outcome eq 'ok') { 5639: my $result=''; 5640: foreach my $key (keys(%rulecheck)) { 5641: $result.=&escape($key).'='.&Apache::lonnet::freeze_escape($rulecheck{$key}).'&'; 5642: } 5643: &Reply($client,\$result,$userinput); 5644: } else { 5645: &Reply($client,"error\n", $userinput); 5646: } 5647: } else { 5648: &Failure($client,"unknown_cmd\n",$userinput); 5649: } 5650: } 5651: ®ister_handler("instidrulecheck",\&institutional_id_check,0,1,0); 5652: 5653: sub institutional_selfcreate_check { 5654: my ($cmd, $tail, $client) = @_; 5655: my $userinput = "$cmd:$tail"; 5656: my %rulecheck; 5657: my $outcome; 5658: my ($udom,$email,@rules) = split(/:/,$tail); 5659: $udom = &unescape($udom); 5660: $email = &unescape($email); 5661: @rules = map {&unescape($_);} (@rules); 5662: eval { 5663: local($SIG{__DIE__})='DEFAULT'; 5664: $outcome = &localenroll::selfcreate_check($udom,$email,\@rules,\%rulecheck); 5665: }; 5666: if (!$@) { 5667: if ($outcome eq 'ok') { 5668: my $result=''; 5669: foreach my $key (keys(%rulecheck)) { 5670: $result.=&escape($key).'='.&Apache::lonnet::freeze_escape($rulecheck{$key}).'&'; 5671: } 5672: &Reply($client,\$result,$userinput); 5673: } else { 5674: &Reply($client,"error\n", $userinput); 5675: } 5676: } else { 5677: &Failure($client,"unknown_cmd\n",$userinput); 5678: } 5679: } 5680: ®ister_handler("instselfcreatecheck",\&institutional_selfcreate_check,0,1,0); 5681: 5682: # Get domain specific conditions for import of student photographs to a course 5683: # 5684: # Retrieves information from photo_permission subroutine in localenroll. 5685: # Returns outcome (ok) if no processing errors, and whether course owner is 5686: # required to accept conditions of use (yes/no). 5687: # 5688: # 5689: sub photo_permission_handler { 5690: my ($cmd, $tail, $client) = @_; 5691: my $userinput = "$cmd:$tail"; 5692: my $cdom = $tail; 5693: my ($perm_reqd,$conditions); 5694: my $outcome; 5695: eval { 5696: local($SIG{__DIE__})='DEFAULT'; 5697: $outcome = &localenroll::photo_permission($cdom,\$perm_reqd, 5698: \$conditions); 5699: }; 5700: if (!$@) { 5701: &Reply($client, &escape($outcome.':'.$perm_reqd.':'. $conditions)."\n", 5702: $userinput); 5703: } else { 5704: &Failure($client,"unknown_cmd\n",$userinput); 5705: } 5706: return 1; 5707: } 5708: ®ister_handler("autophotopermission",\&photo_permission_handler,0,1,0); 5709: 5710: # 5711: # Checks if student photo is available for a user in the domain, in the user's 5712: # directory (in /userfiles/internal/studentphoto.jpg). 5713: # Uses localstudentphoto:fetch() to ensure there is an up to date copy of 5714: # the student's photo. 5715: 5716: sub photo_check_handler { 5717: my ($cmd, $tail, $client) = @_; 5718: my $userinput = "$cmd:$tail"; 5719: my ($udom,$uname,$pid) = split(/:/,$tail); 5720: $udom = &unescape($udom); 5721: $uname = &unescape($uname); 5722: $pid = &unescape($pid); 5723: my $path=&propath($udom,$uname).'/userfiles/internal/'; 5724: if (!-e $path) { 5725: &mkpath($path); 5726: } 5727: my $response; 5728: my $result = &localstudentphoto::fetch($udom,$uname,$pid,\$response); 5729: $result .= ':'.$response; 5730: &Reply($client, &escape($result)."\n",$userinput); 5731: return 1; 5732: } 5733: ®ister_handler("autophotocheck",\&photo_check_handler,0,1,0); 5734: 5735: # 5736: # Retrieve information from localenroll about whether to provide a button 5737: # for users who have enbled import of student photos to initiate an 5738: # update of photo files for registered students. Also include 5739: # comment to display alongside button. 5740: 5741: sub photo_choice_handler { 5742: my ($cmd, $tail, $client) = @_; 5743: my $userinput = "$cmd:$tail"; 5744: my $cdom = &unescape($tail); 5745: my ($update,$comment); 5746: eval { 5747: local($SIG{__DIE__})='DEFAULT'; 5748: ($update,$comment) = &localenroll::manager_photo_update($cdom); 5749: }; 5750: if (!$@) { 5751: &Reply($client,&escape($update).':'.&escape($comment)."\n",$userinput); 5752: } else { 5753: &Failure($client,"unknown_cmd\n",$userinput); 5754: } 5755: return 1; 5756: } 5757: ®ister_handler("autophotochoice",\&photo_choice_handler,0,1,0); 5758: 5759: # 5760: # Gets a student's photo to exist (in the correct image type) in the user's 5761: # directory. 5762: # Formal Parameters: 5763: # $cmd - The command request that got us dispatched. 5764: # $tail - A colon separated set of words that will be split into: 5765: # $domain - student's domain 5766: # $uname - student username 5767: # $type - image type desired 5768: # $client - The socket open on the client. 5769: # Returns: 5770: # 1 - continue processing. 5771: 5772: sub student_photo_handler { 5773: my ($cmd, $tail, $client) = @_; 5774: my ($domain,$uname,$ext,$type) = split(/:/, $tail); 5775: 5776: my $path=&propath($domain,$uname). '/userfiles/internal/'; 5777: my $filename = 'studentphoto.'.$ext; 5778: if ($type eq 'thumbnail') { 5779: $filename = 'studentphoto_tn.'.$ext; 5780: } 5781: if (-e $path.$filename) { 5782: &Reply($client,"ok\n","$cmd:$tail"); 5783: return 1; 5784: } 5785: &mkpath($path); 5786: my $file; 5787: if ($type eq 'thumbnail') { 5788: eval { 5789: local($SIG{__DIE__})='DEFAULT'; 5790: $file=&localstudentphoto::fetch_thumbnail($domain,$uname); 5791: }; 5792: } else { 5793: $file=&localstudentphoto::fetch($domain,$uname); 5794: } 5795: if (!$file) { 5796: &Failure($client,"unavailable\n","$cmd:$tail"); 5797: return 1; 5798: } 5799: if (!-e $path.$filename) { &convert_photo($file,$path.$filename); } 5800: if (-e $path.$filename) { 5801: &Reply($client,"ok\n","$cmd:$tail"); 5802: return 1; 5803: } 5804: &Failure($client,"unable_to_convert\n","$cmd:$tail"); 5805: return 1; 5806: } 5807: ®ister_handler("studentphoto", \&student_photo_handler, 0, 1, 0); 5808: 5809: sub inst_usertypes_handler { 5810: my ($cmd, $domain, $client) = @_; 5811: my $res; 5812: my $userinput = $cmd.":".$domain; # For logging purposes. 5813: my (%typeshash,@order,$result); 5814: eval { 5815: local($SIG{__DIE__})='DEFAULT'; 5816: $result=&localenroll::inst_usertypes($domain,\%typeshash,\@order); 5817: }; 5818: if ($result eq 'ok') { 5819: if (keys(%typeshash) > 0) { 5820: foreach my $key (keys(%typeshash)) { 5821: $res.=&escape($key).'='.&escape($typeshash{$key}).'&'; 5822: } 5823: } 5824: $res=~s/\&$//; 5825: $res .= ':'; 5826: if (@order > 0) { 5827: foreach my $item (@order) { 5828: $res .= &escape($item).'&'; 5829: } 5830: } 5831: $res=~s/\&$//; 5832: } 5833: &Reply($client, \$res, $userinput); 5834: return 1; 5835: } 5836: ®ister_handler("inst_usertypes", \&inst_usertypes_handler, 0, 1, 0); 5837: 5838: # mkpath makes all directories for a file, expects an absolute path with a 5839: # file or a trailing / if just a dir is passed 5840: # returns 1 on success 0 on failure 5841: sub mkpath { 5842: my ($file)=@_; 5843: my @parts=split(/\//,$file,-1); 5844: my $now=$parts[0].'/'.$parts[1].'/'.$parts[2]; 5845: for (my $i=3;$i<= ($#parts-1);$i++) { 5846: $now.='/'.$parts[$i]; 5847: if (!-e $now) { 5848: if (!mkdir($now,0770)) { return 0; } 5849: } 5850: } 5851: return 1; 5852: } 5853: 5854: #--------------------------------------------------------------- 5855: # 5856: # Getting, decoding and dispatching requests: 5857: # 5858: # 5859: # Get a Request: 5860: # Gets a Request message from the client. The transaction 5861: # is defined as a 'line' of text. We remove the new line 5862: # from the text line. 5863: # 5864: sub get_request { 5865: my $input = <$client>; 5866: chomp($input); 5867: 5868: &Debug("get_request: Request = $input\n"); 5869: 5870: &status('Processing '.$clientname.':'.$input); 5871: 5872: return $input; 5873: } 5874: #--------------------------------------------------------------- 5875: # 5876: # Process a request. This sub should shrink as each action 5877: # gets farmed out into a separat sub that is registered 5878: # with the dispatch hash. 5879: # 5880: # Parameters: 5881: # user_input - The request received from the client (lonc). 5882: # Returns: 5883: # true to keep processing, false if caller should exit. 5884: # 5885: sub process_request { 5886: my ($userinput) = @_; # Easier for now to break style than to 5887: # fix all the userinput -> user_input. 5888: my $wasenc = 0; # True if request was encrypted. 5889: # ------------------------------------------------------------ See if encrypted 5890: # for command 5891: # sethost:<server> 5892: # <command>:<args> 5893: # we just send it to the processor 5894: # for 5895: # sethost:<server>:<command>:<args> 5896: # we do the implict set host and then do the command 5897: if ($userinput =~ /^sethost:/) { 5898: (my $cmd,my $newid,$userinput) = split(':',$userinput,3); 5899: if (defined($userinput)) { 5900: &sethost("$cmd:$newid"); 5901: } else { 5902: $userinput = "$cmd:$newid"; 5903: } 5904: } 5905: 5906: if ($userinput =~ /^enc/) { 5907: $userinput = decipher($userinput); 5908: $wasenc=1; 5909: if(!$userinput) { # Cipher not defined. 5910: &Failure($client, "error: Encrypted data without negotated key\n"); 5911: return 0; 5912: } 5913: } 5914: Debug("process_request: $userinput\n"); 5915: 5916: # 5917: # The 'correct way' to add a command to lond is now to 5918: # write a sub to execute it and Add it to the command dispatch 5919: # hash via a call to register_handler.. The comments to that 5920: # sub should give you enough to go on to show how to do this 5921: # along with the examples that are building up as this code 5922: # is getting refactored. Until all branches of the 5923: # if/elseif monster below have been factored out into 5924: # separate procesor subs, if the dispatch hash is missing 5925: # the command keyword, we will fall through to the remainder 5926: # of the if/else chain below in order to keep this thing in 5927: # working order throughout the transmogrification. 5928: 5929: my ($command, $tail) = split(/:/, $userinput, 2); 5930: chomp($command); 5931: chomp($tail); 5932: $tail =~ s/(\r)//; # This helps people debugging with e.g. telnet. 5933: $command =~ s/(\r)//; # And this too for parameterless commands. 5934: if(!$tail) { 5935: $tail =""; # defined but blank. 5936: } 5937: 5938: &Debug("Command received: $command, encoded = $wasenc"); 5939: 5940: if(defined $Dispatcher{$command}) { 5941: 5942: my $dispatch_info = $Dispatcher{$command}; 5943: my $handler = $$dispatch_info[0]; 5944: my $need_encode = $$dispatch_info[1]; 5945: my $client_types = $$dispatch_info[2]; 5946: Debug("Matched dispatch hash: mustencode: $need_encode " 5947: ."ClientType $client_types"); 5948: 5949: # Validate the request: 5950: 5951: my $ok = 1; 5952: my $requesterprivs = 0; 5953: if(&isClient()) { 5954: $requesterprivs |= $CLIENT_OK; 5955: } 5956: if(&isManager()) { 5957: $requesterprivs |= $MANAGER_OK; 5958: } 5959: if($need_encode && (!$wasenc)) { 5960: Debug("Must encode but wasn't: $need_encode $wasenc"); 5961: $ok = 0; 5962: } 5963: if(($client_types & $requesterprivs) == 0) { 5964: Debug("Client not privileged to do this operation"); 5965: $ok = 0; 5966: } 5967: 5968: if($ok) { 5969: Debug("Dispatching to handler $command $tail"); 5970: my $keep_going = &$handler($command, $tail, $client); 5971: return $keep_going; 5972: } else { 5973: Debug("Refusing to dispatch because client did not match requirements"); 5974: Failure($client, "refused\n", $userinput); 5975: return 1; 5976: } 5977: 5978: } 5979: 5980: print $client "unknown_cmd\n"; 5981: # -------------------------------------------------------------------- complete 5982: Debug("process_request - returning 1"); 5983: return 1; 5984: } 5985: # 5986: # Decipher encoded traffic 5987: # Parameters: 5988: # input - Encoded data. 5989: # Returns: 5990: # Decoded data or undef if encryption key was not yet negotiated. 5991: # Implicit input: 5992: # cipher - This global holds the negotiated encryption key. 5993: # 5994: sub decipher { 5995: my ($input) = @_; 5996: my $output = ''; 5997: 5998: 5999: if($cipher) { 6000: my($enc, $enclength, $encinput) = split(/:/, $input); 6001: for(my $encidx = 0; $encidx < length($encinput); $encidx += 16) { 6002: $output .= 6003: $cipher->decrypt(pack("H16", substr($encinput, $encidx, 16))); 6004: } 6005: return substr($output, 0, $enclength); 6006: } else { 6007: return undef; 6008: } 6009: } 6010: 6011: # 6012: # Register a command processor. This function is invoked to register a sub 6013: # to process a request. Once registered, the ProcessRequest sub can automatically 6014: # dispatch requests to an appropriate sub, and do the top level validity checking 6015: # as well: 6016: # - Is the keyword recognized. 6017: # - Is the proper client type attempting the request. 6018: # - Is the request encrypted if it has to be. 6019: # Parameters: 6020: # $request_name - Name of the request being registered. 6021: # This is the command request that will match 6022: # against the hash keywords to lookup the information 6023: # associated with the dispatch information. 6024: # $procedure - Reference to a sub to call to process the request. 6025: # All subs get called as follows: 6026: # Procedure($cmd, $tail, $replyfd, $key) 6027: # $cmd - the actual keyword that invoked us. 6028: # $tail - the tail of the request that invoked us. 6029: # $replyfd- File descriptor connected to the client 6030: # $must_encode - True if the request must be encoded to be good. 6031: # $client_ok - True if it's ok for a client to request this. 6032: # $manager_ok - True if it's ok for a manager to request this. 6033: # Side effects: 6034: # - On success, the Dispatcher hash has an entry added for the key $RequestName 6035: # - On failure, the program will die as it's a bad internal bug to try to 6036: # register a duplicate command handler. 6037: # 6038: sub register_handler { 6039: my ($request_name,$procedure,$must_encode, $client_ok,$manager_ok) = @_; 6040: 6041: # Don't allow duplication# 6042: 6043: if (defined $Dispatcher{$request_name}) { 6044: die "Attempting to define a duplicate request handler for $request_name\n"; 6045: } 6046: # Build the client type mask: 6047: 6048: my $client_type_mask = 0; 6049: if($client_ok) { 6050: $client_type_mask |= $CLIENT_OK; 6051: } 6052: if($manager_ok) { 6053: $client_type_mask |= $MANAGER_OK; 6054: } 6055: 6056: # Enter the hash: 6057: 6058: my @entry = ($procedure, $must_encode, $client_type_mask); 6059: 6060: $Dispatcher{$request_name} = \@entry; 6061: 6062: } 6063: 6064: 6065: #------------------------------------------------------------------ 6066: 6067: 6068: 6069: 6070: # 6071: # Convert an error return code from lcpasswd to a string value. 6072: # 6073: sub lcpasswdstrerror { 6074: my $ErrorCode = shift; 6075: if(($ErrorCode < 0) || ($ErrorCode > $lastpwderror)) { 6076: return "lcpasswd Unrecognized error return value ".$ErrorCode; 6077: } else { 6078: return $passwderrors[$ErrorCode]; 6079: } 6080: } 6081: 6082: # 6083: # Convert an error return code from lcuseradd to a string value: 6084: # 6085: sub lcuseraddstrerror { 6086: my $ErrorCode = shift; 6087: if(($ErrorCode < 0) || ($ErrorCode > $lastadderror)) { 6088: return "lcuseradd - Unrecognized error code: ".$ErrorCode; 6089: } else { 6090: return $adderrors[$ErrorCode]; 6091: } 6092: } 6093: 6094: # grabs exception and records it to log before exiting 6095: sub catchexception { 6096: my ($error)=@_; 6097: $SIG{'QUIT'}='DEFAULT'; 6098: $SIG{__DIE__}='DEFAULT'; 6099: &status("Catching exception"); 6100: &logthis("<font color='red'>CRITICAL: " 6101: ."ABNORMAL EXIT. Child $$ for server ".$perlvar{'lonHostID'}." died through " 6102: ."a crash with this error msg->[$error]</font>"); 6103: &logthis('Famous last words: '.$status.' - '.$lastlog); 6104: if ($client) { print $client "error: $error\n"; } 6105: $server->close(); 6106: die($error); 6107: } 6108: sub timeout { 6109: &status("Handling Timeout"); 6110: &logthis("<font color='red'>CRITICAL: TIME OUT ".$$."</font>"); 6111: &catchexception('Timeout'); 6112: } 6113: # -------------------------------- Set signal handlers to record abnormal exits 6114: 6115: 6116: $SIG{'QUIT'}=\&catchexception; 6117: $SIG{__DIE__}=\&catchexception; 6118: 6119: # ---------------------------------- Read loncapa_apache.conf and loncapa.conf 6120: &status("Read loncapa.conf and loncapa_apache.conf"); 6121: my $perlvarref=LONCAPA::Configuration::read_conf('loncapa.conf'); 6122: %perlvar=%{$perlvarref}; 6123: undef $perlvarref; 6124: 6125: # ----------------------------- Make sure this process is running from user=www 6126: my $wwwid=getpwnam('www'); 6127: if ($wwwid!=$<) { 6128: my $emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}"; 6129: my $subj="LON: $currenthostid User ID mismatch"; 6130: system("echo 'User ID mismatch. lond must be run as user www.' |\ 6131: mailto $emailto -s '$subj' > /dev/null"); 6132: exit 1; 6133: } 6134: 6135: # --------------------------------------------- Check if other instance running 6136: 6137: my $pidfile="$perlvar{'lonDaemons'}/logs/lond.pid"; 6138: 6139: if (-e $pidfile) { 6140: my $lfh=IO::File->new("$pidfile"); 6141: my $pide=<$lfh>; 6142: chomp($pide); 6143: if (kill 0 => $pide) { die "already running"; } 6144: } 6145: 6146: # ------------------------------------------------------------- Read hosts file 6147: 6148: 6149: 6150: # establish SERVER socket, bind and listen. 6151: $server = IO::Socket::INET->new(LocalPort => $perlvar{'londPort'}, 6152: Type => SOCK_STREAM, 6153: Proto => 'tcp', 6154: ReuseAddr => 1, 6155: Listen => 10 ) 6156: or die "making socket: $@\n"; 6157: 6158: # --------------------------------------------------------- Do global variables 6159: 6160: # global variables 6161: 6162: my %children = (); # keys are current child process IDs 6163: 6164: sub REAPER { # takes care of dead children 6165: $SIG{CHLD} = \&REAPER; 6166: &status("Handling child death"); 6167: my $pid; 6168: do { 6169: $pid = waitpid(-1,&WNOHANG()); 6170: if (defined($children{$pid})) { 6171: &logthis("Child $pid died"); 6172: delete($children{$pid}); 6173: } elsif ($pid > 0) { 6174: &logthis("Unknown Child $pid died"); 6175: } 6176: } while ( $pid > 0 ); 6177: foreach my $child (keys(%children)) { 6178: $pid = waitpid($child,&WNOHANG()); 6179: if ($pid > 0) { 6180: &logthis("Child $child - $pid looks like we missed it's death"); 6181: delete($children{$pid}); 6182: } 6183: } 6184: &status("Finished Handling child death"); 6185: } 6186: 6187: sub HUNTSMAN { # signal handler for SIGINT 6188: &status("Killing children (INT)"); 6189: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children 6190: kill 'INT' => keys %children; 6191: &logthis("Free socket: ".shutdown($server,2)); # free up socket 6192: my $execdir=$perlvar{'lonDaemons'}; 6193: unlink("$execdir/logs/lond.pid"); 6194: &logthis("<font color='red'>CRITICAL: Shutting down</font>"); 6195: &status("Done killing children"); 6196: exit; # clean up with dignity 6197: } 6198: 6199: sub HUPSMAN { # signal handler for SIGHUP 6200: local($SIG{CHLD}) = 'IGNORE'; # we're going to kill our children 6201: &status("Killing children for restart (HUP)"); 6202: kill 'INT' => keys %children; 6203: &logthis("Free socket: ".shutdown($server,2)); # free up socket 6204: &logthis("<font color='red'>CRITICAL: Restarting</font>"); 6205: my $execdir=$perlvar{'lonDaemons'}; 6206: unlink("$execdir/logs/lond.pid"); 6207: &status("Restarting self (HUP)"); 6208: exec("$execdir/lond"); # here we go again 6209: } 6210: 6211: # 6212: # Reload the Apache daemon's state. 6213: # This is done by invoking /home/httpd/perl/apachereload 6214: # a setuid perl script that can be root for us to do this job. 6215: # 6216: sub ReloadApache { 6217: # --------------------------- Handle case of another apachereload process (locking) 6218: if (&LONCAPA::try_to_lock('/tmp/lock_apachereload')) { 6219: my $execdir = $perlvar{'lonDaemons'}; 6220: my $script = $execdir."/apachereload"; 6221: system($script); 6222: unlink('/tmp/lock_apachereload'); # Remove the lock file. 6223: } 6224: } 6225: 6226: # 6227: # Called in response to a USR2 signal. 6228: # - Reread hosts.tab 6229: # - All children connected to hosts that were removed from hosts.tab 6230: # are killed via SIGINT 6231: # - All children connected to previously existing hosts are sent SIGUSR1 6232: # - Our internal hosts hash is updated to reflect the new contents of 6233: # hosts.tab causing connections from hosts added to hosts.tab to 6234: # now be honored. 6235: # 6236: sub UpdateHosts { 6237: &status("Reload hosts.tab"); 6238: logthis('<font color="blue"> Updating connections </font>'); 6239: # 6240: # The %children hash has the set of IP's we currently have children 6241: # on. These need to be matched against records in the hosts.tab 6242: # Any ip's no longer in the table get killed off they correspond to 6243: # either dropped or changed hosts. Note that the re-read of the table 6244: # will take care of new and changed hosts as connections come into being. 6245: 6246: &Apache::lonnet::reset_hosts_info(); 6247: 6248: foreach my $child (keys(%children)) { 6249: my $childip = $children{$child}; 6250: if ($childip ne '127.0.0.1' 6251: && !defined(&Apache::lonnet::get_hosts_from_ip($childip))) { 6252: logthis('<font color="blue"> UpdateHosts killing child ' 6253: ." $child for ip $childip </font>"); 6254: kill('INT', $child); 6255: } else { 6256: logthis('<font color="green"> keeping child for ip ' 6257: ." $childip (pid=$child) </font>"); 6258: } 6259: } 6260: ReloadApache; 6261: &status("Finished reloading hosts.tab"); 6262: } 6263: 6264: 6265: sub checkchildren { 6266: &status("Checking on the children (sending signals)"); 6267: &initnewstatus(); 6268: &logstatus(); 6269: &logthis('Going to check on the children'); 6270: my $docdir=$perlvar{'lonDocRoot'}; 6271: foreach (sort keys %children) { 6272: #sleep 1; 6273: unless (kill 'USR1' => $_) { 6274: &logthis ('Child '.$_.' is dead'); 6275: &logstatus($$.' is dead'); 6276: delete($children{$_}); 6277: } 6278: } 6279: sleep 5; 6280: $SIG{ALRM} = sub { Debug("timeout"); 6281: die "timeout"; }; 6282: $SIG{__DIE__} = 'DEFAULT'; 6283: &status("Checking on the children (waiting for reports)"); 6284: foreach (sort keys %children) { 6285: unless (-e "$docdir/lon-status/londchld/$_.txt") { 6286: eval { 6287: alarm(300); 6288: &logthis('Child '.$_.' did not respond'); 6289: kill 9 => $_; 6290: #$emailto="$perlvar{'lonAdmEMail'},$perlvar{'lonSysEMail'}"; 6291: #$subj="LON: $currenthostid killed lond process $_"; 6292: #my $result=`echo 'Killed lond process $_.' | mailto $emailto -s '$subj' > /dev/null`; 6293: #$execdir=$perlvar{'lonDaemons'}; 6294: #$result=`/bin/cp $execdir/logs/lond.log $execdir/logs/lond.log.$_`; 6295: delete($children{$_}); 6296: alarm(0); 6297: } 6298: } 6299: } 6300: $SIG{ALRM} = 'DEFAULT'; 6301: $SIG{__DIE__} = \&catchexception; 6302: &status("Finished checking children"); 6303: &logthis('Finished Checking children'); 6304: } 6305: 6306: # --------------------------------------------------------------------- Logging 6307: 6308: sub logthis { 6309: my $message=shift; 6310: my $execdir=$perlvar{'lonDaemons'}; 6311: my $fh=IO::File->new(">>$execdir/logs/lond.log"); 6312: my $now=time; 6313: my $local=localtime($now); 6314: $lastlog=$local.': '.$message; 6315: print $fh "$local ($$): $message\n"; 6316: } 6317: 6318: # ------------------------- Conditional log if $DEBUG true. 6319: sub Debug { 6320: my $message = shift; 6321: if($DEBUG) { 6322: &logthis($message); 6323: } 6324: } 6325: 6326: # 6327: # Sub to do replies to client.. this gives a hook for some 6328: # debug tracing too: 6329: # Parameters: 6330: # fd - File open on client. 6331: # reply - Text to send to client. 6332: # request - Original request from client. 6333: # 6334: sub Reply { 6335: my ($fd, $reply, $request) = @_; 6336: if (ref($reply)) { 6337: print $fd $$reply; 6338: print $fd "\n"; 6339: if ($DEBUG) { Debug("Request was $request Reply was $$reply"); } 6340: } else { 6341: print $fd $reply; 6342: if ($DEBUG) { Debug("Request was $request Reply was $reply"); } 6343: } 6344: $Transactions++; 6345: } 6346: 6347: 6348: # 6349: # Sub to report a failure. 6350: # This function: 6351: # - Increments the failure statistic counters. 6352: # - Invokes Reply to send the error message to the client. 6353: # Parameters: 6354: # fd - File descriptor open on the client 6355: # reply - Reply text to emit. 6356: # request - The original request message (used by Reply 6357: # to debug if that's enabled. 6358: # Implicit outputs: 6359: # $Failures- The number of failures is incremented. 6360: # Reply (invoked here) sends a message to the 6361: # client: 6362: # 6363: sub Failure { 6364: my $fd = shift; 6365: my $reply = shift; 6366: my $request = shift; 6367: 6368: $Failures++; 6369: Reply($fd, $reply, $request); # That's simple eh? 6370: } 6371: # ------------------------------------------------------------------ Log status 6372: 6373: sub logstatus { 6374: &status("Doing logging"); 6375: my $docdir=$perlvar{'lonDocRoot'}; 6376: { 6377: my $fh=IO::File->new(">$docdir/lon-status/londchld/$$.txt"); 6378: print $fh $status."\n".$lastlog."\n".time."\n$keymode"; 6379: $fh->close(); 6380: } 6381: &status("Finished $$.txt"); 6382: { 6383: open(LOG,">>$docdir/lon-status/londstatus.txt"); 6384: flock(LOG,LOCK_EX); 6385: print LOG $$."\t".$clientname."\t".$currenthostid."\t" 6386: .$status."\t".$lastlog."\t $keymode\n"; 6387: flock(LOG,LOCK_UN); 6388: close(LOG); 6389: } 6390: &status("Finished logging"); 6391: } 6392: 6393: sub initnewstatus { 6394: my $docdir=$perlvar{'lonDocRoot'}; 6395: my $fh=IO::File->new(">$docdir/lon-status/londstatus.txt"); 6396: my $now=time(); 6397: my $local=localtime($now); 6398: print $fh "LOND status $local - parent $$\n\n"; 6399: opendir(DIR,"$docdir/lon-status/londchld"); 6400: while (my $filename=readdir(DIR)) { 6401: unlink("$docdir/lon-status/londchld/$filename"); 6402: } 6403: closedir(DIR); 6404: } 6405: 6406: # -------------------------------------------------------------- Status setting 6407: 6408: sub status { 6409: my $what=shift; 6410: my $now=time; 6411: my $local=localtime($now); 6412: $status=$local.': '.$what; 6413: $0='lond: '.$what.' '.$local; 6414: } 6415: 6416: # -------------------------------------------------------------- Talk to lonsql 6417: 6418: sub sql_reply { 6419: my ($cmd)=@_; 6420: my $answer=&sub_sql_reply($cmd); 6421: if ($answer eq 'con_lost') { $answer=&sub_sql_reply($cmd); } 6422: return $answer; 6423: } 6424: 6425: sub sub_sql_reply { 6426: my ($cmd)=@_; 6427: my $unixsock="mysqlsock"; 6428: my $peerfile="$perlvar{'lonSockDir'}/$unixsock"; 6429: my $sclient=IO::Socket::UNIX->new(Peer =>"$peerfile", 6430: Type => SOCK_STREAM, 6431: Timeout => 10) 6432: or return "con_lost"; 6433: print $sclient "$cmd:$currentdomainid\n"; 6434: my $answer=<$sclient>; 6435: chomp($answer); 6436: if (!$answer) { $answer="con_lost"; } 6437: return $answer; 6438: } 6439: 6440: # --------------------------------------- Is this the home server of an author? 6441: 6442: sub ishome { 6443: my $author=shift; 6444: $author=~s/\/home\/httpd\/html\/res\/([^\/]*)\/([^\/]*).*/$1\/$2/; 6445: my ($udom,$uname)=split(/\//,$author); 6446: my $proname=propath($udom,$uname); 6447: if (-e $proname) { 6448: return 'owner'; 6449: } else { 6450: return 'not_owner'; 6451: } 6452: } 6453: 6454: # ======================================================= Continue main program 6455: # ---------------------------------------------------- Fork once and dissociate 6456: 6457: my $fpid=fork; 6458: exit if $fpid; 6459: die "Couldn't fork: $!" unless defined ($fpid); 6460: 6461: POSIX::setsid() or die "Can't start new session: $!"; 6462: 6463: # ------------------------------------------------------- Write our PID on disk 6464: 6465: my $execdir=$perlvar{'lonDaemons'}; 6466: open (PIDSAVE,">$execdir/logs/lond.pid"); 6467: print PIDSAVE "$$\n"; 6468: close(PIDSAVE); 6469: &logthis("<font color='red'>CRITICAL: ---------- Starting ----------</font>"); 6470: &status('Starting'); 6471: 6472: 6473: 6474: # ----------------------------------------------------- Install signal handlers 6475: 6476: 6477: $SIG{CHLD} = \&REAPER; 6478: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN; 6479: $SIG{HUP} = \&HUPSMAN; 6480: $SIG{USR1} = \&checkchildren; 6481: $SIG{USR2} = \&UpdateHosts; 6482: 6483: # Read the host hashes: 6484: &Apache::lonnet::load_hosts_tab(); 6485: my %iphost = &Apache::lonnet::get_iphost(1); 6486: 6487: $dist=`$perlvar{'lonDaemons'}/distprobe`; 6488: 6489: my $arch = `uname -i`; 6490: chomp($arch); 6491: if ($arch eq 'unknown') { 6492: $arch = `uname -m`; 6493: chomp($arch); 6494: } 6495: 6496: # -------------------------------------------------------------- 6497: # Accept connections. When a connection comes in, it is validated 6498: # and if good, a child process is created to process transactions 6499: # along the connection. 6500: 6501: while (1) { 6502: &status('Starting accept'); 6503: $client = $server->accept() or next; 6504: &status('Accepted '.$client.' off to spawn'); 6505: make_new_child($client); 6506: &status('Finished spawning'); 6507: } 6508: 6509: sub make_new_child { 6510: my $pid; 6511: # my $cipher; # Now global 6512: my $sigset; 6513: 6514: $client = shift; 6515: &status('Starting new child '.$client); 6516: &logthis('<font color="green"> Attempting to start child ('.$client. 6517: ")</font>"); 6518: # block signal for fork 6519: $sigset = POSIX::SigSet->new(SIGINT); 6520: sigprocmask(SIG_BLOCK, $sigset) 6521: or die "Can't block SIGINT for fork: $!\n"; 6522: 6523: die "fork: $!" unless defined ($pid = fork); 6524: 6525: $client->sockopt(SO_KEEPALIVE, 1); # Enable monitoring of 6526: # connection liveness. 6527: 6528: # 6529: # Figure out who we're talking to so we can record the peer in 6530: # the pid hash. 6531: # 6532: my $caller = getpeername($client); 6533: my ($port,$iaddr); 6534: if (defined($caller) && length($caller) > 0) { 6535: ($port,$iaddr)=unpack_sockaddr_in($caller); 6536: } else { 6537: &logthis("Unable to determine who caller was, getpeername returned nothing"); 6538: } 6539: if (defined($iaddr)) { 6540: $clientip = inet_ntoa($iaddr); 6541: Debug("Connected with $clientip"); 6542: } else { 6543: &logthis("Unable to determine clientip"); 6544: $clientip='Unavailable'; 6545: } 6546: 6547: if ($pid) { 6548: # Parent records the child's birth and returns. 6549: sigprocmask(SIG_UNBLOCK, $sigset) 6550: or die "Can't unblock SIGINT for fork: $!\n"; 6551: $children{$pid} = $clientip; 6552: &status('Started child '.$pid); 6553: close($client); 6554: return; 6555: } else { 6556: # Child can *not* return from this subroutine. 6557: $SIG{INT} = 'DEFAULT'; # make SIGINT kill us as it did before 6558: $SIG{CHLD} = 'DEFAULT'; #make this default so that pwauth returns 6559: #don't get intercepted 6560: $SIG{USR1}= \&logstatus; 6561: $SIG{ALRM}= \&timeout; 6562: # 6563: # Block sigpipe as it gets thrownon socket disconnect and we want to 6564: # deal with that as a read faiure instead. 6565: # 6566: my $blockset = POSIX::SigSet->new(SIGPIPE); 6567: sigprocmask(SIG_BLOCK, $blockset); 6568: 6569: $lastlog='Forked '; 6570: $status='Forked'; 6571: 6572: # unblock signals 6573: sigprocmask(SIG_UNBLOCK, $sigset) 6574: or die "Can't unblock SIGINT for fork: $!\n"; 6575: 6576: # my $tmpsnum=0; # Now global 6577: #---------------------------------------------------- kerberos 5 initialization 6578: &Authen::Krb5::init_context(); 6579: unless (($dist eq 'fedora5') || ($dist eq 'fedora4') || 6580: ($dist eq 'fedora6') || ($dist eq 'suse9.3') || 6581: ($dist eq 'suse12.2')) { 6582: &Authen::Krb5::init_ets(); 6583: } 6584: 6585: &status('Accepted connection'); 6586: # ============================================================================= 6587: # do something with the connection 6588: # ----------------------------------------------------------------------------- 6589: # see if we know client and 'check' for spoof IP by ineffective challenge 6590: 6591: my $outsideip=$clientip; 6592: if ($clientip eq '127.0.0.1') { 6593: $outsideip=&Apache::lonnet::get_host_ip($perlvar{'lonHostID'}); 6594: } 6595: &ReadManagerTable(); 6596: my $clientrec=defined(&Apache::lonnet::get_hosts_from_ip($outsideip)); 6597: my $ismanager=($managers{$outsideip} ne undef); 6598: $clientname = "[unknown]"; 6599: if($clientrec) { # Establish client type. 6600: $ConnectionType = "client"; 6601: $clientname = (&Apache::lonnet::get_hosts_from_ip($outsideip))[-1]; 6602: if($ismanager) { 6603: $ConnectionType = "both"; 6604: } 6605: } else { 6606: $ConnectionType = "manager"; 6607: $clientname = $managers{$outsideip}; 6608: } 6609: my $clientok; 6610: 6611: if ($clientrec || $ismanager) { 6612: &status("Waiting for init from $clientip $clientname"); 6613: &logthis('<font color="yellow">INFO: Connection, '. 6614: $clientip. 6615: " ($clientname) connection type = $ConnectionType </font>" ); 6616: &status("Connecting $clientip ($clientname))"); 6617: my $remotereq=<$client>; 6618: chomp($remotereq); 6619: Debug("Got init: $remotereq"); 6620: 6621: if ($remotereq =~ /^init/) { 6622: &sethost("sethost:$perlvar{'lonHostID'}"); 6623: # 6624: # If the remote is attempting a local init... give that a try: 6625: # 6626: (my $i, my $inittype, $clientversion) = split(/:/, $remotereq); 6627: 6628: # If the connection type is ssl, but I didn't get my 6629: # certificate files yet, then I'll drop back to 6630: # insecure (if allowed). 6631: 6632: if($inittype eq "ssl") { 6633: my ($ca, $cert) = lonssl::CertificateFile; 6634: my $kfile = lonssl::KeyFile; 6635: if((!$ca) || 6636: (!$cert) || 6637: (!$kfile)) { 6638: $inittype = ""; # This forces insecure attempt. 6639: &logthis("<font color=\"blue\"> Certificates not " 6640: ."installed -- trying insecure auth</font>"); 6641: } else { # SSL certificates are in place so 6642: } # Leave the inittype alone. 6643: } 6644: 6645: if($inittype eq "local") { 6646: $clientversion = $perlvar{'lonVersion'}; 6647: my $key = LocalConnection($client, $remotereq); 6648: if($key) { 6649: Debug("Got local key $key"); 6650: $clientok = 1; 6651: my $cipherkey = pack("H32", $key); 6652: $cipher = new IDEA($cipherkey); 6653: print $client "ok:local\n"; 6654: &logthis('<font color="green">' 6655: . "Successful local authentication </font>"); 6656: $keymode = "local" 6657: } else { 6658: Debug("Failed to get local key"); 6659: $clientok = 0; 6660: shutdown($client, 3); 6661: close $client; 6662: } 6663: } elsif ($inittype eq "ssl") { 6664: my $key = SSLConnection($client); 6665: if ($key) { 6666: $clientok = 1; 6667: my $cipherkey = pack("H32", $key); 6668: $cipher = new IDEA($cipherkey); 6669: &logthis('<font color="green">' 6670: ."Successfull ssl authentication with $clientname </font>"); 6671: $keymode = "ssl"; 6672: 6673: } else { 6674: $clientok = 0; 6675: close $client; 6676: } 6677: 6678: } else { 6679: my $ok = InsecureConnection($client); 6680: if($ok) { 6681: $clientok = 1; 6682: &logthis('<font color="green">' 6683: ."Successful insecure authentication with $clientname </font>"); 6684: print $client "ok\n"; 6685: $keymode = "insecure"; 6686: } else { 6687: &logthis('<font color="yellow">' 6688: ."Attempted insecure connection disallowed </font>"); 6689: close $client; 6690: $clientok = 0; 6691: 6692: } 6693: } 6694: } else { 6695: &logthis( 6696: "<font color='blue'>WARNING: " 6697: ."$clientip failed to initialize: >$remotereq< </font>"); 6698: &status('No init '.$clientip); 6699: } 6700: 6701: } else { 6702: &logthis( 6703: "<font color='blue'>WARNING: Unknown client $clientip</font>"); 6704: &status('Hung up on '.$clientip); 6705: } 6706: 6707: if ($clientok) { 6708: # ---------------- New known client connecting, could mean machine online again 6709: if (&Apache::lonnet::get_host_ip($currenthostid) ne $clientip 6710: && $clientip ne '127.0.0.1') { 6711: &Apache::lonnet::reconlonc($clientname); 6712: } 6713: &logthis("<font color='green'>Established connection: $clientname</font>"); 6714: &status('Will listen to '.$clientname); 6715: # ------------------------------------------------------------ Process requests 6716: my $keep_going = 1; 6717: my $user_input; 6718: my $clienthost = &Apache::lonnet::hostname($clientname); 6719: my $clientserverhomeID = &Apache::lonnet::get_server_homeID($clienthost); 6720: $clienthomedom = &Apache::lonnet::host_domain($clientserverhomeID); 6721: while(($user_input = get_request) && $keep_going) { 6722: alarm(120); 6723: Debug("Main: Got $user_input\n"); 6724: $keep_going = &process_request($user_input); 6725: alarm(0); 6726: &status('Listening to '.$clientname." ($keymode)"); 6727: } 6728: 6729: # --------------------------------------------- client unknown or fishy, refuse 6730: } else { 6731: print $client "refused\n"; 6732: $client->close(); 6733: &logthis("<font color='blue'>WARNING: " 6734: ."Rejected client $clientip, closing connection</font>"); 6735: } 6736: } 6737: 6738: # ============================================================================= 6739: 6740: &logthis("<font color='red'>CRITICAL: " 6741: ."Disconnect from $clientip ($clientname)</font>"); 6742: 6743: 6744: # this exit is VERY important, otherwise the child will become 6745: # a producer of more and more children, forking yourself into 6746: # process death. 6747: exit; 6748: 6749: } 6750: # 6751: # Determine if a user is an author for the indicated domain. 6752: # 6753: # Parameters: 6754: # domain - domain to check in . 6755: # user - Name of user to check. 6756: # 6757: # Return: 6758: # 1 - User is an author for domain. 6759: # 0 - User is not an author for domain. 6760: sub is_author { 6761: my ($domain, $user) = @_; 6762: 6763: &Debug("is_author: $user @ $domain"); 6764: 6765: my $hashref = &tie_user_hash($domain, $user, "roles", 6766: &GDBM_READER()); 6767: 6768: # Author role should show up as a key /domain/_au 6769: 6770: my $value; 6771: if ($hashref) { 6772: 6773: my $key = "/$domain/_au"; 6774: if (defined($hashref)) { 6775: $value = $hashref->{$key}; 6776: if(!untie_user_hash($hashref)) { 6777: return 'error: ' . ($!+0)." untie (GDBM) Failed"; 6778: } 6779: } 6780: 6781: if(defined($value)) { 6782: &Debug("$user @ $domain is an author"); 6783: } 6784: } else { 6785: return 'error: '.($!+0)." tie (GDBM) Failed"; 6786: } 6787: 6788: return defined($value); 6789: } 6790: # 6791: # Checks to see if the input roleput request was to set 6792: # an author role. If so, creates construction space 6793: # Parameters: 6794: # request - The request sent to the rolesput subchunk. 6795: # We're looking for /domain/_au 6796: # domain - The domain in which the user is having roles doctored. 6797: # user - Name of the user for which the role is being put. 6798: # authtype - The authentication type associated with the user. 6799: # 6800: sub manage_permissions { 6801: my ($request, $domain, $user, $authtype) = @_; 6802: # See if the request is of the form /$domain/_au 6803: if($request =~ /^(\/\Q$domain\E\/_au)$/) { # It's an author rolesput... 6804: my $path=$perlvar{'lonDocRoot'}."/priv/$domain"; 6805: unless (-e $path) { 6806: mkdir($path); 6807: } 6808: unless (-e $path.'/'.$user) { 6809: mkdir($path.'/'.$user); 6810: } 6811: } 6812: } 6813: 6814: 6815: # 6816: # Return the full path of a user password file, whether it exists or not. 6817: # Parameters: 6818: # domain - Domain in which the password file lives. 6819: # user - name of the user. 6820: # Returns: 6821: # Full passwd path: 6822: # 6823: sub password_path { 6824: my ($domain, $user) = @_; 6825: return &propath($domain, $user).'/passwd'; 6826: } 6827: 6828: # Password Filename 6829: # Returns the path to a passwd file given domain and user... only if 6830: # it exists. 6831: # Parameters: 6832: # domain - Domain in which to search. 6833: # user - username. 6834: # Returns: 6835: # - If the password file exists returns its path. 6836: # - If the password file does not exist, returns undefined. 6837: # 6838: sub password_filename { 6839: my ($domain, $user) = @_; 6840: 6841: Debug ("PasswordFilename called: dom = $domain user = $user"); 6842: 6843: my $path = &password_path($domain, $user); 6844: Debug("PasswordFilename got path: $path"); 6845: if(-e $path) { 6846: return $path; 6847: } else { 6848: return undef; 6849: } 6850: } 6851: 6852: # 6853: # Rewrite the contents of the user's passwd file. 6854: # Parameters: 6855: # domain - domain of the user. 6856: # name - User's name. 6857: # contents - New contents of the file. 6858: # Returns: 6859: # 0 - Failed. 6860: # 1 - Success. 6861: # 6862: sub rewrite_password_file { 6863: my ($domain, $user, $contents) = @_; 6864: 6865: my $file = &password_filename($domain, $user); 6866: if (defined $file) { 6867: my $pf = IO::File->new(">$file"); 6868: if($pf) { 6869: print $pf "$contents\n"; 6870: return 1; 6871: } else { 6872: return 0; 6873: } 6874: } else { 6875: return 0; 6876: } 6877: 6878: } 6879: 6880: # 6881: # get_auth_type - Determines the authorization type of a user in a domain. 6882: 6883: # Returns the authorization type or nouser if there is no such user. 6884: # 6885: sub get_auth_type { 6886: my ($domain, $user) = @_; 6887: 6888: Debug("get_auth_type( $domain, $user ) \n"); 6889: my $proname = &propath($domain, $user); 6890: my $passwdfile = "$proname/passwd"; 6891: if( -e $passwdfile ) { 6892: my $pf = IO::File->new($passwdfile); 6893: my $realpassword = <$pf>; 6894: chomp($realpassword); 6895: Debug("Password info = $realpassword\n"); 6896: my ($authtype, $contentpwd) = split(/:/, $realpassword); 6897: Debug("Authtype = $authtype, content = $contentpwd\n"); 6898: return "$authtype:$contentpwd"; 6899: } else { 6900: Debug("Returning nouser"); 6901: return "nouser"; 6902: } 6903: } 6904: 6905: # 6906: # Validate a user given their domain, name and password. This utility 6907: # function is used by both AuthenticateHandler and ChangePasswordHandler 6908: # to validate the login credentials of a user. 6909: # Parameters: 6910: # $domain - The domain being logged into (this is required due to 6911: # the capability for multihomed systems. 6912: # $user - The name of the user being validated. 6913: # $password - The user's propoposed password. 6914: # 6915: # Returns: 6916: # 1 - The domain,user,pasword triplet corresponds to a valid 6917: # user. 6918: # 0 - The domain,user,password triplet is not a valid user. 6919: # 6920: sub validate_user { 6921: my ($domain, $user, $password, $checkdefauth) = @_; 6922: 6923: # Why negative ~pi you may well ask? Well this function is about 6924: # authentication, and therefore very important to get right. 6925: # I've initialized the flag that determines whether or not I've 6926: # validated correctly to a value it's not supposed to get. 6927: # At the end of this function. I'll ensure that it's not still that 6928: # value so we don't just wind up returning some accidental value 6929: # as a result of executing an unforseen code path that 6930: # did not set $validated. At the end of valid execution paths, 6931: # validated shoule be 1 for success or 0 for failuer. 6932: 6933: my $validated = -3.14159; 6934: 6935: # How we authenticate is determined by the type of authentication 6936: # the user has been assigned. If the authentication type is 6937: # "nouser", the user does not exist so we will return 0. 6938: 6939: my $contents = &get_auth_type($domain, $user); 6940: my ($howpwd, $contentpwd) = split(/:/, $contents); 6941: 6942: my $null = pack("C",0); # Used by kerberos auth types. 6943: 6944: if ($howpwd eq 'nouser') { 6945: if ($checkdefauth) { 6946: my %domdefaults = &Apache::lonnet::get_domain_defaults($domain); 6947: if ($domdefaults{'auth_def'} eq 'localauth') { 6948: $howpwd = $domdefaults{'auth_def'}; 6949: $contentpwd = $domdefaults{'auth_arg_def'}; 6950: } elsif ((($domdefaults{'auth_def'} eq 'krb4') || 6951: ($domdefaults{'auth_def'} eq 'krb5')) && 6952: ($domdefaults{'auth_arg_def'} ne '')) { 6953: $howpwd = $domdefaults{'auth_def'}; 6954: $contentpwd = $domdefaults{'auth_arg_def'}; 6955: } 6956: } 6957: } 6958: if ($howpwd ne 'nouser') { 6959: if($howpwd eq "internal") { # Encrypted is in local password file. 6960: $validated = (crypt($password, $contentpwd) eq $contentpwd); 6961: } 6962: elsif ($howpwd eq "unix") { # User is a normal unix user. 6963: $contentpwd = (getpwnam($user))[1]; 6964: if($contentpwd) { 6965: if($contentpwd eq 'x') { # Shadow password file... 6966: my $pwauth_path = "/usr/local/sbin/pwauth"; 6967: open PWAUTH, "|$pwauth_path" or 6968: die "Cannot invoke authentication"; 6969: print PWAUTH "$user\n$password\n"; 6970: close PWAUTH; 6971: $validated = ! $?; 6972: 6973: } else { # Passwords in /etc/passwd. 6974: $validated = (crypt($password, 6975: $contentpwd) eq $contentpwd); 6976: } 6977: } else { 6978: $validated = 0; 6979: } 6980: } elsif ($howpwd eq "krb4") { # user is in kerberos 4 auth. domain. 6981: my $checkwithkrb5 = 0; 6982: if ($dist =~/^fedora(\d+)$/) { 6983: if ($1 > 11) { 6984: $checkwithkrb5 = 1; 6985: } 6986: } elsif ($dist =~ /^suse([\d.]+)$/) { 6987: if ($1 > 11.1) { 6988: $checkwithkrb5 = 1; 6989: } 6990: } 6991: if ($checkwithkrb5) { 6992: $validated = &krb5_authen($password,$null,$user,$contentpwd); 6993: } else { 6994: $validated = &krb4_authen($password,$null,$user,$contentpwd); 6995: } 6996: } elsif ($howpwd eq "krb5") { # User is in kerberos 5 auth. domain. 6997: $validated = &krb5_authen($password,$null,$user,$contentpwd); 6998: } elsif ($howpwd eq "localauth") { 6999: # Authenticate via installation specific authentcation method: 7000: $validated = &localauth::localauth($user, 7001: $password, 7002: $contentpwd, 7003: $domain); 7004: if ($validated < 0) { 7005: &logthis("localauth for $contentpwd $user:$domain returned a $validated"); 7006: $validated = 0; 7007: } 7008: } else { # Unrecognized auth is also bad. 7009: $validated = 0; 7010: } 7011: } else { 7012: $validated = 0; 7013: } 7014: # 7015: # $validated has the correct stat of the authentication: 7016: # 7017: 7018: unless ($validated != -3.14159) { 7019: # I >really really< want to know if this happens. 7020: # since it indicates that user authentication is badly 7021: # broken in some code path. 7022: # 7023: die "ValidateUser - failed to set the value of validated $domain, $user $password"; 7024: } 7025: return $validated; 7026: } 7027: 7028: sub krb4_authen { 7029: my ($password,$null,$user,$contentpwd) = @_; 7030: my $validated = 0; 7031: if (!($password =~ /$null/) ) { # Null password not allowed. 7032: eval { 7033: require Authen::Krb4; 7034: }; 7035: if (!$@) { 7036: my $k4error = &Authen::Krb4::get_pw_in_tkt($user, 7037: "", 7038: $contentpwd,, 7039: 'krbtgt', 7040: $contentpwd, 7041: 1, 7042: $password); 7043: if(!$k4error) { 7044: $validated = 1; 7045: } else { 7046: $validated = 0; 7047: &logthis('krb4: '.$user.', '.$contentpwd.', '. 7048: &Authen::Krb4::get_err_txt($Authen::Krb4::error)); 7049: } 7050: } else { 7051: $validated = krb5_authen($password,$null,$user,$contentpwd); 7052: } 7053: } 7054: return $validated; 7055: } 7056: 7057: sub krb5_authen { 7058: my ($password,$null,$user,$contentpwd) = @_; 7059: my $validated = 0; 7060: if(!($password =~ /$null/)) { # Null password not allowed. 7061: my $krbclient = &Authen::Krb5::parse_name($user.'@' 7062: .$contentpwd); 7063: my $krbservice = "krbtgt/".$contentpwd."\@".$contentpwd; 7064: my $krbserver = &Authen::Krb5::parse_name($krbservice); 7065: my $credentials= &Authen::Krb5::cc_default(); 7066: $credentials->initialize(&Authen::Krb5::parse_name($user.'@' 7067: .$contentpwd)); 7068: my $krbreturn; 7069: if (exists(&Authen::Krb5::get_init_creds_password)) { 7070: $krbreturn = 7071: &Authen::Krb5::get_init_creds_password($krbclient,$password, 7072: $krbservice); 7073: $validated = (ref($krbreturn) eq 'Authen::Krb5::Creds'); 7074: } else { 7075: $krbreturn = 7076: &Authen::Krb5::get_in_tkt_with_password($krbclient,$krbserver, 7077: $password,$credentials); 7078: $validated = ($krbreturn == 1); 7079: } 7080: if (!$validated) { 7081: &logthis('krb5: '.$user.', '.$contentpwd.', '. 7082: &Authen::Krb5::error()); 7083: } 7084: } 7085: return $validated; 7086: } 7087: 7088: sub addline { 7089: my ($fname,$hostid,$ip,$newline)=@_; 7090: my $contents; 7091: my $found=0; 7092: my $expr='^'.quotemeta($hostid).':'.quotemeta($ip).':'; 7093: my $sh; 7094: if ($sh=IO::File->new("$fname.subscription")) { 7095: while (my $subline=<$sh>) { 7096: if ($subline !~ /$expr/) {$contents.= $subline;} else {$found=1;} 7097: } 7098: $sh->close(); 7099: } 7100: $sh=IO::File->new(">$fname.subscription"); 7101: if ($contents) { print $sh $contents; } 7102: if ($newline) { print $sh $newline; } 7103: $sh->close(); 7104: return $found; 7105: } 7106: 7107: sub get_chat { 7108: my ($cdom,$cname,$udom,$uname,$group)=@_; 7109: 7110: my @entries=(); 7111: my $namespace = 'nohist_chatroom'; 7112: my $namespace_inroom = 'nohist_inchatroom'; 7113: if ($group ne '') { 7114: $namespace .= '_'.$group; 7115: $namespace_inroom .= '_'.$group; 7116: } 7117: my $hashref = &tie_user_hash($cdom, $cname, $namespace, 7118: &GDBM_READER()); 7119: if ($hashref) { 7120: @entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref)); 7121: &untie_user_hash($hashref); 7122: } 7123: my @participants=(); 7124: my $cutoff=time-60; 7125: $hashref = &tie_user_hash($cdom, $cname, $namespace_inroom, 7126: &GDBM_WRCREAT()); 7127: if ($hashref) { 7128: $hashref->{$uname.':'.$udom}=time; 7129: foreach my $user (sort(keys(%$hashref))) { 7130: if ($hashref->{$user}>$cutoff) { 7131: push(@participants, 'active_participant:'.$user); 7132: } 7133: } 7134: &untie_user_hash($hashref); 7135: } 7136: return (@participants,@entries); 7137: } 7138: 7139: sub chat_add { 7140: my ($cdom,$cname,$newchat,$group)=@_; 7141: my @entries=(); 7142: my $time=time; 7143: my $namespace = 'nohist_chatroom'; 7144: my $logfile = 'chatroom.log'; 7145: if ($group ne '') { 7146: $namespace .= '_'.$group; 7147: $logfile = 'chatroom_'.$group.'.log'; 7148: } 7149: my $hashref = &tie_user_hash($cdom, $cname, $namespace, 7150: &GDBM_WRCREAT()); 7151: if ($hashref) { 7152: @entries=map { $_.':'.$hashref->{$_} } sort(keys(%$hashref)); 7153: my ($lastid)=($entries[$#entries]=~/^(\w+)\:/); 7154: my ($thentime,$idnum)=split(/\_/,$lastid); 7155: my $newid=$time.'_000000'; 7156: if ($thentime==$time) { 7157: $idnum=~s/^0+//; 7158: $idnum++; 7159: $idnum=substr('000000'.$idnum,-6,6); 7160: $newid=$time.'_'.$idnum; 7161: } 7162: $hashref->{$newid}=$newchat; 7163: my $expired=$time-3600; 7164: foreach my $comment (keys(%$hashref)) { 7165: my ($thistime) = ($comment=~/(\d+)\_/); 7166: if ($thistime<$expired) { 7167: delete $hashref->{$comment}; 7168: } 7169: } 7170: { 7171: my $proname=&propath($cdom,$cname); 7172: if (open(CHATLOG,">>$proname/$logfile")) { 7173: print CHATLOG ("$time:".&unescape($newchat)."\n"); 7174: } 7175: close(CHATLOG); 7176: } 7177: &untie_user_hash($hashref); 7178: } 7179: } 7180: 7181: sub unsub { 7182: my ($fname,$clientip)=@_; 7183: my $result; 7184: my $unsubs = 0; # Number of successful unsubscribes: 7185: 7186: 7187: # An old way subscriptions were handled was to have a 7188: # subscription marker file: 7189: 7190: Debug("Attempting unlink of $fname.$clientname"); 7191: if (unlink("$fname.$clientname")) { 7192: $unsubs++; # Successful unsub via marker file. 7193: } 7194: 7195: # The more modern way to do it is to have a subscription list 7196: # file: 7197: 7198: if (-e "$fname.subscription") { 7199: my $found=&addline($fname,$clientname,$clientip,''); 7200: if ($found) { 7201: $unsubs++; 7202: } 7203: } 7204: 7205: # If either or both of these mechanisms succeeded in unsubscribing a 7206: # resource we can return ok: 7207: 7208: if($unsubs) { 7209: $result = "ok\n"; 7210: } else { 7211: $result = "not_subscribed\n"; 7212: } 7213: 7214: return $result; 7215: } 7216: 7217: sub currentversion { 7218: my $fname=shift; 7219: my $version=-1; 7220: my $ulsdir=''; 7221: if ($fname=~/^(.+)\/[^\/]+$/) { 7222: $ulsdir=$1; 7223: } 7224: my ($fnamere1,$fnamere2); 7225: # remove version if already specified 7226: $fname=~s/\.\d+\.(\w+(?:\.meta)*)$/\.$1/; 7227: # get the bits that go before and after the version number 7228: if ( $fname=~/^(.*\.)(\w+(?:\.meta)*)$/ ) { 7229: $fnamere1=$1; 7230: $fnamere2='.'.$2; 7231: } 7232: if (-e $fname) { $version=1; } 7233: if (-e $ulsdir) { 7234: if(-d $ulsdir) { 7235: if (opendir(LSDIR,$ulsdir)) { 7236: my $ulsfn; 7237: while ($ulsfn=readdir(LSDIR)) { 7238: # see if this is a regular file (ignore links produced earlier) 7239: my $thisfile=$ulsdir.'/'.$ulsfn; 7240: unless (-l $thisfile) { 7241: if ($thisfile=~/\Q$fnamere1\E(\d+)\Q$fnamere2\E$/) { 7242: if ($1>$version) { $version=$1; } 7243: } 7244: } 7245: } 7246: closedir(LSDIR); 7247: $version++; 7248: } 7249: } 7250: } 7251: return $version; 7252: } 7253: 7254: sub thisversion { 7255: my $fname=shift; 7256: my $version=-1; 7257: if ($fname=~/\.(\d+)\.\w+(?:\.meta)*$/) { 7258: $version=$1; 7259: } 7260: return $version; 7261: } 7262: 7263: sub subscribe { 7264: my ($userinput,$clientip)=@_; 7265: my $result; 7266: my ($cmd,$fname)=split(/:/,$userinput,2); 7267: my $ownership=&ishome($fname); 7268: if ($ownership eq 'owner') { 7269: # explitly asking for the current version? 7270: unless (-e $fname) { 7271: my $currentversion=¤tversion($fname); 7272: if (&thisversion($fname)==$currentversion) { 7273: if ($fname=~/^(.+)\.\d+\.(\w+(?:\.meta)*)$/) { 7274: my $root=$1; 7275: my $extension=$2; 7276: symlink($root.'.'.$extension, 7277: $root.'.'.$currentversion.'.'.$extension); 7278: unless ($extension=~/\.meta$/) { 7279: symlink($root.'.'.$extension.'.meta', 7280: $root.'.'.$currentversion.'.'.$extension.'.meta'); 7281: } 7282: } 7283: } 7284: } 7285: if (-e $fname) { 7286: if (-d $fname) { 7287: $result="directory\n"; 7288: } else { 7289: if (-e "$fname.$clientname") {&unsub($fname,$clientip);} 7290: my $now=time; 7291: my $found=&addline($fname,$clientname,$clientip, 7292: "$clientname:$clientip:$now\n"); 7293: if ($found) { $result="$fname\n"; } 7294: # if they were subscribed to only meta data, delete that 7295: # subscription, when you subscribe to a file you also get 7296: # the metadata 7297: unless ($fname=~/\.meta$/) { &unsub("$fname.meta",$clientip); } 7298: $fname=~s/\/home\/httpd\/html\/res/raw/; 7299: my $protocol = $Apache::lonnet::protocol{$perlvar{'lonHostID'}}; 7300: $protocol = 'http' if ($protocol ne 'https'); 7301: $fname=$protocol.'://'.&Apache::lonnet::hostname($perlvar{'lonHostID'})."/".$fname; 7302: $result="$fname\n"; 7303: } 7304: } else { 7305: $result="not_found\n"; 7306: } 7307: } else { 7308: $result="rejected\n"; 7309: } 7310: return $result; 7311: } 7312: # Change the passwd of a unix user. The caller must have 7313: # first verified that the user is a loncapa user. 7314: # 7315: # Parameters: 7316: # user - Unix user name to change. 7317: # pass - New password for the user. 7318: # Returns: 7319: # ok - if success 7320: # other - Some meaningfule error message string. 7321: # NOTE: 7322: # invokes a setuid script to change the passwd. 7323: sub change_unix_password { 7324: my ($user, $pass) = @_; 7325: 7326: &Debug("change_unix_password"); 7327: my $execdir=$perlvar{'lonDaemons'}; 7328: &Debug("Opening lcpasswd pipeline"); 7329: my $pf = IO::File->new("|$execdir/lcpasswd > " 7330: ."$perlvar{'lonDaemons'}" 7331: ."/logs/lcpasswd.log"); 7332: print $pf "$user\n$pass\n$pass\n"; 7333: close $pf; 7334: my $err = $?; 7335: return ($err < @passwderrors) ? $passwderrors[$err] : 7336: "pwchange_falure - unknown error"; 7337: 7338: 7339: } 7340: 7341: 7342: sub make_passwd_file { 7343: my ($uname,$udom,$umode,$npass,$passfilename)=@_; 7344: my $result="ok"; 7345: if ($umode eq 'krb4' or $umode eq 'krb5') { 7346: { 7347: my $pf = IO::File->new(">$passfilename"); 7348: if ($pf) { 7349: print $pf "$umode:$npass\n"; 7350: } else { 7351: $result = "pass_file_failed_error"; 7352: } 7353: } 7354: } elsif ($umode eq 'internal') { 7355: my $salt=time; 7356: $salt=substr($salt,6,2); 7357: my $ncpass=crypt($npass,$salt); 7358: { 7359: &Debug("Creating internal auth"); 7360: my $pf = IO::File->new(">$passfilename"); 7361: if($pf) { 7362: print $pf "internal:$ncpass\n"; 7363: } else { 7364: $result = "pass_file_failed_error"; 7365: } 7366: } 7367: } elsif ($umode eq 'localauth') { 7368: { 7369: my $pf = IO::File->new(">$passfilename"); 7370: if($pf) { 7371: print $pf "localauth:$npass\n"; 7372: } else { 7373: $result = "pass_file_failed_error"; 7374: } 7375: } 7376: } elsif ($umode eq 'unix') { 7377: { 7378: # 7379: # Don't allow the creation of privileged accounts!!! that would 7380: # be real bad!!! 7381: # 7382: my $uid = getpwnam($uname); 7383: if((defined $uid) && ($uid == 0)) { 7384: &logthis(">>>Attempt to create privileged account blocked"); 7385: return "no_priv_account_error\n"; 7386: } 7387: 7388: my $execpath ="$perlvar{'lonDaemons'}/"."lcuseradd"; 7389: 7390: my $lc_error_file = $execdir."/tmp/lcuseradd".$$.".status"; 7391: { 7392: &Debug("Executing external: ".$execpath); 7393: &Debug("user = ".$uname.", Password =". $npass); 7394: my $se = IO::File->new("|$execpath > $perlvar{'lonDaemons'}/logs/lcuseradd.log"); 7395: print $se "$uname\n"; 7396: print $se "$udom\n"; 7397: print $se "$npass\n"; 7398: print $se "$npass\n"; 7399: print $se "$lc_error_file\n"; # Status -> unique file. 7400: } 7401: if (-r $lc_error_file) { 7402: &Debug("Opening error file: $lc_error_file"); 7403: my $error = IO::File->new("< $lc_error_file"); 7404: my $useraddok = <$error>; 7405: $error->close; 7406: unlink($lc_error_file); 7407: 7408: chomp $useraddok; 7409: 7410: if($useraddok > 0) { 7411: my $error_text = &lcuseraddstrerror($useraddok); 7412: &logthis("Failed lcuseradd: $error_text"); 7413: $result = "lcuseradd_failed:$error_text"; 7414: } else { 7415: my $pf = IO::File->new(">$passfilename"); 7416: if($pf) { 7417: print $pf "unix:\n"; 7418: } else { 7419: $result = "pass_file_failed_error"; 7420: } 7421: } 7422: } else { 7423: &Debug("Could not locate lcuseradd error: $lc_error_file"); 7424: $result="bug_lcuseradd_no_output_file"; 7425: } 7426: } 7427: } elsif ($umode eq 'none') { 7428: { 7429: my $pf = IO::File->new("> $passfilename"); 7430: if($pf) { 7431: print $pf "none:\n"; 7432: } else { 7433: $result = "pass_file_failed_error"; 7434: } 7435: } 7436: } else { 7437: $result="auth_mode_error"; 7438: } 7439: return $result; 7440: } 7441: 7442: sub convert_photo { 7443: my ($start,$dest)=@_; 7444: system("convert $start $dest"); 7445: } 7446: 7447: sub sethost { 7448: my ($remotereq) = @_; 7449: my (undef,$hostid)=split(/:/,$remotereq); 7450: # ignore sethost if we are already correct 7451: if ($hostid eq $currenthostid) { 7452: return 'ok'; 7453: } 7454: 7455: if (!defined($hostid)) { $hostid=$perlvar{'lonHostID'}; } 7456: if (&Apache::lonnet::get_host_ip($perlvar{'lonHostID'}) 7457: eq &Apache::lonnet::get_host_ip($hostid)) { 7458: $currenthostid =$hostid; 7459: $currentdomainid=&Apache::lonnet::host_domain($hostid); 7460: # &logthis("Setting hostid to $hostid, and domain to $currentdomainid"); 7461: } else { 7462: &logthis("Requested host id $hostid not an alias of ". 7463: $perlvar{'lonHostID'}." refusing connection"); 7464: return 'unable_to_set'; 7465: } 7466: return 'ok'; 7467: } 7468: 7469: sub version { 7470: my ($userinput)=@_; 7471: $remoteVERSION=(split(/:/,$userinput))[1]; 7472: return "version:$VERSION"; 7473: } 7474: 7475: sub get_usersession_config { 7476: my ($dom,$name) = @_; 7477: my ($usersessionconf,$cached)=&Apache::lonnet::is_cached_new($name,$dom); 7478: if (defined($cached)) { 7479: return $usersessionconf; 7480: } else { 7481: my %domconfig = &Apache::lonnet::get_dom('configuration',['usersessions'],$dom); 7482: if (ref($domconfig{'usersessions'}) eq 'HASH') { 7483: &Apache::lonnet::do_cache_new($name,$dom,$domconfig{'usersessions'},3600); 7484: return $domconfig{'usersessions'}; 7485: } 7486: } 7487: return; 7488: } 7489: 7490: # 7491: # releasereqd_check() will determine if a LON-CAPA version (defined in the 7492: # $major,$minor args passed) is not too old to allow use of a role in a 7493: # course ($cnum,$cdom args passed), if at least one of the following applies: 7494: # (a) the course is a Community, (b) the course's home server is *not* the 7495: # current server, or (c) cached course information is not stale. 7496: # 7497: # For the case where none of these apply, the course is added to the 7498: # $homecourse hash ref (keys = courseIDs, values = array of a hash of roles). 7499: # The $homecourse hash ref is for courses for which the current server is the 7500: # home server. LON-CAPA version requirements are checked elsewhere for the 7501: # items in $homecourse. 7502: # 7503: 7504: sub releasereqd_check { 7505: my ($cnum,$cdom,$key,$value,$major,$minor,$homecourses,$ids) = @_; 7506: my $home = &Apache::lonnet::homeserver($cnum,$cdom); 7507: return if ($home eq 'no_host'); 7508: my ($reqdmajor,$reqdminor,$displayrole); 7509: if ($cnum =~ /$LONCAPA::match_community/) { 7510: if ($major eq '' && $minor eq '') { 7511: return unless ((ref($ids) eq 'ARRAY') && 7512: (grep(/^\Q$home\E$/,@{$ids}))); 7513: } else { 7514: $reqdmajor = 2; 7515: $reqdminor = 9; 7516: return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor)); 7517: } 7518: } 7519: my $hashid = $cdom.':'.$cnum; 7520: my ($courseinfo,$cached) = 7521: &Apache::lonnet::is_cached_new('courseinfo',$hashid); 7522: if (defined($cached)) { 7523: if (ref($courseinfo) eq 'HASH') { 7524: if (exists($courseinfo->{'releaserequired'})) { 7525: my ($reqdmajor,$reqdminor) = split(/\./,$courseinfo->{'releaserequired'}); 7526: return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor)); 7527: } 7528: } 7529: } else { 7530: if (ref($ids) eq 'ARRAY') { 7531: if (grep(/^\Q$home\E$/,@{$ids})) { 7532: if (ref($homecourses) eq 'HASH') { 7533: if (ref($homecourses->{$cdom}) eq 'HASH') { 7534: if (ref($homecourses->{$cdom}{$cnum}) eq 'HASH') { 7535: if (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY') { 7536: push(@{$homecourses->{$cdom}{$cnum}},{$key=>$value}); 7537: } else { 7538: $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; 7539: } 7540: } else { 7541: $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; 7542: } 7543: } else { 7544: $homecourses->{$cdom}{$cnum} = [{$key=>$value}]; 7545: } 7546: } 7547: return; 7548: } 7549: } 7550: my $courseinfo = &get_courseinfo_hash($cnum,$cdom,$home); 7551: if (ref($courseinfo) eq 'HASH') { 7552: if (exists($courseinfo->{'releaserequired'})) { 7553: my ($reqdmajor,$reqdminor) = split(/\./,$courseinfo->{'releaserequired'}); 7554: return unless (&useable_role($reqdmajor,$reqdminor,$major,$minor)); 7555: } 7556: } else { 7557: return; 7558: } 7559: } 7560: return 1; 7561: } 7562: 7563: # 7564: # get_courseinfo_hash() is used to retrieve course information from the db 7565: # file: nohist_courseids.db for a course for which the current server is *not* 7566: # the home server. 7567: # 7568: # A hash of a hash will be retrieved. The outer hash contains a single key -- 7569: # courseID -- for the course for which the data are being requested. 7570: # The contents of the inner hash, for that single item in the outer hash 7571: # are returned (and cached in memcache for 10 minutes). 7572: # 7573: 7574: sub get_courseinfo_hash { 7575: my ($cnum,$cdom,$home) = @_; 7576: my %info; 7577: eval { 7578: local($SIG{ALRM}) = sub { die "timeout\n"; }; 7579: local($SIG{__DIE__})='DEFAULT'; 7580: alarm(3); 7581: %info = &Apache::lonnet::courseiddump($cdom,'.',1,'.','.',$cnum,1,[$home],'.'); 7582: alarm(0); 7583: }; 7584: if ($@) { 7585: if ($@ eq "timeout\n") { 7586: &logthis("<font color='blue'>WARNING courseiddump for $cnum:$cdom from $home timedout</font>"); 7587: } else { 7588: &logthis("<font color='yellow'>WARNING unexpected error during eval of call for courseiddump from $home</font>"); 7589: } 7590: } else { 7591: if (ref($info{$cdom.'_'.$cnum}) eq 'HASH') { 7592: my $hashid = $cdom.':'.$cnum; 7593: return &Apache::lonnet::do_cache_new('courseinfo',$hashid,$info{$cdom.'_'.$cnum},600); 7594: } 7595: } 7596: return; 7597: } 7598: 7599: # 7600: # check_homecourses() will retrieve course information for those courses which 7601: # are keys of the $homecourses hash ref (first arg). The nohist_courseids.db 7602: # GDBM file is tied and course information for each course retrieved. Last 7603: # visit (lasttime key) is also retrieved for each, and cached values updated 7604: # for any courses last visited less than 24 hours ago. Cached values are also 7605: # updated for any courses included in the $homecourses hash ref. 7606: # 7607: # The reason for the 24 hours constraint is that the cron entry in 7608: # /etc/cron.d/loncapa for /home/httpd/perl/refresh_courseids_db.pl causes 7609: # cached course information to be updated nightly for courses with activity 7610: # within the past 24 hours. 7611: # 7612: # Role information for the user (included in a ref to an array of hashes as the 7613: # value for each key in $homecourses) is appended to the result returned by the 7614: # routine, which will in turn be appended to the string returned to the client 7615: # hosting the user's session. 7616: # 7617: 7618: sub check_homecourses { 7619: my ($homecourses,$regexp,$count,$range,$start,$end,$major,$minor) = @_; 7620: my ($result,%addtocache); 7621: my $yesterday = time - 24*3600; 7622: if (ref($homecourses) eq 'HASH') { 7623: my (%okcourses,%courseinfo,%recent); 7624: foreach my $domain (keys(%{$homecourses})) { 7625: my $hashref = 7626: &tie_domain_hash($domain, "nohist_courseids", &GDBM_WRCREAT()); 7627: if (ref($hashref) eq 'HASH') { 7628: while (my ($key,$value) = each(%$hashref)) { 7629: my $unesc_key = &unescape($key); 7630: if ($unesc_key =~ /^lasttime:(\w+)$/) { 7631: my $cid = $1; 7632: $cid =~ s/_/:/; 7633: if ($value > $yesterday ) { 7634: $recent{$cid} = 1; 7635: } 7636: next; 7637: } 7638: my $items = &Apache::lonnet::thaw_unescape($value); 7639: if (ref($items) eq 'HASH') { 7640: my ($cdom,$cnum) = split(/_/,$unesc_key); 7641: my $hashid = $cdom.':'.$cnum; 7642: $courseinfo{$hashid} = $items; 7643: if (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY') { 7644: my ($reqdmajor,$reqdminor) = split(/\./,$items->{'releaserequired'}); 7645: if (&useable_role($reqdmajor,$reqdminor,$major,$minor)) { 7646: $okcourses{$hashid} = 1; 7647: } 7648: } 7649: } 7650: } 7651: unless (&untie_domain_hash($hashref)) { 7652: &logthis("Failed to untie tied hash for nohist_courseids.db for $domain"); 7653: } 7654: } else { 7655: &logthis("Failed to tie hash for nohist_courseids.db for $domain"); 7656: } 7657: } 7658: foreach my $hashid (keys(%recent)) { 7659: my ($result,$cached)=&Apache::lonnet::is_cached_new('courseinfo',$hashid); 7660: unless ($cached) { 7661: &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600); 7662: } 7663: } 7664: foreach my $cdom (keys(%{$homecourses})) { 7665: if (ref($homecourses->{$cdom}) eq 'HASH') { 7666: foreach my $cnum (keys(%{$homecourses->{$cdom}})) { 7667: my $hashid = $cdom.':'.$cnum; 7668: next if ($recent{$hashid}); 7669: &Apache::lonnet::do_cache_new('courseinfo',$hashid,$courseinfo{$hashid},600); 7670: } 7671: } 7672: } 7673: foreach my $hashid (keys(%okcourses)) { 7674: my ($cdom,$cnum) = split(/:/,$hashid); 7675: if ((ref($homecourses->{$cdom}) eq 'HASH') && 7676: (ref($homecourses->{$cdom}{$cnum}) eq 'ARRAY')) { 7677: foreach my $role (@{$homecourses->{$cdom}{$cnum}}) { 7678: if (ref($role) eq 'HASH') { 7679: while (my ($key,$value) = each(%{$role})) { 7680: if ($regexp eq '.') { 7681: $count++; 7682: if (defined($range) && $count >= $end) { last; } 7683: if (defined($range) && $count < $start) { next; } 7684: $result.=$key.'='.$value.'&'; 7685: } else { 7686: my $unescapeKey = &unescape($key); 7687: if (eval('$unescapeKey=~/$regexp/')) { 7688: $count++; 7689: if (defined($range) && $count >= $end) { last; } 7690: if (defined($range) && $count < $start) { next; } 7691: $result.="$key=$value&"; 7692: } 7693: } 7694: } 7695: } 7696: } 7697: } 7698: } 7699: } 7700: return $result; 7701: } 7702: 7703: # 7704: # useable_role() will compare the LON-CAPA version required by a course with 7705: # the version available on the client server. If the client server's version 7706: # is compatible, 1 will be returned. 7707: # 7708: 7709: sub useable_role { 7710: my ($reqdmajor,$reqdminor,$major,$minor) = @_; 7711: if ($reqdmajor ne '' && $reqdminor ne '') { 7712: return if (($major eq '' && $minor eq '') || 7713: ($major < $reqdmajor) || 7714: (($major == $reqdmajor) && ($minor < $reqdminor))); 7715: } 7716: return 1; 7717: } 7718: 7719: sub distro_and_arch { 7720: return $dist.':'.$arch; 7721: } 7722: 7723: # ----------------------------------- POD (plain old documentation, CPAN style) 7724: 7725: =head1 NAME 7726: 7727: lond - "LON Daemon" Server (port "LOND" 5663) 7728: 7729: =head1 SYNOPSIS 7730: 7731: Usage: B<lond> 7732: 7733: Should only be run as user=www. This is a command-line script which 7734: is invoked by B<loncron>. There is no expectation that a typical user 7735: will manually start B<lond> from the command-line. (In other words, 7736: DO NOT START B<lond> YOURSELF.) 7737: 7738: =head1 DESCRIPTION 7739: 7740: There are two characteristics associated with the running of B<lond>, 7741: PROCESS MANAGEMENT (starting, stopping, handling child processes) 7742: and SERVER-SIDE ACTIVITIES (password authentication, user creation, 7743: subscriptions, etc). These are described in two large 7744: sections below. 7745: 7746: B<PROCESS MANAGEMENT> 7747: 7748: Preforker - server who forks first. Runs as a daemon. HUPs. 7749: Uses IDEA encryption 7750: 7751: B<lond> forks off children processes that correspond to the other servers 7752: in the network. Management of these processes can be done at the 7753: parent process level or the child process level. 7754: 7755: B<logs/lond.log> is the location of log messages. 7756: 7757: The process management is now explained in terms of linux shell commands, 7758: subroutines internal to this code, and signal assignments: 7759: 7760: =over 4 7761: 7762: =item * 7763: 7764: PID is stored in B<logs/lond.pid> 7765: 7766: This is the process id number of the parent B<lond> process. 7767: 7768: =item * 7769: 7770: SIGTERM and SIGINT 7771: 7772: Parent signal assignment: 7773: $SIG{INT} = $SIG{TERM} = \&HUNTSMAN; 7774: 7775: Child signal assignment: 7776: $SIG{INT} = 'DEFAULT'; (and SIGTERM is DEFAULT also) 7777: (The child dies and a SIGALRM is sent to parent, awaking parent from slumber 7778: to restart a new child.) 7779: 7780: Command-line invocations: 7781: B<kill> B<-s> SIGTERM I<PID> 7782: B<kill> B<-s> SIGINT I<PID> 7783: 7784: Subroutine B<HUNTSMAN>: 7785: This is only invoked for the B<lond> parent I<PID>. 7786: This kills all the children, and then the parent. 7787: The B<lonc.pid> file is cleared. 7788: 7789: =item * 7790: 7791: SIGHUP 7792: 7793: Current bug: 7794: This signal can only be processed the first time 7795: on the parent process. Subsequent SIGHUP signals 7796: have no effect. 7797: 7798: Parent signal assignment: 7799: $SIG{HUP} = \&HUPSMAN; 7800: 7801: Child signal assignment: 7802: none (nothing happens) 7803: 7804: Command-line invocations: 7805: B<kill> B<-s> SIGHUP I<PID> 7806: 7807: Subroutine B<HUPSMAN>: 7808: This is only invoked for the B<lond> parent I<PID>, 7809: This kills all the children, and then the parent. 7810: The B<lond.pid> file is cleared. 7811: 7812: =item * 7813: 7814: SIGUSR1 7815: 7816: Parent signal assignment: 7817: $SIG{USR1} = \&USRMAN; 7818: 7819: Child signal assignment: 7820: $SIG{USR1}= \&logstatus; 7821: 7822: Command-line invocations: 7823: B<kill> B<-s> SIGUSR1 I<PID> 7824: 7825: Subroutine B<USRMAN>: 7826: When invoked for the B<lond> parent I<PID>, 7827: SIGUSR1 is sent to all the children, and the status of 7828: each connection is logged. 7829: 7830: =item * 7831: 7832: SIGUSR2 7833: 7834: Parent Signal assignment: 7835: $SIG{USR2} = \&UpdateHosts 7836: 7837: Child signal assignment: 7838: NONE 7839: 7840: 7841: =item * 7842: 7843: SIGCHLD 7844: 7845: Parent signal assignment: 7846: $SIG{CHLD} = \&REAPER; 7847: 7848: Child signal assignment: 7849: none 7850: 7851: Command-line invocations: 7852: B<kill> B<-s> SIGCHLD I<PID> 7853: 7854: Subroutine B<REAPER>: 7855: This is only invoked for the B<lond> parent I<PID>. 7856: Information pertaining to the child is removed. 7857: The socket port is cleaned up. 7858: 7859: =back 7860: 7861: B<SERVER-SIDE ACTIVITIES> 7862: 7863: Server-side information can be accepted in an encrypted or non-encrypted 7864: method. 7865: 7866: =over 4 7867: 7868: =item ping 7869: 7870: Query a client in the hosts.tab table; "Are you there?" 7871: 7872: =item pong 7873: 7874: Respond to a ping query. 7875: 7876: =item ekey 7877: 7878: Read in encrypted key, make cipher. Respond with a buildkey. 7879: 7880: =item load 7881: 7882: Respond with CPU load based on a computation upon /proc/loadavg. 7883: 7884: =item currentauth 7885: 7886: Reply with current authentication information (only over an 7887: encrypted channel). 7888: 7889: =item auth 7890: 7891: Only over an encrypted channel, reply as to whether a user's 7892: authentication information can be validated. 7893: 7894: =item passwd 7895: 7896: Allow for a password to be set. 7897: 7898: =item makeuser 7899: 7900: Make a user. 7901: 7902: =item passwd 7903: 7904: Allow for authentication mechanism and password to be changed. 7905: 7906: =item home 7907: 7908: Respond to a question "are you the home for a given user?" 7909: 7910: =item update 7911: 7912: Update contents of a subscribed resource. 7913: 7914: =item unsubscribe 7915: 7916: The server is unsubscribing from a resource. 7917: 7918: =item subscribe 7919: 7920: The server is subscribing to a resource. 7921: 7922: =item log 7923: 7924: Place in B<logs/lond.log> 7925: 7926: =item put 7927: 7928: stores hash in namespace 7929: 7930: =item rolesput 7931: 7932: put a role into a user's environment 7933: 7934: =item get 7935: 7936: returns hash with keys from array 7937: reference filled in from namespace 7938: 7939: =item eget 7940: 7941: returns hash with keys from array 7942: reference filled in from namesp (encrypts the return communication) 7943: 7944: =item rolesget 7945: 7946: get a role from a user's environment 7947: 7948: =item del 7949: 7950: deletes keys out of array from namespace 7951: 7952: =item keys 7953: 7954: returns namespace keys 7955: 7956: =item dump 7957: 7958: dumps the complete (or key matching regexp) namespace into a hash 7959: 7960: =item store 7961: 7962: stores hash permanently 7963: for this url; hashref needs to be given and should be a \%hashname; the 7964: remaining args aren't required and if they aren't passed or are '' they will 7965: be derived from the ENV 7966: 7967: =item restore 7968: 7969: returns a hash for a given url 7970: 7971: =item querysend 7972: 7973: Tells client about the lonsql process that has been launched in response 7974: to a sent query. 7975: 7976: =item queryreply 7977: 7978: Accept information from lonsql and make appropriate storage in temporary 7979: file space. 7980: 7981: =item idput 7982: 7983: Defines usernames as corresponding to IDs. (These "IDs" are unique identifiers 7984: for each student, defined perhaps by the institutional Registrar.) 7985: 7986: =item idget 7987: 7988: Returns usernames corresponding to IDs. (These "IDs" are unique identifiers 7989: for each student, defined perhaps by the institutional Registrar.) 7990: 7991: =item tmpput 7992: 7993: Accept and store information in temporary space. 7994: 7995: =item tmpget 7996: 7997: Send along temporarily stored information. 7998: 7999: =item ls 8000: 8001: List part of a user's directory. 8002: 8003: =item pushtable 8004: 8005: Pushes a file in /home/httpd/lonTab directory. Currently limited to: 8006: hosts.tab and domain.tab. The old file is copied to *.tab.backup but 8007: must be restored manually in case of a problem with the new table file. 8008: pushtable requires that the request be encrypted and validated via 8009: ValidateManager. The form of the command is: 8010: enc:pushtable tablename <tablecontents> \n 8011: where pushtable, tablename and <tablecontents> will be encrypted, but \n is a 8012: cleartext newline. 8013: 8014: =item Hanging up (exit or init) 8015: 8016: What to do when a client tells the server that they (the client) 8017: are leaving the network. 8018: 8019: =item unknown command 8020: 8021: If B<lond> is sent an unknown command (not in the list above), 8022: it replys to the client "unknown_cmd". 8023: 8024: 8025: =item UNKNOWN CLIENT 8026: 8027: If the anti-spoofing algorithm cannot verify the client, 8028: the client is rejected (with a "refused" message sent 8029: to the client, and the connection is closed. 8030: 8031: =back 8032: 8033: =head1 PREREQUISITES 8034: 8035: IO::Socket 8036: IO::File 8037: Apache::File 8038: POSIX 8039: Crypt::IDEA 8040: LWP::UserAgent() 8041: GDBM_File 8042: Authen::Krb4 8043: Authen::Krb5 8044: 8045: =head1 COREQUISITES 8046: 8047: =head1 OSNAMES 8048: 8049: linux 8050: 8051: =head1 SCRIPT CATEGORIES 8052: 8053: Server/Process 8054: 8055: =cut 8056: 8057: 8058: =pod 8059: 8060: =head1 LOG MESSAGES 8061: 8062: The messages below can be emitted in the lond log. This log is located 8063: in ~httpd/perl/logs/lond.log Many log messages have HTML encapsulation 8064: to provide coloring if examined from inside a web page. Some do not. 8065: Where color is used, the colors are; Red for sometihhng to get excited 8066: about and to follow up on. Yellow for something to keep an eye on to 8067: be sure it does not get worse, Green,and Blue for informational items. 8068: 8069: In the discussions below, sometimes reference is made to ~httpd 8070: when describing file locations. There isn't really an httpd 8071: user, however there is an httpd directory that gets installed in the 8072: place that user home directories go. On linux, this is usually 8073: (always?) /home/httpd. 8074: 8075: 8076: Some messages are colorless. These are usually (not always) 8077: Green/Blue color level messages. 8078: 8079: =over 2 8080: 8081: =item (Red) LocalConnection rejecting non local: <ip> ne 127.0.0.1 8082: 8083: A local connection negotiation was attempted by 8084: a host whose IP address was not 127.0.0.1. 8085: The socket is closed and the child will exit. 8086: lond has three ways to establish an encyrption 8087: key with a client: 8088: 8089: =over 2 8090: 8091: =item local 8092: 8093: The key is written and read from a file. 8094: This is only valid for connections from localhost. 8095: 8096: =item insecure 8097: 8098: The key is generated by the server and 8099: transmitted to the client. 8100: 8101: =item ssl (secure) 8102: 8103: An ssl connection is negotiated with the client, 8104: the key is generated by the server and sent to the 8105: client across this ssl connection before the 8106: ssl connectionis terminated and clear text 8107: transmission resumes. 8108: 8109: =back 8110: 8111: =item (Red) LocalConnection: caller is insane! init = <init> and type = <type> 8112: 8113: The client is local but has not sent an initialization 8114: string that is the literal "init:local" The connection 8115: is closed and the child exits. 8116: 8117: =item Red CRITICAL Can't get key file <error> 8118: 8119: SSL key negotiation is being attempted but the call to 8120: lonssl::KeyFile failed. This usually means that the 8121: configuration file is not correctly defining or protecting 8122: the directories/files lonCertificateDirectory or 8123: lonnetPrivateKey 8124: <error> is a string that describes the reason that 8125: the key file could not be located. 8126: 8127: =item (Red) CRITICAL Can't get certificates <error> 8128: 8129: SSL key negotiation failed because we were not able to retrives our certificate 8130: or the CA's certificate in the call to lonssl::CertificateFile 8131: <error> is the textual reason this failed. Usual reasons: 8132: 8133: =over 2 8134: 8135: =item Apache config file for loncapa incorrect: 8136: 8137: one of the variables 8138: lonCertificateDirectory, lonnetCertificateAuthority, or lonnetCertificate 8139: undefined or incorrect 8140: 8141: =item Permission error: 8142: 8143: The directory pointed to by lonCertificateDirectory is not readable by lond 8144: 8145: =item Permission error: 8146: 8147: Files in the directory pointed to by lonCertificateDirectory are not readable by lond. 8148: 8149: =item Installation error: 8150: 8151: Either the certificate authority file or the certificate have not 8152: been installed in lonCertificateDirectory. 8153: 8154: =item (Red) CRITICAL SSL Socket promotion failed: <err> 8155: 8156: The promotion of the connection from plaintext to SSL failed 8157: <err> is the reason for the failure. There are two 8158: system calls involved in the promotion (one of which failed), 8159: a dup to produce 8160: a second fd on the raw socket over which the encrypted data 8161: will flow and IO::SOcket::SSL->new_from_fd which creates 8162: the SSL connection on the duped fd. 8163: 8164: =item (Blue) WARNING client did not respond to challenge 8165: 8166: This occurs on an insecure (non SSL) connection negotiation request. 8167: lond generates some number from the time, the PID and sends it to 8168: the client. The client must respond by echoing this information back. 8169: If the client does not do so, that's a violation of the challenge 8170: protocols and the connection will be failed. 8171: 8172: =item (Red) No manager table. Nobody can manage!! 8173: 8174: lond has the concept of privileged hosts that 8175: can perform remote management function such 8176: as update the hosts.tab. The manager hosts 8177: are described in the 8178: ~httpd/lonTabs/managers.tab file. 8179: this message is logged if this file is missing. 8180: 8181: 8182: =item (Green) Registering manager <dnsname> as <cluster_name> with <ipaddress> 8183: 8184: Reports the successful parse and registration 8185: of a specific manager. 8186: 8187: =item Green existing host <clustername:dnsname> 8188: 8189: The manager host is already defined in the hosts.tab 8190: the information in that table, rather than the info in the 8191: manager table will be used to determine the manager's ip. 8192: 8193: =item (Red) Unable to craete <filename> 8194: 8195: lond has been asked to create new versions of an administrative 8196: file (by a manager). When this is done, the new file is created 8197: in a temp file and then renamed into place so that there are always 8198: usable administrative files, even if the update fails. This failure 8199: message means that the temp file could not be created. 8200: The update is abandoned, and the old file is available for use. 8201: 8202: =item (Green) CopyFile from <oldname> to <newname> failed 8203: 8204: In an update of administrative files, the copy of the existing file to a 8205: backup file failed. The installation of the new file may still succeed, 8206: but there will not be a back up file to rever to (this should probably 8207: be yellow). 8208: 8209: =item (Green) Pushfile: backed up <oldname> to <newname> 8210: 8211: See above, the backup of the old administrative file succeeded. 8212: 8213: =item (Red) Pushfile: Unable to install <filename> <reason> 8214: 8215: The new administrative file could not be installed. In this case, 8216: the old administrative file is still in use. 8217: 8218: =item (Green) Installed new < filename>. 8219: 8220: The new administrative file was successfullly installed. 8221: 8222: =item (Red) Reinitializing lond pid=<pid> 8223: 8224: The lonc child process <pid> will be sent a USR2 8225: signal. 8226: 8227: =item (Red) Reinitializing self 8228: 8229: We've been asked to re-read our administrative files,and 8230: are doing so. 8231: 8232: =item (Yellow) error:Invalid process identifier <ident> 8233: 8234: A reinit command was received, but the target part of the 8235: command was not valid. It must be either 8236: 'lond' or 'lonc' but was <ident> 8237: 8238: =item (Green) isValideditCommand checking: Command = <command> Key = <key> newline = <newline> 8239: 8240: Checking to see if lond has been handed a valid edit 8241: command. It is possible the edit command is not valid 8242: in that case there are no log messages to indicate that. 8243: 8244: =item Result of password change for <username> pwchange_success 8245: 8246: The password for <username> was 8247: successfully changed. 8248: 8249: =item Unable to open <user> passwd to change password 8250: 8251: Could not rewrite the 8252: internal password file for a user 8253: 8254: =item Result of password change for <user> : <result> 8255: 8256: A unix password change for <user> was attempted 8257: and the pipe returned <result> 8258: 8259: =item LWP GET: <message> for <fname> (<remoteurl>) 8260: 8261: The lightweight process fetch for a resource failed 8262: with <message> the local filename that should 8263: have existed/been created was <fname> the 8264: corresponding URI: <remoteurl> This is emitted in several 8265: places. 8266: 8267: =item Unable to move <transname> to <destname> 8268: 8269: From fetch_user_file_handler - the user file was replicated but could not 8270: be mv'd to its final location. 8271: 8272: =item Looking for <domain> <username> 8273: 8274: From user_has_session_handler - This should be a Debug call instead 8275: it indicates lond is about to check whether the specified user has a 8276: session active on the specified domain on the local host. 8277: 8278: =item Client <ip> (<name>) hanging up: <input> 8279: 8280: lond has been asked to exit by its client. The <ip> and <name> identify the 8281: client systemand <input> is the full exit command sent to the server. 8282: 8283: =item Red CRITICAL: ABNORMAL EXIT. child <pid> for server <hostname> died through a crass with this error->[<message>]. 8284: 8285: A lond child terminated. NOte that this termination can also occur when the 8286: child receives the QUIT or DIE signals. <pid> is the process id of the child, 8287: <hostname> the host lond is working for, and <message> the reason the child died 8288: to the best of our ability to get it (I would guess that any numeric value 8289: represents and errno value). This is immediately followed by 8290: 8291: =item Famous last words: Catching exception - <log> 8292: 8293: Where log is some recent information about the state of the child. 8294: 8295: =item Red CRITICAL: TIME OUT <pid> 8296: 8297: Some timeout occured for server <pid>. THis is normally a timeout on an LWP 8298: doing an HTTP::GET. 8299: 8300: =item child <pid> died 8301: 8302: The reaper caught a SIGCHILD for the lond child process <pid> 8303: This should be modified to also display the IP of the dying child 8304: $children{$pid} 8305: 8306: =item Unknown child 0 died 8307: A child died but the wait for it returned a pid of zero which really should not 8308: ever happen. 8309: 8310: =item Child <which> - <pid> looks like we missed it's death 8311: 8312: When a sigchild is received, the reaper process checks all children to see if they are 8313: alive. If children are dying quite quickly, the lack of signal queuing can mean 8314: that a signal hearalds the death of more than one child. If so this message indicates 8315: which other one died. <which> is the ip of a dead child 8316: 8317: =item Free socket: <shutdownretval> 8318: 8319: The HUNTSMAN sub was called due to a SIGINT in a child process. The socket is being shutdown. 8320: for whatever reason, <shutdownretval> is printed but in fact shutdown() is not documented 8321: to return anything. This is followed by: 8322: 8323: =item Red CRITICAL: Shutting down 8324: 8325: Just prior to exit. 8326: 8327: =item Free socket: <shutdownretval> 8328: 8329: The HUPSMAN sub was called due to a SIGHUP. all children get killsed, and lond execs itself. 8330: This is followed by: 8331: 8332: =item (Red) CRITICAL: Restarting 8333: 8334: lond is about to exec itself to restart. 8335: 8336: =item (Blue) Updating connections 8337: 8338: (In response to a USR2). All the children (except the one for localhost) 8339: are about to be killed, the hosts tab reread, and Apache reloaded via apachereload. 8340: 8341: =item (Blue) UpdateHosts killing child <pid> for ip <ip> 8342: 8343: Due to USR2 as above. 8344: 8345: =item (Green) keeping child for ip <ip> (pid = <pid>) 8346: 8347: In response to USR2 as above, the child indicated is not being restarted because 8348: it's assumed that we'll always need a child for the localhost. 8349: 8350: 8351: =item Going to check on the children 8352: 8353: Parent is about to check on the health of the child processes. 8354: Note that this is in response to a USR1 sent to the parent lond. 8355: there may be one or more of the next two messages: 8356: 8357: =item <pid> is dead 8358: 8359: A child that we have in our child hash as alive has evidently died. 8360: 8361: =item Child <pid> did not respond 8362: 8363: In the health check the child <pid> did not update/produce a pid_.txt 8364: file when sent it's USR1 signal. That process is killed with a 9 signal, as it's 8365: assumed to be hung in some un-fixable way. 8366: 8367: =item Finished checking children 8368: 8369: Master processs's USR1 processing is cojmplete. 8370: 8371: =item (Red) CRITICAL: ------- Starting ------ 8372: 8373: (There are more '-'s on either side). Lond has forked itself off to 8374: form a new session and is about to start actual initialization. 8375: 8376: =item (Green) Attempting to start child (<client>) 8377: 8378: Started a new child process for <client>. Client is IO::Socket object 8379: connected to the child. This was as a result of a TCP/IP connection from a client. 8380: 8381: =item Unable to determine who caller was, getpeername returned nothing 8382: 8383: In child process initialization. either getpeername returned undef or 8384: a zero sized object was returned. Processing continues, but in my opinion, 8385: this should be cause for the child to exit. 8386: 8387: =item Unable to determine clientip 8388: 8389: In child process initialization. The peer address from getpeername was not defined. 8390: The client address is stored as "Unavailable" and processing continues. 8391: 8392: =item (Yellow) INFO: Connection <ip> <name> connection type = <type> 8393: 8394: In child initialization. A good connectionw as received from <ip>. 8395: 8396: =over 2 8397: 8398: =item <name> 8399: 8400: is the name of the client from hosts.tab. 8401: 8402: =item <type> 8403: 8404: Is the connection type which is either 8405: 8406: =over 2 8407: 8408: =item manager 8409: 8410: The connection is from a manager node, not in hosts.tab 8411: 8412: =item client 8413: 8414: the connection is from a non-manager in the hosts.tab 8415: 8416: =item both 8417: 8418: The connection is from a manager in the hosts.tab. 8419: 8420: =back 8421: 8422: =back 8423: 8424: =item (Blue) Certificates not installed -- trying insecure auth 8425: 8426: One of the certificate file, key file or 8427: certificate authority file could not be found for a client attempting 8428: SSL connection intiation. COnnection will be attemptied in in-secure mode. 8429: (this would be a system with an up to date lond that has not gotten a 8430: certificate from us). 8431: 8432: =item (Green) Successful local authentication 8433: 8434: A local connection successfully negotiated the encryption key. 8435: In this case the IDEA key is in a file (that is hopefully well protected). 8436: 8437: =item (Green) Successful ssl authentication with <client> 8438: 8439: The client (<client> is the peer's name in hosts.tab), has successfully 8440: negotiated an SSL connection with this child process. 8441: 8442: =item (Green) Successful insecure authentication with <client> 8443: 8444: 8445: The client has successfully negotiated an insecure connection withthe child process. 8446: 8447: =item (Yellow) Attempted insecure connection disallowed 8448: 8449: The client attempted and failed to successfully negotiate a successful insecure 8450: connection. This can happen either because the variable londAllowInsecure is false 8451: or undefined, or becuse the child did not successfully echo back the challenge 8452: string. 8453: 8454: 8455: =back 8456: 8457: =back 8458: 8459: 8460: =cut