(* camlp4r *)
(* $Id: robot.ml,v 4.13 2004/12/14 09:30:17 ddr Exp $ *)
(* Copyright (c) 1998-2005 INRIA *)
open Util;
open Printf;
value magic_robot = "GWRB0002";
module W = Map.Make (struct type t = string; value compare = compare; end);
type excl =
{ excl : mutable list (string * ref int);
who : mutable W.t (list float * float * int);
max_conn : mutable (int * string) }
;
value robot_error cgi from cnt sec =
do {
if not cgi then Wserver.http "403 Forbidden" else ();
Wserver.wprint "Content-type: text/html; charset=iso-8859-1";
Util.nl ();
Util.nl ();
let env =
[('c', fun _ -> string_of_int cnt); ('s', fun _ -> string_of_int sec)]
in
match open_etc_file "robot" with
[ Some ic -> copy_from_etc env "en" "geneweb" ic
| None ->
let title _ = Wserver.wprint "Access refused" in
do {
Wserver.wprint "
";
title True;
Wserver.wprint "\n\n";
title False;
Wserver.wprint "\n";
} ];
raise Exit
}
;
value purge_who tm xcl sec =
let sec = float sec in
let to_remove =
W.fold
(fun k (v, _, _) l ->
match v with
[ [tm0 :: _] -> if tm -. tm0 > sec then [k :: l] else l
| [] -> [k :: l] ])
xcl.who []
in
List.iter (fun k -> xcl.who := W.remove k xcl.who) to_remove
;
value fprintf_date oc tm =
fprintf oc "%4d-%02d-%02d %02d:%02d:%02d" (1900 + tm.Unix.tm_year)
(succ tm.Unix.tm_mon) tm.Unix.tm_mday tm.Unix.tm_hour tm.Unix.tm_min
tm.Unix.tm_sec
;
value input_excl =
let b = String.create (String.length magic_robot) in
fun ic ->
do {
really_input ic b 0 (String.length b);
if b <> magic_robot then raise Not_found else (input_value ic : excl)
}
;
value output_excl oc xcl =
do {
output_string oc magic_robot;
output_value oc (xcl : excl);
}
;
value robot_excl () =
let fname = Srcfile.adm_file "robot" in
let xcl =
match try Some (Secure.open_in_bin fname) with _ -> None with
[ Some ic ->
let v =
try input_excl ic with _ ->
{excl = []; who = W.empty; max_conn = (0, "")}
in
do { close_in ic; v }
| None -> {excl = []; who = W.empty; max_conn = (0, "")} ]
in
(xcl, fname)
;
value min_disp_req = ref 6;
value check oc tm from max_call sec cgi suicide =
let (xcl, fname) = robot_excl () in
let refused =
match try Some (List.assoc from xcl.excl) with [ Not_found -> None ] with
[ Some att ->
do {
incr att;
if att.val mod max_call == 0 then do {
fprintf_date oc (Unix.localtime tm);
fprintf oc "\n";
fprintf oc " From: %s\n" from;
fprintf oc " %d refused attempts;" att.val;
fprintf oc " to restore access, delete file \"%s\"\n"
fname;
}
else ();
True
}
| None ->
do {
purge_who tm xcl sec;
let (r, _, _) =
try W.find from xcl.who with [ Not_found -> ([], tm, 0) ]
in
let (cnt, tml, tm0) =
let sec = float sec in
let rec count cnt tml =
fun
[ [] -> (cnt, tml, tm)
| [tm1] ->
if tm -. tm1 < sec then (cnt + 1, [tm1 :: tml], tm1)
else (cnt, tml, tm1)
| [tm1 :: tml1] ->
if tm -. tm1 < sec then count (cnt + 1) [tm1 :: tml] tml1
else (cnt, tml, tm1) ]
in
count 1 [] r
in
let r = List.rev tml in
xcl.who := W.add from ([tm :: r], tm0, cnt) xcl.who;
let refused =
if suicide || cnt > max_call then do {
fprintf oc "--- %s is a robot" from;
if suicide then
fprintf oc " (called the \"suicide\" request)\n"
else
fprintf oc
" (%d > %d connections in %g <= %d seconds)\n" cnt max_call
(tm -. tm0) sec;
flush Pervasives.stderr;
xcl.excl := [(from, ref 1) :: xcl.excl];
xcl.who := W.remove from xcl.who;
xcl.max_conn := (0, "");
True
}
else False
in
if xcl.excl <> [] then do {
List.iter
(fun (s, att) ->
do {
fprintf oc "--- excluded:";
fprintf oc " %s (%d refused attempts)\n" s att.val;
()
})
xcl.excl;
fprintf oc "--- to restore access, delete file \"%s\"\n"
fname;
}
else ();
let (list, nconn) =
W.fold
(fun k (_, tm, nb) (list, nconn) ->
do {
if nb > fst xcl.max_conn then xcl.max_conn := (nb, k)
else ();
(if nb < min_disp_req.val then list
else [(k, tm, nb) :: list],
nconn + 1)
})
xcl.who ([], 0)
in
let list =
List.sort
(fun (_, tm1, nb1) (_, tm2, nb2) ->
match compare nb2 nb1 with
[ 0 -> compare tm2 tm1
| x -> x ])
list
in
List.iter
(fun (k, tm0, nb) ->
fprintf oc "--- %3d req - %3.0f sec - %s\n" nb
(tm -. tm0) k)
list;
fprintf oc "--- max %d req by %s / conn %d\n"
(fst xcl.max_conn) (snd xcl.max_conn) nconn;
refused
} ]
in
do {
match
try Some (Secure.open_out_bin fname) with [ Sys_error _ -> None ]
with
[ Some oc -> do { output_excl oc xcl; close_out oc; }
| None -> () ];
if refused then robot_error cgi from max_call sec else ()
}
;