Start at external networking (socat)

This commit is contained in:
Neale Pickett 2010-12-10 17:03:24 -07:00
parent 089dd6b846
commit 1a06efd5fc
11 changed files with 218 additions and 57 deletions

View File

@ -1,11 +1,11 @@
INCLUDES = INCLUDES =
OCAMLFLAGS = $(INCLUDES) OCAMLFLAGS = $(INCLUDES)
OCAMLOPT = ocamlopt OCAMLOPT = ocamlopt
OCAMLC = ocamlc OCAMLC = ocamlc -g
OCAMLDEP = ocamldep $(INCLUDES) OCAMLDEP = ocamldep $(INCLUDES)
OCAMLLIBS = unix.cma str.cma nums.cma OCAMLLIBS = unix.cma str.cma nums.cma
bot: irc.cmo dispatch.cmo command.cmo iobuf.cmo cdb.cmo bindings.cmo infobot.cmo bot.cmo infobot.cmo bot: irc.cmo dispatch.cmo process.cmo command.cmo iobuf.cmo cdb.cmo bindings.cmo infobot.cmo bot.cmo infobot.cmo
$(OCAMLC) -o $@ $(OCAMLLIBS) $^ $(OCAMLC) -o $@ $(OCAMLLIBS) $^
.PHONY: clean .PHONY: clean

108
bot.ml
View File

@ -2,63 +2,103 @@ type bot = {
store: Infobot.t; store: Infobot.t;
} }
let debug = prerr_endline
let file_descr_of_int (i:int) =
let blob = Marshal.to_string i [] in
(Marshal.from_string blob 0 : Unix.file_descr)
let write iobuf command args text = let write iobuf command args text =
let cmd = Command.create None command args text in let cmd = Command.create None command args text in
print_endline ("--> " ^ (Command.as_string cmd)); debug ("--> " ^ (Command.as_string cmd));
Iobuf.write iobuf cmd Iobuf.write iobuf cmd
let msg iobuf recip text = let msg iobuf recip text =
write iobuf "PRIVMSG" [recip] (Some text) write iobuf "PRIVMSG" [recip] (Some text)
let calc_re = Str.regexp "^calc \\(.*\\)" let split = Str.split (Str.regexp "[ \t]*\r?\n")
let calc iobuf forum text =
if Str.string_match calc_re text 0 then (** Callback upon completion of the external command helper *)
msg iobuf forum (Str.matched_group 1 text) let extern_callback iobuf sender forum text =
let lines = split text in
let nlines = List.length lines in
let recip =
if (nlines > 5) then begin
if (forum <> sender) then
msg iobuf forum (Format.sprintf "%d lines, sending privately" nlines);
sender
end else
forum
in
let rec f = function
| [] ->
()
| "" :: tl ->
f tl
| line :: tl ->
if line.[0] == ':' then
(* Interpret as raw IRC commands *)
let ine = Str.string_after line 1 in
let cmd = Command.from_string ine in
Iobuf.write iobuf cmd
else
(* Naive case: send to the recipient *)
msg iobuf recip line;
f tl
in
f lines
let handle_privmsg bot iobuf sender forum text = let handle_privmsg bot iobuf sender forum text =
calc iobuf forum text; if text.[0] == '.' then
match (Infobot.lookup bot.store text) with Process.create_canned
| Some reply -> (Iobuf.dispatcher iobuf)
msg iobuf forum reply text
| None -> (extern_callback iobuf sender forum)
() "./helper"
[|"./helper"; sender; forum|]
else
Infobot.handle_privmsg bot.store (msg iobuf forum) sender forum text
let handle_command bot outbuf thisbuf cmd =
let handle_command bot iobuf cmd = debug ("<-- " ^ (Command.as_string cmd));
print_endline ("<-- " ^ (Command.as_string cmd));
match (Command.as_tuple cmd) with match (Command.as_tuple cmd) with
| (Some sender, "PRIVMSG", [target], Some text) -> | (Some suhost, "PRIVMSG", [target], Some text) ->
let forum = let sender = Irc.nick (Irc.nuhost_of_string suhost) in
if Irc.is_channel target then let forum =
target if Irc.is_channel target then
else target
sender else
in sender
handle_privmsg bot iobuf sender forum text in
handle_privmsg bot outbuf sender forum text
| (_, "PING", _, text) -> | (_, "PING", _, text) ->
write iobuf "PONG" [] text write outbuf "PONG" [] text
| (_, "001", _, _) -> | (_, "001", _, _) ->
write iobuf "JOIN" ["#bot"] None write outbuf "JOIN" ["#bot"] None;
| (Some sender, "JOIN", [], Some chan) -> | (Some sender, "JOIN", [], Some chan) ->
msg iobuf chan "hi asl" msg outbuf chan "hi asl"
| _ -> | _ ->
() ()
let discard_command iobuf cmd = ()
let handle_error iobuf str = let handle_error iobuf str =
print_endline str prerr_endline ("!!! " ^ str)
let main () = let main () =
let host = Unix.gethostbyname "woozle.org" in
let dispatcher = Dispatch.create () in
let conn = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
let bot = {store = Infobot.create "info.cdb"} in let bot = {store = Infobot.create "info.cdb"} in
let _ = Unix.connect conn (Unix.ADDR_INET (host.Unix.h_addr_list.(0), 6667)) in let dispatcher = Dispatch.create () in
let iobuf = Iobuf.create dispatcher conn "woozle"
(handle_command bot) let iobuf_out = Iobuf.create dispatcher Unix.stdout "collab_out"
discard_command
handle_error handle_error
in in
write iobuf "NICK" ["bot"] None; let _ = Iobuf.create dispatcher Unix.stdin "collab_in"
write iobuf "USER" ["bot"; "bot"; "bot"] (Some "A Bot"); (handle_command bot iobuf_out)
handle_error
in
write iobuf_out "NICK" ["zinc"] None;
write iobuf_out "USER" ["zinc"; "zinc"; "zinc"] (Some "I'm a little printf, short and stdout");
Dispatch.run dispatcher Dispatch.run dispatcher

