home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-385-Vol-1of3.iso / c / cops_104.zip / cops_104 / kuang.pl.shar / kuang.pl < prev    next >
Perl Script  |  1992-03-10  |  17KB  |  772 lines

  1. #! /usr/local/bin/perl
  2.  
  3. #
  4. # kuang - rule based analysis of Unix security
  5. #
  6. # Perl version by Steve Romig of the CIS department, The Ohio State
  7. # University, October 1990. 
  8. # Based on the shell script version by Dan Farmer from his COPS
  9. # package, which in turn is based on a shell version by Robert
  10. # Baldwin. 
  11. #
  12.  
  13. do 'yagrip.pl' ||
  14.   die "can't do yagrip.pl";
  15.  
  16. $options = "vdlf:D";
  17. $usage = "usage: kuang [-v] [-d] [-l] [-D] [-f filedata] [u.username|g.groupname]\n";
  18.  
  19. #
  20. # Simple Unix Kuang, a security checking program.
  21. #
  22. # This is a perl version of Dan Farmer's version of Bob Baldwin's
  23. # shell scripts. 
  24. #
  25.  
  26. #
  27. # passwd_byuid lookup a password entry by uid, return as 
  28. # (name, password, directory, shell) list.
  29. #
  30. sub passwd_byuid {
  31.     local($uid) = @_;
  32.     local($name, $passwd, $gid, $quota, $comment, $gcos, $dir, $shell);
  33.  
  34.     if (! defined($passwd_byuid_list{$uid})) {
  35.     ($name, $passwd, $t_uid, $gid, $quota, $comment, $gcos, $dir, $shell) =
  36.         getpwuid($uid);
  37.  
  38.     if ($t_uid eq "") {
  39.         return();
  40.     }
  41.  
  42.     $passwd_byuid_list{$uid} = join(':', $name, $passwd, $dir, $shell);
  43.     $passwd_byname_list{$name} = $uid;
  44.     }
  45.  
  46.     return(split(/:/, $passwd_byuid_list{$uid}));
  47. }
  48.  
  49. sub passwd_byname {
  50.     local($name) = @_;
  51.     local($passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell);
  52.  
  53.     if (! defined($passwd_byname_list{$name})) {
  54.     ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) =
  55.         getpwnam($name);
  56.  
  57.     if ($uid eq "") {
  58.         return();
  59.     }
  60.  
  61.     $passwd_byuid_list{$uid} = join(':', $name, $passwd, $dir, $shell);
  62.     $passwd_byname_list{$name} = $uid;
  63.     }
  64.  
  65.     return($passwd_byname_list{$name});
  66. }
  67.  
  68. #
  69. # group_bygid lookup a group entry by gid, return as 
  70. # (name, members) list.
  71. #
  72. sub group_bygid {
  73.     local($gid) = @_;
  74.     local($name, $t_passwd, $t_gid, $members);
  75.  
  76.     if (! defined($group_bygid_list{$gid})) {
  77.     ($name, $t_passwd, $t_gid, $members) = getgrgid($gid);
  78.  
  79.     if ($t_gid eq "") {
  80.         return();
  81.     }
  82.  
  83.     $group_bygid_list{$gid} = join(':', $name, $members);
  84.     $group_byname_list{$name} = $gid;
  85.     }
  86.  
  87.     return(split(/:/, $group_bygid_list{$gid}));
  88. }
  89.  
  90. sub group_byname {
  91.     local($name) = @_;
  92.     local($gname, $passwd, $gid, $members);
  93.  
  94.     if (! defined($group_byname_list{$name})) {
  95.     ($gname, $passwd, $gid, $members) = getgrnam($name);
  96.  
  97.     if ($gid eq "") {
  98.         printf(stderr "A group named '$name' does not exist!\n");
  99.         exit(1);
  100.         }
  101.  
  102.     $group_bygid_list{$gid} = join(':', $name, $members);
  103.     $group_byname_list{$name} = $gid;
  104.     }
  105.  
  106.     return($group_byname_list{$name});
  107. }
  108.  
  109. #
  110. # Do various initialization type things.
  111. #
  112.  
  113. sub init_kuang {
  114.     local($which, $name, $uid, $gid);
  115.     local($f_type, $f_uid, $f_gid, $f_mode, $f_name);
  116.     local($count);
  117.  
  118.     $which = "u";
  119.     $name = "root";
  120.  
  121.     #
  122.     # Deal with args...
  123.     #
  124.  
  125.     &getopt($options) ||
  126.       die $usage;
  127.  
  128.     if ($#ARGV == 0) {
  129.     ($which, $name) = split(/\./, $ARGV[0]);
  130.  
  131.     if ($name eq "") {
  132.         $name = $which;
  133.         $which = "u";
  134.     }
  135.  
  136.     if ($which ne "u" && $which ne "g") {
  137.         printf(stderr "target must be given as u.user or g.group\n");
  138.         exit(1);
  139.     }
  140.     } elsif ($#ARGV > 0) {
  141.     printf(stderr $usage);
  142.     exit(1);
  143.     }
  144.  
  145.     #
  146.     # Preload the file data...
  147.     #
  148.     if (defined($opt_f)) {
  149.     #
  150.     # If we are dumping the file data to a DBM file, nuke the existing 
  151.     # ones and open the dbm file.  Otherwise, open the DBM file
  152.      # only if they exist. 
  153.     #
  154.     $read_from_file = 1;
  155.  
  156.     if (defined($opt_D)) {    
  157.         unlink("$opt_f.dir", "$opt_f.pag");
  158.  
  159.         dbmopen(files, $opt_f, 0644) ||
  160.           die sprintf("can't open DBM file '%s'", $opt_f);
  161.     } elsif (-f "$opt_f.dir") {
  162.         dbmopen(files, $opt_f, 0644) ||
  163.           die sprintf("can't open DBM file '%s'", $opt_f);
  164.  
  165.         $read_from_file = 0;
  166.     }
  167.  
  168.     if ($read_from_file) {
  169.         open(FILEDATA, $opt_f) || 
  170.           die sprintf("kuang: can't open '%s'", $opt_f);
  171.  
  172.         $count = 0;
  173.         while (<FILEDATA>) {
  174.         $count++;
  175.  
  176.         chop;
  177.         ($f_type, $f_uid, $f_gid, $f_mode, $f_name) = split;
  178.  
  179.         if ($count % 1000 == 0) {
  180.             printf("line $count, reading entry for $f_name\n");
  181.         }
  182.         $files{$f_name} = join(' ', $f_uid, $f_gid, $f_mode);
  183.         }
  184.  
  185.         close(FILEDATA);
  186.     }
  187.     }
  188.  
  189.     if (defined($opt_D)) {
  190.     dbmclose(files);
  191.  
  192.     exit(0);
  193.     }
  194. if (defined($opt_v)) {
  195.     printf("done with files\n");
  196.     }
  197.     #
  198.     # Need some of the password and group stuff.  Suck in passwd and 
  199.     # group info, store by uid and gid in an associative array of strings
  200.     # which consist of fields corresponding to the passwd and group file 
  201.     # entries (and what the heck, we'll use : as a delimiter also...:-)
  202.     #
  203.  
  204.     $passwd_byuid_list{-1} = "OTHER:::";    # add an entry for OTHER
  205.     $passwd_byname_list{"OTHER"} = -1;
  206.  
  207.     $uids_known{-1} = "";            # we can access OTHER
  208.     %uids_new = ();
  209.  
  210.     %gids_known = ();
  211.     %gids_new = ();
  212.  
  213.     %files_new = ();
  214.  
  215.     #
  216.     # Set up initial goal: become target user or group
  217.     #
  218.     if ($which eq "u") {
  219.     $uid = &passwd_byname($name);
  220.     if ($uid ne "") {
  221.         &addto("uids", $uid, "grant u.$name do anything");
  222.     } else {
  223.         printf(stderr "There is no user with username '$name'.\n");
  224.         exit(1);
  225.     }
  226.     } else {
  227.     $gid = &group_byname($name);
  228.     if ($gid ne "") {
  229.         &addto("gids", $gid, "grant g.$name");
  230.     } else {
  231.         printf(stderr "There is no group named '$name'.\n");
  232.         exit(1);
  233.     }
  234.     }
  235. }
  236.  
  237. #
  238. # Get the home directory for this UID from the passwd file cache.
  239. #
  240. sub gethome {
  241.     local($uid) = @_;
  242.     local($tmp, $home);
  243.  
  244.     ($tmp, $tmp, $home, $tmp) = &passwd_byuid($uid);
  245.     return($home);
  246. }
  247.  
  248. #
  249. # Get the writers of the named file - return as (UID, GID, OTHER)
  250. # triplet.  Owner can always write, since he can chmod the file if he
  251. # wants. 
  252. #
  253. # (fixme) are there any problems in this sort of builtin rule?  should
  254. # we make this knowledge more explicit?
  255. #
  256. sub filewriters {
  257.     local($name) = @_;
  258.     local($tmp, $mode, $uid, $gid, $other);
  259.     
  260.     #
  261.     # Check the file cache - avoid disk lookups for performance and 
  262.     # to avoid shadows...
  263.     #
  264.     if (defined($files{$name})) {
  265.     $cache_hit++;
  266.     
  267.     ($uid, $gid, $mode) = split(/ /, $files{$name});
  268.     $mode = oct($mode);
  269.     } else {
  270.     $cache_miss++;
  271.  
  272.     if (! -e $name && $read_from_file) {
  273.         $files{$name} = "";
  274.         return;
  275.     }
  276.  
  277.     ($tmp,$tmp,$mode,$tmp,$uid,$gid) = stat(_);
  278.     if ($read_from_file) {
  279.         $files{$name} = join(' ', $uid, $gid, $mode);
  280.     }
  281.     }
  282.  
  283.     if (($mode & 020) != 020) {
  284.     $gid = "";
  285.     }
  286.     
  287.     if (($mode & 02) == 02) {
  288.     $other = 1;
  289.     } else {
  290.     $other = 0;
  291.     }
  292.  
  293.     return($uid, $gid, $other);
  294. }
  295.  
  296. #
  297. # return # of entries in given associative array.
  298. #
  299. sub sizeof {
  300.     local(*which) = @_;
  301.     local(@keywords);
  302.  
  303.     @keywords = keys %which;
  304.     return($#keywords + 1);
  305. }
  306.  
  307. #
  308. # return appropriate entry from named associative array of given type.
  309. # returns a (key, value) pair - if key is "", there was no entry.
  310. #
  311. sub getentry {
  312.     local($which, $type, $key) = @_;
  313.     local($newkey, $value);
  314.  
  315.     $newkey = "";
  316.     $value = "";
  317.  
  318.     which: {
  319.     if ($which eq "uids") {
  320.         type0: {
  321.             if ($type eq "known") {
  322.             if (defined($uids_known{$key})) {
  323.                 $newkey = $key;    $value = $uids_known{$key};
  324.             }
  325.                 last type0;
  326.             }
  327.  
  328.             if ($type eq "new") {
  329.             if (defined($uids_new{$key})) {
  330.                 $newkey = $key;    $value = $uids_new{$key};
  331.             }
  332.                 last type0;
  333.             }
  334.  
  335.             if ($type eq "pending") {
  336.             if (defined($uids_pending{$key})) {
  337.                 $newkey = $key;    $value = $uids_pending{$key};
  338.             }
  339.                 last type0;
  340.             }
  341.  
  342.             if ($type eq "old") {
  343.             if (defined($uids_old{$key})) {
  344.                 $newkey = $key;    $value = $uids_old{$key};
  345.             }
  346.                 last type0;
  347.             }
  348.  
  349.             printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n", 
  350.             $type);
  351.             exit(1);
  352.         }
  353.  
  354.         last which;
  355.         }
  356.  
  357.     if ($which eq "gids") {
  358.         type1: {
  359.             if ($type eq "known") {
  360.             if (defined($gids_known{$key})) {
  361.                 $newkey = $key;    $value = $gids_known{$key};
  362.             }
  363.                 last type1;
  364.             }
  365.  
  366.             if ($type eq "new") {
  367.             if (defined($gids_new{$key})) {
  368.                 $newkey = $key;    $value = $gids_new{$key};
  369.             }
  370.                 last type1;
  371.             }
  372.  
  373.             if ($type eq "pending") {
  374.             if (defined($gids_pending{$key})) {
  375.                 $newkey = $key;    $value = $gids_pending{$key};
  376.             }
  377.                 last type1;
  378.             }
  379.  
  380.             if ($type eq "old") {
  381.             if (defined($gids_old{$key})) {
  382.                 $newkey = $key;    $value = $gids_old{$key};
  383.             }
  384.                 last type1;
  385.             }
  386.  
  387.             printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n", 
  388.             $type);
  389.             exit(1);
  390.         }
  391.  
  392.         last which;
  393.     }
  394.  
  395.     if ($which eq "files") {
  396.         type2: {
  397.             if ($type eq "known") {
  398.             if (defined($files_known{$key})) {
  399.                 $newkey = $key;    $value = $files_known{$key};
  400.             }
  401.                 last type2;
  402.             }
  403.  
  404.             if ($type eq "new") {
  405.             if (defined($files_new{$key})) {
  406.                 $newkey = $key;    $value = $files_new{$key};
  407.             }
  408.                 last type2;
  409.             }
  410.  
  411.             if ($type eq "pending") {
  412.             if (defined($files_pending{$key})) {
  413.                 $newkey = $key;    $value = $files_pending{$key};
  414.             }
  415.                 last type2;
  416.             }
  417.  
  418.             if ($type eq "old") {
  419.             if (defined($files_old{$key})) {
  420.                 $newkey = $key;    $value = $files_old{$key};
  421.             }
  422.                 last type2;
  423.             }
  424.  
  425.             printf(stderr "kuang: fatal error in getentry: type is wrong (%s)\n", 
  426.             $type);
  427.             exit(1);
  428.         }
  429.  
  430.         last which;
  431.     }
  432.  
  433.     printf(stderr "kuang: fatal error in getentry: which is wrong (%s)\n",
  434.         $which);
  435.     exit(1);
  436.     }
  437.  
  438.     return($newkey, $value);
  439. }
  440.  
  441.  
  442. #
  443. # stores a (key, value) in the associative array of the given type.
  444. #
  445. sub putentry {
  446.     local($which, $type, $key, $value) = @_;
  447.  
  448.     which: {
  449.     if ($which eq "uids") {
  450.         type0: {
  451.             if ($type eq "known") {
  452.             $uids_known{$key} = $value;    last type0;
  453.             }
  454.  
  455.             if ($type eq "new") {
  456.             $uids_new{$key} = $value;    last type0;
  457.             }
  458.  
  459.             if ($type eq "pending") {
  460.             $uids_pending{$key} = $value;    last type0;
  461.             }
  462.  
  463.             if ($type eq "old") {
  464.             $uids_old{$key} = $value;    last type0;
  465.             }
  466.  
  467.             printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n", 
  468.             $type);
  469.             exit(1);
  470.         }
  471.  
  472.         last which;
  473.         }
  474.  
  475.     if ($which eq "gids") {
  476.         type1: {
  477.             if ($type eq "known") {
  478.             $gids_known{$key} = $value;    last type1;
  479.             }
  480.  
  481.             if ($type eq "new") {
  482.             $gids_new{$key} = $value;    last type1;
  483.             }
  484.  
  485.             if ($type eq "pending") {
  486.             $gids_pending{$key} = $value;    last type1;
  487.             }
  488.  
  489.             if ($type eq "old") {
  490.             $gids_old{$key} = $value;    last type1;
  491.             }
  492.  
  493.             printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n", 
  494.             $type);
  495.             exit(1);
  496.         }
  497.  
  498.         last which;
  499.     }
  500.  
  501.     if ($which eq "files") {
  502.         type2: {
  503.             if ($type eq "known") {
  504.             $files_known{$key} = $value;    last type2;
  505.             }
  506.  
  507.             if ($type eq "new") {
  508.             $files_new{$key} = $value;    last type2;
  509.             }
  510.  
  511.             if ($type eq "pending") {
  512.             $files_pending{$key} = $value;    last type2;
  513.             }
  514.  
  515.             if ($type eq "old") {
  516.             $files_old{$key} = $value;    last type2;
  517.             }
  518.  
  519.             printf(stderr "kuang: fatal error in putentry: type is wrong (%s)\n", 
  520.             $type);
  521.             exit(1);
  522.         }
  523.  
  524.         last which;
  525.     }
  526.  
  527.     printf(stderr "kuang: fatal error in putentry: which is wrong (%s)\n",
  528.         $which);
  529.     exit(1);
  530.     }
  531. }
  532.  
  533.  
  534. sub addto {
  535.     local($which, $key, $plan) = @_;
  536.     local($tkey, $tvalue);
  537.  
  538.     #
  539.     # See whether there's an entry for $key in the known list for the
  540.     # $which array.  If so - success, we've found a suitable breakin
  541.     # path. 
  542.     #
  543.  
  544.     ($tkey, $tvalue) = &getentry($which, "known", $key);
  545.     if ($tkey eq $key) {
  546.     printf("Success! $key $plan\n");
  547.     return;
  548.     }
  549.  
  550.     #
  551.     # Check to see if its a duplicate - if so, don't need to do anything.
  552.     #
  553.     ($tkey, $tvalue) = &getentry($which, "pending", $key);
  554.     if ($tkey eq $key) {
  555.     return;
  556.     }
  557.  
  558.     #
  559.     # Add to pending list for $which...
  560.     #
  561.     &putentry($which, "pending", $key, $plan);
  562.  
  563.     #
  564.     # Add to next goal list for $which...
  565.     #
  566.     &putentry($which, "new", $key, $plan);
  567.  
  568.     if (defined($opt_v)) {
  569.     printf("addto: $which --> $plan\n");
  570.     }
  571.  
  572.     #
  573.     # If this is a uid goal, then add the plan to the accessible list.
  574.     #
  575.     if ($which eq "uids" && $key ne "0" && defined($opt_l)) {
  576.     $accessible{$plan} = "1";
  577.     }
  578. }
  579.  
  580. #
  581. #----------------------------------------------------------------------
  582. #Main program follows...initialize and loop till we're done.
  583. #
  584.  
  585. &init_kuang();
  586.  
  587. #
  588. # While there's still something to pursue...
  589. #
  590. while (&sizeof(*uids_new) != 0 || 
  591.        &sizeof(*gids_new) != 0 || 
  592.        &sizeof(*files_new) != 0) {
  593.  
  594.     #
  595.     # Deal with uids first...
  596.     #
  597.     if (&sizeof(*uids_new) != 0) {
  598.         %uids_old = %uids_new;
  599.         %uids_new = ();
  600.  
  601.         foreach $uid (keys %uids_old) {
  602.         $plan = $uids_old{$uid};
  603.  
  604.         if (defined($opd_d)) {
  605.         printf("uids evel: $uid '$plan'\n");
  606.         }
  607.             
  608.             &addto("files", "/etc/passwd", "replace /etc/passwd $plan");
  609.             &addto("files", "/usr/lib/aliases", "replace /usr/lib/aliases $plan");
  610.  
  611.         #
  612.         # Add controlling files for this user.  There are probably 
  613.         # others (such as .logout, X tool start things and so on).
  614.         # (fixme) add other CF's...
  615.         #
  616.         $home = &gethome($uid);
  617.  
  618.         if ($home ne "") {
  619.         if ($home eq "/") {
  620.             $home = "";
  621.         }
  622.  
  623.         if (-e "$home/.rhosts") {
  624.             &addto("files", "$home/.rhosts", "write $home/.rhosts $plan");
  625.         }
  626.  
  627.         if (-e "$home/.login") {
  628.             &addto("files", "$home/.login", "replace $home/.login $plan");
  629.         }
  630.  
  631.         if (-e "$home/.logout") {
  632.             &addto("files", "$home/.logout", "replace $home/.logout $plan");
  633.         }
  634.  
  635.         if (-e "$home/.cshrc") {
  636.             &addto("files", "$home/.cshrc", "replace $home/.cshrc $plan");
  637.         }
  638.  
  639.         if (-e "$home/.profile") {
  640.             &addto("files", "$home/.profile", "replace $home/.profile $plan");
  641.         }
  642.         }
  643.  
  644.         #
  645.         # Controlling files for root...
  646.         #
  647.         if ($uid+0 == 0) {
  648.         foreach $file ("/etc/rc", "/etc/rc.boot", "/etc/rc.single", "/etc/rc.config", "/etc/rc.local", "/usr/lib/crontab", "/usr/spool/cron/crontabs") {
  649.             if (-e $file) {
  650.             &addto("files", $file, "replace $file $plan");
  651.             }
  652.         }
  653.         }
  654.  
  655.         if ($uid+0 != 0) {
  656.         &addto("files", "/etc/hosts.equiv", "replace /etc/hosts.equiv allow rlogin $plan");
  657.  
  658.         if (-s "/etc/hosts.equiv") {
  659.             &addto("files", "/etc/hosts", "replace /etc/hosts fake hostaddress allow rlogin $plan");
  660.         }
  661.         }
  662.     }
  663.     }
  664.  
  665.     #
  666.     # Deal with groups...
  667.     #
  668.     if (&sizeof(*gids_new) != 0) {
  669.         %gids_old = %gids_new;
  670.         %gids_new = ();
  671.  
  672. bar_loop:
  673.         foreach $gid (keys %gids_old) {
  674.         $plan = $gids_old{$gid};
  675.         if (defined($opt_d)) {
  676.         printf("gids eval: $gid '$plan'\n");
  677.         }
  678.             
  679.         ($gname, $members) = &group_bygid($gid);
  680.         if ($gname eq "") {
  681.         printf("There is no group with gid $gid.\n");
  682.         next bar_loop;
  683.         }
  684.  
  685. foo_loop:
  686.         foreach $uname (split(/[ \t\n]+/, $members)) {
  687.         $uid = &passwd_byname($uname);
  688.  
  689.         if ($uid eq "") {
  690.             printf(stderr "Group $gname has an unknown user $uname\n");
  691.             next foo_loop;
  692.         }
  693.  
  694.         &addto("uids", "$uid", "grant u.$uname $plan");
  695.         }
  696.  
  697.         &addto("files", "/etc/group", "replace /etc/group $plan");
  698.     }
  699.     }
  700.  
  701.     #
  702.     # Deal with files...
  703.     #
  704.     if (&sizeof(*files_new) != 0) {
  705.         %files_old = %files_new;
  706.         %files_new = ();
  707.  
  708. file_loop:
  709.         foreach $file (keys %files_old) {
  710.         $plan = $files_old{$file};
  711.         ($mode) = split(/[ \t\n]+/, $plan);
  712.  
  713.         if (defined($opt_d)) {
  714.         printf("files eval: $file '$plan'\n");
  715.         }
  716.  
  717.         ($owner, $group, $other) = &filewriters($file);
  718.  
  719.         if ($owner eq "") {
  720.         printf("%s does not exist\n", $file);
  721.         next file_loop;
  722.         }
  723.  
  724.         ($uname) = &passwd_byuid($owner);
  725.         if ($uname eq "") {
  726.         $uname = $owner;
  727.         }
  728.  
  729.         &addto("uids", $owner, "grant u.$uname $plan");
  730.  
  731.         if ($group ne "") {
  732.         ($gname, $tmp) = &group_bygid($group);
  733.  
  734.         if ($gname ne "") {
  735.             &addto("gids", $group, "grant g.$gname $plan");
  736.         } else {
  737.             printf(stderr "There is no group with gid $group.\n");
  738.         }
  739.         }
  740.  
  741.         if ($other) {
  742.         &addto("uids", -1, "grant u.OTHER $plan");
  743.         }
  744.  
  745.         if ($mode eq "replace") {
  746.         $parent = $file;
  747.         $parent =~ s|/[^/]*$||;        # strip last / and remaining
  748.  
  749.         if ($parent eq "") {        # if nothing left, use /
  750.             $parent = "/";
  751.         }
  752.  
  753.         if ($parent ne $file) {        # since $file might've been /
  754.             &addto("files", $parent, "replace $parent $plan");
  755.         }
  756.         }
  757.     }
  758.     }
  759. }
  760.  
  761. if (defined($opt_l)) {
  762.     foreach $key (keys %accessible) {
  763.     printf("$key\n");
  764.     }
  765. }
  766.  
  767. if (defined($opt_v) || $cache_hit) {
  768.     printf("File info cache hit/access ratio: %g\n", 
  769.             $cache_hit / ($cache_hit + $cache_miss));
  770.     }
  771.