talircd/lib/server/connection.ml

435 lines
13 KiB
OCaml
Raw Normal View History

2024-01-07 20:54:39 +00:00
open! Import
2024-01-11 04:38:25 +00:00
open Result_syntax
module User = Router.User
2024-01-10 02:20:16 +00:00
module Chan = Router.Chan
2024-01-07 20:54:39 +00:00
2024-01-12 02:49:48 +00:00
include (val Logging.sublogs logger "Connection")
2024-01-07 20:54:39 +00:00
type t = {
2024-01-10 02:20:16 +00:00
router : Router.t;
addr : sockaddr;
outbox : Outbox.t;
mutable user : User.t option;
mutable pending_nick : string option;
mutable pending_userinfo : Irc.userinfo 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 outbox = Outbox.make () in
{ router; addr; outbox; user = None; pending_nick = None; pending_userinfo = None }
2024-01-07 20:54:39 +00:00
let outbox t = t.outbox
2024-01-07 20:54:39 +00:00
let shutdown ?reason t =
Option.iter
(fun me ->
let reason = Option.value reason ~default:"Goot bye" in
Router.relay (Irc.Msg.make "QUIT" [reason]) ~from:me [`to_interested];
User.part_all me;
2024-01-11 04:38:25 +00:00
(* TODO: BUG: unregister empty channels *)
User.unregister me ~router:t.router)
t.user;
Outbox.close t.outbox
2024-01-08 03:28:31 +00:00
2024-01-07 20:54:39 +00:00
(* TODO: configure these in some centralized location *)
2024-01-11 04:38:25 +00:00
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_motd_lines = [
"MEOW MEOW MEOW MEOW MEOW";
"meow meow meow meow meow";
"meowmeowmeowmeowmeowmeow";
]
let initial_user_modestr = "iw"
2024-01-18 18:28:48 +00:00
let initial_chan_modestr = "nst"
2024-01-11 04:38:25 +00:00
(* numeric replies *)
type reply = string * string list
type 'a result = ('a, reply) Result.t
2024-01-11 04:38:25 +00:00
let reply t (num, params) =
let prefix = Irc.Msg.Server_prefix srv_host in
let target = match t.user with
| Some me -> User.nick me
| None -> "*"
in
Outbox.send t.outbox
(Irc.Msg.make ~prefix num (target :: params))
let tryagain cmd = "263", [cmd; "Please wait a while and try again."]
2024-01-11 04:38:25 +00:00
let nosuchnick tgt = "401", [tgt; "No such nick/channel"]
let nosuchchannel tgt = "403", [tgt; "No such channel"]
let norecipient = "411", ["No recipient given (PRIVMSG)"]
let notexttosend = "412", ["No text to send"]
let unknowncommand cmd = "421", [cmd; "Unknown command"]
let nonicknamegiven = "431", ["No nickname given"]
let erroneusnickname nick = "432", [nick; "Erroneus nickname"]
let nicknameinuse nick = "433", [nick; "Nickname is already in use"]
let notonchannel chan = "442", [chan; "You're not on that channel"]
let notregistered = "451", ["You have not registered"]
let needmoreparams cmd = "461", [cmd; "Not enough parameters"]
let alreadyregistered = "462", ["Unauthorized command (already registered)"]
2024-01-14 17:46:42 +00:00
let modeunknownflag = "501", ["Unknown MODE flag"]
let usersdontmatch_set = "502", ["Can't change mode for other users"]
let usersdontmatch_get = "502", ["Can't view mode for other users"]
2024-01-11 04:38:25 +00:00
(* user registration *)
let require_registered t : User.t result =
match t.user with
2024-01-11 04:38:25 +00:00
| Some me -> Ok me
| None -> Error notregistered
2024-01-07 20:54:39 +00:00
let attempt_to_register ~welcome t =
(* [welcome : t -> user -> unit result] is defined near the bottom of this file *)
match t.pending_nick, t.pending_userinfo with
| Some nick, Some userinfo ->
t.pending_nick <- None;
let me = User.make ~userinfo ~outbox:t.outbox in
begin match User.set_nick me nick ~router:t.router with
2024-01-11 04:38:25 +00:00
| `nick_in_use -> Error (nicknameinuse nick)
2024-01-08 03:28:31 +00:00
| `nick_set ->
t.user <- Some me;
welcome t me;
Ok ()
2024-01-07 20:54:39 +00:00
end
2024-01-11 04:38:25 +00:00
| _, _ ->
Ok ()
2024-01-08 03:28:31 +00:00
let on_msg_nick ~welcome t nick =
2024-01-11 04:38:25 +00:00
let* () =
match Irc.name_type nick with
| `nick -> Ok ()
| `chan | `invalid -> Error (erroneusnickname nick)
in
match t.user with
| Some me ->
begin
let msg = Irc.Msg.make "NICK" [nick] in
match
User.set_nick me nick
~router:t.router
~success_callback:(fun () -> Router.relay msg ~from:me [`to_interested])
2024-01-11 04:38:25 +00:00
with
| `nick_in_use -> Error (nicknameinuse nick)
| `nick_set -> Ok ()
end
| None ->
t.pending_nick <- Some nick;
attempt_to_register t ~welcome
2024-01-08 03:28:31 +00:00
let on_msg_user ~welcome t username realname =
match t.user with
2024-01-11 04:38:25 +00:00
| Some _me -> Error alreadyregistered
| None ->
(* TODO: configure hiding hostnames *)
let hostname = match t.addr with
| ADDR_INET (ia, _) -> Unix.string_of_inet_addr ia
2024-01-11 04:38:25 +00:00
| ADDR_UNIX path -> path
in
t.pending_userinfo <- Some { username; realname; hostname };
attempt_to_register t ~welcome
2024-01-08 03:28:31 +00:00
2024-01-11 04:38:25 +00:00
(* messages and channels *)
let on_msg_privmsg t name txt =
2024-01-11 04:38:25 +00:00
let* me = require_registered t in
let* tgt =
2024-01-10 02:20:16 +00:00
try
match Irc.name_type name with
| `chan -> Ok (`chan (Router.find_chan t.router name))
| _ -> Ok (`user (Router.find_user t.router name))
2024-01-11 04:38:25 +00:00
with Not_found ->
Error (nosuchnick name)
2024-01-10 02:20:16 +00:00
in
2024-01-11 04:38:25 +00:00
(* TODO: check if user is away *)
(* TODO: check if channel is +n and user is not a member *)
(* TODO: check if channel is +m and user is not priviledged *)
(* TODO: check if channel is +b <user> *)
let name, dst =
match tgt with
| `chan c -> Chan.name c, [`to_chan c]
| `user u -> User.nick u, [`to_user u]
in
let msg = Irc.Msg.make "PRIVMSG" [name; txt] ~always_trailing:true in
2024-01-11 04:38:25 +00:00
Router.relay msg ~from:me dst;
Ok ()
2024-01-10 02:20:16 +00:00
2024-01-11 04:38:25 +00:00
let list_names t chan =
begin
List.iter
(fun user ->
(* TODO: check if user is +i and not in channel with them *)
reply t ("353", ["@"; Chan.name chan; User.nick user]))
(Chan.members chan);
reply t ("366", [Chan.name chan; "End of NAMES list"])
end
let on_msg_names t name =
let* _me = require_registered t in
let* chan =
try
match Irc.name_type name with
| `chan -> Ok (Router.find_chan t.router name)
| _ -> Error (nosuchchannel name)
with Not_found ->
Error (nosuchchannel name)
in
(* TODO: check if channel is +s and user not member of channel *)
list_names t chan;
Ok ()
2024-01-18 18:28:48 +00:00
let on_msg_join t name ~set_chan_mode =
2024-01-11 04:38:25 +00:00
let* me = require_registered t in
(* TODO: keys parameter *)
(* TODO: "0" parameter means part from all channels *)
2024-01-11 04:38:25 +00:00
let* chan =
try
match Irc.name_type name with
| `chan -> Ok (Router.find_chan t.router name)
| _ -> Error (nosuchchannel name)
with Not_found ->
2024-01-12 02:49:48 +00:00
debug (fun m -> m "making new channel %S" name);
2024-01-18 18:28:48 +00:00
Ok (Chan.make ~name)
2024-01-11 04:38:25 +00:00
in
2024-01-18 18:28:48 +00:00
(* TODO: check channel mode +k, +l *)
2024-01-11 04:38:25 +00:00
let msg = Irc.Msg.make "JOIN" [name] in
Router.relay msg ~from:me [`to_chan chan; `to_self];
2024-01-18 18:28:48 +00:00
Chan.join chan me;
if not (Chan.is_registered chan ~router:t.router) then
(* set up newly created channel *)
begin
Chan.register chan ~router:t.router;
set_chan_mode chan ~from:me
Irc.Mode.Set.{
add = of_string initial_chan_modestr;
rem = empty;
};
(* TODO: make founder +o / +q etc. *)
end;
2024-01-11 04:38:25 +00:00
(* TODO: send channel topic *)
list_names t chan;
Ok ()
2024-01-10 02:20:16 +00:00
2024-01-11 04:38:25 +00:00
let on_msg_part t name =
let* me = require_registered t in
(* TODO: part reason *)
2024-01-11 04:38:25 +00:00
let* chan =
try
match Irc.name_type name with
| `chan -> Ok (Router.find_chan t.router name)
| `nick | `invalid -> raise Not_found
2024-01-11 04:38:25 +00:00
with Not_found ->
Error (nosuchchannel name)
in
let* () = if Chan.is_member chan me then Ok () else Error (notonchannel name) in
let msg = Irc.Msg.make "PART" [name] in
Router.relay msg ~from:me [`to_chan chan; `to_self];
2024-01-11 04:38:25 +00:00
Chan.part chan me;
if Chan.no_members chan then begin
2024-01-12 02:49:48 +00:00
debug (fun m -> m "recycling channel %S" name);
2024-01-11 04:38:25 +00:00
Chan.unregister chan ~router:t.router;
end;
Ok ()
2024-01-08 05:31:05 +00:00
(* modes *)
let set_user_mode user chg =
let mode, chg =
Irc.Mode.Set.normalize
(User.mode user)
Irc.Mode.Set.{ chg with add = remove `o chg.add (* can't set +o *) }
in
if chg <> Irc.Mode.Set.no_change then
let modestr = Fmt.str "%a" Irc.Mode.Set.pp_change chg in
let msg = Irc.Msg.make "MODE" [User.nick user; modestr] ~always_trailing:true in
begin
Router.relay msg ~from:user [`to_self];
User.set_mode user mode;
end
2024-01-18 18:28:48 +00:00
let set_chan_mode chan ~from chg =
let mode, chg = Irc.Mode.Set.normalize (Chan.mode chan) chg in
if chg <> Irc.Mode.Set.no_change then
let modestr = Fmt.str "%a" Irc.Mode.Set.pp_change chg in
let msg = Irc.Msg.make "MODE" [Chan.name chan; modestr] ~always_trailing:true in
begin
Router.relay msg ~from [`to_chan chan; `to_self];
Chan.set_mode chan mode;
end
let on_get_user_mode user me =
let* () = if user != me then Error usersdontmatch_get else Ok () in
Ok [
"221", [Fmt.str "+%a" Irc.Mode.Set.pp (User.mode me)]
]
let on_set_user_mode user me modestr _args =
let* () = if user != me then Error usersdontmatch_set else Ok () in
let* chg = try Ok (Irc.Mode.Parse.user_modes modestr)
2024-01-14 17:46:42 +00:00
with Irc.Mode.Parse.Error ->
(* TODO: "If one or more modes sent are not implemented on the server, the server
MUST apply the modes that are implemented, and then send the ERR_UMODEUNKNOWNFLAG
(501) in reply along with the MODE message." *)
2024-01-14 17:46:42 +00:00
Error modeunknownflag
in
set_user_mode me chg;
2024-01-18 18:28:48 +00:00
Ok []
2024-01-18 18:28:48 +00:00
let on_get_chan_mode chan _me =
Ok [
"324", [Chan.name chan; Fmt.str "+%a" Irc.Mode.Set.pp (Chan.mode chan)];
(* TODO: +k *)
(* TODO: +l *)
(* TODO: RPL_CREATIONTIME (329) *)
]
let on_set_chan_mode chan me modestr args =
2024-01-18 18:28:48 +00:00
(* TODO: If <modestring> is given, the user sending the command MUST have appropriate
channel privileges on the target channel to change the modes given. If a user does
not have appropriate privileges to change modes on the target channel, the server
MUST NOT process the message, and ERR_CHANOPRIVSNEEDED (482) numeric is returned. *)
let _ = me, chan in
let* chg = try Ok (Irc.Mode.Parse.chan_modes modestr args)
with Irc.Mode.Parse.Error ->
(* TODO: "If one or more modes sent are not implemented on the server, the server
MUST apply the modes that are implemented, and then send the ERR_UMODEUNKNOWNFLAG
(501) in reply along with the MODE message." *)
Error modeunknownflag
in
set_chan_mode chan chg.chan_modes ~from:me;
(* TODO: chg.chan_key *)
(* TODO: chg.chan_limit *)
(* TODO: chg.chan_priv *)
Ok []
let on_msg_mode t name args =
let* me = require_registered t in
let* on_set, on_get =
try
match Irc.name_type name with
| `nick ->
let u = Router.find_user t.router name in
Ok (on_set_user_mode u, on_get_user_mode u)
| `chan ->
let c = Router.find_chan t.router name in
Ok (on_set_chan_mode c, on_get_chan_mode c)
| `invalid -> raise Not_found
with Not_found ->
Error (nosuchnick name)
in
2024-01-18 18:28:48 +00:00
let+ rpls =
match args with
| [] -> on_get me
| modestr :: args -> on_set me modestr args
in
List.iter (reply t) rpls
let on_msg_join = on_msg_join ~set_chan_mode
2024-01-11 04:38:25 +00:00
(* misc *)
let about t me : unit =
let who = Irc.Msg.prefix_string (User.prefix me) in
begin
reply t ("001", [Fmt.str "Welcome to the tali IRC network %s" who]);
reply t ("002", [Fmt.str "Your host is %s, running version %s" srv_host srv_ver]);
reply t ("003", [Fmt.str "This server was created %s" srv_created]);
reply t ("004", [srv_host; srv_ver; "iow"; "imnst"; "bklov"]);
reply t ("005", ["CASEMAPPING=ascii";
"CHANTYPES=#";
"CHANMODES=b,k,l,imstn";
"PREFIX=(ov)@+";
"are supported by this server"]);
end
let motd t =
begin
reply t ("375", [Fmt.str "- %s Message of the day - " srv_host]);
List.iter (fun ln -> reply t ("372", ["- " ^ ln])) srv_motd_lines;
reply t ("376", ["End of /MOTD command"]);
end
let welcome t me =
about t me;
motd t;
set_user_mode me {
add = Irc.Mode.Set.of_string initial_user_modestr;
rem = Irc.Mode.Set.empty;
}
let on_msg_nick = on_msg_nick ~welcome
let on_msg_user = on_msg_user ~welcome
2024-01-11 04:38:25 +00:00
let on_msg_motd t =
let* _me = require_registered t in
motd t;
Ok ()
2024-01-07 20:54:39 +00:00
let on_msg_quit t reason =
(* TODO: '''When connections are terminated by a client-sent QUIT command, servers
SHOULD prepend <reason> with the ASCII string "Quit: " when sending QUIT messages to
other clients''' *)
let reason = String.concat " " reason in
shutdown t ~reason;
2024-01-11 04:38:25 +00:00
Ok ()
2024-01-08 05:31:05 +00:00
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
let dispatch t = function
| "NICK", nick :: _ when nick <> "" -> on_msg_nick t nick
| "NICK", _ -> Error nonicknamegiven
| "USER", unm :: _ :: _ :: rnm :: _ -> on_msg_user t unm rnm
| "QUIT", why -> on_msg_quit t why
| "MOTD", _ -> on_msg_motd t
| "PRIVMSG", ([] | "" :: _) -> Error norecipient
| "PRIVMSG", ([_] | _ :: "" :: _) -> Error notexttosend
| "PRIVMSG", tgt :: msg :: _ -> on_msg_privmsg t tgt msg
| "JOIN", tgt :: _ when tgt <> "" -> on_msg_join t tgt
| "NAMES", tgt :: _ when tgt <> "" -> on_msg_names t tgt
| "PART", tgt :: _ when tgt <> "" -> on_msg_part t tgt
| "MODE", tgt :: args when tgt <> "" -> on_msg_mode t tgt args
| ("USER" | "JOIN" | "NAMES" | "PART" | "MODE") as cmd, _ ->
Error (needmoreparams cmd)
| cmd, _ ->
Error (unknowncommand cmd)
let split_command_params cmd params =
match cmd, params with
| ("PRIVMSG" | "JOIN" | "NAMES" | "PART"), tgts :: rest
when String.contains tgts ',' ->
(* TODO: "JOIN" should be handled specially *)
String.split_on_char ',' tgts |>
List.map (fun tgt -> cmd, tgt :: rest)
| _ ->
[cmd, params]
let pp_args ppf (cmd, params) =
2024-01-12 02:49:48 +00:00
Fmt.pf ppf "@[%s@ %a@]" cmd (Fmt.list (Fmt.fmt "%S") ~sep:Fmt.sp) params
2024-01-07 21:29:12 +00:00
let on_msg t (msg : Irc.Msg.t) : unit =
split_command_params msg.command msg.params |>
List.iter
(fun args ->
2024-01-12 03:33:40 +00:00
trace (fun m -> m "@[%a:@ %a@]" pp_sockaddr t.addr pp_args args);
match dispatch t args with
| Ok () -> ()
| Error err -> reply t err)