View File

@ -22,7 +22,7 @@ type t = {
timers : Timer.t ref; timers : Timer.t ref;
} }
let create ?(size=5) () = let create () =
{read_fds = ref []; {read_fds = ref [];
write_fds = ref []; write_fds = ref [];
except_fds = ref []; except_fds = ref [];
@ -119,9 +119,14 @@ let once d =
let s = { Unix.it_interval = interval; Unix.it_value = 0.0 } in let s = { Unix.it_interval = interval; Unix.it_value = 0.0 } in
let _ = Sys.set_signal Sys.sigalrm Sys.Signal_ignore in let _ = Sys.set_signal Sys.sigalrm Sys.Signal_ignore in
let _ = Unix.setitimer Unix.ITIMER_REAL s in let _ = Unix.setitimer Unix.ITIMER_REAL s in
let result = Unix.select !(d.read_fds) !(d.write_fds) !(d.except_fds) (-1.0) in try
dispatch_results d result; let result =
dispatch_timers d (Unix.gettimeofday ()) Unix.select !(d.read_fds) !(d.write_fds) !(d.except_fds) (-1.0)
in
dispatch_results d result;
dispatch_timers d (Unix.gettimeofday ())
with Unix.Unix_error (Unix.EINTR, _, _) ->
()
let rec run d = let rec run d =
if (Fd_map.is_empty !(d.handlers)) && (Timer.is_empty !(d.timers)) then if (Fd_map.is_empty !(d.handlers)) && (Timer.is_empty !(d.timers)) then

View File

@ -10,9 +10,8 @@ type fd_handler = Unix.file_descr -> event -> unit
type timer_handler = float -> unit type timer_handler = float -> unit
(** [timer_handler d when] is called at or after [when] *) (** [timer_handler d when] is called at or after [when] *)
val create : ?size:int -> unit -> t val create : unit -> t
(** Create a new event dispatcher, preallocating [size] fd events. (** Create a new event dispatcher. *)
[size] is just a hint, the fd list will grow on demand. *)
val destroy : t -> unit val destroy : t -> unit
(** Destroy an event dispatcher *) (** Destroy an event dispatcher *)

16
helper
View File

@ -8,8 +8,13 @@ case $cmd in
.hi) .hi)
echo "Hi, ASL" echo "Hi, ASL"
;; ;;
.date) .time)
date if [ -n "$args" ]; then
a=$(echo $args | awk '{print "-d " $1}')
fi
echo $(date $a +%s) --- \
$(date $a --rfc-3339=seconds) --- \
$(date $a --utc --rfc-3339=seconds)
;; ;;
.msgme) .msgme)
echo ":PRIVMSG $sender :hey baby" echo ":PRIVMSG $sender :hey baby"
@ -20,7 +25,12 @@ case $cmd in
echo $args | cut -d@ -f 1 | nc $host finger echo $args | cut -d@ -f 1 | nc $host finger
;; ;;
.calc) .calc)
echo $args | bc -l echo $args | (echo -n "$args = "; bc -l)
;;
.units)
a=$(echo $args | sed 's/->.*//')
b=$(echo $args | sed 's/.*->//')
units -v "$a" "$b"
;; ;;
*) *)
echo "I'm sorry, $sender, I don't understand that command." echo "I'm sorry, $sender, I don't understand that command."

View File

@ -60,3 +60,11 @@ let lookup store text =
Some (Printf.sprintf "I overheard that %s is %s" text factoid) Some (Printf.sprintf "I overheard that %s is %s" text factoid)
with Not_found -> with Not_found ->
None None
let handle_privmsg store msg sender forum text =
match (lookup store text) with
| Some reply ->
msg reply
| None ->
()

View File

@ -16,11 +16,13 @@ and error_handler = t -> string -> unit
let ibuf_max = 4096 let ibuf_max = 4096
let max_outq = 50 let max_outq = 2000
let obuf_max = 4096 let obuf_max = 4096
let name iobuf = iobuf.name let name iobuf = iobuf.name
let dispatcher iobuf = iobuf.d
let crlf = Str.regexp "\r?\n" let crlf = Str.regexp "\r?\n"
let handle_input iobuf = let handle_input iobuf =

