add validation of channel modes in some cases

- only +o may set channel mods
- only +v/+o may talk in moderated channels
- filter NAMES reply by channel +s and user +i
This commit is contained in:
tali 2024-01-27 15:34:30 -05:00
parent 5f573c98e7
commit d5cdc71ac0
2 changed files with 83 additions and 44 deletions

View File

@ -34,5 +34,8 @@ let members t =
let membership t =
Dllist.fold_r (fun m xs -> m :: xs) t.members []
let membership_when f t =
Dllist.fold_r (fun m xs -> if f m then m :: xs else xs) t.members []
let no_members t =
Dllist.is_empty t.members

View File

@ -52,6 +52,7 @@ let reply t (num, params) =
let tryagain cmd = "263", [cmd; "Please wait a while and try again."]
let nosuchnick tgt = "401", [tgt; "No such nick/channel"]
let nosuchchannel tgt = "403", [tgt; "No such channel"]
let cannotsendtochan tgt = "404", [tgt; "Cannot send to channel"]
let norecipient = "411", ["No recipient given (PRIVMSG)"]
let notexttosend = "412", ["No text to send"]
let unknowncommand cmd = "421", [cmd; "Unknown command"]
@ -62,6 +63,7 @@ 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)"]
let chanoprivsneeded chan = "482", [chan; "You're not channel operator"]
let modeunknownflag = "501", ["Didn't understand MODE command"]
let usersdontmatch_set = "502", ["Can't change mode for other users"]
let usersdontmatch_get = "502", ["Can't view mode for other users"]
@ -137,14 +139,22 @@ let set_member_priv ~from (mem : Router.membership) (priv : Router.priv) =
Router.relay msg ~from [`to_chan chan; `to_self];
mem.mem_priv <- priv
let require_same_user user me =
if (user : User.t) == me then Ok () else Error usersdontmatch_get
let require_chan_op chan me =
match Router.membership chan me with
| m when m.mem_priv = Operator -> Ok (* m *) ()
| _ | exception Not_found -> Error (chanoprivsneeded (Chan.name chan))
let on_get_user_mode user me =
let* () = if user != me then Error usersdontmatch_get else Ok () in
let* () = require_same_user user me in
Ok [
"221", [Fmt.str "+%a" Mode.Set.pp (User.mode me)]
]
let on_set_user_mode user me modestr _args =
let* () = if user == me then Ok () else Error usersdontmatch_set in
let* () = require_same_user user me in
let* chg = try Ok (Mode.Parse.user_modes modestr)
with Mode.Parse.Error ->
(* TODO: "If one or more modes sent are not implemented on the server, the server
@ -155,7 +165,7 @@ let on_set_user_mode user me modestr _args =
set_user_mode me ~add:chg.add ~rem:chg.rem;
Ok []
let on_get_chan_mode chan _me =
let on_get_chan_mode chan me =
let rpls = [
["324", [Chan.name chan; Fmt.str "+%a" Mode.Set.pp (Chan.mode chan)]];
@ -164,10 +174,13 @@ let on_get_chan_mode chan _me =
| None -> []
end;
(* TODO: only display key if priveledged enough to see it *)
_todo_validation_please [];
begin match Chan.key chan with
| Some key -> ["324", [Chan.name chan; "+k"; key]]
| Some key ->
let key = match Router.membership chan me with
| _is_member -> key
| exception Not_found -> "*"
in
["324", [Chan.name chan; "+k"; key]]
| None -> []
end;
@ -176,14 +189,12 @@ let on_get_chan_mode chan _me =
Ok (List.flatten rpls)
let on_set_chan_mode chan me modestr args ~router =
(* 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. *)
_todo_validation_please ();
let* () = require_chan_op chan me in
let* chg = try Ok (Mode.Parse.chan_modes modestr args)
with Mode.Parse.Error ->
(* TODO: ERR_INVALIDMODEPARAM (696)
"<client> <target chan/user> <mode char> <parameter> :<description>" *)
(* 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." *)
@ -193,7 +204,6 @@ let on_set_chan_mode chan me modestr args ~router =
set_chan_mode chan ~from:me ~add:chg.chan_modes.add ~rem:chg.chan_modes.rem;
Option.iter (set_chan_key chan ~from:me) chg.chan_key;
Option.iter (set_chan_limit chan ~from:me) chg.chan_limit;
List.iter
(fun (op, mode, nick) ->
try
@ -240,53 +250,81 @@ let on_msg_mode t name args =
(* messages and channels *)
let on_msg_privmsg t name txt =
let* me = require_registered t in
let* tgt =
let on_privmsg_chan from chan =
let cannot_send =
try
match name_type name with
| `chan -> Ok (`chan (Router.find_chan t.router name))
| _ -> Ok (`user (Router.find_user t.router name))
let mem = Router.membership chan from in
(* check if moderated (+m) *)
if Mode.Set.mem `m (Chan.mode chan) then
mem.mem_priv < Voice
else
false
with Not_found ->
Error (nosuchnick name)
(* check if no external messages (+n) *)
Mode.Set.mem `n (Chan.mode chan)
in
if cannot_send then
Error (cannotsendtochan (Chan.name chan))
else
Ok (Chan.name chan, [`to_chan chan])
let on_privmsg_user _from user =
(* 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> *)
_todo_validation_please ();
let name, dst =
match tgt with
| `chan c -> Chan.name c, [`to_chan c]
| `user u -> User.nick u, [`to_user u]
Ok (User.nick user, [`to_user user])
let on_msg_privmsg t tgt txt =
let* me = require_registered t in
let* name, tgts =
try
match name_type tgt with
| `chan -> on_privmsg_chan me (Router.find_chan t.router tgt)
| `nick -> on_privmsg_user me (Router.find_user t.router tgt)
| `invalid -> raise Not_found
with Not_found ->
Error (nosuchnick tgt)
in
let msg = Msg.make "PRIVMSG" [name; txt] ~always_trailing:true in
Router.relay msg ~from:me dst;
Router.relay msg ~from:me tgts;
Ok ()
let list_names t chan =
let name = Chan.name chan in
let sym = if Mode.Set.mem `s (Chan.mode chan) then "@" else "=" in
let mems =
let list_names t me chan =
let is_secret = Mode.Set.mem `s (Chan.mode chan) in
let is_invisible user = Mode.Set.mem `i (User.mode user) in
let members =
match Router.membership chan me with
| _is_member -> Chan.membership chan
| exception Not_found ->
if is_secret then
[]
else
Chan.membership_when
(fun (m : Router.membership) ->
not (is_invisible m.mem_user))
chan
in
let nicks =
List.map
(fun (m : Router.membership) ->
let nick = User.nick m.mem_user in
(* TODO: dont list users who are +i if you are not a member w/ them *)
_todo_validation_please ();
match m.mem_priv with
| Normal -> nick
| Voice -> "+" ^ nick
| Operator -> "@" ^ nick)
(Chan.membership chan)
members
in
let chan_name = Chan.name chan in
let chan_sym = if is_secret then "@" else "=" in
begin
(* TODO: concat member names until message becomes too long *)
List.iter (fun nick -> reply t ("353", [sym; name; nick])) mems;
reply t ("366", [Chan.name chan; "End of NAMES list"])
List.iter (fun nick -> reply t ("353", [chan_sym; chan_name; nick])) nicks;
reply t ("366", [chan_name; "End of NAMES list"])
end
let on_msg_names t name =
let* _me = require_registered t in
let* me = require_registered t in
let* chan =
try
match name_type name with
@ -295,9 +333,7 @@ let on_msg_names t name =
with Not_found ->
Error (nosuchchannel name)
in
(* TODO: check if channel is +s and user not member of channel *)
_todo_validation_please ();
list_names t chan;
list_names t me chan;
Ok ()
let get_topic ?(reply_if_missing=true) t chan =
@ -350,7 +386,6 @@ let join t user chan =
let on_msg_join t name =
let* me = require_registered t in
(* TODO: keys parameter *)
let* chan =
try
match name_type name with
@ -367,11 +402,12 @@ let on_msg_join t name =
| _already_a_member -> Ok ()
| exception Not_found ->
begin
(* TODO: check channel mode +k, +l *)
(* TODO: +k *)
(* TODO: +l *)
_todo_validation_please ();
join t me chan;
get_topic t chan ~reply_if_missing:false;
list_names t chan;
list_names t me chan;
Ok ()
end