talircd/lib/server/connection.ml

244 lines
7.7 KiB
OCaml

open! Import
module User = Router.User
module Chan = Router.Chan
type t = {
addr : sockaddr;
router : Router.t;
user : User.t;
outbox : Outbox.t;
mutable pending_nick : string option;
}
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
let outbox = Outbox.make () in
let user = User.make ~hostname ~outbox in
{ addr; router; user; outbox; pending_nick = None }
let outbox t = t.outbox
let shutdown t =
User.quit t.user ~router:t.router;
Outbox.close t.outbox
(* message handling *)
let require_registered t f =
if User.is_registered t.user then
f (Option.get t.user.userinfo)
else
`notregistered
(* > user registration *)
let attempt_to_register t =
match t.pending_nick, t.user.userinfo with
| Some nick, Some _userinfo ->
t.pending_nick <- None;
begin match User.set_nick t.user nick ~router:t.router with
| `nick_in_use -> `nicknameinuse nick
| `nick_set ->
let mode_str = Fmt.str "+%a" Irc.Mode.pp t.user.mode in
let mode_msg = Irc.Msg.make "MODE" [nick; mode_str] in
defer (fun () -> Router.relay mode_msg ~from:t.user `to_self);
`welcome
end
| _, _ -> `ok
let on_msg_nick t nick =
if Irc.name_type nick <> `nick then
(if nick = "" then `nonicknamegiven else `erroneusnickname nick)
else if User.is_registered t.user then
match User.set_nick t.user nick ~router:t.router with
| `nick_in_use -> `nicknameinuse nick
| `nick_set ->
((* TODO: relay NICK message *));
`ok
else begin
t.pending_nick <- Some nick;
attempt_to_register t
end
let on_msg_user t username modestr realname =
if User.is_registered t.user then
`alreadyregistered
else begin
(* NB: "+iw" is automatically set, so it's impossible to actually affect the initial
mode with the parameter to USER *)
ignore modestr;
t.user.userinfo <- Some { username; realname };
attempt_to_register t
end
(* > messages and channels *)
let on_msg_privmsg t tgt txt _ =
let msg = Irc.Msg.make "PRIVMSG" [tgt; txt] in
let dst =
try
match Irc.name_type tgt with
| `chan -> `to_chan (Router.find_chan t.router tgt)
| `nick -> `to_user (Router.find_user t.router tgt)
| `invalid -> `not_found
with Not_found -> `not_found
in
match dst with
| `not_found -> `nosuchnick tgt
| (`to_user _ | `to_chan _) as dst ->
Router.relay msg ~from:t.user dst;
`ok
let list_names chan =
let names =
List.map
(fun u -> "", User.nick u)
(Chan.members chan)
in
`names ("@", Chan.name chan, names)
let on_msg_join t name _ =
match Irc.name_type name with
| `nick | `invalid ->
if name = "" then `needmoreparams else `nosuchchannel name
| `chan ->
let chan = try Router.find_chan t.router name
with Not_found ->
Logs.debug (fun m -> m "making new channel %S" name);
let chan = Chan.make ~name in
(* TODO: op user after joining *)
Chan.register chan ~router:t.router;
chan
in
if not (Chan.is_member chan t.user) then begin
Chan.join chan t.user;
let join_msg = Irc.Msg.make "JOIN" [name] in
Router.relay join_msg ~from:t.user `to_self;
Router.relay join_msg ~from:t.user (`to_chan chan);
end;
list_names chan
let on_msg_names t name _ =
match Irc.name_type name with
| `nick | `invalid ->
if name = "" then `needmoreparams else `nosuchchannel name
| `chan ->
let chan = try Some (Router.find_chan t.router name)
with Not_found -> None
in
match chan with
| None -> `nosuchchannel name
| Some chan -> list_names chan
let on_msg_privmsg t tgt msg = require_registered t (on_msg_privmsg t tgt msg)
let on_msg_join t name = require_registered t (on_msg_join t name)
let on_msg_names t name = require_registered t (on_msg_names t name)
(* > misc *)
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);
`quit
(* 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"
let srv_isupport = [
"CASEMAPPING=ascii";
"CHANTYPES=#";
"PREFIX=(ov)@+";
]
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
let target = User.nick t.user in
Outbox.send t.outbox (Irc.Msg.make ~prefix cmd (target :: params))
let rpl_tryagain t cmd = rpl t "263" [cmd; "Please wait a while and try again."]
let rpl_welcome t =
let who = Irc.Msg.prefix_string (User.prefix t.user) in
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"];
rpl t "005" (srv_isupport @ ["are supported by this server"]);
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
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
let err_nosuchnick t tgt = rpl t "401" [tgt; "No such nick/channel"]
let err_nosuchchannel t tgt = rpl t "403" [tgt; "No such channel"]
let err_norecipient t cmd = rpl t "411" [Fmt.str "No recipient given (%s)" cmd]
let err_notexttosend t = rpl t "412" ["No text to send"]
let err_unknowncommand t cmd = rpl t "421" [cmd; "Unknown command"]
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)"]
(* message parsing *)
let on_msg t (msg : Irc.Msg.t) : unit =
(* Logs.debug (fun m -> m "%a: %a" pp_sockaddr t.addr Irc.Msg.pp msg); *)
let result =
match msg.command, msg.params with
| "NICK", new_nick :: _ -> on_msg_nick t new_nick
| "NICK", [] -> `nonicknamegiven
| "USER", u :: m :: _h :: r :: _ -> on_msg_user t u m r
| "USER", _ -> `needmoreparams
| "QUIT", why -> on_msg_quit t why
| "MOTD", _ -> `motd
| "PRIVMSG", tgt :: msg :: _ -> on_msg_privmsg t tgt msg
| "PRIVMSG", [_] -> `notexttosend | "PRIVMSG", [] -> `norecipient
| "JOIN", tgt :: _ -> on_msg_join t tgt
| "JOIN", _ -> `needmoreparams
| "NAMES", tgt :: _ -> on_msg_names t tgt
| "NAMES", _ -> `needmoreparams
| _, _ -> `unknowncommand
in
match result with
| `ok -> ()
| `quit -> shutdown t
| `welcome -> rpl_welcome t; rpl_motd t
| `motd -> rpl_motd t
| `names (cp, ch, us) -> rpl_names t cp ch us
| `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
| `nosuchchannel c -> err_nosuchchannel t c
| `notexttosend -> err_notexttosend t
| `notregistered -> err_notregistered t
| `unknowncommand -> err_unknowncommand t msg.command
| `nonicknamegiven -> err_nonicknamegiven t
| `erroneusnickname n -> err_erroneousnickname t n