(* $Id: plasma_ug.ml 418 2011-09-23 11:31:30Z gerd $ *) open Plasma_util class type ug_admin = object method getpwnam : string -> Unix.passwd_entry method getpwuid : int -> Unix.passwd_entry method getgrnam : string -> Unix.group_entry method getgrgid : int -> Unix.group_entry method getgroups : string -> Plasma_util.StrSet.t method users : Unix.passwd_entry list method groups : Unix.group_entry list method add_group : Unix.group_entry -> unit method del_group : string -> unit method add_user : Unix.passwd_entry -> unit method del_user : string -> unit method admin_tables : (string * string) list end class type ug_admin_ro = object method getpwnam : string -> Unix.passwd_entry method getpwuid : int -> Unix.passwd_entry method getgrnam : string -> Unix.group_entry method getgrgid : int -> Unix.group_entry method getgroups : string -> Plasma_util.StrSet.t method users : Unix.passwd_entry list method groups : Unix.group_entry list method admin_tables : (string * string) list end let req_admin_tables = [ "passwd"; "group" ] let colon_re = Netstring_str.regexp ":" let comma_re = Netstring_str.regexp "," let split_line s = Netstring_str.split_delim colon_re s let split_members s = Netstring_str.split comma_re s let passwd_entry_of_line s = try match split_line s with | [ uname; pw; uid_s; gid_s; gecos; home; sh ] -> let uid = int_of_string uid_s in let gid = int_of_string gid_s in { Unix.pw_name = uname; pw_passwd = pw; pw_uid = uid; pw_gid = gid; pw_gecos = gecos; pw_dir = home; pw_shell = sh } | _ -> raise Not_found with | _ -> failwith "Bad passwd line" let line_of_passwd_entry u = let open Unix in String.concat ":" [ u.pw_name; u.pw_passwd; string_of_int u.pw_uid; string_of_int u.pw_gid; u.pw_gecos; u.pw_dir; u.pw_shell ] let group_entry_of_line s = try match split_line s with | [ gname; pw; gid_s; mems ] -> let gid = int_of_string gid_s in { Unix.gr_name = gname; gr_passwd = pw; gr_gid = gid; gr_mem = Array.of_list(split_members mems) } | _ -> raise Not_found with | _ -> failwith "Bad group line" let line_of_group_entry g = let open Unix in String.concat ":" [ g.gr_name; g.gr_passwd; string_of_int g.gr_gid; String.concat "," (Array.to_list g.gr_mem) ] let parse_ug_admin tabs = let open Unix in let passwd_text = try List.assoc "passwd" tabs with Not_found -> failwith "Plasma_ug: 'passwd' is required but missing" in let passwd_lines = Netchannels.lines_of_in_obj_channel (new Netchannels.input_string passwd_text) in let group_text = try List.assoc "group" tabs with Not_found -> failwith "Plasma_ug: 'group' is required but missing" in let group_lines = Netchannels.lines_of_in_obj_channel (new Netchannels.input_string group_text) in let users = List.flatten (List.map (fun line -> try [passwd_entry_of_line line] with _ -> []) passwd_lines) in let groups = List.flatten (List.map (fun line -> try [group_entry_of_line line] with _ -> []) group_lines) in let pwnam = Hashtbl.create 5 in let pwuid = Hashtbl.create 5 in let grnam = Hashtbl.create 5 in let grgid = Hashtbl.create 5 in let ugroups = Hashtbl.create 5 in let mgroups = Hashtbl.create 5 in List.iter (fun g -> Hashtbl.replace grnam g.gr_name g; Hashtbl.replace grgid g.gr_gid g.gr_name ) groups; List.iter (fun u -> if Hashtbl.mem grgid u.pw_gid then ( Hashtbl.replace pwnam u.pw_name u; Hashtbl.replace pwuid u.pw_uid u.pw_name; ) ) users; let refresh() = (* ugroups and mgroups *) (* We cannot delay this recomputation until needed, because we want that [ug_admin_ro] can be put into shared memory as constant. *) Hashtbl.clear ugroups; Hashtbl.clear mgroups; Hashtbl.iter (fun _ u -> if Hashtbl.mem grgid u.pw_gid then ( let gname = Hashtbl.find grgid u.pw_gid in Hashtbl.replace mgroups gname (); Hashtbl.replace ugroups u.pw_name (StrSet.add gname StrSet.empty) ) ) pwnam; Hashtbl.iter (fun _ g -> Array.iter (fun uname -> let s = try Hashtbl.find ugroups uname with Not_found -> StrSet.empty in let s' = StrSet.add g.gr_name s in Hashtbl.replace ugroups uname s' ) g.gr_mem ) grnam in refresh(); ( object (self) method getpwnam n = Hashtbl.find pwnam n method getpwuid id = Hashtbl.find pwnam (Hashtbl.find pwuid id) method getgrnam n = Hashtbl.find grnam n method getgrgid id = Hashtbl.find grnam (Hashtbl.find grgid id) method getgroups n = Hashtbl.find ugroups n method users = Hashtbl.fold (fun _ u acc -> u::acc) pwnam [] method groups = Hashtbl.fold (fun _ g acc -> g::acc) grnam [] method add_group g = Hashtbl.replace grnam g.gr_name g; Hashtbl.replace grgid g.gr_gid g.gr_name; refresh() method del_group gname = if Hashtbl.mem mgroups gname then failwith "del_group: this group is still used"; if Hashtbl.mem grnam gname then ( let g = Hashtbl.find grnam gname in Hashtbl.remove grnam gname; Hashtbl.remove grgid g.gr_gid; refresh() ) method add_user u = if Hashtbl.mem grgid u.pw_gid then ( Hashtbl.replace pwnam u.pw_name u; Hashtbl.replace pwuid u.pw_uid u.pw_name; refresh() ) else failwith "add_user: unknown group" method del_user uname = if Hashtbl.mem pwnam uname then ( let u = Hashtbl.find pwnam uname in Hashtbl.remove pwnam uname; Hashtbl.remove pwuid u.pw_uid; refresh() ) method admin_tables = let passwd_buf = Buffer.create 500 in let group_buf = Buffer.create 500 in Hashtbl.iter (fun uname u -> Buffer.add_string passwd_buf (line_of_passwd_entry u); Buffer.add_string passwd_buf "\n"; ) pwnam; Hashtbl.iter (fun gname g -> Buffer.add_string group_buf (line_of_group_entry g); Buffer.add_string group_buf "\n"; ) grnam; [ "passwd", Buffer.contents passwd_buf; "group", Buffer.contents group_buf ] end ) let serialize_auth_ticket ~rpc_user ~rpc_password ~user ~group ~supp_groups ~verifier = let enc = Netencoding.Base64.encode in String.concat ":" [ "SCRAM-SHA1"; enc rpc_user; enc rpc_password; enc user; enc group; String.concat "," (List.map enc (StrSet.elements supp_groups)); Int64.to_string verifier ] let rpc_login_of_auth_ticket s = let dec = Netencoding.Base64.decode in try match split_line s with | [ "SCRAM-SHA1"; rpc_user_e; rpc_password_e; _; _; _; _ ] -> (dec rpc_user_e, dec rpc_password_e) | _ -> raise Not_found with | _ -> failwith "Plasma_ug.rpc_login_of_auth_ticket" let principal_of_auth_ticket s = let dec = Netencoding.Base64.decode in try match split_line s with | [ "SCRAM-SHA1"; _; _; user_e; group_e; supp_groups_e; _ ] -> let l1 = split_members supp_groups_e in let l2 = List.map dec l1 in let supp_groups = List.fold_left (fun acc g -> StrSet.add g acc) StrSet.empty l2 in (dec user_e, dec group_e, supp_groups) | _ -> raise Not_found with | _ -> failwith "Plasma_ug.principal_of_auth_ticket" let verifier_of_auth_ticket s = try match split_line s with | [ "SCRAM-SHA1"; _; _; _; _; _; verifier_s ] -> Int64.of_string verifier_s | _ -> raise Not_found with | _ -> failwith "Plasma_ug.verifier_of_auth_ticket"