Plasma GitLab Archive
Projects Blog Knowledge

(* $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"

This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml