diff options
Diffstat (limited to 'erts/epmd')
-rw-r--r-- | erts/epmd/src/Makefile.in | 25 | ||||
-rw-r--r-- | erts/epmd/src/epmd.c | 53 | ||||
-rw-r--r-- | erts/epmd/src/epmd.h | 21 | ||||
-rw-r--r-- | erts/epmd/src/epmd_cli.c | 8 | ||||
-rw-r--r-- | erts/epmd/src/epmd_int.h | 9 | ||||
-rw-r--r-- | erts/epmd/src/epmd_srv.c | 212 | ||||
-rw-r--r-- | erts/epmd/test/epmd_SUITE.erl | 356 |
7 files changed, 429 insertions, 255 deletions
diff --git a/erts/epmd/src/Makefile.in b/erts/epmd/src/Makefile.in index 498756b468..70a54fe46e 100644 --- a/erts/epmd/src/Makefile.in +++ b/erts/epmd/src/Makefile.in @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1998-2009. All Rights Reserved. +# Copyright Ericsson AB 1998-2010. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -49,15 +49,27 @@ include ../epmd.mk BINDIR = $(ERL_TOP)/bin/$(TARGET) OBJDIR = $(ERL_TOP)/erts/obj$(TYPEMARKER)/$(TARGET) +ERTS_INCL = -I$(ERL_TOP)/erts/include \ + -I$(ERL_TOP)/erts/include/$(TARGET) \ + -I$(ERL_TOP)/erts/include/internal \ + -I$(ERL_TOP)/erts/include/internal/$(TARGET) + +# On windows we always need reentrant libraries. +ifeq ($(TARGET),win32) +ERTS_INTERNAL_LIBS=-L../../lib/internal/$(TARGET) -lerts_internal_r$(ERTS_LIB_TYPEMARKER) @ERTS_INTERNAL_X_LIBS@ +else +ERTS_INTERNAL_LIBS=-L../../lib/internal/$(TARGET) -lerts_internal$(ERTS_LIB_TYPEMARKER) @ERTS_INTERNAL_X_LIBS@ -lm +endif CC = @CC@ WFLAGS = @WFLAGS@ -CFLAGS = @CFLAGS@ @DEFS@ $(TYPE_FLAGS) $(WFLAGS) +CFLAGS = @CFLAGS@ @DEFS@ $(TYPE_FLAGS) $(WFLAGS) $(ERTS_INCL) LD = @LD@ -LIBS = @LIBS@ +LIBS = @LIBS@ $(ERTS_INTERNAL_LIBS) LDFLAGS = @LDFLAGS@ + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- @@ -90,7 +102,7 @@ EPMD_OBJS = $(OBJDIR)/epmd.o \ #--------------------------------- -all: $(BINDIR)/$(EPMD) +all: erts_lib $(BINDIR)/$(EPMD) docs: @@ -109,9 +121,12 @@ clean: $(BINDIR)/$(EPMD): $(EPMD_OBJS) $(PURIFY) $(LD) $(LDFLAGS) -o $@ $(EPMD_OBJS) $(LIBS) -$(OBJDIR)/%.o: %.c +$(OBJDIR)/%.o: %.c epmd.h epmd_int.h $(CC) $(CFLAGS) $(EPMD_FLAGS) -o $@ -c $< +erts_lib: + cd $(ERL_TOP)/erts/lib_src && $(MAKE) $(TYPE) + include $(ERL_TOP)/make/otp_release_targets.mk release_spec: all diff --git a/erts/epmd/src/epmd.c b/erts/epmd/src/epmd.c index 9c2ce065bb..65ff0cd6b2 100644 --- a/erts/epmd/src/epmd.c +++ b/erts/epmd/src/epmd.c @@ -23,6 +23,7 @@ #endif #include "epmd.h" /* Renamed from 'epmd_r4.h' */ #include "epmd_int.h" +#include "erl_printf.h" #ifdef HAVE_STDLIB_H # include <stdlib.h> @@ -33,6 +34,7 @@ static void usage(EpmdVars *); static void run_daemon(EpmdVars*); static int get_port_no(void); +static int check_relaxed(void); #ifdef __WIN32__ static int has_console(void); #endif @@ -161,6 +163,7 @@ int main(int argc, char** argv) g->silent = 0; g->is_daemon = 0; + g->brutal_kill = check_relaxed(); g->packet_timeout = CLOSE_TIMEOUT; /* Default timeout */ g->delay_accept = 0; g->delay_write = 0; @@ -196,6 +199,9 @@ int main(int argc, char** argv) } else if (strcmp(argv[0], "-daemon") == 0) { g->is_daemon = 1; argv++; argc--; + } else if (strcmp(argv[0], "-relaxed_command_check") == 0) { + g->brutal_kill = 1; + argv++; argc--; } else if (strcmp(argv[0], "-kill") == 0) { if (argc == 1) kill_epmd(g); @@ -388,9 +394,10 @@ static void run_daemon(EpmdVars *g) static void usage(EpmdVars *g) { fprintf(stderr, "usage: epmd [-d|-debug] [DbgExtra...] [-port No] [-daemon]\n"); - fprintf(stderr, " [-d|-debug] [-port No] [-names|-kill|-stop name]\n\n"); - fprintf(stderr, "See the Erlang epmd manual page for info about the usage.\n"); - fprintf(stderr, "The -port and DbgExtra options are\n\n"); + fprintf(stderr, " [-relaxed_command_check]\n"); + fprintf(stderr, " epmd [-d|-debug] [-port No] [-names|-kill|-stop name]\n\n"); + fprintf(stderr, "See the Erlang epmd manual page for info about the usage.\n\n"); + fprintf(stderr, "Regular options\n"); fprintf(stderr, " -port No\n"); fprintf(stderr, " Let epmd listen to another port than default %d\n", EPMD_PORT_NO); @@ -400,8 +407,16 @@ static void usage(EpmdVars *g) fprintf(stderr, " the standard error stream. It will shorten\n"); fprintf(stderr, " the number of saved used node names to 5.\n\n"); fprintf(stderr, " If you give more than one debug flag you may\n"); - fprintf(stderr, " get more debugging information.\n\n"); - fprintf(stderr, " -packet_timout Seconds\n"); + fprintf(stderr, " get more debugging information.\n"); + fprintf(stderr, " -daemon\n"); + fprintf(stderr, " Start epmd detached (as a daemon)\n"); + fprintf(stderr, " -relaxed_command_check\n"); + fprintf(stderr, " Allow this instance of epmd to be killed with\n"); + fprintf(stderr, " epmd -kill even if there " + "are registered nodes.\n"); + fprintf(stderr, " Also allows forced unregister (epmd -stop).\n"); + fprintf(stderr, "\nDbgExtra options\n"); + fprintf(stderr, " -packet_timeout Seconds\n"); fprintf(stderr, " Set the number of seconds a connection can be\n"); fprintf(stderr, " inactive before epmd times out and closes the\n"); fprintf(stderr, " connection (default 60).\n\n"); @@ -413,6 +428,18 @@ static void usage(EpmdVars *g) fprintf(stderr, " -delay_write Seconds\n"); fprintf(stderr, " Also a simulation of a busy server. Inserts\n"); fprintf(stderr, " a delay before a reply is sent.\n"); + fprintf(stderr, "\nInteractive options\n"); + fprintf(stderr, " -names\n"); + fprintf(stderr, " List names registered with the currently " + "running epmd\n"); + fprintf(stderr, " -kill\n"); + fprintf(stderr, " Kill the currently runniing epmd\n"); + fprintf(stderr, " (only allowed if -names show empty database or\n"); + fprintf(stderr, " -relaxed_command_check was given when epmd was started).\n"); + fprintf(stderr, " -stop Name\n"); + fprintf(stderr, " Forcibly unregisters a name with epmd\n"); + fprintf(stderr, " (only allowed if -relaxed_command_check was given when \n"); + fprintf(stderr, " epmd was started).\n"); epmd_cleanup_exit(g,1); } @@ -432,20 +459,20 @@ static void usage(EpmdVars *g) * args... Arguments to print out according to the format * */ - +#define DEBUG_BUFFER_SIZE 2048 static void dbg_gen_printf(int onsyslog,int perr,int from_level, EpmdVars *g,const char *format, va_list args) { time_t now; char *timestr; - char buf[2048]; + char buf[DEBUG_BUFFER_SIZE]; if (g->is_daemon) { #ifndef NO_SYSLOG if (onsyslog) { - vsprintf(buf, format, args); + erts_vsnprintf(buf, DEBUG_BUFFER_SIZE, format, args); syslog(LOG_ERR,"epmd: %s",buf); } #endif @@ -456,9 +483,10 @@ static void dbg_gen_printf(int onsyslog,int perr,int from_level, time(&now); timestr = (char *)ctime(&now); - sprintf(buf, "epmd: %.*s: ", (int) strlen(timestr)-1, timestr); + erts_snprintf(buf, DEBUG_BUFFER_SIZE, "epmd: %.*s: ", + (int) strlen(timestr)-1, timestr); len = strlen(buf); - vsprintf(buf + len, format, args); + erts_vsnprintf(buf + len, DEBUG_BUFFER_SIZE - len, format, args); if (perr == 1) perror(buf); else @@ -545,4 +573,9 @@ static int get_port_no(void) char* port_str = getenv("ERL_EPMD_PORT"); return (port_str != NULL) ? atoi(port_str) : EPMD_PORT_NO; } +static int check_relaxed(void) +{ + char* port_str = getenv("ERL_EPMD_RELAXED_COMMAND_CHECK"); + return (port_str != NULL) ? 1 : 0; +} diff --git a/erts/epmd/src/epmd.h b/erts/epmd/src/epmd.h index 9e939ee38e..5d6e9ac165 100644 --- a/erts/epmd/src/epmd.h +++ b/erts/epmd/src/epmd.h @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * Copyright Ericsson AB 1998-2010. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -17,21 +17,18 @@ * %CopyrightEnd% */ -/* The port number is now defined in a makefile */ +/* The port number is defined in a makefile */ /* Definitions of message codes */ -#define EPMD_ALIVE_REQ 'a' -#define EPMD_ALIVE_OK_RESP 'Y' -#define EPMD_PORT_REQ 'p' +/* Registration and queries */ +#define EPMD_ALIVE2_REQ 'x' +#define EPMD_PORT2_REQ 'z' +#define EPMD_ALIVE2_RESP 'y' +#define EPMD_PORT2_RESP 'w' #define EPMD_NAMES_REQ 'n' + +/* Interactive client command codes */ #define EPMD_DUMP_REQ 'd' #define EPMD_KILL_REQ 'k' #define EPMD_STOP_REQ 's' - -/* New epmd messages */ - -#define EPMD_ALIVE2_REQ 'x' /* 120 */ -#define EPMD_PORT2_REQ 'z' /* 122 */ -#define EPMD_ALIVE2_RESP 'y' /* 121 */ -#define EPMD_PORT2_RESP 'w' /* 119 */ diff --git a/erts/epmd/src/epmd_cli.c b/erts/epmd/src/epmd_cli.c index 2aed861390..7c60ba0420 100644 --- a/erts/epmd/src/epmd_cli.c +++ b/erts/epmd/src/epmd_cli.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * Copyright Ericsson AB 1998-2010. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -42,7 +42,11 @@ void kill_epmd(EpmdVars *g) epmd_cleanup_exit(g,1); } if ((rval = read_fill(fd,buf,2)) == 2) { - printf("Killed\n"); + if (buf[0] == 'O' && buf[1] == 'K') { + printf("Killed\n"); + } else { + printf("Killing not allowed - living nodes in database.\n"); + } epmd_cleanup_exit(g,0); } else if (rval < 0) { printf("epmd: failed to read answer from local epmd\n"); diff --git a/erts/epmd/src/epmd_int.h b/erts/epmd/src/epmd_int.h index 5ead553f36..c2558d52a1 100644 --- a/erts/epmd/src/epmd_int.h +++ b/erts/epmd/src/epmd_int.h @@ -1,3 +1,4 @@ +/* -*- c-indent-level: 2; c-continued-statement-offset: 2 -*- */ /* * %CopyrightBegin% * @@ -245,8 +246,10 @@ typedef struct { int fd; /* File descriptor */ - unsigned open:1; /* TRUE if open */ - unsigned keep:1; /* Don't close when sent reply */ + unsigned char open; /* TRUE if open */ + unsigned char keep; /* Don't close when sent reply */ + unsigned char local_peer; /* The peer of this connection is via + loopback interface */ unsigned got; /* # of bytes we have got */ unsigned want; /* Number of bytes we want */ char *buf; /* The remaining buffer */ @@ -286,6 +289,7 @@ typedef struct { int debug; int silent; int is_daemon; + int brutal_kill; unsigned packet_timeout; unsigned delay_accept; unsigned delay_write; @@ -307,6 +311,7 @@ void epmd_call(EpmdVars*,int); void run(EpmdVars*); void epmd_cleanup_exit(EpmdVars*, int); int epmd_conn_close(EpmdVars*,Connection*); +void stop_cli(EpmdVars *g, char *name); #ifdef DONT_USE_MAIN int start_epmd(char *,char *,char *,char *,char *,char *,char *,char *,char *,char *); diff --git a/erts/epmd/src/epmd_srv.c b/erts/epmd/src/epmd_srv.c index c836bf0bb7..df4d1a5715 100644 --- a/erts/epmd/src/epmd_srv.c +++ b/erts/epmd/src/epmd_srv.c @@ -47,38 +47,6 @@ * | Length | Request | * +--------+---------+ * - * In all but one case there is only one request for each connection made - * to this server so we can safely close the socket after sending the - * reply. The exception is ALIVE_REQ where we keep the connection - * open without sending any data. When we receive a "close" this is - * an indication that the Erlang node was terminated. The termination - * may have been "normal" or caused by a crash. The operating system - * ensure that the connection is closed either way. - * - * Reading is done non-blocking, i.e. we call a "read" only if we are - * told by the "select" function that there are data to read. - * - * Two databases are used: One node database where the registered names - * of the nodes are stored, and one connection database where the state - * of sockets and the data buffers is stored. - * - * Incomplete packets are thrown away after a timout. The Erlang node - * doing the request is responsible for completing in it in a reasonable time. - * - * Note that if the server gets busy it may not have time to - * process all requests for connection. The "accept()" function - * will on most operating systems silently refuse to accept more - * than 5 outstanding requests. It is the client's responsibility - * to retry the request a number of times with random time interval. - * The "-debug" flag will insert a delay so you can test this - * behaviour. - * - * FIXME: In this code we assume that the packets we send on each - * socket is so small that a "write()" never block - * - * FIXME: We never restarts a read or write that was terminated - * by an interrupt. Do we need to? - * */ /* We use separate data structures for node names and connections @@ -98,7 +66,6 @@ static int conn_open(EpmdVars*,int); static int conn_close_fd(EpmdVars*,int); static void node_init(EpmdVars*); -static Node *node_reg(EpmdVars*,char*,int,int); static Node *node_reg2(EpmdVars*,char*, int, int, unsigned char, unsigned char, int, int, int, char*); static int node_unreg(EpmdVars*,char*); static int node_unreg_sock(EpmdVars*,int); @@ -303,11 +270,9 @@ static void do_read(EpmdVars *g,Connection *s) s->fd,val); dbg_print_buf(g,s->buf,val); - /* FIXME: Shouldn't be needed to close down.... */ node_unreg_sock(g,s->fd); epmd_conn_close(g,s); } - /* FIXME: We always close, probably the right thing to do */ return; } @@ -405,6 +370,8 @@ static int do_accept(EpmdVars *g,int listensock) return conn_open(g,msgsock); } +/* buf is actually one byte larger than bsize, + giving place for null termination */ static void do_request(g, fd, s, buf, bsize) EpmdVars *g; int fd; @@ -415,117 +382,23 @@ static void do_request(g, fd, s, buf, bsize) char wbuf[OUTBUF_SIZE]; /* Buffer for writing */ int i; - /* - * Terminate packet as a C string. Needed for requests received from Erlang - * nodes with lower version than R3A. - */ - - buf[bsize] = '\0'; + buf[bsize] = '\0'; /* Needed for strcmp in PORT2 and STOP requests + buf is always large enough */ switch (*buf) { - case EPMD_ALIVE_REQ: - dbg_printf(g,1,"** got ALIVE_REQ"); - - /* The packet has the format "axxyyyyyy" where xx is port, given - in network byte order, and yyyyyy is symname, possibly null - terminated. */ - - if (buf[bsize - 1] == '\000') /* Skip null termination */ - bsize--; - - if (bsize <= 3) - { - dbg_printf(g,0,"packet to small for request ALIVE_REQ (%d)", bsize); - return; - } - - for (i = 3; i < bsize; i++) - if (buf[i] == '\000') - { - dbg_printf(g,0,"node name contains ascii 0 in ALIVE_REQ"); - return; - } - - { - Node *node; - int eport; - char *name = &buf[3]; /* points to node name */ - - eport = get_int16(&buf[1]); - - if ((node = node_reg(g, name, fd, eport)) == NULL) - return; - - wbuf[0] = EPMD_ALIVE_OK_RESP; - put_int16(node->creation, wbuf+1); - - if (g->delay_write) /* Test of busy server */ - sleep(g->delay_write); - - if (reply(g, fd, wbuf, 3) != 3) - { - dbg_tty_printf(g,1,"failed to send ALIVE_OK_RESP for \"%s\"",name); - return; - } - - dbg_tty_printf(g,1,"** sent ALIVE_OK_RESP for \"%s\"",name); - s->keep = EPMD_TRUE; /* Don't close on inactivity */ - } - break; - - case EPMD_PORT_REQ: - dbg_printf(g,1,"** got PORT_REQ"); - - if (buf[bsize - 1] == '\000') /* Skip null termination */ - bsize--; - - if (bsize <= 1) - { - dbg_printf(g,0,"packet to small for request PORT_REQ (%d)", bsize); - return; - } - - for (i = 1; i < bsize; i++) - if (buf[i] == '\000') - { - dbg_printf(g,0,"node name contains ascii 0 in PORT_REQ"); - return; - } - - { - char *name = &buf[1]; /* Points to node name */ - Node *node; - - for (node = g->nodes.reg; node; node = node->next) - { - if (strcmp(node->symname, name) == 0) - { - put_int16(node->port,wbuf); - if (reply(g, fd, wbuf, 2) != 2) - { - dbg_tty_printf(g,1,"failed to send PORT_RESP for %s: %d", - name,node->port); - return; - } - dbg_tty_printf(g,1,"** sent PORT_RESP for %s: %d", - name,node->port); - return; - } - } - dbg_tty_printf(g,1,"Closed on PORT_REQ for %s",name); - } - /* FIXME: How about an answer if no port? Is a close enough? */ - break; - case EPMD_ALIVE2_REQ: dbg_printf(g,1,"** got ALIVE2_REQ"); + if (!s->local_peer) { + dbg_printf(g,0,"ALIVE2_REQ from non local address"); + return; + } /* The packet has the format "axxyyyyyy" where xx is port, given in network byte order, and yyyyyy is symname, possibly null terminated. */ - if (bsize <= 13) + if (bsize <= 13) /* at least one character for the node name */ { dbg_printf(g,0,"packet to small for request ALIVE2_REQ (%d)",bsize); return; @@ -548,7 +421,17 @@ static void do_request(g, fd, s, buf, bsize) highvsn = get_int16(&buf[5]); lowvsn = get_int16(&buf[7]); namelen = get_int16(&buf[9]); + if (namelen + 13 > bsize) { + dbg_printf(g,0,"Node name size error in ALIVE2_REQ"); + return; + } extralen = get_int16(&buf[11+namelen]); + + if (extralen + namelen + 13 > bsize) { + dbg_printf(g,0,"Extra info size error in ALIVE2_REQ"); + return; + } + for (i = 11 ; i < 11 + namelen; i ++) if (buf[i] == '\000') { dbg_printf(g,0,"node name contains ascii 0 in ALIVE2_REQ"); @@ -679,6 +562,10 @@ static void do_request(g, fd, s, buf, bsize) case EPMD_DUMP_REQ: dbg_printf(g,1,"** got DUMP_REQ"); + if (!s->local_peer) { + dbg_printf(g,0,"DUMP_REQ from non local address"); + return; + } { Node *node; @@ -728,7 +615,19 @@ static void do_request(g, fd, s, buf, bsize) break; case EPMD_KILL_REQ: + if (!s->local_peer) { + dbg_printf(g,0,"KILL_REQ from non local address"); + return; + } dbg_printf(g,1,"** got KILL_REQ"); + + if (!g->brutal_kill && (g->nodes.reg != NULL)) { + dbg_printf(g,0,"Disallowed KILL_REQ, live nodes"); + if (reply(g, fd,"NO",2) != 2) + dbg_printf(g,0,"failed to send reply to KILL_REQ"); + return; + } + if (reply(g, fd,"OK",2) != 2) dbg_printf(g,0,"failed to send reply to KILL_REQ"); dbg_tty_printf(g,1,"epmd killed"); @@ -738,6 +637,15 @@ static void do_request(g, fd, s, buf, bsize) case EPMD_STOP_REQ: dbg_printf(g,1,"** got STOP_REQ"); + if (!s->local_peer) { + dbg_printf(g,0,"STOP_REQ from non local address"); + return; + } + if (!g->brutal_kill) { + dbg_printf(g,0,"Disallowed STOP_REQ, no relaxed_command_check"); + return; + } + if (bsize <= 1 ) { dbg_printf(g,0,"packet too small for request STOP_REQ (%d)",bsize); @@ -825,6 +733,14 @@ static int conn_open(EpmdVars *g,int fd) for (i = 0; i < g->max_conn; i++) { if (g->conn[i].open == EPMD_FALSE) { + struct sockaddr_in si; +#ifdef HAVE_SOCKLEN_T + socklen_t st; +#else + int st; +#endif + st = sizeof(si); + g->active_conn++; s = &g->conn[i]; @@ -834,6 +750,20 @@ static int conn_open(EpmdVars *g,int fd) s->fd = fd; s->open = EPMD_TRUE; s->keep = EPMD_FALSE; + + /* Determine if connection is from localhost */ + if (getpeername(s->fd,(struct sockaddr*) &si,&st) || + st < sizeof(si)) { + /* Failure to get peername is regarder as non local host */ + s->local_peer = EPMD_FALSE; + } else { + s->local_peer = + ((((unsigned) ntohl(si.sin_addr.s_addr)) & 0xFF000000U) == + 0x7F000000U); /* Only 127.x.x.x allowed, no false positives */ + } + dbg_tty_printf(g,2,(s->local_peer) ? "Local peer connected" : + "Non-local peer connected"); + s->want = 0; /* Currently unknown */ s->got = 0; s->mod_time = current_time(g); /* Note activity */ @@ -990,11 +920,6 @@ static int node_unreg_sock(EpmdVars *g,int fd) * Perhaps use the oldest or something. */ -static Node *node_reg(EpmdVars *g,char *name,int fd, int port) -{ - return node_reg2(g, name, fd, port, 0, 0, 0, 0, 0, NULL); -} - static Node *node_reg2(EpmdVars *g, char* name, int fd, @@ -1020,6 +945,11 @@ static Node *node_reg2(EpmdVars *g, dbg_printf(g,0,"node name is too long (%d) %s", strlen(name), name); return NULL; } + if (extralen > MAXSYMLEN) + { + dbg_printf(g,0,"extra data is too long (%d) %s", strlen(name), name); + return NULL; + } /* Fail if it is already registered */ diff --git a/erts/epmd/test/epmd_SUITE.erl b/erts/epmd/test/epmd_SUITE.erl index 91e09faf75..da69412e12 100644 --- a/erts/epmd/test/epmd_SUITE.erl +++ b/erts/epmd/test/epmd_SUITE.erl @@ -63,7 +63,13 @@ alive_req_too_large/1, returns_valid_empty_extra/1, - returns_valid_populated_extra_with_nulls/1 + returns_valid_populated_extra_with_nulls/1, + buffer_overrun/1, + buffer_overrun_1/1, + buffer_overrun_2/1, + no_nonlocal_register/1, + no_nonlocal_kill/1, + no_live_killing/1 ]). @@ -82,11 +88,8 @@ -define(REG_REPEAT_LIM,1000). % Message codes in epmd protocol --define(EPMD_ALIVE_REQ, $a). -define(EPMD_ALIVE2_REQ, $x). --define(EPMD_ALIVE_OK_RESP, $Y). -define(EPMD_ALIVE2_RESP, $y). --define(EPMD_PORT_REQ, $p). -define(EPMD_PORT_PLEASE2_REQ, $z). -define(EPMD_PORT2_RESP, $w). -define(EPMD_NAMES_REQ, $n). @@ -124,7 +127,15 @@ all(suite) -> alive_req_too_large, returns_valid_empty_extra, - returns_valid_populated_extra_with_nulls + returns_valid_populated_extra_with_nulls, + + buffer_overrun, + %buffer_overrun_1, + %buffer_overrun_2, + + no_nonlocal_register, + no_nonlocal_kill, + no_live_killing ]. %% @@ -132,7 +143,7 @@ all(suite) -> %% init_per_testcase(_Func, Config) -> - Dog = test_server:timetrap(?SHORT_TEST_TIMEOUT), + Dog = test_server:timetrap(?MEDIUM_TEST_TIMEOUT), cleanup(), [{watchdog, Dog} | Config]. @@ -148,7 +159,7 @@ register_name(doc) -> ["Register a name"]; register_name(suite) -> []; -register_name(Config) when list(Config) -> +register_name(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node("foobar"), ?line ok = close(Sock), % Unregister @@ -158,7 +169,7 @@ register_names_1(doc) -> ["Register and unregister two nodes"]; register_names_1(suite) -> []; -register_names_1(Config) when list(Config) -> +register_names_1(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock1} = register_node("foobar"), ?line {ok,Sock2} = register_node("foozap"), @@ -170,7 +181,7 @@ register_names_2(doc) -> ["Register and unregister two nodes"]; register_names_2(suite) -> []; -register_names_2(Config) when list(Config) -> +register_names_2(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock1} = register_node("foobar"), ?line {ok,Sock2} = register_node("foozap"), @@ -182,7 +193,7 @@ register_duplicate_name(doc) -> ["Two nodes with the same name"]; register_duplicate_name(suite) -> []; -register_duplicate_name(Config) when list(Config) -> +register_duplicate_name(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node("foobar"), ?line error = register_node("foobar"), @@ -192,22 +203,9 @@ register_duplicate_name(Config) when list(Config) -> % Internal function to register a node name, no close, i.e. unregister register_node(Name) -> - register_node(Name,?DUMMY_PORT). - -register_node(Name, Port) -> - case send_req([?EPMD_ALIVE_REQ, put16(Port), Name]) of - {ok,Sock} -> - case recv(Sock,3) of - {ok, [?EPMD_ALIVE_OK_RESP,_D1,_D0]} -> - {ok,Sock}; - Other -> - test_server:format("recv on sock ~w: ~p~n", - [Sock,Other]), - error - end; - error -> - error - end. + register_node_v2(?DUMMY_PORT,$M,0,5,5,Name,""). +register_node(Name,Port) -> + register_node_v2(Port,$M,0,5,5,Name,""). register_node_v2(Port, NodeType, Prot, HVsn, LVsn, Name, Extra) -> Req = [?EPMD_ALIVE2_REQ, put16(Port), NodeType, Prot, @@ -254,7 +252,7 @@ parse_port2_resp(Resp) -> hvsn=HVsn,lvsn=LVsn, node_name=binary_to_list(NodeName), extra=binary_to_list(Extra)}}; - Other -> + _Other -> test_server:format("invalid port2 resp: ~p~n", [Resp]), error @@ -266,7 +264,7 @@ name_with_null_inside(doc) -> ["Register a name with a null char in it"]; name_with_null_inside(suite) -> []; -name_with_null_inside(Config) when list(Config) -> +name_with_null_inside(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line error = register_node("foo\000bar"), ok. @@ -277,11 +275,9 @@ name_null_terminated(doc) -> ["Register a name with terminating null byte"]; name_null_terminated(suite) -> []; -name_null_terminated(Config) when list(Config) -> +name_null_terminated(Config) when is_list(Config) -> ?line ok = epmdrun(), - ?line {ok,Sock} = register_node("foobar\000"), - ?line error = register_node("foobar"), - ?line ok = close(Sock), % Unregister + ?line error = register_node("foobar\000"), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -290,7 +286,7 @@ stupid_names_req(doc) -> ["Read names from epmd in a stupid way"]; stupid_names_req(suite) -> []; -stupid_names_req(Config) when list(Config) -> +stupid_names_req(Config) when is_list(Config) -> Dog = ?config(watchdog, Config), test_server:timetrap_cancel(Dog), LongDog = test_server:timetrap(?MEDIUM_TEST_TIMEOUT), @@ -394,15 +390,15 @@ get_port_nr(doc) -> ["Register a name on a port and ask about port nr"]; get_port_nr(suite) -> []; -get_port_nr(Config) when list(Config) -> - port_request([?EPMD_PORT_REQ,"foo"]). +get_port_nr(Config) when is_list(Config) -> + port_request([?EPMD_PORT_PLEASE2_REQ,"foo"]). slow_get_port_nr(doc) -> ["Register with slow write and ask about port nr"]; slow_get_port_nr(suite) -> []; -slow_get_port_nr(Config) when list(Config) -> - port_request([?EPMD_PORT_REQ,d,$f,d,$o,d,$o]). +slow_get_port_nr(Config) when is_list(Config) -> + port_request([?EPMD_PORT_PLEASE2_REQ,d,$f,d,$o,d,$o]). % Internal function used above @@ -413,9 +409,18 @@ port_request(M) -> ?line {ok,RSock} = register_node("foo", Port), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[size16(M),M]), - R = put16(Port), - ?line {ok,R} = recv(Sock, length(R)), - ?line ok = close(RSock), + ?line case recv_until_sock_closes(Sock) of + {ok, Resp} -> + ?line close(RSock), + ?line {ok,Rec} = parse_port2_resp(Resp), + ?line Port = Rec#node_info.port, + ok; + Other -> + ?line close(RSock), + ?line test_server:format("recv on sock ~w: ~p~n", + [Sock,Other]), + ?line throw({error,Other}) + end, ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -424,8 +429,8 @@ unregister_others_name_1(doc) -> ["Unregister name of other node"]; unregister_others_name_1(suite) -> []; -unregister_others_name_1(Config) when list(Config) -> - ?line ok = epmdrun(), +unregister_others_name_1(Config) when is_list(Config) -> + ?line ok = epmdrun("-relaxed_command_check"), ?line {ok,RSock} = register_node("foo"), ?line {ok,Sock} = connect(), M = [?EPMD_STOP_REQ,"foo"], @@ -441,8 +446,8 @@ unregister_others_name_2(doc) -> ["Unregister name of other node"]; unregister_others_name_2(suite) -> []; -unregister_others_name_2(Config) when list(Config) -> - ?line ok = epmdrun(), +unregister_others_name_2(Config) when is_list(Config) -> + ?line ok = epmdrun("-relaxed_command_check"), ?line {ok,Sock} = connect(), M = [?EPMD_STOP_REQ,"xxx42"], ?line ok = send(Sock,[size16(M),M]), @@ -456,7 +461,7 @@ register_overflow(doc) -> ["Register too many, clean and redo 10 times"]; register_overflow(suite) -> []; -register_overflow(Config) when list(Config) -> +register_overflow(Config) when is_list(Config) -> Dog = ?config(watchdog, Config), test_server:timetrap_cancel(Dog), LongDog = test_server:timetrap(?LONG_TEST_TIMEOUT), @@ -546,7 +551,7 @@ no_data(doc) -> ["Open but send no data"]; no_data(suite) -> []; -no_data(Config) when list(Config) -> +no_data(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), sleep(?LONG_PAUSE), @@ -559,7 +564,7 @@ one_byte(doc) -> ["Send one byte only"]; one_byte(suite) -> []; -one_byte(Config) when list(Config) -> +one_byte(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[0]), @@ -573,7 +578,7 @@ two_bytes(doc) -> ["Send packet size only"]; two_bytes(suite) -> []; -two_bytes(Config) when list(Config) -> +two_bytes(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[put16(3)]), @@ -587,7 +592,7 @@ partial_packet(doc) -> ["Got only part of a packet"]; partial_packet(suite) -> []; -partial_packet(Config) when list(Config) -> +partial_packet(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[put16(100),"only a few bytes"]), @@ -601,7 +606,7 @@ zero_length(doc) -> ["Invalid zero packet size"]; zero_length(suite) -> []; -zero_length(Config) when list(Config) -> +zero_length(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), ?line ok = send(Sock,[0,0,0,0,0,0,0,0,0,0]), @@ -615,15 +620,20 @@ too_large(doc) -> ["Invalid large packet"]; too_large(suite) -> []; -too_large(Config) when list(Config) -> +too_large(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), Size = 63000, M = lists:duplicate(Size, $z), ?line ok = send(Sock,[put16(Size),M]), sleep(?MEDIUM_PAUSE), - ?line closed = recv(Sock,1), - ok. + % With such a large packet, even the writes can fail as the + % daemon closes before everything is delivered -> econnaborted + case recv(Sock,1) of + closed -> ok; + {error,econnaborted} -> ok; + Other -> exit({unexpected,Other}) + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -631,10 +641,11 @@ alive_req_too_small_1(doc) -> ["Try to register but not enough data"]; alive_req_too_small_1(suite) -> []; -alive_req_too_small_1(Config) when list(Config) -> +alive_req_too_small_1(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), - M = [?EPMD_ALIVE_REQ, 42], + M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), + put16(5),put16(0)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), ?line closed = recv(Sock,1), @@ -646,10 +657,11 @@ alive_req_too_small_2(doc) -> ["Try to register but not enough data"]; alive_req_too_small_2(suite) -> []; -alive_req_too_small_2(Config) when list(Config) -> +alive_req_too_small_2(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), - M = [?EPMD_ALIVE_REQ, put16(?DUMMY_PORT)], + M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), + put16(5)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), ?line closed = recv(Sock,1), @@ -661,7 +673,7 @@ alive_req_too_large(doc) -> ["Try to register but node name too large"]; alive_req_too_large(suite) -> []; -alive_req_too_large(Config) when list(Config) -> +alive_req_too_large(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = connect(), L = [ @@ -678,10 +690,12 @@ alive_req_too_large(Config) when list(Config) -> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ], - M = [?EPMD_ALIVE_REQ, put16(?DUMMY_PORT), L], + S = length(lists:flatten(L)), + M = [?EPMD_ALIVE2_REQ, put16(?DUMMY_PORT),$M,0, put16(5), + put16(5), put16(S),L,put16(0)], ?line ok = send(Sock, [size16(M), M]), sleep(?MEDIUM_PAUSE), - ?line closed = recv(Sock,1), + ?line {ok,[?EPMD_ALIVE2_RESP,1]} = recv(Sock,2), ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -690,7 +704,7 @@ returns_valid_empty_extra(doc) -> ["Check that an empty extra is prefixed by a two byte length"]; returns_valid_empty_extra(suite) -> []; -returns_valid_empty_extra(Config) when list(Config) -> +returns_valid_empty_extra(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", []), ?line {ok,#node_info{extra=[]}} = port_please_v2("foo"), @@ -703,7 +717,7 @@ returns_valid_populated_extra_with_nulls(doc) -> ["Check a populated extra with embedded null characters"]; returns_valid_populated_extra_with_nulls(suite) -> []; -returns_valid_populated_extra_with_nulls(Config) when list(Config) -> +returns_valid_populated_extra_with_nulls(Config) when is_list(Config) -> ?line ok = epmdrun(), ?line {ok,Sock} = register_node_v2(4711, 72, 0, 5, 5, "foo", "ABC\000\000"), ?line {ok,#node_info{extra="ABC\000\000"}} = port_please_v2("foo"), @@ -711,6 +725,167 @@ returns_valid_populated_extra_with_nulls(Config) when list(Config) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +buffer_overrun(suite) -> + [buffer_overrun_1,buffer_overrun_2]. + +buffer_overrun_1(suite) -> + []; +buffer_overrun_1(doc) -> + ["Test security vulnerability in fake extra lengths in alive2_req"]; +buffer_overrun_1(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line true = alltrue([hostile(N) || N <- lists:seq(1,10000)]), + ok. +buffer_overrun_2(suite) -> + []; +buffer_overrun_2(doc) -> + ["Test security vulnerability in fake extra lengths in alive2_req"]; +buffer_overrun_2(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line [false | Rest] = [hostile2(N) || N <- lists:seq(255,10000)], + ?line true = alltrue(Rest), + ok. +hostile(N) -> + try + Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16>>, + S = size(Bin), + {ok,E}=connect_sturdy(), + gen_tcp:send(E,[<<S:16>>,Bin]), + closed = recv(E,1), + gen_tcp:close(E), + true + catch + _:_ -> + false + end. +hostile2(N) -> + try + B2 = list_to_binary(lists:duplicate(N,255)), + Bin= <<$x:8,4747:16,$M:8,0:8,5:16,5:16,5:16,"gurka",N:16,B2/binary>>, + S = size(Bin), + {ok,E}=connect_sturdy(), + gen_tcp:send(E,[<<S:16>>,Bin]), + Z = recv(E,2), + gen_tcp:close(E), + (Z =:= closed) or (Z =:= {ok, [$y,1]}) + catch + _A:_B -> + false + end. + +alltrue([]) -> + true; +alltrue([true|T]) -> + alltrue(T); +alltrue([_|_]) -> + false. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +no_nonlocal_register(suite) -> + []; +no_nonlocal_register(doc) -> + ["Ensure that we cannot register throug a nonlocal connection"]; +no_nonlocal_register(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Ifs} = inet:getiflist(), + ?line Addr0 = [ inet:ifget(I, [addr]) || I <- Ifs ], + ?line Addr1 = [ A || {ok,[{addr,A}]} <- Addr0], + ?line Addr = lists:filter(fun({127,_,_,_}) -> + false; + (_) -> + true + end,Addr1), + %% Now we should have all non loopback interface addresses, + %% none should accept a alive2 registration. + ?line Res = lists:map(fun(Ad={A1,A2,A3,A4}) -> + try + Name = "gurka_"++ + integer_to_list(A1)++"_"++ + integer_to_list(A2)++"_"++ + integer_to_list(A3)++"_"++ + integer_to_list(A4), + Bname = list_to_binary(Name), + NameS = byte_size(Bname), + ?line Bin= <<$x:8,4747:16,$M:8,0:8,5:16, + 5:16,NameS:16,Bname/binary, + 0:16>>, + ?line S = size(Bin), + {ok, E} = connect(Ad), + gen_tcp:send(E,[<<S:16>>,Bin]), + closed = recv(E,1), + gen_tcp:close(E), + true + catch + _:_ -> + false + end + end, Addr), + erlang:display(Res), + ?line true = alltrue(Res), + ok. + +no_nonlocal_kill(suite) -> + []; +no_nonlocal_kill(doc) -> + ["Ensure that we cannot kill through nonlocal connection"]; +no_nonlocal_kill(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line {ok,Ifs} = inet:getiflist(), + ?line Addr0 = [ inet:ifget(I, [addr]) || I <- Ifs ], + ?line Addr1 = [ A || {ok,[{addr,A}]} <- Addr0], + ?line Addr = lists:filter(fun({127,_,_,_}) -> + false; + (_) -> + true + end,Addr1), + %% Now we should have all non loopback interface addresses, + %% none should accept a alive2 registration. + ?line Res = lists:map(fun(Ad) -> + try + {ok, E} = connect(Ad), + M = [?EPMD_KILL_REQ], + send(E, [size16(M), M]), + closed = recv(E,2), + gen_tcp:close(E), + sleep(?MEDIUM_PAUSE), + {ok, E2} = connect(Ad), + gen_tcp:close(E2), + true + catch + _:_ -> + false + end + end, Addr), + erlang:display(Res), + ?line true = alltrue(Res), + ok. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +no_live_killing(doc) -> + ["Dont allow killing with live nodes or any unregistering w/o -relaxed_command_check"]; +no_live_killing(suite) -> + []; +no_live_killing(Config) when is_list(Config) -> + ?line ok = epmdrun(), + ?line {ok,RSock} = register_node("foo"), + ?line {ok,Sock} = connect(), + ?line M = [?EPMD_KILL_REQ], + ?line ok = send(Sock,[size16(M),M]), + ?line {ok,"NO"} = recv(Sock,2), + ?line close(Sock), + ?line {ok,Sock2} = connect(), + ?line M2 = [?EPMD_STOP_REQ,"foo"], + ?line ok = send(Sock2,[size16(M2),M2]), + ?line closed = recv(Sock2,1), + ?line close(Sock2), + ?line close(RSock), + ?line sleep(?MEDIUM_PAUSE), + ?line {ok,Sock3} = connect(), + ?line M3 = [?EPMD_KILL_REQ], + ?line ok = send(Sock3,[size16(M3),M3]), + ?line {ok,"OK"} = recv(Sock3,2), + ?line close(Sock3), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Terminate all tests with killing epmd. cleanup() -> @@ -731,16 +906,24 @@ cleanup() -> % Normal debug start of epmd epmdrun() -> + epmdrun([]). +epmdrun(Args) -> case os:find_executable(epmd) of false -> {error, {could_not_find_epmd_in_path}}; Path -> - epmdrun(Path) + epmdrun(Path,Args) end. -epmdrun(Epmd) -> +epmdrun(Epmd,Args0) -> %% test_server:format("epmdrun() => Epmd = ~p",[Epmd]), - osrun(Epmd ++ " " ?EPMDARGS " -port " ++ integer_to_list(?PORT)). + Args = case Args0 of + [] -> + []; + O -> + " "++O + end, + osrun(Epmd ++ Args ++ " " ?EPMDARGS " -port " ++ integer_to_list(?PORT)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -753,20 +936,27 @@ osrun(Cmd) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Wrappers of TCP functions -% These two functions is the interface for connect. +% These functions is the interface for connect. % Passive mode is the default connect() -> - connect(?PORT, passive). + connect("localhost",?PORT, passive). + +connect(Addr) -> + connect(Addr,?PORT, passive). connect_active() -> - connect(?PORT, active). + connect("localhost",?PORT, active). +%% Retry after 15 seconds, to avoid TIME_WAIT socket exhaust. +connect_sturdy() -> + connect("localhost",?PORT, passive, 15000, 3). % Try a few times before giving up - -connect(Port, Mode) -> - case connect_repeat(?CONN_RETRY, Port, Mode) of +connect(Addr, Port, Mode) -> + connect(Addr, Port, Mode, ?CONN_SLEEP, ?CONN_RETRY). +connect(Addr, Port, Mode, Sleep, Retry) -> + case connect_repeat(Addr, Retry, Port, Mode, Sleep) of {ok,Sock} -> {ok,Sock}; {error,timeout} -> @@ -783,25 +973,25 @@ connect(Port, Mode) -> % Try a few times before giving up. Pause a small time between % each try. -connect_repeat(1, Port, Mode) -> - connect_mode(Port, Mode); -connect_repeat(Retry, Port, Mode) -> - case connect_mode(Port, Mode) of +connect_repeat(Addr, 1, Port, Mode, _Sleep) -> + connect_mode(Addr,Port, Mode); +connect_repeat(Addr,Retry, Port, Mode, Sleep) -> + case connect_mode(Addr,Port, Mode) of {ok,Sock} -> {ok,Sock}; {error,Reason} -> test_server:format("connect: error: ~w~n",[Reason]), - timer:sleep(?CONN_SLEEP), - connect_repeat(Retry - 1, Port, Mode); + timer:sleep(Sleep), + connect_repeat(Addr, Retry - 1, Port, Mode, Sleep); Any -> test_server:format("connect: unknown message: ~w~n",[Any]), exit(1) end. -connect_mode(Port, active) -> - gen_tcp:connect("localhost", Port, [{packet, 0}], ?CONN_TIMEOUT); -connect_mode(Port, passive) -> - gen_tcp:connect("localhost", Port, [{packet, 0}, {active, false}], +connect_mode(Addr,Port, active) -> + gen_tcp:connect(Addr, Port, [{packet, 0}], ?CONN_TIMEOUT); +connect_mode(Addr, Port, passive) -> + gen_tcp:connect(Addr, Port, [{packet, 0}, {active, false}], ?CONN_TIMEOUT). @@ -858,9 +1048,9 @@ send(Sock, SendSpec) -> send([], RevBytes, _Sock) -> {ok,RevBytes}; -send([Byte | Spec], RevBytes, Sock) when integer(Byte) -> +send([Byte | Spec], RevBytes, Sock) when is_integer(Byte) -> send(Spec, [Byte | RevBytes], Sock); -send([List | Spec], RevBytes, Sock) when list(List) -> +send([List | Spec], RevBytes, Sock) when is_list(List) -> case send(List, RevBytes, Sock) of {ok,Left} -> send(Spec, Left, Sock); |