Permalink
Browse files

Figure out how to run procket setuid helper

Modify the way the procket external setuid helper binary is called based
on:

* whether a progname has explicitly been passed in (run the command
  immediately)

* if the procket helper is setuid/setgid (run the command immediately)

* if a device is requested to be opened, check the access rights of the
  process (if read/write, run command immediately)

* otherwise, use sudo
  • Loading branch information...
1 parent 00baf5a commit 2a30b69092ff7272507c63b18a2dba7b05d101b6 @msantos committed Jul 29, 2012
Showing with 114 additions and 61 deletions.
  1. +3 −7 README.md
  2. +1 −1 c_src/procket_cmd.c
  3. +110 −53 src/procket.erl
View
@@ -64,9 +64,9 @@ Try running: make
## SETUID vs SUDO vs Capabilities
-The procket executable needs root privileges. Either allow your user to
-run procket using sudo or copy procket to somewhere owned by root and
-make it setuid.
+The procket helper executable needs root privileges. Either allow your
+user to run procket using sudo or copy procket to somewhere owned by
+root and make it setuid.
* for sudo
@@ -80,10 +80,6 @@ make it setuid.
sudo chmod 750 /usr/local/bin/procket
sudo chmod u+s /usr/local/bin/procket
- Use procket:open/2 and pass in the progname option:
-
- procket:open(22, [{progname, "/usr/local/bin/procket"}] ++ Opt).
-
* use Linux capabilities: beam or the user running beam can be
given whatever socket privileges are needed. For example, using file
capabilities:
View
@@ -455,7 +455,7 @@ usage(PROCKET_STATE *ps)
{
(void)fprintf(stderr, "%s, %s\n", __progname, PROCKET_VERSION);
(void)fprintf(stderr,
- "usage: %s <options> ipaddress>\n"
+ "usage: %s <options> <ipaddress>\n"
" -u <path> path to Unix socket\n"
" -p <port> port\n"
" -F <family> family [default: PF_UNSPEC]\n"
View
@@ -30,6 +30,7 @@
%% POSSIBILITY OF SUCH DAMAGE.
-module(procket).
-include("procket.hrl").
+-include_lib("kernel/include/file.hrl").
-export([
init/0,
@@ -61,6 +62,12 @@
ntohl/1,
ntohs/1
]).
+% for debugging
+-export([
+ get_progname/2,
+ make_cli_args/1,
+ progname/0
+ ]).
-on_load(on_load/0).
@@ -72,7 +79,9 @@ on_load() ->
erlang:load_nif(progname(), []).
-
+%%--------------------------------------------------------------------
+%%% NIF Stubs
+%%--------------------------------------------------------------------
close(_) ->
erlang:error(not_implemented).
@@ -149,55 +158,111 @@ errno_id(_) ->
erlang:error(not_implemented).
+%%--------------------------------------------------------------------
+%%% Setuid helper
+%%--------------------------------------------------------------------
dev(Dev) when is_list(Dev) ->
open(0, [{dev, Dev}]).
open(Port) ->
open(Port, []).
open(Port, Options) when is_integer(Port), is_list(Options) ->
- Opt = case proplists:get_value(pipe, Options) of
- undefined ->
- Tmp = procket_mktmp:dirname(),
- ok = procket_mktmp:make_dir(Tmp),
- Path = Tmp ++ "/sock",
- [{pipe, Path}, {tmpdir, Tmp}] ++ Options;
- _ ->
- [{tmpdir, false}] ++ Options
- end,
- Result = open_1(Port, Opt),
- case proplists:get_value(tmpdir, Options) of
- false ->
- ok;
- Tmp2 ->
- procket_mktmp:close(Tmp2)
+ Progname = get_progname(progname(), Options),
+ {Tmpdir, Pipe} = make_unix_socket_path(Options),
+ Cmd = make_cli_args([
+ {progname, Progname},
+ {port, Port},
+ {pipe, Pipe}
+ ] ++ Options),
+
+ Result = case fdopen(Pipe) of
+ {ok, FD} ->
+ Socket = exec(FD, Cmd),
+ close(FD),
+ Socket;
+ Error ->
+ Error
end,
+
+ cleanup_unix_socket(Tmpdir, Pipe),
Result.
-open_1(Port, Options) ->
- case open_1(Port, Options, false) of
- {error, Error} when Error == eacces; Error == eperm ->
- open_1(Port, Options, true);
- Result ->
- Result
- end.
+% Figure out how the procket helper should be called.
+get_progname(Progname, Options) ->
+ get_progname(progname, Progname, Options).
-open_1(Port, Options, UseSudo) ->
- Pipe = proplists:get_value(pipe, Options),
- {ok, Sockfd} = fdopen(Pipe),
- Cmd = make_args(Port, Options, UseSudo),
+% Caller has passed in a path?
+get_progname(progname, Default, Options) ->
+ case proplists:get_value(progname, Options) of
+ undefined ->
+ get_progname(setuid, Default, Options);
+ Progname ->
+ Progname
+ end;
+
+% Is the default executable setuid/setgid?
+get_progname(setuid, Progname, Options) ->
+ case file:read_file_info(Progname) of
+ {ok, #file_info{mode = Mode}} ->
+ if
+ % setuid
+ Mode band 16#800 =:= 16#800 ->
+ Progname;
+ % setgid
+ Mode band 16#400 =:= 16#400 ->
+ Progname;
+ true ->
+ get_progname(dev, Progname, Options)
+ end
+ end;
+
+% Device requested and accessible?
+get_progname(dev, Progname, Options) ->
+ case proplists:get_value(dev, Options) of
+ undefined ->
+ get_progname(sudo, Progname, Options);
+ Dev ->
+ case file:read_file_info(Dev) of
+ {ok, #file_info{access = read_write}} ->
+ Progname;
+ {ok, _} ->
+ get_progname(sudo, Progname, Options);
+ Error ->
+ Error
+ end
+ end;
+
+% Fall back to sudo
+get_progname(sudo, Progname, _Options) ->
+ "sudo " ++ Progname.
+
+% Run the setuid helper
+exec(FD, Cmd) ->
case os:cmd(Cmd) of
"0" ->
- FD = fdget(Sockfd),
- cleanup(Sockfd, Pipe),
- FD;
+ fdget(FD);
Error ->
- cleanup(Sockfd, Pipe),
{error, errno_id(list_to_integer(Error))}
end.
-cleanup(Sockfd, Pipe) ->
- close(Sockfd),
- ok = file:delete(Pipe).
+% Unix socket handling: retrieves the fd from the setuid helper
+make_unix_socket_path(Options) ->
+ {Tmpdir, Socket} = case proplists:get_value(pipe, Options) of
+ undefined ->
+ Tmp = procket_mktmp:dirname(),
+ ok = procket_mktmp:make_dir(Tmp),
+ Path = Tmp ++ "/sock",
+ {Tmp, Path};
+ Path ->
+ {false, Path}
+ end,
+ {Tmpdir, Socket}.
+
+cleanup_unix_socket(false, Pipe) ->
+ file:delete(Pipe);
+cleanup_unix_socket(Tmpdir, Pipe) ->
+ file:delete(Pipe),
+ procket_mktmp:close(Tmpdir).
fdopen(Path) when is_list(Path) ->
fdopen(list_to_binary(Path));
@@ -216,27 +281,19 @@ fdget(Socket) ->
{ok, S} = accept(Socket),
fdrecv(S).
-make_args(Port, Options, UseSudo) ->
- Args = reorder_args(Port, Options),
- Prefix = case UseSudo of
- true ->
- "sudo ";
- false ->
- ""
+% Construct the cli arguments for the helper
+make_cli_args(Options) ->
+ {[[{progname,Progname}|_],
+ IPaddrs], Rest} = proplists:split(Options, [progname, ip]),
+ IP = case IPaddrs of
+ [] -> [];
+ [Addr|_] -> Addr
end,
- proplists:get_value(progname, Options, Prefix ++ progname()) ++ " " ++
+ Args = Rest ++ [IP],
+ Progname ++ " " ++
string:join([ get_switch(Arg) || Arg <- Args ], " ") ++
" > /dev/null 2>&1; printf $?".
-reorder_args(Port, Options) ->
- NewOpts = case proplists:lookup(ip, Options) of
- none ->
- Options;
- IP ->
- proplists:delete(ip, Options) ++ [IP]
- end,
- [{port, Port}] ++ NewOpts.
-
get_switch({pipe, Arg}) ->
"-u " ++ Arg;
@@ -309,8 +366,8 @@ progname_priv() ->
progname() ->
% Is there a proper way of getting App-Name in this context?
case code:priv_dir( ?MODULE ) of
- {error,bad_name} -> progname_ebin();
- ________________ -> progname_priv()
+ {error, bad_name} -> progname_ebin();
+ _ -> progname_priv()
end.
%% Protocol family (aka domain)

0 comments on commit 2a30b69

Please sign in to comment.