aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIngela Anderton Andin <[email protected]>2015-09-08 16:39:38 +0200
committerIngela Anderton Andin <[email protected]>2015-09-10 09:04:54 +0200
commit628553c2946649ef1715feacc05e8f75d38ef2e3 (patch)
tree4e3c55305a35454ad20e87783fe7838c5c184389
parentda6854e9557a045ae1a858c6f2949594228341f7 (diff)
downloadotp-628553c2946649ef1715feacc05e8f75d38ef2e3.tar.gz
otp-628553c2946649ef1715feacc05e8f75d38ef2e3.tar.bz2
otp-628553c2946649ef1715feacc05e8f75d38ef2e3.zip
inets: Add test suite for socket_wrap feature
On Linux systems a socket may be opened pre starting Erlang and then passed to Erlangs ftpd or httpd daemon. This is useful as the wrap program can open a privileged port and Erlang does not have to be run as root. The test program will only open a random port, but it exercises the code so that the feature will remain intact.
-rw-r--r--lib/inets/test/Makefile7
-rw-r--r--lib/inets/test/inets_socketwrap_SUITE.erl154
-rw-r--r--lib/inets/test/inets_socketwrap_SUITE_data/Makefile.src39
-rw-r--r--lib/inets/test/inets_socketwrap_SUITE_data/setuid_socket_wrap.c259
-rw-r--r--lib/inets/test/inets_test_lib.erl9
5 files changed, 465 insertions, 3 deletions
diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile
index cae77a05f3..607ec7c182 100644
--- a/lib/inets/test/Makefile
+++ b/lib/inets/test/Makefile
@@ -174,7 +174,8 @@ MODULES = \
inets_appup_test \
tftp_test_lib \
tftp_SUITE \
- uri_SUITE
+ uri_SUITE \
+ inets_socketwrap_SUITE
EBIN = .
@@ -203,7 +204,7 @@ INETS_FILES = inets.config $(INETS_SPECS)
# inets_ftp_suite \
# inets_tftp_suite
-INETS_DATADIRS = inets_SUITE_data inets_sup_SUITE_data
+INETS_DATADIRS = inets_SUITE_data inets_socketwrap_SUITE_data
HTTPD_DATADIRS = httpd_test_data httpd_SUITE_data httpd_basic_SUITE_data old_httpd_SUITE_data
HTTPC_DATADIRS = httpc_SUITE_data httpc_proxy_SUITE_data
FTP_DATADIRS = ftp_SUITE_data
@@ -250,7 +251,7 @@ ERL_COMPILE_FLAGS += \
# 1) INETS_PRIV_DIR must be created
# ----------------------------------------------------
-tests debug opt: $(BUILDTARGET)
+tests debug opt: $(BUILDTARGET)
targets: $(TARGET_FILES)
diff --git a/lib/inets/test/inets_socketwrap_SUITE.erl b/lib/inets/test/inets_socketwrap_SUITE.erl
new file mode 100644
index 0000000000..cfbda3ccf5
--- /dev/null
+++ b/lib/inets/test/inets_socketwrap_SUITE.erl
@@ -0,0 +1,154 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+-module(inets_socketwrap_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include("inets_test_lib.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [start_httpd_fd, start_tftpd_fd].
+
+init_per_suite(Config) ->
+ case os:type() of
+ {unix, linux} ->
+ Config;
+ _ ->
+ {skip, linux_feature}
+ end.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_testcase(Case, Config) ->
+ end_per_testcase(Case, Config),
+ Config.
+
+end_per_testcase(_, Config) ->
+ inets:stop(),
+ Config.
+
+%%-------------------------------------------------------------------------
+start_httpd_fd() ->
+ [{doc, "Start/stop of httpd service with socket wrapper"}].
+start_httpd_fd(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ DataDir = ?config(data_dir, Config),
+ HttpdConf = [{port, 80}, {ipfamily, inet},
+ {server_name, "httpd_fd_test"}, {server_root, PrivDir},
+ {document_root, PrivDir}, {bind_address, any}],
+ case setup_node_info(node()) of
+ {skip, _} = Skip ->
+ Skip;
+ {Node, NodeArg} ->
+ InetPort = inets_test_lib:inet_port(node()),
+ ct:pal("Node: ~p Port ~p~n", [Node, InetPort]),
+ Wrapper = filename:join(DataDir, "setuid_socket_wrap"),
+ Cmd = Wrapper ++
+ " -s -httpd_80,0:" ++ integer_to_list(InetPort)
+ ++ " -p " ++ os:find_executable("erl") ++
+ " -- " ++ NodeArg,
+ ct:pal("cmd: ~p~n", [Cmd]),
+ case open_port({spawn, Cmd}, [stderr_to_stdout]) of
+ Port when is_port(Port) ->
+ wait_node_up(Node, 10),
+ ct:pal("~p", [rpc:call(Node, init, get_argument, [httpd_80])]),
+ ok = rpc:call(Node, inets, start, []),
+ {ok, Pid} = rpc:call(Node, inets, start, [httpd, HttpdConf]),
+ [{port, InetPort}] = rpc:call(Node, httpd, info, [Pid, [port]]),
+ rpc:call(Node, erlang, halt, []);
+ _ ->
+ ct:fail(open_port_failed)
+ end
+ end.
+%%-------------------------------------------------------------------------
+start_tftpd_fd() ->
+ [{doc, "Start/stop of tfpd service with socket wrapper"}].
+start_tftpd_fd(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ case setup_node_info(node()) of
+ {skip, _} = Skip ->
+ Skip;
+ {Node, NodeArg} ->
+ InetPort = inets_test_lib:inet_port(node()),
+ ct:pal("Node: ~p~n", [Node]),
+ Wrapper = filename:join(DataDir, "setuid_socket_wrap"),
+ Cmd = Wrapper ++
+ " -s -tftpd_69,0:" ++ integer_to_list(InetPort)
+ ++ " -p " ++ os:find_executable("erl") ++
+ " -- " ++ NodeArg,
+ ct:pal("cmd: ~p~n", [Cmd]),
+ case open_port({spawn, Cmd}, [stderr_to_stdout]) of
+ Port when is_port(Port) ->
+ wait_node_up(Node, 10),
+ ct:pal("~p", [rpc:call(Node, init, get_argument, [tftpd_69])]),
+ ok = rpc:call(Node, inets, start, []),
+ {ok, Pid} = rpc:call(Node, inets, start,
+ [tftpd,[{host, "localhost"}]]),
+ {ok, Info} = rpc:call(Node, tftp, info, [Pid]),
+ {value,{port, InetPort}} = lists:keysearch(port, 1, Info),
+ rpc:call(Node, erlang, halt, []);
+ _ ->
+ ct:fail(open_port_failed)
+ end
+ end.
+%%-------------------------------------------------------------------------
+%% Internal functions
+%%-------------------------------------------------------------------------
+setup_node_info(nonode@nohost) ->
+ {skip, needs_distributed_node};
+setup_node_info(Node) ->
+ Static = "-detached -noinput",
+ Name = "inets_fd_test",
+ NameSw = case net_kernel:longnames() of
+ false -> "-sname ";
+ _ -> "-name "
+ end,
+ StrNode =
+ Static ++ " "
+ ++ NameSw ++ " " ++ Name ++ " "
+ ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()),
+ [_, Location] = string:tokens(atom_to_list(Node), "$@"),
+ TestNode = Name ++ "@" ++ Location,
+ {list_to_atom(TestNode), StrNode}.
+
+wait_node_up(Node, 0) ->
+ ct:fail({failed_to_start_node, Node});
+wait_node_up(Node, N) ->
+ ct:pal("(Node ~p: net_adm:ping(~p)~n", [node(), Node]),
+ case net_adm:ping(Node) of
+ pong ->
+ ok;
+ pang ->
+ ct:sleep(5000),
+ wait_node_up(Node, N-1)
+ end.
diff --git a/lib/inets/test/inets_socketwrap_SUITE_data/Makefile.src b/lib/inets/test/inets_socketwrap_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..0933815b58
--- /dev/null
+++ b/lib/inets/test/inets_socketwrap_SUITE_data/Makefile.src
@@ -0,0 +1,39 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2015-2015. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# %CopyrightEnd%
+#
+
+CC = @CC@
+LD = @LD@
+CFLAGS = @CFLAGS@
+CROSSLDFLAGS = @CROSSLDFLAGS@
+
+PROGS = setuid_socket_wrap@exe@
+
+.PHONY: all
+@IFEQ@ (@os@, linux-gnu)
+all: $(PROGS)
+@ELSE@
+all:
+@ENDIF@
+
+setuid_socket_wrap@exe@: setuid_socket_wrap@obj@
+ $(LD) $(CROSSLDFLAGS) -o setuid_socket_wrap setuid_socket_wrap@obj@ @LIBS@
+
+setuid_socket_wrap@obj@: setuid_socket_wrap.c
+ $(CC) -c $(CFLAGS) -o setuid_socket_wrap@obj@ setuid_socket_wrap.c
diff --git a/lib/inets/test/inets_socketwrap_SUITE_data/setuid_socket_wrap.c b/lib/inets/test/inets_socketwrap_SUITE_data/setuid_socket_wrap.c
new file mode 100644
index 0000000000..b28f6b1c08
--- /dev/null
+++ b/lib/inets/test/inets_socketwrap_SUITE_data/setuid_socket_wrap.c
@@ -0,0 +1,259 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1999-2009. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+/*
+ * setuid_socket_wrap.c
+ *
+ * ./a.out [-s [tag,][addr]:[port]]* [-d [tag,][addr]:[port]]*
+ * [-r [tag,]proto]* [-p erl_path]* -- program args
+ *
+ * Where: -s = stream socket, -d datagram socket and -r means raw socket.
+ *
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#ifndef INADDR_NONE
+#define INADDR_NONE 0xffffffff
+#endif
+
+struct sock_list {
+ struct sock_list *next;
+ int fd;
+ int type;
+ int protocol;
+ struct sockaddr_in addr;
+ char *arg;
+};
+
+int parse_addr(addr, str)
+ struct sockaddr_in *addr;
+ char *str;
+{
+ int port = 0;
+ char *cp;
+ struct hostent *hp;
+ struct servent *se;
+
+ if ((cp = strrchr(str, (int)':')) != NULL)
+ *cp++ = '\0';
+ if (cp) {
+ if (!isdigit((int)cp[0])) {
+ if ((se = getservbyname(cp, "tcp")) != NULL) {
+ port = ntohs(se->s_port);
+ } else {
+ fprintf(stderr, "unknown port %s\n", cp);
+ return -1;
+ }
+ } else {
+ port = atoi(cp);
+ }
+ }
+ if (port < 0 || port > 0xffff) {
+ fprintf(stderr, "bad port number %d\n", port);
+ return -1;
+ }
+
+ bzero(addr, sizeof(*addr));
+ addr->sin_family = AF_INET;
+ addr->sin_port = htons(port);
+ if (*str == '\000') {
+ addr->sin_addr.s_addr = INADDR_ANY;
+ } else {
+ if ((addr->sin_addr.s_addr = inet_addr(str)) == INADDR_NONE) {
+ if ((hp = gethostbyname(str)) == NULL) {
+ fprintf(stderr, "\"%s\" unknown host or address!\n", str);
+ return -1;
+ } else {
+ bcopy(hp->h_addr_list[0], &addr->sin_addr.s_addr,hp->h_length);
+ }
+ }
+ }
+ return 0;
+}
+
+struct sock_list *new_entry(type, argstr)
+ int type;
+ char *argstr;
+{
+ struct sock_list *sle;
+ char *cp;
+
+ sle = (struct sock_list *)malloc(sizeof(struct sock_list));
+ if (!sle)
+ return NULL;
+ sle->next = NULL;
+ sle->fd = -1;
+
+ if ((cp = strchr(argstr, (int)',')) != NULL) {
+ *cp++ = '\0';
+ sle->arg = argstr;
+ argstr = cp;
+ } else {
+ sle->arg = "-fd";
+ }
+ sle->type = type;
+ switch (type) {
+ case SOCK_RAW: {
+ struct protoent *pe;
+ pe = getprotobyname(argstr);
+ if (!pe) {
+ fprintf(stderr, "Unknown protocol: %s\n", argstr);
+ free(sle);
+ return NULL;
+ }
+ sle->protocol = pe->p_proto;
+ break;
+ }
+ case SOCK_STREAM:
+ case SOCK_DGRAM:
+ sle->protocol = 0;
+ if (parse_addr(&sle->addr, argstr) < 0) {
+ free(sle);
+ return NULL;
+ }
+ break;
+ }
+ return sle;
+}
+
+int open_socket(sle)
+ struct sock_list *sle;
+{
+ sle->fd = socket(AF_INET, sle->type, sle->protocol);
+ if (sle->fd < 0) {
+ perror("socket");
+ return -1;
+ }
+ if (sle->type != SOCK_RAW) {
+#if 0
+ printf("binding fd %d to %s:%d\n", sle->fd,
+ inet_ntoa(sle->addr.sin_addr), ntohs(sle->addr.sin_port));
+#endif
+ if (bind(sle->fd, (struct sockaddr *)&sle->addr, sizeof(sle->addr))<0){
+ perror("bind");
+ close(sle->fd);
+ return -1;
+ }
+ }
+ return sle->fd;
+}
+
+int main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ struct sock_list *sl = NULL, *sltmp = NULL;
+ int count = 0;
+ int c;
+ char *run_prog = NULL;
+
+ while ((c = getopt(argc, argv, "s:d:r:p:")) != EOF)
+ switch (c) {
+ case 's':
+ sltmp = new_entry(SOCK_STREAM, optarg);
+ if (!sltmp) {
+ exit(1);
+ }
+ sltmp->next = sl;
+ sl = sltmp;
+ count++;
+ break;
+ case 'd':
+ sltmp = new_entry(SOCK_DGRAM, optarg);
+ if (!sltmp) {
+ exit(1);
+ }
+ sltmp->next = sl;
+ sl = sltmp;
+ count++;
+ break;
+ case 'r':
+ sltmp = new_entry(SOCK_RAW, optarg);
+ if (!sltmp) {
+ exit(1);
+ }
+ sltmp->next = sl;
+ sl = sltmp;
+ count++;
+ break;
+ case 'p':
+ run_prog = optarg;
+ break;
+ default:
+ exit(1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ for(sltmp = sl; sltmp != NULL; sltmp = sltmp->next)
+ if (open_socket(sltmp) < 0) {
+ fprintf(stderr, "failed to create socket!\n");
+ exit(1);
+ }
+
+ setuid(getuid());
+
+ {
+ int i;
+ char **newargv;
+ char *run_prog_name;
+
+ newargv = (char **)malloc((1 + 2*count + argc + 1) * sizeof(char*));
+
+ if ((run_prog_name = strrchr(run_prog, (int)'/')) == NULL)
+ run_prog_name = run_prog;
+ else
+ run_prog_name++;
+
+ i = 0;
+ newargv[i++] = run_prog_name;
+
+ for (; argc; argc--, argv++, i++)
+ newargv[i] = *argv;
+ for(sltmp = sl; sltmp != NULL; ) {
+ char *fd_str = (char *)malloc(8);
+ if (!fd_str) exit(1);
+ sprintf(fd_str, "%d", sltmp->fd);
+ if (sltmp->arg && *(sltmp->arg))
+ newargv[i++] = sltmp->arg;
+ newargv[i++] = fd_str;
+ sl = sltmp;
+ sltmp = sltmp->next;
+ free(sl);
+ }
+ newargv[i] = (char *)NULL;
+ execv(run_prog, newargv);
+ perror("exec");
+ exit(1);
+ }
+ exit(0);
+}
diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl
index b471dcf784..f1185f7574 100644
--- a/lib/inets/test/inets_test_lib.erl
+++ b/lib/inets/test/inets_test_lib.erl
@@ -563,3 +563,12 @@ stop_apps(Apps) ->
application:stop(App)
end, Apps).
+inet_port(Node) ->
+ {Port, Socket} = do_inet_port(Node),
+ rpc:call(Node, gen_tcp, close, [Socket]),
+ Port.
+
+do_inet_port(Node) ->
+ {ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]),
+ {ok, Port} = rpc:call(Node, inet, port, [Socket]),
+ {Port, Socket}.