Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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...
commit 2a30b69092ff7272507c63b18a2dba7b05d101b6 1 parent 00baf5a
Michael Santos authored July 29, 2012
10  README.md
Source Rendered
@@ -64,9 +64,9 @@ Try running: make
64 64
 
65 65
 ## SETUID vs SUDO vs Capabilities
66 66
 
67  
-The procket executable needs root privileges. Either allow your user to
68  
-run procket using sudo or copy procket to somewhere owned by root and
69  
-make it setuid.
  67
+The procket helper executable needs root privileges. Either allow your
  68
+user to run procket using sudo or copy procket to somewhere owned by
  69
+root and make it setuid.
70 70
 
71 71
 * for sudo
72 72
 
@@ -80,10 +80,6 @@ make it setuid.
80 80
         sudo chmod 750 /usr/local/bin/procket
81 81
         sudo chmod u+s /usr/local/bin/procket
82 82
 
83  
-  Use procket:open/2 and pass in the progname option:
84  
-
85  
-        procket:open(22, [{progname, "/usr/local/bin/procket"}] ++ Opt).
86  
-
87 83
 * use Linux capabilities: beam or the user running beam can be
88 84
 given whatever socket privileges are needed. For example, using file
89 85
 capabilities:
2  c_src/procket_cmd.c
@@ -455,7 +455,7 @@ usage(PROCKET_STATE *ps)
455 455
 {
456 456
     (void)fprintf(stderr, "%s, %s\n", __progname, PROCKET_VERSION);
457 457
     (void)fprintf(stderr,
458  
-            "usage: %s <options> ipaddress>\n"
  458
+            "usage: %s <options> <ipaddress>\n"
459 459
             "              -u <path>        path to Unix socket\n"
460 460
             "              -p <port>        port\n"
461 461
             "              -F <family>      family [default: PF_UNSPEC]\n"
163  src/procket.erl
@@ -30,6 +30,7 @@
30 30
 %% POSSIBILITY OF SUCH DAMAGE.
31 31
 -module(procket).
32 32
 -include("procket.hrl").
  33
+-include_lib("kernel/include/file.hrl").
33 34
 
34 35
 -export([
35 36
         init/0,
@@ -61,6 +62,12 @@
61 62
         ntohl/1,
62 63
         ntohs/1
63 64
     ]).
  65
+% for debugging
  66
+-export([
  67
+    get_progname/2,
  68
+    make_cli_args/1,
  69
+    progname/0
  70
+    ]).
64 71
 
65 72
 -on_load(on_load/0).
66 73
 
@@ -72,7 +79,9 @@ on_load() ->
72 79
     erlang:load_nif(progname(), []).
73 80
 
74 81
 
75  
-
  82
+%%--------------------------------------------------------------------
  83
+%%% NIF Stubs
  84
+%%--------------------------------------------------------------------
76 85
 close(_) ->
77 86
     erlang:error(not_implemented).
78 87
 
@@ -149,55 +158,111 @@ errno_id(_) ->
149 158
     erlang:error(not_implemented).
150 159
 
151 160
 
  161
+%%--------------------------------------------------------------------
  162
+%%% Setuid helper
  163
+%%--------------------------------------------------------------------
152 164
 dev(Dev) when is_list(Dev) ->
153 165
     open(0, [{dev, Dev}]).
154 166
 
155 167
 open(Port) ->
156 168
     open(Port, []).
157 169
 open(Port, Options) when is_integer(Port), is_list(Options) ->
158  
-    Opt = case proplists:get_value(pipe, Options) of
159  
-        undefined ->
160  
-            Tmp = procket_mktmp:dirname(),
161  
-            ok = procket_mktmp:make_dir(Tmp),
162  
-            Path = Tmp ++ "/sock",
163  
-            [{pipe, Path}, {tmpdir, Tmp}] ++ Options;
164  
-        _ ->
165  
-            [{tmpdir, false}] ++ Options
166  
-    end,
167  
-    Result = open_1(Port, Opt),
168  
-    case proplists:get_value(tmpdir, Options) of
169  
-        false ->
170  
-            ok;
171  
-        Tmp2 ->
172  
-            procket_mktmp:close(Tmp2)
  170
+    Progname = get_progname(progname(), Options),
  171
+    {Tmpdir, Pipe} = make_unix_socket_path(Options),
  172
+    Cmd = make_cli_args([
  173
+                {progname, Progname},
  174
+                {port, Port},
  175
+                {pipe, Pipe}
  176
+                ] ++ Options),
  177
+
  178
+    Result = case fdopen(Pipe) of
  179
+        {ok, FD} ->
  180
+            Socket = exec(FD, Cmd),
  181
+            close(FD),
  182
+            Socket;
  183
+        Error ->
  184
+            Error
173 185
     end,
  186
+
  187
+    cleanup_unix_socket(Tmpdir, Pipe),
174 188
     Result.
175 189
 
176  
-open_1(Port, Options) ->
177  
-    case open_1(Port, Options, false) of
178  
-        {error, Error} when Error == eacces; Error == eperm ->
179  
-            open_1(Port, Options, true);
180  
-        Result ->
181  
-            Result
182  
-    end.
  190
+% Figure out how the procket helper should be called.
  191
+get_progname(Progname, Options) ->
  192
+    get_progname(progname, Progname, Options).
183 193
 
184  
-open_1(Port, Options, UseSudo) ->
185  
-    Pipe = proplists:get_value(pipe, Options),
186  
-    {ok, Sockfd} = fdopen(Pipe),
187  
-    Cmd = make_args(Port, Options, UseSudo),
  194
+% Caller has passed in a path?
  195
+get_progname(progname, Default, Options) ->
  196
+    case proplists:get_value(progname, Options) of
  197
+        undefined ->
  198
+            get_progname(setuid, Default, Options);
  199
+        Progname ->
  200
+            Progname
  201
+    end;
  202
+
  203
+% Is the default executable setuid/setgid?
  204
+get_progname(setuid, Progname, Options) ->
  205
+    case file:read_file_info(Progname) of
  206
+        {ok, #file_info{mode = Mode}} ->
  207
+            if
  208
+                % setuid
  209
+                Mode band 16#800 =:= 16#800 ->
  210
+                    Progname;
  211
+                % setgid
  212
+                Mode band 16#400 =:= 16#400 ->
  213
+                    Progname;
  214
+                true ->
  215
+                    get_progname(dev, Progname, Options)
  216
+            end
  217
+    end;
  218
+
  219
+% Device requested and accessible?
  220
+get_progname(dev, Progname, Options) ->
  221
+    case proplists:get_value(dev, Options) of
  222
+        undefined ->
  223
+            get_progname(sudo, Progname, Options);
  224
+        Dev ->
  225
+            case file:read_file_info(Dev) of
  226
+                {ok, #file_info{access = read_write}} ->
  227
+                    Progname;
  228
+                {ok, _} ->
  229
+                    get_progname(sudo, Progname, Options);
  230
+                Error ->
  231
+                    Error
  232
+            end
  233
+    end;
  234
+
  235
+% Fall back to sudo
  236
+get_progname(sudo, Progname, _Options) ->
  237
+    "sudo " ++ Progname.
  238
+
  239
+% Run the setuid helper
  240
+exec(FD, Cmd) ->
188 241
     case os:cmd(Cmd) of
189 242
         "0" ->
190  
-            FD = fdget(Sockfd),
191  
-            cleanup(Sockfd, Pipe),
192  
-            FD;
  243
+            fdget(FD);
193 244
         Error ->
194  
-            cleanup(Sockfd, Pipe),
195 245
             {error, errno_id(list_to_integer(Error))}
196 246
     end.
197 247
 
198  
-cleanup(Sockfd, Pipe) ->
199  
-    close(Sockfd),
200  
-    ok = file:delete(Pipe).
  248
+% Unix socket handling: retrieves the fd from the setuid helper
  249
+make_unix_socket_path(Options) ->
  250
+    {Tmpdir, Socket} = case proplists:get_value(pipe, Options) of
  251
+        undefined ->
  252
+            Tmp = procket_mktmp:dirname(),
  253
+            ok = procket_mktmp:make_dir(Tmp),
  254
+            Path = Tmp ++ "/sock",
  255
+            {Tmp, Path};
  256
+        Path ->
  257
+            {false, Path}
  258
+    end,
  259
+    {Tmpdir, Socket}.
  260
+
  261
+cleanup_unix_socket(false, Pipe) ->
  262
+    file:delete(Pipe);
  263
+cleanup_unix_socket(Tmpdir, Pipe) ->
  264
+    file:delete(Pipe),
  265
+    procket_mktmp:close(Tmpdir).
201 266
 
202 267
 fdopen(Path) when is_list(Path) ->
203 268
     fdopen(list_to_binary(Path));
@@ -216,27 +281,19 @@ fdget(Socket) ->
216 281
     {ok, S} = accept(Socket),
217 282
     fdrecv(S).
218 283
 
219  
-make_args(Port, Options, UseSudo) ->
220  
-    Args = reorder_args(Port, Options),
221  
-    Prefix = case UseSudo of
222  
-        true ->
223  
-            "sudo ";
224  
-        false ->
225  
-            ""
  284
+% Construct the cli arguments for the helper
  285
+make_cli_args(Options) ->
  286
+    {[[{progname,Progname}|_],
  287
+      IPaddrs], Rest} = proplists:split(Options, [progname, ip]),
  288
+    IP = case IPaddrs of
  289
+        [] -> [];
  290
+        [Addr|_] -> Addr
226 291
     end,
227  
-    proplists:get_value(progname, Options, Prefix ++ progname()) ++ " " ++
  292
+    Args = Rest ++ [IP],
  293
+    Progname ++ " " ++
228 294
     string:join([ get_switch(Arg) || Arg <- Args ], " ") ++
229 295
     " > /dev/null 2>&1; printf $?".
230 296
 
231  
-reorder_args(Port, Options) ->
232  
-    NewOpts = case proplists:lookup(ip, Options) of
233  
-        none ->
234  
-            Options;
235  
-        IP ->
236  
-            proplists:delete(ip, Options) ++ [IP]
237  
-    end,
238  
-    [{port, Port}] ++ NewOpts.
239  
-
240 297
 get_switch({pipe, Arg}) ->
241 298
     "-u " ++ Arg;
242 299
 
@@ -309,8 +366,8 @@ progname_priv() ->
309 366
 progname() ->
310 367
     % Is there a proper way of getting App-Name in this context?
311 368
     case code:priv_dir( ?MODULE ) of
312  
-        {error,bad_name} -> progname_ebin();
313  
-        ________________ -> progname_priv()
  369
+        {error, bad_name} -> progname_ebin();
  370
+        _ -> progname_priv()
314 371
     end.
315 372
 
316 373
 %% Protocol family (aka domain)

0 notes on commit 2a30b69

Please sign in to comment.
Something went wrong with that request. Please try again.