2024-01-07 20:54:39 +00:00
|
|
|
open! Import
|
2024-01-08 05:05:01 +00:00
|
|
|
module User = Router.User
|
2024-01-07 20:54:39 +00:00
|
|
|
|
|
|
|
type t = {
|
|
|
|
addr : sockaddr;
|
2024-01-08 05:05:01 +00:00
|
|
|
user : User.t;
|
2024-01-10 00:35:03 +00:00
|
|
|
outbox : Outbox.t;
|
2024-01-08 05:39:39 +00:00
|
|
|
mutable pending_nick : string option;
|
2024-01-07 20:54:39 +00:00
|
|
|
}
|
|
|
|
|
2024-01-08 03:28:31 +00:00
|
|
|
let make ~(router : Router.t) ~(addr : sockaddr) : t =
|
|
|
|
let hostname = match addr with
|
|
|
|
| ADDR_INET (ia, _) -> Unix.string_of_inet_addr ia
|
|
|
|
| ADDR_UNIX path -> path
|
|
|
|
in
|
2024-01-10 00:35:03 +00:00
|
|
|
let outbox = Outbox.make () in
|
|
|
|
let user = User.make ~router ~hostname ~outbox in
|
|
|
|
{ addr; user; outbox; pending_nick = None }
|
2024-01-07 20:54:39 +00:00
|
|
|
|
2024-01-10 00:35:03 +00:00
|
|
|
let outbox t = t.outbox
|
2024-01-07 20:54:39 +00:00
|
|
|
|
2024-01-10 00:35:03 +00:00
|
|
|
let shutdown t =
|
|
|
|
User.quit t.user;
|
|
|
|
Outbox.close t.outbox
|
2024-01-08 03:28:31 +00:00
|
|
|
|
|
|
|
(* message handling *)
|
2024-01-07 20:54:39 +00:00
|
|
|
|
2024-01-08 03:28:31 +00:00
|
|
|
let require_registered t f =
|
2024-01-08 05:05:01 +00:00
|
|
|
if User.is_registered t.user then
|
2024-01-08 03:28:31 +00:00
|
|
|
f (Option.get t.user.userinfo)
|
|
|
|
else
|
|
|
|
`notregistered
|
2024-01-07 20:54:39 +00:00
|
|
|
|
|
|
|
(* > user registration *)
|
|
|
|
|
2024-01-08 03:28:31 +00:00
|
|
|
let attempt_to_register t =
|
2024-01-08 05:39:39 +00:00
|
|
|
match t.pending_nick, t.user.userinfo with
|
2024-01-08 03:28:31 +00:00
|
|
|
| Some nick, Some _userinfo ->
|
2024-01-08 05:39:39 +00:00
|
|
|
t.pending_nick <- None;
|
2024-01-08 05:05:01 +00:00
|
|
|
begin match User.set_nick t.user nick with
|
2024-01-08 03:28:31 +00:00
|
|
|
| `nick_in_use -> `nicknameinuse nick
|
|
|
|
| `nick_set ->
|
2024-01-10 00:35:03 +00:00
|
|
|
let send_mode () =
|
|
|
|
Outbox.send t.outbox
|
|
|
|
(Irc.Msg.make "MODE" [nick; Fmt.str "+%a" Irc.Mode.pp t.user.mode]
|
|
|
|
~prefix:(User.prefix t.user));
|
|
|
|
in
|
|
|
|
Lwt.on_success (Lwt.pause ()) send_mode;
|
2024-01-08 03:28:31 +00:00
|
|
|
`welcome
|
2024-01-07 20:54:39 +00:00
|
|
|
end
|
2024-01-08 03:28:31 +00:00
|
|
|
| _, _ -> `ok
|
|
|
|
|
|
|
|
let on_msg_nick t nick =
|
2024-01-10 01:20:39 +00:00
|
|
|
if Irc.name_type nick <> `nick then
|
|
|
|
(if nick = "" then `nonicknamegiven else `erroneusnickname nick)
|
|
|
|
else if User.is_registered t.user then
|
2024-01-08 05:05:01 +00:00
|
|
|
match User.set_nick t.user nick with
|
2024-01-08 03:28:31 +00:00
|
|
|
| `nick_in_use -> `nicknameinuse nick
|
2024-01-10 01:20:39 +00:00
|
|
|
| `nick_set ->
|
|
|
|
((* TODO: relay NICK message *));
|
|
|
|
`ok
|
2024-01-08 03:28:31 +00:00
|
|
|
else begin
|
2024-01-08 05:39:39 +00:00
|
|
|
t.pending_nick <- Some nick;
|
2024-01-08 03:28:31 +00:00
|
|
|
attempt_to_register t
|
|
|
|
end
|
|
|
|
|
|
|
|
let on_msg_user t username modestr realname =
|
2024-01-08 05:05:01 +00:00
|
|
|
if User.is_registered t.user then
|
2024-01-07 20:54:39 +00:00
|
|
|
`alreadyregistered
|
2024-01-08 03:28:31 +00:00
|
|
|
else begin
|
2024-01-08 05:05:01 +00:00
|
|
|
(* NB: "+iw" is automatically set, so it's impossible to actually affect the initial
|
2024-01-08 03:28:31 +00:00
|
|
|
mode with the parameter to USER *)
|
|
|
|
ignore modestr;
|
|
|
|
t.user.userinfo <- Some { username; realname };
|
|
|
|
attempt_to_register t
|
|
|
|
end
|
|
|
|
|
|
|
|
(* > messages and channels *)
|
|
|
|
|
2024-01-08 05:05:01 +00:00
|
|
|
let on_msg_privmsg t tgt msg _ =
|
2024-01-08 03:28:31 +00:00
|
|
|
match Router.find_user t.user.router tgt with
|
|
|
|
| None -> `nosuchnick tgt
|
2024-01-08 05:05:01 +00:00
|
|
|
| Some dst -> Router.privmsg t.user (`user dst) msg; `ok
|
2024-01-07 20:54:39 +00:00
|
|
|
|
2024-01-08 05:31:05 +00:00
|
|
|
let on_msg_join t tgt _ =
|
2024-01-10 00:35:03 +00:00
|
|
|
Outbox.send t.outbox
|
|
|
|
(Irc.Msg.make "JOIN" [tgt] ~prefix:(User.prefix t.user));
|
2024-01-08 05:31:05 +00:00
|
|
|
`names ("@", tgt, ["@", User.nick t.user; "", "moe"; "", "barry"])
|
|
|
|
|
2024-01-08 03:28:31 +00:00
|
|
|
let on_msg_privmsg t tgt msg = require_registered t (on_msg_privmsg t tgt msg)
|
2024-01-08 05:31:05 +00:00
|
|
|
let on_msg_join t tgt = require_registered t (on_msg_join t tgt)
|
2024-01-08 03:28:31 +00:00
|
|
|
|
2024-01-07 20:54:39 +00:00
|
|
|
(* > misc *)
|
|
|
|
|
2024-01-08 03:28:31 +00:00
|
|
|
let on_msg_quit t why =
|
|
|
|
let why = String.concat " " why in
|
|
|
|
Logs.debug (fun m -> m "%a: quit: %S" pp_sockaddr t.addr why);
|
2024-01-08 05:05:01 +00:00
|
|
|
`quit
|
2024-01-07 20:54:39 +00:00
|
|
|
|
2024-01-08 03:28:31 +00:00
|
|
|
(* message sending *)
|
|
|
|
|
|
|
|
(* TODO: configure these *)
|
|
|
|
let srv_host = "irc.tali.software"
|
|
|
|
let srv_ver = "0.0.0"
|
|
|
|
let srv_created = "Sun Jan 7 09:58:24 PM EST 2024"
|
2024-01-08 05:56:06 +00:00
|
|
|
let srv_isupport = [
|
|
|
|
"CASEMAPPING=ascii";
|
|
|
|
"CHANTYPES=#";
|
|
|
|
"PREFIX=(ov)@+";
|
|
|
|
]
|
2024-01-08 03:28:31 +00:00
|
|
|
let srv_motd_lines = [
|
|
|
|
"MEOW MEOW MEOW MEOW MEOW";
|
|
|
|
"meow meow meow meow meow";
|
|
|
|
"meowmeowmeowmeowmeowmeow";
|
|
|
|
]
|
|
|
|
|
|
|
|
let rpl t cmd params =
|
|
|
|
let prefix = Irc.Msg.Server_prefix srv_host in
|
2024-01-08 05:05:01 +00:00
|
|
|
let target = User.nick t.user in
|
2024-01-10 00:35:03 +00:00
|
|
|
Outbox.send t.outbox (Irc.Msg.make ~prefix cmd (target :: params))
|
2024-01-08 03:28:31 +00:00
|
|
|
|
|
|
|
let rpl_tryagain t cmd = rpl t "263" [cmd; "Please wait a while and try again."]
|
|
|
|
|
|
|
|
let rpl_welcome t =
|
2024-01-08 05:05:01 +00:00
|
|
|
let who = Irc.Msg.prefix_string (User.prefix t.user) in
|
2024-01-08 03:28:31 +00:00
|
|
|
begin
|
|
|
|
rpl t "001" [Fmt.str "Welcome to the tali IRC network %s" who];
|
|
|
|
rpl t "002" [Fmt.str "Your host is %s, running version %s" srv_host srv_ver];
|
|
|
|
rpl t "003" [Fmt.str "This server was created %s" srv_created];
|
|
|
|
rpl t "004" [srv_host; srv_ver; "aiwroOs"; "oinvm"];
|
2024-01-08 05:56:06 +00:00
|
|
|
rpl t "005" (srv_isupport @ ["are supported by this server"]);
|
2024-01-08 03:28:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
let rpl_motd t =
|
|
|
|
begin
|
|
|
|
rpl t "375" [Fmt.str "- %s Message of the day - " srv_host];
|
|
|
|
List.iter (fun ln -> rpl t "372" ["- " ^ ln]) srv_motd_lines;
|
|
|
|
rpl t "376" ["End of /MOTD command"];
|
|
|
|
end
|
|
|
|
|
2024-01-08 05:31:05 +00:00
|
|
|
let rpl_names t chan_prefix chan users =
|
|
|
|
begin
|
|
|
|
List.iter (fun (up, u) -> rpl t "353" [chan_prefix; chan; up ^ u]) users;
|
|
|
|
rpl t "366" [chan; "End of NAMES list"]
|
|
|
|
end
|
|
|
|
|
2024-01-08 03:28:31 +00:00
|
|
|
let err_nosuchnick t tgt = rpl t "401" [tgt; "No such nick/channel"]
|
2024-01-10 01:20:39 +00:00
|
|
|
let err_norecipient t cmd = rpl t "411" [Fmt.str "No recipient given (%s)" cmd]
|
2024-01-08 05:39:39 +00:00
|
|
|
let err_notexttosend t = rpl t "412" ["No text to send"]
|
2024-01-08 03:28:31 +00:00
|
|
|
let err_unknowncommand t cmd = rpl t "421" [cmd; "Unknown command"]
|
2024-01-10 01:20:39 +00:00
|
|
|
let err_nonicknamegiven t = rpl t "431" ["No nickname given"]
|
|
|
|
let err_erroneousnickname t nick = rpl t "432" [nick; "Erroneus nickname"]
|
|
|
|
let err_nicknameinuse t nick = rpl t "433" [nick; "Nickname is already in use"]
|
|
|
|
let err_notregistered t = rpl t "451" ["You have not registered"]
|
|
|
|
let err_needmoreparams t cmd = rpl t "461" [cmd; "Not enough parameters"]
|
|
|
|
let err_alreadyregistered t = rpl t "462" ["Unauthorized command (already registered)"]
|
2024-01-07 20:54:39 +00:00
|
|
|
|
2024-01-08 03:28:31 +00:00
|
|
|
(* message parsing *)
|
2024-01-07 20:54:39 +00:00
|
|
|
|
2024-01-07 21:29:12 +00:00
|
|
|
let on_msg t (msg : Irc.Msg.t) : unit =
|
|
|
|
Logs.debug (fun m -> m "%a: %a" pp_sockaddr t.addr Irc.Msg.pp msg);
|
2024-01-07 20:54:39 +00:00
|
|
|
let result =
|
|
|
|
match msg.command, msg.params with
|
|
|
|
| "NICK", new_nick :: _ ->
|
2024-01-08 03:28:31 +00:00
|
|
|
on_msg_nick t new_nick
|
2024-01-10 01:20:39 +00:00
|
|
|
| "NICK", [] -> `nonicknamegiven
|
2024-01-08 03:28:31 +00:00
|
|
|
| "USER", username :: modestr :: _host :: realname :: _ ->
|
|
|
|
on_msg_user t username modestr realname
|
2024-01-07 20:54:39 +00:00
|
|
|
| "QUIT", why ->
|
2024-01-08 03:28:31 +00:00
|
|
|
on_msg_quit t why
|
2024-01-08 06:15:41 +00:00
|
|
|
| "MOTD", _ -> `motd
|
2024-01-08 03:28:31 +00:00
|
|
|
| "PRIVMSG", tgt :: msg :: _ ->
|
|
|
|
on_msg_privmsg t tgt msg
|
|
|
|
| "PRIVMSG", [_] -> `notexttosend | "PRIVMSG", [] -> `norecipient
|
2024-01-08 05:31:05 +00:00
|
|
|
| "JOIN", tgt :: _ ->
|
|
|
|
on_msg_join t tgt
|
2024-01-10 01:20:39 +00:00
|
|
|
| "USER", _ | "JOIN", _ -> `needmoreparams
|
2024-01-07 20:54:39 +00:00
|
|
|
| _, _ -> `unknowncommand
|
|
|
|
in
|
|
|
|
match result with
|
|
|
|
| `ok -> ()
|
2024-01-10 00:35:03 +00:00
|
|
|
| `quit -> shutdown t
|
2024-01-08 03:28:31 +00:00
|
|
|
| `welcome -> rpl_welcome t; rpl_motd t
|
2024-01-08 06:15:41 +00:00
|
|
|
| `motd -> rpl_motd t
|
2024-01-08 05:31:05 +00:00
|
|
|
| `names (cp, ch, us) -> rpl_names t cp ch us
|
2024-01-08 03:28:31 +00:00
|
|
|
| `tryagain -> rpl_tryagain t msg.command
|
|
|
|
| `alreadyregistered -> err_alreadyregistered t
|
|
|
|
| `needmoreparams -> err_needmoreparams t msg.command
|
|
|
|
| `nicknameinuse n -> err_nicknameinuse t n
|
|
|
|
| `norecipient -> err_norecipient t msg.command
|
|
|
|
| `nosuchnick n -> err_nosuchnick t n
|
|
|
|
| `notexttosend -> err_notexttosend t
|
|
|
|
| `notregistered -> err_notregistered t
|
|
|
|
| `unknowncommand -> err_unknowncommand t msg.command
|
2024-01-10 01:20:39 +00:00
|
|
|
| `nonicknamegiven -> err_nonicknamegiven t
|
|
|
|
| `erroneusnickname n -> err_erroneousnickname t n
|