View File

@ -7,5 +7,6 @@ val create : Dispatch.t -> Unix.file_descr -> string -> command_handler -> error
val close: t -> string -> unit val close: t -> string -> unit
val name : t -> string val name : t -> string
val dispatcher : t -> Dispatch.t
val write : t -> Command.t -> unit val write : t -> Command.t -> unit
val bind : t -> command_handler -> error_handler -> unit val bind : t -> command_handler -> error_handler -> unit

12
irc.ml
View File

@ -49,7 +49,17 @@ let truncate s len =
else else
Str.string_before s (min slen len) Str.string_before s (min slen len)
let string_of_nuhost (nick, user, host) = nick ^ "!" ^ user ^ "@" ^ host let nuhost_re = Str.regexp "\\(.*\\)!\\(.*\\)@\\(.*\\)"
let nuhost_of_string str =
if Str.string_match nuhost_re str 0 then
(Str.matched_group 1 str,
Str.matched_group 2 str,
Str.matched_group 3 str)
else
raise Not_found
let string_of_nuhost (nick, user, host) =
nick ^ "!" ^ user ^ "@" ^ host
let nick (nick, user, host) = nick let nick (nick, user, host) = nick
let user (nick, user, host) = user let user (nick, user, host) = user
let host (nick, user, host) = host let host (nick, user, host) = host

View File

@ -10,6 +10,7 @@ val uppercase : string -> string
val lowercase : string -> string val lowercase : string -> string
val truncate : string -> int -> string val truncate : string -> int -> string
val nuhost_of_string : string -> nuhost
val string_of_nuhost : nuhost -> string val string_of_nuhost : nuhost -> string
val nick : nuhost -> string val nick : nuhost -> string
val user : nuhost -> string val user : nuhost -> string

View File

@ -1,15 +1,100 @@
type t = { let spawn prog args =
}
let create iobuf prog args =
let fd0_exit, fd0_entr = Unix.pipe () in let fd0_exit, fd0_entr = Unix.pipe () in
let fd1_exit, fd1_entr = Unix.pipe () in let fd1_exit, fd1_entr = Unix.pipe () in
let fd2_exit, fd2_entr = Unix.pipe () in match (Unix.fork ()) with
let pid = Unix.create_process prog args fd0_exit fd1_entr fd2_entr in | 0 -> (* Child *)
Unix.dup2 fd0_exit Unix.stdin;
Unix.close fd0_entr;
Unix.close fd0_exit;
Unix.dup2 fd1_entr Unix.stdout;
Unix.dup2 fd1_entr Unix.stderr;
Unix.close fd1_entr;
Unix.close fd1_exit;
Unix.execvp prog args
| pid -> (* Parent *)
Unix.close fd0_exit;
Unix.close fd1_entr;
(fd0_entr, fd1_exit)
let handle_event process fd event = let create d text input_handler output_handler prog args =
let child_stdin, child_stdout = spawn prog args in
Dispatch.add d child_stdin output_handler [Dispatch.Output];
Dispatch.add d child_stdout input_handler [Dispatch.Input]
(** Canned process: sends a string on stdin, collects stdout and stderr,
and calls a callback when everything's finished. *)
type canned = {
finished : string -> unit;
stdin : string;
stdout : string;
stderr : string;
mutable stdin_pos : int;
mutable stdout_pos : int;
mutable stderr_pos : int;
}
let canned_handler d p fd event =
match event with match event with
| Dispatch.Input -> | Dispatch.Input ->
let len =
Unix.read fd p.stdout p.stdout_pos
((String.length p.stdout) - p.stdout_pos)
in
if (len > 0) then
p.stdout_pos <- p.stdout_pos + len
else begin
Dispatch.delete d fd;
p.finished (String.sub p.stdout 0 p.stdout_pos)
end
| Dispatch.Output ->
let len =
Unix.write fd p.stdin p.stdin_pos
((String.length p.stdin) - p.stdin_pos)
in
p.stdin_pos <- p.stdin_pos + len;
if (p.stdin_pos == String.length p.stdin) then begin
Unix.close fd;
Dispatch.delete d fd
end
| Dispatch.Exception ->
()
let create_canned d text finished prog args =
let p =
{
finished=finished;
stdin=text; stdin_pos=0;
stdout=String.create 8192; stdout_pos=0;
stderr=String.create 8192; stderr_pos=0;
}
in
let handler = (canned_handler d p)
in
create d text handler handler prog args
(** Zombie reapin' mayhem *)
let rec sigchld s =
try
match Unix.waitpid [Unix.WNOHANG] (-1) with
| (0, _) ->
()
| _ ->
sigchld s
with Unix.Unix_error (Unix.ECHILD, _, _) ->
()
let _ =
Sys.set_signal Sys.sigchld (Sys.Signal_handle sigchld)