···11+open Capnp_rpc
22+33+let v sr ~add_user ~remove_user =
44+ let module X = Raw.Service.Admin in
55+ Capnp_rpc.Persistence.with_sturdy_ref sr X.local
66+ @@ object
77+ inherit X.service
88+99+ method add_user_impl params release_param_caps =
1010+ let open X.AddUser in
1111+ let id = Params.user_get params in
1212+ release_param_caps ();
1313+ let cap = add_user id in
1414+ let response, results = Service.Response.create Results.init_pointer in
1515+ Results.cap_set results (Some cap);
1616+ Capability.dec_ref cap;
1717+ Service.return response
1818+1919+ method remove_user_impl params release_param_caps =
2020+ let open X.RemoveUser in
2121+ let id = Params.user_get params in
2222+ release_param_caps ();
2323+ remove_user id;
2424+ Service.return @@ Service.Response.create_empty ()
2525+ end
2626+2727+module X = Raw.Client.Admin
2828+2929+type t = X.t Capability.t
3030+3131+let add_user t user =
3232+ let open X.AddUser in
3333+ let request, params = Capability.Request.create Params.init_pointer in
3434+ Params.user_set params user;
3535+ Capability.call_for_caps t method_id request Results.cap_get_pipelined
3636+3737+let remove_user t user =
3838+ let open X.RemoveUser in
3939+ let request, params = Capability.Request.create Params.init_pointer in
4040+ Params.user_set params user;
4141+ let _ : _ StructStorage.reader_t =
4242+ Capability.call_for_value_exn t method_id request
4343+ in
4444+ ()
···11+@0x91b3108e7ebb3830;
22+interface Session {
33+ stdin @0 (input :Text) -> ();
44+ stdout @1 () -> (output :Text);
55+ stderr @2 () -> (output :Text);
66+}
77+88+interface User {
99+ connect @0 (config :Text) -> (cap :Session);
1010+ # Connect to the daemon and get a live session.
1111+}
1212+1313+1414+interface Admin {
1515+ addUser @0 (user :Text) -> (cap :User);
1616+ # Add a new user, returning a capability to act as a full
1717+ # Shelter user.
1818+1919+ removeUser @1 (user :Text) -> ();
2020+ # Remove a user, this will also cancel existing connections
2121+ # this user may have to the daemon.
2222+}
+57
src/common/session.ml
···11+open Capnp_rpc
22+33+let or_fail = function
44+ | Ok v -> v
55+ | Error (`Capnp e) -> Fmt.failwith "%a" Capnp_rpc.Error.pp e
66+77+let local ~stdin ~stdout ~stderr =
88+ let module X = Raw.Service.Session in
99+ X.local
1010+ @@ object
1111+ inherit X.service
1212+1313+ method stdout_impl _ release_param_caps =
1414+ let open X.Stdout in
1515+ release_param_caps ();
1616+ let s = stdout () in
1717+ let response, results = Service.Response.create Results.init_pointer in
1818+ Results.output_set results s;
1919+ Service.return response
2020+2121+ method stderr_impl _ release_param_caps =
2222+ let open X.Stderr in
2323+ release_param_caps ();
2424+ let s = stderr () in
2525+ let response, results = Service.Response.create Results.init_pointer in
2626+ Results.output_set results s;
2727+ Service.return response
2828+2929+ method stdin_impl params release_param_caps =
3030+ let open X.Stdin in
3131+ let data = Params.input_get params in
3232+ release_param_caps ();
3333+ stdin data;
3434+ Service.return_empty ()
3535+ end
3636+3737+module X = Raw.Client.Session
3838+3939+type t = X.t Capability.t
4040+4141+let stdout t () =
4242+ let open X.Stdout in
4343+ let request = Capability.Request.create_no_args () in
4444+ let result = Capability.call_for_value t method_id request |> or_fail in
4545+ Results.output_get result
4646+4747+let stderr t () =
4848+ let open X.Stderr in
4949+ let request = Capability.Request.create_no_args () in
5050+ let result = Capability.call_for_value t method_id request |> or_fail in
5151+ Results.output_get result
5252+5353+let stdin t input =
5454+ let open X.Stdin in
5555+ let request, params = Capability.Request.create Params.init_pointer in
5656+ Params.input_set params input;
5757+ Capability.call_for_unit t method_id request |> or_fail
+6
src/common/shelter_common.ml
···11+let or_fail = function Ok v -> v | Error (`Msg m) -> failwith m
22+33+module Raw = Raw
44+module Admin = Admin
55+module User = User
66+module Session = Session
+31
src/common/user.ml
···11+open Capnp_rpc
22+33+let v sr connect =
44+ let module X = Raw.Service.User in
55+ Capnp_rpc.Persistence.with_sturdy_ref sr X.local
66+ @@ object
77+ inherit X.service
88+99+ method connect_impl params release_param_caps =
1010+ let open X.Connect in
1111+ let config =
1212+ Params.config_get params |> Yojson.Safe.from_string
1313+ |> Config.of_yojson |> Result.get_ok
1414+ in
1515+ release_param_caps ();
1616+ let cap = connect config in
1717+ let response, results = Service.Response.create Results.init_pointer in
1818+ Results.cap_set results (Some cap);
1919+ Capability.dec_ref cap;
2020+ Service.return response
2121+ end
2222+2323+module X = Raw.Client.User
2424+2525+type t = X.t Capability.t
2626+2727+let connect t config =
2828+ let open X.Connect in
2929+ let request, params = Capability.Request.create Params.init_pointer in
3030+ Params.config_set params (Config.to_yojson config |> Yojson.Safe.to_string);
3131+ Capability.call_for_caps t method_id request Results.cap_get_pipelined
···11+open Shelter_common
22+open Capnp_rpc
33+44+module Admin = struct
55+ module Secret = Capnp_rpc_net.Restorer.Id
66+77+ let add_user t restorer name =
88+ match Store.lookup t name with
99+ | Some _ -> Fmt.failwith "User %s already exists!" name
1010+ | None -> (
1111+ let secret = Store.add_client t name in
1212+ match Capnp_rpc_net.Restorer.restore restorer secret with
1313+ | Ok v -> v
1414+ | Error exn ->
1515+ Fmt.failwith "%a" Capnp_rpc_proto.Error.pp (`Exception exn))
1616+1717+ let remove_user t name = Store.remove t name
1818+1919+ let v sr restorer t =
2020+ let add_user = add_user t restorer in
2121+ let remove_user = remove_user t in
2222+ Admin.v ~add_user ~remove_user sr
2323+end
2424+2525+open Capnp_rpc_net
2626+2727+let export ~secrets_dir ~vat ~name id =
2828+ let ( / ) = Filename.concat in
2929+ let path = secrets_dir / (name ^ ".cap") in
3030+ Capnp_rpc_unix.Cap_file.save_service vat id path |> or_fail;
3131+ Logs.app (fun f -> f "Wrote capability reference to %S" path)
3232+3333+let daemon capnp services store secrets_dir =
3434+ let restore = Restorer.of_table services in
3535+ let admin_id = Capnp_rpc_unix.Vat_config.derived_id capnp "admin" in
3636+ let admin =
3737+ let sr = Capnp_rpc_net.Restorer.Table.sturdy_ref services admin_id in
3838+ Admin.v sr restore store
3939+ in
4040+ Restorer.Table.add services admin_id admin;
4141+ Eio.Switch.run @@ fun sw ->
4242+ let vat = Capnp_rpc_unix.serve capnp ~sw ~restore in
4343+ export ~secrets_dir ~vat ~name:"admin" admin_id;
4444+ Logs.app (fun f -> f "shelterd running...");
4545+ Eio.Promise.await (Eio.Promise.create () |> fst)
4646+4747+open Cmdliner
4848+4949+let setup_log style_renderer level =
5050+ Fmt_tty.setup_std_outputs ?style_renderer ();
5151+ Logs.set_level level;
5252+ Logs.set_reporter (Logs_fmt.reporter ());
5353+ ()
5454+5555+let setup_log =
5656+ let docs = Manpage.s_common_options in
5757+ Term.(
5858+ const setup_log $ Fmt_cli.style_renderer ~docs () $ Logs_cli.level ~docs ())
5959+6060+let admin =
6161+ Arg.required
6262+ @@ Arg.opt Arg.(some file) None
6363+ @@ Arg.info ~doc:"Path of the admin capability." ~docv:"ADDR"
6464+ [ "c"; "connect" ]
6565+6666+let username =
6767+ Arg.required
6868+ @@ Arg.pos 0 Arg.(some string) None
6969+ @@ Arg.info ~doc:"The name of the new user to add." ~docv:"NAME" []
7070+7171+let daemon env =
7272+ let doc = "run the shelter daemon" in
7373+ let man =
7474+ [
7575+ `S Manpage.s_description;
7676+ `P "The shelter daemon provides a way to run sessions for shelter users.";
7777+ ]
7878+ in
7979+ let info = Cmd.info ~man "daemon" ~doc in
8080+ let daemon () capnp =
8181+ let make_sturdy = Capnp_rpc_unix.Vat_config.sturdy_uri capnp in
8282+ let connect = Obj.magic () in
8383+ let load ~validate:_ ~sturdy_ref =
8484+ let sr = Capnp_rpc.Sturdy_ref.cast sturdy_ref in
8585+ Restorer.grant (User.v sr connect)
8686+ in
8787+ let loader = Store.create ~make_sturdy ~load "shelter.index" in
8888+ Eio.Switch.run @@ fun sw ->
8989+ let services = Restorer.Table.of_loader ~sw (module Store) loader in
9090+ daemon capnp services loader.store "./secrets"
9191+ in
9292+ let term =
9393+ Term.(const daemon $ setup_log $ Capnp_rpc_unix.Vat_config.cmd env)
9494+ in
9595+ (Cmd.v info term, term)
9696+9797+let add_cmd env =
9898+ let doc = "add a new client" in
9999+ let man =
100100+ [
101101+ `S Manpage.s_description;
102102+ `P
103103+ "Add a new client and get a capablity back to use for that client to \
104104+ run shelter sessions.";
105105+ ]
106106+ in
107107+ let info = Cmd.info ~man "add" ~doc in
108108+ let add () cap_path name =
109109+ Eio.Switch.run @@ fun sw ->
110110+ let vat = Capnp_rpc_unix.client_only_vat ~sw env#net in
111111+ let sr = Capnp_rpc_unix.Cap_file.load vat cap_path |> or_fail in
112112+ Capnp_rpc_unix.with_cap_exn sr @@ fun service ->
113113+ let cap = Shelter_common.Admin.add_user service name in
114114+ Capability.with_ref cap @@ fun client ->
115115+ let uri = Persistence.save_exn client in
116116+ Fmt.pr "%a" Uri.pp uri
117117+ in
118118+ Cmd.v info Term.(const add $ setup_log $ admin $ username)
119119+120120+let () =
121121+ Eio_main.run @@ fun env ->
122122+ let doc = "Shelterd" in
123123+ let man =
124124+ [
125125+ `S Manpage.s_authors;
126126+ `P "Patrick Ferris";
127127+ `S Manpage.s_bugs;
128128+ `P "Email bug reports to <patrick@sirref.org>.";
129129+ ]
130130+ in
131131+ let info = Cmd.info ~doc ~man "shelterd" in
132132+ let daemon_cmd, daemon_term = daemon env in
133133+ exit
134134+ (Cmd.eval @@ Cmd.group ~default:daemon_term info [ daemon_cmd; add_cmd env ])
+71
src/shelterd/store.ml
···11+(* let () = Mirage_crypto_rng_lwt.initialize (module Mirage_crypto_rng.Fortuna) *)
22+let hash_size = 256
33+44+module Fixed_string = Index.Key.String_fixed (struct
55+ let length = 256
66+end)
77+88+module I = Index_unix.Make (Fixed_string) (Fixed_string) (Index.Cache.Noop)
99+module Secret = Capnp_rpc_net.Restorer.Id
1010+1111+type t = {
1212+ store : I.t;
1313+ make_sturdy : Secret.t -> Uri.t;
1414+ load :
1515+ validate:(unit -> bool) ->
1616+ sturdy_ref:[ `Generic ] Capnp_rpc.Sturdy_ref.t ->
1717+ Capnp_rpc_net.Restorer.resolution;
1818+}
1919+2020+let create ~make_sturdy ~load path =
2121+ let store = I.v ~log_size:4096 path in
2222+ { store; make_sturdy; load }
2323+2424+let pad_name name =
2525+ let diff = hash_size - String.length name in
2626+ if diff >= 0 then String.make diff ' ' ^ name else failwith "Name too long!"
2727+2828+let add_client t name =
2929+ let name = String.trim name in
3030+ let secret = Secret.generate () in
3131+ let hash = Secret.digest `SHA256 secret in
3232+ let name = pad_name name in
3333+ let store_secret = pad_name hash in
3434+ I.replace t name store_secret;
3535+ I.replace t store_secret name;
3636+ I.merge t;
3737+ secret
3838+3939+let lookup t name =
4040+ let name = pad_name name in
4141+ try Some (I.find t name) with Not_found -> None
4242+4343+let lookup_by_hash t digest =
4444+ try Some (I.find t (pad_name digest)) with Not_found -> None
4545+4646+let remove t name =
4747+ let name = String.trim name in
4848+ let padded_name = pad_name name in
4949+ I.filter t (fun (k, _) -> k = padded_name);
5050+ I.merge t
5151+5252+let list t =
5353+ let lst = ref [] in
5454+ I.iter (fun k _ -> lst := String.trim k :: !lst) t;
5555+ List.stable_sort String.compare !lst
5656+5757+module type T = Capnp_rpc_net.Restorer.LOADER
5858+5959+let hash _ = `SHA256
6060+let make_sturdy t = t.make_sturdy
6161+6262+let validate t digest () =
6363+ match lookup t.store digest with None -> false | Some _ -> true
6464+6565+let load t self digest =
6666+ Logs.info (fun f -> f "Looking up %s" digest);
6767+ match lookup_by_hash t.store digest with
6868+ | None -> Capnp_rpc_net.Restorer.unknown_service_id
6969+ | Some _ ->
7070+ t.load ~validate:(validate t digest)
7171+ ~sturdy_ref:(Capnp_rpc.Sturdy_ref.cast self)