diff options
Diffstat (limited to '')
-rw-r--r-- | source4/lib/socket/access.c | 129 | ||||
-rw-r--r-- | source4/lib/socket/connect.c | 158 | ||||
-rw-r--r-- | source4/lib/socket/connect_multi.c | 392 | ||||
-rw-r--r-- | source4/lib/socket/interface.c | 525 | ||||
-rw-r--r-- | source4/lib/socket/netif.h | 24 | ||||
-rw-r--r-- | source4/lib/socket/socket.c | 640 | ||||
-rw-r--r-- | source4/lib/socket/socket.h | 256 | ||||
-rw-r--r-- | source4/lib/socket/socket_ip.c | 1033 | ||||
-rw-r--r-- | source4/lib/socket/socket_unix.c | 436 | ||||
-rw-r--r-- | source4/lib/socket/testsuite.c | 194 | ||||
-rw-r--r-- | source4/lib/socket/wscript_build | 29 |
11 files changed, 3816 insertions, 0 deletions
diff --git a/source4/lib/socket/access.c b/source4/lib/socket/access.c new file mode 100644 index 0000000..c019fd6 --- /dev/null +++ b/source4/lib/socket/access.c @@ -0,0 +1,129 @@ +/* + Unix SMB/CIFS implementation. + + check access rules for socket connections + + Copyright (C) Andrew Tridgell 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +/* + This module is an adaption of code from the tcpd-1.4 package written + by Wietse Venema, Eindhoven University of Technology, The Netherlands. + + The code is used here with permission. + + The code has been considerably changed from the original. Bug reports + should be sent to samba-technical@lists.samba.org +*/ + +#include "includes.h" +#include "system/network.h" +#include "lib/socket/socket.h" +#include "lib/util/util_net.h" +#include "lib/util/access.h" + +/* return true if the char* contains ip addrs only. Used to avoid +gethostbyaddr() calls */ + +static bool only_ipaddrs_in_list(const char** list) +{ + bool only_ip = true; + + if (!list) + return true; + + for (; *list ; list++) { + /* factor out the special strings */ + if (strcmp(*list, "ALL")==0 || + strcmp(*list, "FAIL")==0 || + strcmp(*list, "EXCEPT")==0) { + continue; + } + + if (!is_ipaddress(*list)) { + /* + * if we failed, make sure that it was not because the token + * was a network/netmask pair. Only network/netmask pairs + * have a '/' in them + */ + if ((strchr(*list, '/')) == NULL) { + only_ip = false; + DEBUG(3,("only_ipaddrs_in_list: list has non-ip address (%s)\n", *list)); + break; + } + } + } + + return only_ip; +} + +/* return true if access should be allowed to a service for a socket */ +bool socket_check_access(struct socket_context *sock, + const char *service_name, + const char **allow_list, const char **deny_list) +{ + bool ret; + const char *name=""; + struct socket_address *addr; + TALLOC_CTX *mem_ctx; + + if ((!deny_list || *deny_list==0) && + (!allow_list || *allow_list==0)) { + return true; + } + + mem_ctx = talloc_init("socket_check_access"); + if (!mem_ctx) { + return false; + } + + addr = socket_get_peer_addr(sock, mem_ctx); + if (!addr) { + DEBUG(0,("socket_check_access: Denied connection from unknown host: could not get peer address from kernel\n")); + talloc_free(mem_ctx); + return false; + } + + /* bypass gethostbyaddr() calls if the lists only contain IP addrs */ + if (!only_ipaddrs_in_list(allow_list) || + !only_ipaddrs_in_list(deny_list)) { + name = socket_get_peer_name(sock, mem_ctx); + if (!name) { + name = addr->addr; + } + } + + if (!addr) { + DEBUG(0,("socket_check_access: Denied connection from unknown host\n")); + talloc_free(mem_ctx); + return false; + } + + ret = allow_access(deny_list, allow_list, name, addr->addr); + + if (ret) { + DEBUG(2,("socket_check_access: Allowed connection to '%s' from %s (%s)\n", + service_name, name, addr->addr)); + } else { + DEBUG(0,("socket_check_access: Denied connection to '%s' from %s (%s)\n", + service_name, name, addr->addr)); + } + + talloc_free(mem_ctx); + + return ret; +} diff --git a/source4/lib/socket/connect.c b/source4/lib/socket/connect.c new file mode 100644 index 0000000..1da8b41 --- /dev/null +++ b/source4/lib/socket/connect.c @@ -0,0 +1,158 @@ +/* + Unix SMB/CIFS implementation. + + implements a non-blocking connect operation that is aware of the samba4 + events system + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Volker Lendecke 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "lib/events/events.h" +#include "libcli/composite/composite.h" + + +struct connect_state { + struct socket_context *sock; + const struct socket_address *my_address; + const struct socket_address *server_address; + uint32_t flags; +}; + +static void socket_connect_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *private_data); + +/* + call the real socket_connect() call, and setup event handler +*/ +static void socket_send_connect(struct composite_context *result) +{ + struct tevent_fd *fde; + struct connect_state *state = talloc_get_type(result->private_data, + struct connect_state); + + result->status = socket_connect(state->sock, + state->my_address, + state->server_address, + state->flags); + if (NT_STATUS_IS_ERR(result->status) && + !NT_STATUS_EQUAL(result->status, + NT_STATUS_MORE_PROCESSING_REQUIRED)) { + composite_error(result, result->status); + return; + } + + fde = tevent_add_fd(result->event_ctx, result, + socket_get_fd(state->sock), + TEVENT_FD_READ|TEVENT_FD_WRITE, + socket_connect_handler, result); + composite_nomem(fde, result); +} + + +/* + send a socket connect, potentially doing some name resolution first +*/ +struct composite_context *socket_connect_send(struct socket_context *sock, + struct socket_address *my_address, + struct socket_address *server_address, + uint32_t flags, + struct tevent_context *event_ctx) +{ + struct composite_context *result; + struct connect_state *state; + + result = composite_create(sock, event_ctx); + if (result == NULL) return NULL; + + state = talloc_zero(result, struct connect_state); + if (composite_nomem(state, result)) return result; + result->private_data = state; + + state->sock = talloc_reference(state, sock); + if (composite_nomem(state->sock, result)) return result; + + if (my_address) { + void *ref = talloc_reference(state, my_address); + if (composite_nomem(ref, result)) { + return result; + } + state->my_address = my_address; + } + + { + void *ref = talloc_reference(state, server_address); + if (composite_nomem(ref, result)) { + return result; + } + state->server_address = server_address; + } + + state->flags = flags; + + set_blocking(socket_get_fd(sock), false); + + socket_send_connect(result); + + return result; +} + +/* + handle write events on connect completion +*/ +static void socket_connect_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct composite_context *result = + talloc_get_type(private_data, struct composite_context); + struct connect_state *state = talloc_get_type(result->private_data, + struct connect_state); + + result->status = socket_connect_complete(state->sock, state->flags); + if (!composite_is_ok(result)) return; + + composite_done(result); +} + +/* + wait for a socket_connect_send() to finish +*/ +NTSTATUS socket_connect_recv(struct composite_context *result) +{ + NTSTATUS status = composite_wait(result); + talloc_free(result); + return status; +} + + +/* + like socket_connect() but takes an event context, doing a semi-async connect +*/ +NTSTATUS socket_connect_ev(struct socket_context *sock, + struct socket_address *my_address, + struct socket_address *server_address, + uint32_t flags, + struct tevent_context *ev) +{ + struct composite_context *ctx; + ctx = socket_connect_send(sock, my_address, + server_address, flags, ev); + return socket_connect_recv(ctx); +} diff --git a/source4/lib/socket/connect_multi.c b/source4/lib/socket/connect_multi.c new file mode 100644 index 0000000..b29fffb --- /dev/null +++ b/source4/lib/socket/connect_multi.c @@ -0,0 +1,392 @@ +/* + Unix SMB/CIFS implementation. + + Fire connect requests to a host and a number of ports, with a timeout + between the connect request. Return if the first connect comes back + successfully or return the last error. + + Copyright (C) Volker Lendecke 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "lib/events/events.h" +#include "libcli/composite/composite.h" +#include "libcli/resolve/resolve.h" + +#define MULTI_PORT_DELAY 2000 /* microseconds */ + +/* + overall state +*/ +struct connect_multi_state { + struct socket_address **server_address; + unsigned num_address, current_address, current_port; + int num_ports; + uint16_t *ports; + + struct socket_context *sock; + uint16_t result_port; + + int num_connects_sent, num_connects_recv; + + struct socket_connect_multi_ex *ex; +}; + +/* + state of an individual socket_connect_send() call +*/ +struct connect_one_state { + struct composite_context *result; + struct socket_context *sock; + struct socket_address *addr; +}; + +static void continue_resolve_name(struct composite_context *creq); +static void connect_multi_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *p); +static void connect_multi_next_socket(struct composite_context *result); +static void continue_one(struct composite_context *creq); +static void continue_one_ex(struct tevent_req *subreq); + +/* + setup an async socket_connect, with multiple ports +*/ +_PUBLIC_ struct composite_context *socket_connect_multi_ex_send( + TALLOC_CTX *mem_ctx, + const char *server_name, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex) +{ + struct composite_context *result; + struct connect_multi_state *multi; + int i; + + struct nbt_name name; + struct composite_context *creq; + + result = talloc_zero(mem_ctx, struct composite_context); + if (result == NULL) return NULL; + result->state = COMPOSITE_STATE_IN_PROGRESS; + result->event_ctx = event_ctx; + + multi = talloc_zero(result, struct connect_multi_state); + if (composite_nomem(multi, result)) goto failed; + result->private_data = multi; + + multi->num_ports = num_server_ports; + multi->ports = talloc_array(multi, uint16_t, multi->num_ports); + if (composite_nomem(multi->ports, result)) goto failed; + + for (i=0; i<multi->num_ports; i++) { + multi->ports[i] = server_ports[i]; + } + + multi->ex = ex; + + /* + we don't want to do the name resolution separately + for each port, so start it now, then only start on + the real sockets once we have an IP + */ + make_nbt_name_server(&name, server_name); + + creq = resolve_name_all_send(resolve_ctx, multi, 0, multi->ports[0], &name, result->event_ctx); + if (composite_nomem(creq, result)) goto failed; + + composite_continue(result, creq, continue_resolve_name, result); + + return result; + + + failed: + composite_error(result, result->status); + return result; +} + +/* + start connecting to the next socket/port in the list +*/ +static void connect_multi_next_socket(struct composite_context *result) +{ + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + struct connect_one_state *state; + struct composite_context *creq; + int next = multi->num_connects_sent; + + if (next == multi->num_address * multi->num_ports) { + /* don't do anything, just wait for the existing ones to finish */ + return; + } + + if (multi->current_address == multi->num_address) { + multi->current_address = 0; + multi->current_port += 1; + } + multi->num_connects_sent += 1; + + if (multi->server_address == NULL || multi->server_address[multi->current_address] == NULL) { + composite_error(result, NT_STATUS_OBJECT_NAME_NOT_FOUND); + return; + } + + state = talloc(multi, struct connect_one_state); + if (composite_nomem(state, result)) return; + + state->result = result; + result->status = socket_create( + state, multi->server_address[multi->current_address]->family, + SOCKET_TYPE_STREAM, &state->sock, 0); + if (!composite_is_ok(result)) return; + + state->addr = socket_address_copy(state, multi->server_address[multi->current_address]); + if (composite_nomem(state->addr, result)) return; + + socket_address_set_port(state->addr, multi->ports[multi->current_port]); + + creq = socket_connect_send(state->sock, NULL, + state->addr, 0, + result->event_ctx); + if (composite_nomem(creq, result)) return; + talloc_steal(state, creq); + + multi->current_address++; + composite_continue(result, creq, continue_one, state); + + /* if there are more ports / addresses to go then setup a timer to fire when we have waited + for a couple of milli-seconds, when that goes off we try the next port regardless + of whether this port has completed */ + if (multi->num_ports * multi->num_address > multi->num_connects_sent) { + /* note that this timer is a child of the single + connect attempt state, so it will go away when this + request completes */ + tevent_add_timer(result->event_ctx, state, + timeval_current_ofs_usec(MULTI_PORT_DELAY), + connect_multi_timer, result); + } +} + +/* + a timer has gone off telling us that we should try the next port +*/ +static void connect_multi_timer(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *p) +{ + struct composite_context *result = talloc_get_type(p, struct composite_context); + connect_multi_next_socket(result); +} + + +/* + recv name resolution reply then send the next connect +*/ +static void continue_resolve_name(struct composite_context *creq) +{ + struct composite_context *result = talloc_get_type(creq->async.private_data, + struct composite_context); + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + struct socket_address **addr; + unsigned i; + + result->status = resolve_name_all_recv(creq, multi, &addr, NULL); + if (!composite_is_ok(result)) return; + + for(i=0; addr[i]; i++); + multi->num_address = i; + multi->server_address = talloc_steal(multi, addr); + + connect_multi_next_socket(result); +} + +/* + one of our socket_connect_send() calls hash finished. If it got a + connection or there are none left then we are done +*/ +static void continue_one(struct composite_context *creq) +{ + struct connect_one_state *state = talloc_get_type(creq->async.private_data, + struct connect_one_state); + struct composite_context *result = state->result; + struct connect_multi_state *multi = talloc_get_type(result->private_data, + struct connect_multi_state); + NTSTATUS status; + + status = socket_connect_recv(creq); + + if (multi->ex) { + struct tevent_req *subreq; + + subreq = multi->ex->establish_send(state, + result->event_ctx, + state->sock, + state->addr, + multi->ex->private_data); + if (composite_nomem(subreq, result)) return; + tevent_req_set_callback(subreq, continue_one_ex, state); + return; + } + + multi->num_connects_recv++; + + if (NT_STATUS_IS_OK(status)) { + multi->sock = talloc_steal(multi, state->sock); + multi->result_port = state->addr->port; + } + + talloc_free(state); + + if (NT_STATUS_IS_OK(status) || + multi->num_connects_recv == (multi->num_address * multi->num_ports)) { + result->status = status; + composite_done(result); + return; + } + + /* try the next port */ + connect_multi_next_socket(result); +} + +/* + one of our multi->ex->establish_send() calls hash finished. If it got a + connection or there are none left then we are done +*/ +static void continue_one_ex(struct tevent_req *subreq) +{ + struct connect_one_state *state = + tevent_req_callback_data(subreq, + struct connect_one_state); + struct composite_context *result = state->result; + struct connect_multi_state *multi = + talloc_get_type_abort(result->private_data, + struct connect_multi_state); + NTSTATUS status; + multi->num_connects_recv++; + + status = multi->ex->establish_recv(subreq); + TALLOC_FREE(subreq); + + if (NT_STATUS_IS_OK(status)) { + multi->sock = talloc_steal(multi, state->sock); + multi->result_port = state->addr->port; + } + + talloc_free(state); + + if (NT_STATUS_IS_OK(status) || + multi->num_connects_recv == (multi->num_address * multi->num_ports)) { + result->status = status; + composite_done(result); + return; + } + + /* try the next port */ + connect_multi_next_socket(result); +} + +/* + async recv routine for socket_connect_multi() + */ +_PUBLIC_ NTSTATUS socket_connect_multi_ex_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **sock, + uint16_t *port) +{ + NTSTATUS status = composite_wait(ctx); + if (NT_STATUS_IS_OK(status)) { + struct connect_multi_state *multi = + talloc_get_type(ctx->private_data, + struct connect_multi_state); + *sock = talloc_steal(mem_ctx, multi->sock); + *port = multi->result_port; + } + talloc_free(ctx); + return status; +} + +NTSTATUS socket_connect_multi_ex(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex, + struct socket_context **result, + uint16_t *result_port) +{ + struct composite_context *ctx = + socket_connect_multi_ex_send(mem_ctx, server_address, + num_server_ports, server_ports, + resolve_ctx, + event_ctx, + ex); + return socket_connect_multi_ex_recv(ctx, mem_ctx, result, result_port); +} + +/* + setup an async socket_connect, with multiple ports +*/ +_PUBLIC_ struct composite_context *socket_connect_multi_send( + TALLOC_CTX *mem_ctx, + const char *server_name, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx) +{ + return socket_connect_multi_ex_send(mem_ctx, + server_name, + num_server_ports, + server_ports, + resolve_ctx, + event_ctx, + NULL); /* ex */ +} + +/* + async recv routine for socket_connect_multi() + */ +_PUBLIC_ NTSTATUS socket_connect_multi_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **sock, + uint16_t *port) +{ + return socket_connect_multi_ex_recv(ctx, mem_ctx, sock, port); +} + +NTSTATUS socket_connect_multi(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_context **result, + uint16_t *result_port) +{ + return socket_connect_multi_ex(mem_ctx, + server_address, + num_server_ports, + server_ports, + resolve_ctx, + event_ctx, + NULL, /* ex */ + result, + result_port); +} diff --git a/source4/lib/socket/interface.c b/source4/lib/socket/interface.c new file mode 100644 index 0000000..65543c6 --- /dev/null +++ b/source4/lib/socket/interface.c @@ -0,0 +1,525 @@ +/* + Unix SMB/CIFS implementation. + + multiple interface handling + + Copyright (C) Andrew Tridgell 1992-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "param/param.h" +#include "lib/socket/netif.h" +#include "../lib/util/util_net.h" +#include "../lib/util/dlinklist.h" +#include "lib/util/smb_strtox.h" + +/* used for network interfaces */ +struct interface { + struct interface *next, *prev; + char *name; + int flags; + struct sockaddr_storage ip; + struct sockaddr_storage netmask; + struct sockaddr_storage bcast; + const char *ip_s; + const char *bcast_s; + const char *nmask_s; +}; + +#define ALLONES ((uint32_t)0xFFFFFFFF) +/* + address construction based on a patch from fred@datalync.com +*/ +#define MKBCADDR(_IP, _NM) ((_IP & _NM) | (_NM ^ ALLONES)) +#define MKNETADDR(_IP, _NM) (_IP & _NM) + +/**************************************************************************** +Try and find an interface that matches an ip. If we cannot, return NULL + **************************************************************************/ +static struct interface *iface_list_find(struct interface *interfaces, + const struct sockaddr *ip, + bool check_mask) +{ + struct interface *i; + + if (is_address_any(ip)) { + return interfaces; + } + + for (i=interfaces;i;i=i->next) { + if (check_mask) { + if (same_net(ip, (struct sockaddr *)&i->ip, (struct sockaddr *)&i->netmask)) { + return i; + } + } else if (sockaddr_equal((struct sockaddr *)&i->ip, ip)) { + return i; + } + } + + return NULL; +} + +/**************************************************************************** +add an interface to the linked list of interfaces +****************************************************************************/ +static void add_interface(TALLOC_CTX *mem_ctx, const struct iface_struct *ifs, struct interface **interfaces) +{ + char addr[INET6_ADDRSTRLEN]; + struct interface *iface; + + if (iface_list_find(*interfaces, (const struct sockaddr *)&ifs->ip, false)) { + DEBUG(3,("add_interface: not adding duplicate interface %s\n", + print_sockaddr(addr, sizeof(addr), &ifs->ip) )); + return; + } + + if (ifs->ip.ss_family == AF_INET && + !(ifs->flags & (IFF_BROADCAST|IFF_LOOPBACK))) { + DEBUG(3,("not adding non-broadcast interface %s\n", + ifs->name )); + return; + } + + if (*interfaces != NULL) { + mem_ctx = *interfaces; + } + + iface = talloc_zero(mem_ctx, struct interface); + if (iface == NULL) { + return; + } + + iface->name = talloc_strdup(iface, ifs->name); + if (!iface->name) { + SAFE_FREE(iface); + return; + } + iface->flags = ifs->flags; + iface->ip = ifs->ip; + iface->netmask = ifs->netmask; + iface->bcast = ifs->bcast; + + /* keep string versions too, to avoid people tripping over the implied + static in inet_ntoa() */ + print_sockaddr(addr, sizeof(addr), &iface->ip); + DEBUG(4,("added interface %s ip=%s ", + iface->name, addr)); + iface->ip_s = talloc_strdup(iface, addr); + + print_sockaddr(addr, sizeof(addr), + &iface->bcast); + DEBUG(4,("bcast=%s ", addr)); + iface->bcast_s = talloc_strdup(iface, addr); + + print_sockaddr(addr, sizeof(addr), + &iface->netmask); + DEBUG(4,("netmask=%s\n", addr)); + iface->nmask_s = talloc_strdup(iface, addr); + + /* + this needs to be a ADD_END, as some tests (such as the + spoolss notify test) depend on the interfaces ordering + */ + DLIST_ADD_END(*interfaces, iface); +} + +/** +interpret a single element from a interfaces= config line + +This handles the following different forms: + +1) wildcard interface name +2) DNS name +3) IP/masklen +4) ip/mask +5) bcast/mask +**/ +static void interpret_interface(TALLOC_CTX *mem_ctx, + const char *token, + struct iface_struct *probed_ifaces, + int total_probed, + struct interface **local_interfaces) +{ + struct sockaddr_storage ss; + struct sockaddr_storage ss_mask; + struct sockaddr_storage ss_net; + struct sockaddr_storage ss_bcast; + struct iface_struct ifs; + char *p; + int i; + bool added=false; + bool goodaddr = false; + + /* first check if it is an interface name */ + for (i=0;i<total_probed;i++) { + if (gen_fnmatch(token, probed_ifaces[i].name) == 0) { + add_interface(mem_ctx, &probed_ifaces[i], + local_interfaces); + added = true; + } + } + if (added) { + return; + } + + p = strchr_m(token, ';'); + if (p != NULL) { + /* + * skip smbd-specific extra data: + * link speed, capabilities, and interface index + */ + *p = 0; + } + + /* maybe it is a DNS name */ + p = strchr_m(token,'/'); + if (p == NULL) { + if (!interpret_string_addr(&ss, token, 0)) { + DEBUG(2, ("interpret_interface: Can't find address " + "for %s\n", token)); + return; + } + + for (i=0;i<total_probed;i++) { + if (sockaddr_equal((struct sockaddr *)&ss, (struct sockaddr *)&probed_ifaces[i].ip)) { + add_interface(mem_ctx, &probed_ifaces[i], + local_interfaces); + return; + } + } + DEBUG(2,("interpret_interface: " + "can't determine interface for %s\n", + token)); + return; + } + + /* parse it into an IP address/netmasklength pair */ + *p = 0; + goodaddr = interpret_string_addr(&ss, token, 0); + *p++ = '/'; + + if (!goodaddr) { + DEBUG(2,("interpret_interface: " + "can't determine interface for %s\n", + token)); + return; + } + + if (strlen(p) > 2) { + goodaddr = interpret_string_addr(&ss_mask, p, 0); + if (!goodaddr) { + DEBUG(2,("interpret_interface: " + "can't determine netmask from %s\n", + p)); + return; + } + } else { + int error = 0; + + unsigned long val = smb_strtoul(p, + NULL, + 0, + &error, + SMB_STR_FULL_STR_CONV); + if (error != 0) { + DEBUG(2,("interpret_interface: " + "can't determine netmask value from %s\n", + p)); + return; + } + if (!make_netmask(&ss_mask, &ss, val)) { + DEBUG(2,("interpret_interface: " + "can't apply netmask value %lu from %s\n", + val, + p)); + return; + } + } + + make_bcast(&ss_bcast, &ss, &ss_mask); + make_net(&ss_net, &ss, &ss_mask); + + /* Maybe the first component was a broadcast address. */ + if (sockaddr_equal((struct sockaddr *)&ss_bcast, (struct sockaddr *)&ss) || + sockaddr_equal((struct sockaddr *)&ss_net, (struct sockaddr *)&ss)) { + for (i=0;i<total_probed;i++) { + if (same_net((struct sockaddr *)&ss, + (struct sockaddr *)&probed_ifaces[i].ip, + (struct sockaddr *)&ss_mask)) { + /* Temporarily replace netmask on + * the detected interface - user knows + * best.... */ + struct sockaddr_storage saved_mask = + probed_ifaces[i].netmask; + probed_ifaces[i].netmask = ss_mask; + DEBUG(2,("interpret_interface: " + "using netmask value %s from " + "config file on interface %s\n", + p, + probed_ifaces[i].name)); + add_interface(mem_ctx, &probed_ifaces[i], + local_interfaces); + probed_ifaces[i].netmask = saved_mask; + return; + } + } + DEBUG(2,("interpret_interface: Can't determine ip for " + "broadcast address %s\n", + token)); + return; + } + + /* Just fake up the interface definition. User knows best. */ + + DEBUG(2,("interpret_interface: Adding interface %s\n", + token)); + + ZERO_STRUCT(ifs); + (void)strlcpy(ifs.name, token, sizeof(ifs.name)); + ifs.flags = IFF_BROADCAST; + ifs.ip = ss; + ifs.netmask = ss_mask; + ifs.bcast = ss_bcast; + add_interface(mem_ctx, &ifs, local_interfaces); +} + + +/** +load the list of network interfaces +**/ +void load_interface_list(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx, struct interface **local_interfaces) +{ + const char **ptr = lpcfg_interfaces(lp_ctx); + int i; + struct iface_struct *ifaces = NULL; + int total_probed; + + *local_interfaces = NULL; + + /* probe the kernel for interfaces */ + total_probed = get_interfaces(mem_ctx, &ifaces); + + /* if we don't have a interfaces line then use all interfaces + except loopback */ + if (!ptr || !*ptr || !**ptr) { + if (total_probed <= 0) { + DEBUG(0,("ERROR: Could not determine network interfaces, you must use a interfaces config line\n")); + } + for (i=0;i<total_probed;i++) { + if (!is_loopback_addr((struct sockaddr *)&ifaces[i].ip)) { + add_interface(mem_ctx, &ifaces[i], local_interfaces); + } + } + } + + while (ptr && *ptr) { + interpret_interface(mem_ctx, *ptr, ifaces, total_probed, local_interfaces); + ptr++; + } + + if (!*local_interfaces) { + DEBUG(0,("WARNING: no network interfaces found\n")); + } + talloc_free(ifaces); +} + +/** + how many interfaces do we have + **/ +int iface_list_count(struct interface *ifaces) +{ + int ret = 0; + struct interface *i; + + for (i=ifaces;i;i=i->next) + ret++; + return ret; +} + +/** + return IP of the Nth interface + **/ +const char *iface_list_n_ip(struct interface *ifaces, int n) +{ + struct interface *i; + + for (i=ifaces;i && n;i=i->next) + n--; + + if (i) { + return i->ip_s; + } + return NULL; +} + + +/** + return the first IPv4 interface address we have registered + **/ +const char *iface_list_first_v4(struct interface *ifaces) +{ + struct interface *i; + + for (i=ifaces; i; i=i->next) { + if (i->ip.ss_family == AF_INET) { + return i->ip_s; + } + } + return NULL; +} + +/** + return the first IPv6 interface address we have registered + **/ +static const char *iface_list_first_v6(struct interface *ifaces) +{ + struct interface *i; + +#ifdef HAVE_IPV6 + for (i=ifaces; i; i=i->next) { + if (i->ip.ss_family == AF_INET6) { + return i->ip_s; + } + } +#endif + return NULL; +} + +/** + check if an interface is IPv4 + **/ +bool iface_list_n_is_v4(struct interface *ifaces, int n) +{ + struct interface *i; + + for (i=ifaces;i && n;i=i->next) + n--; + + if (i) { + return i->ip.ss_family == AF_INET; + } + return false; +} + +/** + return bcast of the Nth interface + **/ +const char *iface_list_n_bcast(struct interface *ifaces, int n) +{ + struct interface *i; + + for (i=ifaces;i && n;i=i->next) + n--; + + if (i) { + return i->bcast_s; + } + return NULL; +} + +/** + return netmask of the Nth interface + **/ +const char *iface_list_n_netmask(struct interface *ifaces, int n) +{ + struct interface *i; + + for (i=ifaces;i && n;i=i->next) + n--; + + if (i) { + return i->nmask_s; + } + return NULL; +} + +/** + return the local IP address that best matches a destination IP, or + our first interface if none match +*/ +const char *iface_list_best_ip(struct interface *ifaces, const char *dest) +{ + struct interface *iface; + struct sockaddr_storage ss; + + if (!interpret_string_addr(&ss, dest, AI_NUMERICHOST)) { + return iface_list_n_ip(ifaces, 0); + } + iface = iface_list_find(ifaces, (const struct sockaddr *)&ss, true); + if (iface) { + return iface->ip_s; + } +#ifdef HAVE_IPV6 + if (ss.ss_family == AF_INET6) { + return iface_list_first_v6(ifaces); + } +#endif + return iface_list_first_v4(ifaces); +} + +/** + return true if an IP is one one of our local networks +*/ +bool iface_list_is_local(struct interface *ifaces, const char *dest) +{ + struct sockaddr_storage ss; + + if (!interpret_string_addr(&ss, dest, AI_NUMERICHOST)) { + return false; + } + if (iface_list_find(ifaces, (const struct sockaddr *)&ss, true)) { + return true; + } + return false; +} + +/** + return true if a IP matches a IP/netmask pair +*/ +bool iface_list_same_net(const char *ip1, const char *ip2, const char *netmask) +{ + struct sockaddr_storage ip1_ss, ip2_ss, nm_ss; + + if (!interpret_string_addr(&ip1_ss, ip1, AI_NUMERICHOST)) { + return false; + } + if (!interpret_string_addr(&ip2_ss, ip2, AI_NUMERICHOST)) { + return false; + } + if (!interpret_string_addr(&nm_ss, netmask, AI_NUMERICHOST)) { + return false; + } + + return same_net((struct sockaddr *)&ip1_ss, + (struct sockaddr *)&ip2_ss, + (struct sockaddr *)&nm_ss); +} + +/** + return the list of wildcard interfaces + this will include the IPv4 0.0.0.0, and may include IPv6 :: +*/ +char **iface_list_wildcard(TALLOC_CTX *mem_ctx) +{ + char **ret; +#ifdef HAVE_IPV6 + ret = str_list_make(mem_ctx, "::,0.0.0.0", NULL); +#else + ret = str_list_make(mem_ctx, "0.0.0.0", NULL); +#endif + return ret; +} diff --git a/source4/lib/socket/netif.h b/source4/lib/socket/netif.h new file mode 100644 index 0000000..1d90a4f --- /dev/null +++ b/source4/lib/socket/netif.h @@ -0,0 +1,24 @@ +/* + Unix SMB/CIFS implementation. + + structures for lib/netif/ + + Copyright (C) Andrew Tridgell 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "system/network.h" +#include "lib/socket/interfaces.h" +#include "lib/socket/netif_proto.h" diff --git a/source4/lib/socket/socket.c b/source4/lib/socket/socket.c new file mode 100644 index 0000000..26f23f5 --- /dev/null +++ b/source4/lib/socket/socket.c @@ -0,0 +1,640 @@ +/* + Unix SMB/CIFS implementation. + Socket functions + Copyright (C) Andrew Tridgell 1992-1998 + Copyright (C) Tim Potter 2000-2001 + Copyright (C) Stefan Metzmacher 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "system/filesys.h" +#include "system/network.h" +#include "param/param.h" +#include "../lib/tsocket/tsocket.h" +#include "lib/util/util_net.h" + +/* + auto-close sockets on free +*/ +static int socket_destructor(struct socket_context *sock) +{ + if (sock->ops->fn_close && + !(sock->flags & SOCKET_FLAG_NOCLOSE)) { + sock->ops->fn_close(sock); + } + return 0; +} + +_PUBLIC_ void socket_tevent_fd_close_fn(struct tevent_context *ev, + struct tevent_fd *fde, + int fd, + void *private_data) +{ + /* this might be the socket_wrapper swrap_close() */ + close(fd); +} + +_PUBLIC_ NTSTATUS socket_create_with_ops(TALLOC_CTX *mem_ctx, const struct socket_ops *ops, + struct socket_context **new_sock, + enum socket_type type, uint32_t flags) +{ + NTSTATUS status; + + (*new_sock) = talloc(mem_ctx, struct socket_context); + if (!(*new_sock)) { + return NT_STATUS_NO_MEMORY; + } + + (*new_sock)->type = type; + (*new_sock)->state = SOCKET_STATE_UNDEFINED; + (*new_sock)->flags = flags; + + (*new_sock)->fd = -1; + + (*new_sock)->private_data = NULL; + (*new_sock)->ops = ops; + (*new_sock)->backend_name = NULL; + + status = (*new_sock)->ops->fn_init((*new_sock)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(*new_sock); + return status; + } + + /* by enabling "testnonblock" mode, all socket receive and + send calls on non-blocking sockets will randomly recv/send + less data than requested */ + + if (type == SOCKET_TYPE_STREAM && + getenv("SOCKET_TESTNONBLOCK") != NULL) { + (*new_sock)->flags |= SOCKET_FLAG_TESTNONBLOCK; + } + + /* we don't do a connect() on dgram sockets, so need to set + non-blocking at socket create time */ + if (type == SOCKET_TYPE_DGRAM) { + set_blocking(socket_get_fd(*new_sock), false); + } + + talloc_set_destructor(*new_sock, socket_destructor); + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS socket_create(TALLOC_CTX *mem_ctx, + const char *name, enum socket_type type, + struct socket_context **new_sock, uint32_t flags) +{ + const struct socket_ops *ops; + + ops = socket_getops_byname(name, type); + if (!ops) { + return NT_STATUS_INVALID_PARAMETER; + } + + return socket_create_with_ops(mem_ctx, ops, new_sock, type, flags); +} + +_PUBLIC_ NTSTATUS socket_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *server_address, + uint32_t flags) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->state != SOCKET_STATE_UNDEFINED) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_connect) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_connect(sock, my_address, server_address, flags); +} + +_PUBLIC_ NTSTATUS socket_connect_complete(struct socket_context *sock, uint32_t flags) +{ + if (!sock->ops->fn_connect_complete) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return sock->ops->fn_connect_complete(sock, flags); +} + +_PUBLIC_ NTSTATUS socket_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->state != SOCKET_STATE_UNDEFINED) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_listen) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_listen(sock, my_address, queue_size, flags); +} + +_PUBLIC_ NTSTATUS socket_accept(struct socket_context *sock, struct socket_context **new_sock) +{ + NTSTATUS status; + + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->type != SOCKET_TYPE_STREAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (sock->state != SOCKET_STATE_SERVER_LISTEN) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_accept) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + status = sock->ops->fn_accept(sock, new_sock); + + if (NT_STATUS_IS_OK(status)) { + talloc_set_destructor(*new_sock, socket_destructor); + (*new_sock)->flags = 0; + } + + return status; +} + +_PUBLIC_ NTSTATUS socket_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->state != SOCKET_STATE_CLIENT_CONNECTED && + sock->state != SOCKET_STATE_SERVER_CONNECTED && + sock->type != SOCKET_TYPE_DGRAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_recv) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + if ((sock->flags & SOCKET_FLAG_TESTNONBLOCK) + && wantlen > 1) { + + if (random() % 10 == 0) { + *nread = 0; + return STATUS_MORE_ENTRIES; + } + return sock->ops->fn_recv(sock, buf, 1+(random() % wantlen), nread); + } + return sock->ops->fn_recv(sock, buf, wantlen, nread); +} + +_PUBLIC_ NTSTATUS socket_recvfrom(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread, + TALLOC_CTX *mem_ctx, struct socket_address **src_addr) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->type != SOCKET_TYPE_DGRAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_recvfrom) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_recvfrom(sock, buf, wantlen, nread, + mem_ctx, src_addr); +} + +_PUBLIC_ NTSTATUS socket_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->state != SOCKET_STATE_CLIENT_CONNECTED && + sock->state != SOCKET_STATE_SERVER_CONNECTED) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_send) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + if ((sock->flags & SOCKET_FLAG_TESTNONBLOCK) + && blob->length > 1) { + DATA_BLOB blob2 = *blob; + if (random() % 10 == 0) { + *sendlen = 0; + return STATUS_MORE_ENTRIES; + } + /* The random size sends are incompatible with TLS and SASL + * sockets, which require re-sends to be consistant */ + if (!(sock->flags & SOCKET_FLAG_ENCRYPT)) { + blob2.length = 1+(random() % blob2.length); + } else { + /* This is particularly stressful on buggy + * LDAP clients, that don't expect on LDAP + * packet in many SASL packets */ + blob2.length = 1 + blob2.length/2; + } + return sock->ops->fn_send(sock, &blob2, sendlen); + } + return sock->ops->fn_send(sock, blob, sendlen); +} + + +_PUBLIC_ NTSTATUS socket_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (sock->type != SOCKET_TYPE_DGRAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (sock->state == SOCKET_STATE_CLIENT_CONNECTED || + sock->state == SOCKET_STATE_SERVER_CONNECTED) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!sock->ops->fn_sendto) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_sendto(sock, blob, sendlen, dest_addr); +} + + +/* + ask for the number of bytes in a pending incoming packet +*/ +_PUBLIC_ NTSTATUS socket_pending(struct socket_context *sock, size_t *npending) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (!sock->ops->fn_pending) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return sock->ops->fn_pending(sock, npending); +} + + +_PUBLIC_ NTSTATUS socket_set_option(struct socket_context *sock, const char *option, const char *val) +{ + if (sock == NULL) { + return NT_STATUS_CONNECTION_DISCONNECTED; + } + if (!sock->ops->fn_set_option) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return sock->ops->fn_set_option(sock, option, val); +} + +_PUBLIC_ char *socket_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + if (!sock->ops->fn_get_peer_name) { + return NULL; + } + + return sock->ops->fn_get_peer_name(sock, mem_ctx); +} + +_PUBLIC_ struct socket_address *socket_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + if (!sock->ops->fn_get_peer_addr) { + return NULL; + } + + return sock->ops->fn_get_peer_addr(sock, mem_ctx); +} + +_PUBLIC_ struct socket_address *socket_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + if (!sock->ops->fn_get_my_addr) { + return NULL; + } + + return sock->ops->fn_get_my_addr(sock, mem_ctx); +} + +_PUBLIC_ struct tsocket_address *socket_address_to_tsocket_address(TALLOC_CTX *mem_ctx, + const struct socket_address *a) +{ + struct tsocket_address *r; + int ret; + + if (!a) { + return NULL; + } + if (a->sockaddr) { + ret = tsocket_address_bsd_from_sockaddr(mem_ctx, + a->sockaddr, + a->sockaddrlen, + &r); + } else { + ret = tsocket_address_inet_from_strings(mem_ctx, + a->family, + a->addr, + a->port, + &r); + } + + if (ret != 0) { + return NULL; + } + + return r; +} + +_PUBLIC_ void socket_address_set_port(struct socket_address *a, + uint16_t port) +{ + if (a->sockaddr) { + set_sockaddr_port(a->sockaddr, port); + } else { + a->port = port; + } + +} + +_PUBLIC_ struct socket_address *tsocket_address_to_socket_address(TALLOC_CTX *mem_ctx, + const struct tsocket_address *a) +{ + ssize_t ret; + struct sockaddr_storage ss; + size_t sslen = sizeof(ss); + + ret = tsocket_address_bsd_sockaddr(a, (struct sockaddr *)(void *)&ss, sslen); + if (ret < 0) { + return NULL; + } + + return socket_address_from_sockaddr(mem_ctx, (struct sockaddr *)(void *)&ss, ret); +} + +_PUBLIC_ struct tsocket_address *socket_get_remote_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct socket_address *a; + struct tsocket_address *r; + + a = socket_get_peer_addr(sock, mem_ctx); + if (a == NULL) { + return NULL; + } + + r = socket_address_to_tsocket_address(mem_ctx, a); + talloc_free(a); + return r; +} + +_PUBLIC_ struct tsocket_address *socket_get_local_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct socket_address *a; + struct tsocket_address *r; + + a = socket_get_my_addr(sock, mem_ctx); + if (a == NULL) { + return NULL; + } + + r = socket_address_to_tsocket_address(mem_ctx, a); + talloc_free(a); + return r; +} + +_PUBLIC_ int socket_get_fd(struct socket_context *sock) +{ + if (!sock->ops->fn_get_fd) { + return -1; + } + + return sock->ops->fn_get_fd(sock); +} + +/* + call dup() on a socket, and close the old fd. This is used to change + the fd to the lowest available number, to make select() more + efficient (select speed depends on the maxiumum fd number passed to + it) +*/ +_PUBLIC_ NTSTATUS socket_dup(struct socket_context *sock) +{ + int fd; + if (sock->fd == -1) { + return NT_STATUS_INVALID_HANDLE; + } + fd = dup(sock->fd); + if (fd == -1) { + return map_nt_error_from_unix_common(errno); + } + close(sock->fd); + sock->fd = fd; + return NT_STATUS_OK; + +} + +/* Create a new socket_address. The type must match the socket type. + * The host parameter may be an IP or a hostname + */ + +_PUBLIC_ struct socket_address *socket_address_from_strings(TALLOC_CTX *mem_ctx, + const char *family, + const char *host, + int port) +{ + struct socket_address *addr = talloc(mem_ctx, struct socket_address); + if (!addr) { + return NULL; + } + + if (strcmp(family, "ip") == 0 && is_ipaddress_v6(host)) { + /* leaving as "ip" would force IPv4 */ + family = "ipv6"; + } + + addr->family = family; + addr->addr = talloc_strdup(addr, host); + if (!addr->addr) { + talloc_free(addr); + return NULL; + } + addr->port = port; + addr->sockaddr = NULL; + addr->sockaddrlen = 0; + + return addr; +} + +/* Create a new socket_address. Copy the struct sockaddr into the new + * structure. Used for hooks in the kerberos libraries, where they + * supply only a struct sockaddr */ + +_PUBLIC_ struct socket_address *socket_address_from_sockaddr(TALLOC_CTX *mem_ctx, + struct sockaddr *sockaddr, + size_t sockaddrlen) +{ + struct socket_address *addr = talloc(mem_ctx, struct socket_address); + if (!addr) { + return NULL; + } + switch (sockaddr->sa_family) { + case AF_INET: + addr->family = "ipv4"; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + addr->family = "ipv6"; + break; +#endif + case AF_UNIX: + addr->family = "unix"; + break; + } + addr->addr = NULL; + addr->port = 0; + addr->sockaddr = (struct sockaddr *)talloc_memdup(addr, sockaddr, sockaddrlen); + if (!addr->sockaddr) { + talloc_free(addr); + return NULL; + } + addr->sockaddrlen = sockaddrlen; + return addr; +} + + +/* + Create a new socket_address from sockaddr_storage + */ +_PUBLIC_ struct socket_address *socket_address_from_sockaddr_storage(TALLOC_CTX *mem_ctx, + const struct sockaddr_storage *sockaddr, + uint16_t port) +{ + struct socket_address *addr = talloc_zero(mem_ctx, struct socket_address); + char addr_str[INET6_ADDRSTRLEN+1]; + const char *str; + + if (!addr) { + return NULL; + } + addr->port = port; + switch (sockaddr->ss_family) { + case AF_INET: + addr->family = "ipv4"; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + addr->family = "ipv6"; + break; +#endif + default: + talloc_free(addr); + return NULL; + } + + str = print_sockaddr(addr_str, sizeof(addr_str), sockaddr); + if (str == NULL) { + talloc_free(addr); + return NULL; + } + addr->addr = talloc_strdup(addr, str); + if (addr->addr == NULL) { + talloc_free(addr); + return NULL; + } + + return addr; +} + +/* Copy a socket_address structure */ +struct socket_address *socket_address_copy(TALLOC_CTX *mem_ctx, + const struct socket_address *oaddr) +{ + struct socket_address *addr = talloc_zero(mem_ctx, struct socket_address); + if (!addr) { + return NULL; + } + addr->family = oaddr->family; + if (oaddr->addr) { + addr->addr = talloc_strdup(addr, oaddr->addr); + if (!addr->addr) { + goto nomem; + } + } + addr->port = oaddr->port; + if (oaddr->sockaddr) { + addr->sockaddr = (struct sockaddr *)talloc_memdup(addr, + oaddr->sockaddr, + oaddr->sockaddrlen); + if (!addr->sockaddr) { + goto nomem; + } + addr->sockaddrlen = oaddr->sockaddrlen; + } + + return addr; + +nomem: + talloc_free(addr); + return NULL; +} + +_PUBLIC_ const struct socket_ops *socket_getops_byname(const char *family, enum socket_type type) +{ + extern const struct socket_ops *socket_ipv4_ops(enum socket_type); + extern const struct socket_ops *socket_ipv6_ops(enum socket_type); + extern const struct socket_ops *socket_unixdom_ops(enum socket_type); + + if (strcmp("ip", family) == 0 || + strcmp("ipv4", family) == 0) { + return socket_ipv4_ops(type); + } + +#ifdef HAVE_IPV6 + if (strcmp("ipv6", family) == 0) { + return socket_ipv6_ops(type); + } +#endif + + if (strcmp("unix", family) == 0) { + return socket_unixdom_ops(type); + } + + return NULL; +} + +/* + set some flags on a socket + */ +void socket_set_flags(struct socket_context *sock, unsigned flags) +{ + sock->flags |= flags; +} diff --git a/source4/lib/socket/socket.h b/source4/lib/socket/socket.h new file mode 100644 index 0000000..d0fb5e0 --- /dev/null +++ b/source4/lib/socket/socket.h @@ -0,0 +1,256 @@ +/* + Unix SMB/CIFS implementation. + Socket functions + Copyright (C) Stefan Metzmacher 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _SAMBA_SOCKET_H +#define _SAMBA_SOCKET_H + +struct tevent_context; +struct tevent_fd; +struct socket_context; + +enum socket_type { + SOCKET_TYPE_STREAM, + SOCKET_TYPE_DGRAM +}; + +struct socket_address { + const char *family; + char *addr; + int port; + struct sockaddr *sockaddr; + size_t sockaddrlen; +}; + +struct socket_ops { + const char *name; + + NTSTATUS (*fn_init)(struct socket_context *sock); + + /* client ops */ + NTSTATUS (*fn_connect)(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *server_address, + uint32_t flags); + + /* complete a non-blocking connect */ + NTSTATUS (*fn_connect_complete)(struct socket_context *sock, + uint32_t flags); + + /* server ops */ + NTSTATUS (*fn_listen)(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags); + NTSTATUS (*fn_accept)(struct socket_context *sock, + struct socket_context **new_sock); + + /* general ops */ + NTSTATUS (*fn_recv)(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread); + NTSTATUS (*fn_send)(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen); + + NTSTATUS (*fn_sendto)(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr); + NTSTATUS (*fn_recvfrom)(struct socket_context *sock, + void *buf, size_t wantlen, size_t *nread, + TALLOC_CTX *addr_ctx, struct socket_address **src_addr); + NTSTATUS (*fn_pending)(struct socket_context *sock, size_t *npending); + + void (*fn_close)(struct socket_context *sock); + + NTSTATUS (*fn_set_option)(struct socket_context *sock, const char *option, const char *val); + + char *(*fn_get_peer_name)(struct socket_context *sock, TALLOC_CTX *mem_ctx); + struct socket_address *(*fn_get_peer_addr)(struct socket_context *sock, TALLOC_CTX *mem_ctx); + struct socket_address *(*fn_get_my_addr)(struct socket_context *sock, TALLOC_CTX *mem_ctx); + + int (*fn_get_fd)(struct socket_context *sock); +}; + +enum socket_state { + SOCKET_STATE_UNDEFINED, + + SOCKET_STATE_CLIENT_START, + SOCKET_STATE_CLIENT_CONNECTED, + SOCKET_STATE_CLIENT_STARTTLS, + SOCKET_STATE_CLIENT_ERROR, + + SOCKET_STATE_SERVER_LISTEN, + SOCKET_STATE_SERVER_CONNECTED, + SOCKET_STATE_SERVER_STARTTLS, + SOCKET_STATE_SERVER_ERROR +}; + +#define SOCKET_FLAG_PEEK 0x00000002 +#define SOCKET_FLAG_TESTNONBLOCK 0x00000004 +#define SOCKET_FLAG_ENCRYPT 0x00000008 /* This socket + * implementation requires + * that re-sends be + * consistant, because it + * is encrypting data. + * This modifies the + * TESTNONBLOCK case */ +#define SOCKET_FLAG_NOCLOSE 0x00000010 /* don't auto-close on free */ + + +struct socket_context { + enum socket_type type; + enum socket_state state; + uint32_t flags; + + int fd; + + void *private_data; + const struct socket_ops *ops; + const char *backend_name; + + /* specific to the ip backend */ + int family; +}; + +struct resolve_context; +struct tsocket_address; + +/* prototypes */ +NTSTATUS socket_create_with_ops(TALLOC_CTX *mem_ctx, const struct socket_ops *ops, + struct socket_context **new_sock, + enum socket_type type, uint32_t flags); +NTSTATUS socket_create(TALLOC_CTX *mem_ctx, + const char *name, enum socket_type type, + struct socket_context **new_sock, uint32_t flags); +NTSTATUS socket_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *server_address, + uint32_t flags); +NTSTATUS socket_connect_complete(struct socket_context *sock, uint32_t flags); +NTSTATUS socket_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags); +NTSTATUS socket_accept(struct socket_context *sock, struct socket_context **new_sock); +NTSTATUS socket_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread); +NTSTATUS socket_recvfrom(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread, + TALLOC_CTX *addr_ctx, struct socket_address **src_addr); +NTSTATUS socket_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen); +NTSTATUS socket_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr); +NTSTATUS socket_pending(struct socket_context *sock, size_t *npending); +NTSTATUS socket_set_option(struct socket_context *sock, const char *option, const char *val); +char *socket_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx); +struct socket_address *socket_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx); +struct socket_address *socket_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx); +struct tsocket_address *socket_address_to_tsocket_address(TALLOC_CTX *mem_ctx, + const struct socket_address *a); +struct socket_address *tsocket_address_to_socket_address(TALLOC_CTX *mem_ctx, + const struct tsocket_address *a); +struct tsocket_address *socket_get_remote_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx); +struct tsocket_address *socket_get_local_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx); +int socket_get_fd(struct socket_context *sock); +NTSTATUS socket_dup(struct socket_context *sock); +struct socket_address *socket_address_from_strings(TALLOC_CTX *mem_ctx, + const char *type, + const char *host, + int port); +struct socket_address *socket_address_from_sockaddr(TALLOC_CTX *mem_ctx, + struct sockaddr *sockaddr, + size_t addrlen); +struct sockaddr_storage; +struct socket_address *socket_address_from_sockaddr_storage(TALLOC_CTX *mem_ctx, + const struct sockaddr_storage *sockaddr, + uint16_t port); +_PUBLIC_ void socket_address_set_port(struct socket_address *a, + uint16_t port); +struct socket_address *socket_address_copy(TALLOC_CTX *mem_ctx, + const struct socket_address *oaddr); +const struct socket_ops *socket_getops_byname(const char *name, enum socket_type type); +bool socket_check_access(struct socket_context *sock, + const char *service_name, + const char **allow_list, const char **deny_list); + +struct composite_context *socket_connect_send(struct socket_context *sock, + struct socket_address *my_address, + struct socket_address *server_address, + uint32_t flags, + struct tevent_context *event_ctx); +NTSTATUS socket_connect_recv(struct composite_context *ctx); +NTSTATUS socket_connect_ev(struct socket_context *sock, + struct socket_address *my_address, + struct socket_address *server_address, + uint32_t flags, + struct tevent_context *ev); + +struct socket_connect_multi_ex { + void *private_data; + struct tevent_req *(*establish_send)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct socket_context *sock, + struct socket_address *addr, + void *private_data); + NTSTATUS (*establish_recv)(struct tevent_req *req); +}; +struct composite_context *socket_connect_multi_ex_send(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex); +NTSTATUS socket_connect_multi_ex_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **result, + uint16_t *port); +NTSTATUS socket_connect_multi_ex(TALLOC_CTX *mem_ctx, const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_connect_multi_ex *ex, + struct socket_context **result, + uint16_t *port); + +struct composite_context *socket_connect_multi_send(TALLOC_CTX *mem_ctx, + const char *server_address, + int num_server_ports, + uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx); +NTSTATUS socket_connect_multi_recv(struct composite_context *ctx, + TALLOC_CTX *mem_ctx, + struct socket_context **result, + uint16_t *port); +NTSTATUS socket_connect_multi(TALLOC_CTX *mem_ctx, const char *server_address, + int num_server_ports, uint16_t *server_ports, + struct resolve_context *resolve_ctx, + struct tevent_context *event_ctx, + struct socket_context **result, + uint16_t *port); +void set_socket_options(int fd, const char *options); +void socket_set_flags(struct socket_context *sock, unsigned flags); + +void socket_tevent_fd_close_fn(struct tevent_context *ev, + struct tevent_fd *fde, + int fd, + void *private_data); + +extern bool testnonblock; + +#endif /* _SAMBA_SOCKET_H */ diff --git a/source4/lib/socket/socket_ip.c b/source4/lib/socket/socket_ip.c new file mode 100644 index 0000000..62dbf1d --- /dev/null +++ b/source4/lib/socket/socket_ip.c @@ -0,0 +1,1033 @@ +/* + Unix SMB/CIFS implementation. + + Socket IPv4/IPv6 functions + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Tridgell 2004-2005 + Copyright (C) Jelmer Vernooij 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "lib/socket/socket.h" +#include "system/network.h" +#include "lib/util/util_net.h" + +#undef strcasecmp + +_PUBLIC_ const struct socket_ops *socket_ipv4_ops(enum socket_type type); +_PUBLIC_ const struct socket_ops *socket_ipv6_ops(enum socket_type type); + +static NTSTATUS ipv4_init(struct socket_context *sock) +{ + int type; + + switch (sock->type) { + case SOCKET_TYPE_STREAM: + type = SOCK_STREAM; + break; + case SOCKET_TYPE_DGRAM: + type = SOCK_DGRAM; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + sock->fd = socket(PF_INET, type, 0); + if (sock->fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + smb_set_close_on_exec(sock->fd); + + sock->backend_name = "ipv4"; + sock->family = AF_INET; + + return NT_STATUS_OK; +} + +static void ip_close(struct socket_context *sock) +{ + if (sock->fd != -1) { + close(sock->fd); + sock->fd = -1; + } +} + +static NTSTATUS ip_connect_complete(struct socket_context *sock, uint32_t flags) +{ + int error=0, ret; + socklen_t len = sizeof(error); + + /* check for any errors that may have occurred - this is needed + for non-blocking connect */ + ret = getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &error, &len); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + if (error != 0) { + return map_nt_error_from_unix_common(error); + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + sock->state = SOCKET_STATE_CLIENT_CONNECTED; + + return NT_STATUS_OK; +} + + +static NTSTATUS ipv4_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *srv_address, + uint32_t flags) +{ + struct sockaddr_in srv_addr; + struct in_addr my_ip; + struct in_addr srv_ip; + int ret; + + if (my_address && my_address->sockaddr) { + ret = bind(sock->fd, my_address->sockaddr, my_address->sockaddrlen); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } else if (my_address) { + my_ip = interpret_addr2(my_address->addr); + + if (my_ip.s_addr != 0 || my_address->port != 0) { + struct sockaddr_in my_addr; + ZERO_STRUCT(my_addr); +#ifdef HAVE_SOCK_SIN_LEN + my_addr.sin_len = sizeof(my_addr); +#endif + my_addr.sin_addr.s_addr = my_ip.s_addr; + my_addr.sin_port = htons(my_address->port); + my_addr.sin_family = PF_INET; + + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + } + + if (srv_address->sockaddr) { + ret = connect(sock->fd, srv_address->sockaddr, srv_address->sockaddrlen); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } else { + srv_ip = interpret_addr2(srv_address->addr); + if (!srv_ip.s_addr) { + return NT_STATUS_BAD_NETWORK_NAME; + } + + SMB_ASSERT(srv_address->port != 0); + + ZERO_STRUCT(srv_addr); +#ifdef HAVE_SOCK_SIN_LEN + srv_addr.sin_len = sizeof(srv_addr); +#endif + srv_addr.sin_addr.s_addr= srv_ip.s_addr; + srv_addr.sin_port = htons(srv_address->port); + srv_addr.sin_family = PF_INET; + + ret = connect(sock->fd, (const struct sockaddr *)&srv_addr, sizeof(srv_addr)); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + + return ip_connect_complete(sock, flags); +} + + +/* + note that for simplicity of the API, socket_listen() is also + use for DGRAM sockets, but in reality only a bind() is done +*/ +static NTSTATUS ipv4_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags) +{ + struct sockaddr_in my_addr; + struct in_addr ip_addr; + int ret; + + socket_set_option(sock, "SO_REUSEADDR=1", NULL); + + if (my_address->sockaddr) { + ret = bind(sock->fd, my_address->sockaddr, my_address->sockaddrlen); + } else { + ip_addr = interpret_addr2(my_address->addr); + + ZERO_STRUCT(my_addr); +#ifdef HAVE_SOCK_SIN_LEN + my_addr.sin_len = sizeof(my_addr); +#endif + my_addr.sin_addr.s_addr = ip_addr.s_addr; + my_addr.sin_port = htons(my_address->port); + my_addr.sin_family = PF_INET; + + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + } + + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + if (sock->type == SOCKET_TYPE_STREAM) { + ret = listen(sock->fd, queue_size); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + sock->state= SOCKET_STATE_SERVER_LISTEN; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv4_accept(struct socket_context *sock, struct socket_context **new_sock) +{ + struct sockaddr_in cli_addr; + socklen_t cli_addr_len = sizeof(cli_addr); + int new_fd, ret; + + if (sock->type != SOCKET_TYPE_STREAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + new_fd = accept(sock->fd, (struct sockaddr *)&cli_addr, &cli_addr_len); + if (new_fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + ret = set_blocking(new_fd, false); + if (ret == -1) { + close(new_fd); + return map_nt_error_from_unix_common(errno); + } + + smb_set_close_on_exec(new_fd); + + + /* TODO: we could add a 'accept_check' hook here + * which get the black/white lists via socket_set_accept_filter() + * or something like that + * --metze + */ + + (*new_sock) = talloc(NULL, struct socket_context); + if (!(*new_sock)) { + close(new_fd); + return NT_STATUS_NO_MEMORY; + } + + /* copy the socket_context */ + (*new_sock)->type = sock->type; + (*new_sock)->state = SOCKET_STATE_SERVER_CONNECTED; + (*new_sock)->flags = sock->flags; + + (*new_sock)->fd = new_fd; + + (*new_sock)->private_data = NULL; + (*new_sock)->ops = sock->ops; + (*new_sock)->backend_name = sock->backend_name; + + return NT_STATUS_OK; +} + +static NTSTATUS ip_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread) +{ + ssize_t gotlen; + + *nread = 0; + + gotlen = recv(sock->fd, buf, wantlen, 0); + if (gotlen == 0) { + return NT_STATUS_END_OF_FILE; + } else if (gotlen == -1) { + return map_nt_error_from_unix_common(errno); + } + + *nread = gotlen; + + return NT_STATUS_OK; +} + + +static NTSTATUS ipv4_recvfrom(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread, + TALLOC_CTX *addr_ctx, struct socket_address **_src) +{ + ssize_t gotlen; + struct sockaddr_in *from_addr; + socklen_t from_len = sizeof(*from_addr); + struct socket_address *src; + char addrstring[INET_ADDRSTRLEN]; + + src = talloc(addr_ctx, struct socket_address); + if (!src) { + return NT_STATUS_NO_MEMORY; + } + + src->family = sock->backend_name; + + from_addr = talloc(src, struct sockaddr_in); + if (!from_addr) { + talloc_free(src); + return NT_STATUS_NO_MEMORY; + } + + src->sockaddr = (struct sockaddr *)from_addr; + + *nread = 0; + + gotlen = recvfrom(sock->fd, buf, wantlen, 0, + src->sockaddr, &from_len); + if (gotlen == 0) { + talloc_free(src); + return NT_STATUS_END_OF_FILE; + } + if (gotlen == -1) { + talloc_free(src); + return map_nt_error_from_unix_common(errno); + } + + src->sockaddrlen = from_len; + + if (inet_ntop(AF_INET, &from_addr->sin_addr, addrstring, + sizeof(addrstring)) == NULL) { + talloc_free(src); + return NT_STATUS_INTERNAL_ERROR; + } + src->addr = talloc_strdup(src, addrstring); + if (src->addr == NULL) { + talloc_free(src); + return NT_STATUS_NO_MEMORY; + } + src->port = ntohs(from_addr->sin_port); + + *nread = gotlen; + *_src = src; + return NT_STATUS_OK; +} + +static NTSTATUS ip_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen) +{ + ssize_t len; + + *sendlen = 0; + + len = send(sock->fd, blob->data, blob->length, 0); + if (len == -1) { + return map_nt_error_from_unix_common(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv4_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr) +{ + ssize_t len; + + if (dest_addr->sockaddr) { + len = sendto(sock->fd, blob->data, blob->length, 0, + dest_addr->sockaddr, dest_addr->sockaddrlen); + } else { + struct sockaddr_in srv_addr; + struct in_addr addr; + + SMB_ASSERT(dest_addr->port != 0); + + ZERO_STRUCT(srv_addr); +#ifdef HAVE_SOCK_SIN_LEN + srv_addr.sin_len = sizeof(srv_addr); +#endif + addr = interpret_addr2(dest_addr->addr); + if (addr.s_addr == 0) { + return NT_STATUS_HOST_UNREACHABLE; + } + srv_addr.sin_addr.s_addr = addr.s_addr; + srv_addr.sin_port = htons(dest_addr->port); + srv_addr.sin_family = PF_INET; + + *sendlen = 0; + + len = sendto(sock->fd, blob->data, blob->length, 0, + (struct sockaddr *)&srv_addr, sizeof(srv_addr)); + } + if (len == -1) { + return map_nt_error_from_unix_common(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv4_set_option(struct socket_context *sock, const char *option, const char *val) +{ + set_socket_options(sock->fd, option); + return NT_STATUS_OK; +} + +static char *ipv4_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in peer_addr; + socklen_t len = sizeof(peer_addr); + struct hostent *he; + int ret; + + ret = getpeername(sock->fd, (struct sockaddr *)&peer_addr, &len); + if (ret == -1) { + return NULL; + } + + he = gethostbyaddr((char *)&peer_addr.sin_addr, sizeof(peer_addr.sin_addr), AF_INET); + if (he == NULL) { + return NULL; + } + + return talloc_strdup(mem_ctx, he->h_name); +} + +static struct socket_address *ipv4_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in *peer_addr; + socklen_t len = sizeof(*peer_addr); + struct socket_address *peer; + char addrstring[INET_ADDRSTRLEN]; + int ret; + + peer = talloc(mem_ctx, struct socket_address); + if (!peer) { + return NULL; + } + + peer->family = sock->backend_name; + peer_addr = talloc(peer, struct sockaddr_in); + if (!peer_addr) { + talloc_free(peer); + return NULL; + } + + peer->sockaddr = (struct sockaddr *)peer_addr; + + ret = getpeername(sock->fd, peer->sockaddr, &len); + if (ret == -1) { + talloc_free(peer); + return NULL; + } + + peer->sockaddrlen = len; + + if (inet_ntop(AF_INET, &peer_addr->sin_addr, addrstring, + sizeof(addrstring)) == NULL) { + talloc_free(peer); + return NULL; + } + peer->addr = talloc_strdup(peer, addrstring); + if (!peer->addr) { + talloc_free(peer); + return NULL; + } + peer->port = ntohs(peer_addr->sin_port); + + return peer; +} + +static struct socket_address *ipv4_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in *local_addr; + socklen_t len = sizeof(*local_addr); + struct socket_address *local; + char addrstring[INET_ADDRSTRLEN]; + int ret; + + local = talloc(mem_ctx, struct socket_address); + if (!local) { + return NULL; + } + + local->family = sock->backend_name; + local_addr = talloc(local, struct sockaddr_in); + if (!local_addr) { + talloc_free(local); + return NULL; + } + + local->sockaddr = (struct sockaddr *)local_addr; + + ret = getsockname(sock->fd, local->sockaddr, &len); + if (ret == -1) { + talloc_free(local); + return NULL; + } + + local->sockaddrlen = len; + + if (inet_ntop(AF_INET, &local_addr->sin_addr, addrstring, + sizeof(addrstring)) == NULL) { + talloc_free(local); + return NULL; + } + local->addr = talloc_strdup(local, addrstring); + if (!local->addr) { + talloc_free(local); + return NULL; + } + local->port = ntohs(local_addr->sin_port); + + return local; +} +static int ip_get_fd(struct socket_context *sock) +{ + return sock->fd; +} + +static NTSTATUS ip_pending(struct socket_context *sock, size_t *npending) +{ + int value = 0; + if (ioctl(sock->fd, FIONREAD, &value) == 0) { + *npending = value; + return NT_STATUS_OK; + } + return map_nt_error_from_unix_common(errno); +} + +static const struct socket_ops ipv4_ops = { + .name = "ipv4", + .fn_init = ipv4_init, + .fn_connect = ipv4_connect, + .fn_connect_complete = ip_connect_complete, + .fn_listen = ipv4_listen, + .fn_accept = ipv4_accept, + .fn_recv = ip_recv, + .fn_recvfrom = ipv4_recvfrom, + .fn_send = ip_send, + .fn_sendto = ipv4_sendto, + .fn_pending = ip_pending, + .fn_close = ip_close, + + .fn_set_option = ipv4_set_option, + + .fn_get_peer_name = ipv4_get_peer_name, + .fn_get_peer_addr = ipv4_get_peer_addr, + .fn_get_my_addr = ipv4_get_my_addr, + + .fn_get_fd = ip_get_fd +}; + +_PUBLIC_ const struct socket_ops *socket_ipv4_ops(enum socket_type type) +{ + return &ipv4_ops; +} + +#ifdef HAVE_IPV6 + +static struct in6_addr interpret_addr6(const char *name) +{ + char addr[INET6_ADDRSTRLEN]; + struct in6_addr dest6; + const char *sp = name; + char *p; + int ret; + + if (sp == NULL) return in6addr_any; + + p = strchr_m(sp, '%'); + + if (strcasecmp(sp, "localhost") == 0) { + sp = "::1"; + } + + /* + * Cope with link-local. + * This is IP:v6:addr%ifname. + */ + + if (p && (p > sp) && (if_nametoindex(p+1) != 0)) { + strlcpy(addr, sp, + MIN(PTR_DIFF(p,sp)+1, + sizeof(addr))); + sp = addr; + } + + ret = inet_pton(AF_INET6, sp, &dest6); + if (ret > 0) { + return dest6; + } + + return in6addr_any; +} + +static NTSTATUS ipv6_init(struct socket_context *sock) +{ + int type; + + switch (sock->type) { + case SOCKET_TYPE_STREAM: + type = SOCK_STREAM; + break; + case SOCKET_TYPE_DGRAM: + type = SOCK_DGRAM; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + sock->fd = socket(PF_INET6, type, 0); + if (sock->fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + smb_set_close_on_exec(sock->fd); + + sock->backend_name = "ipv6"; + sock->family = AF_INET6; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_tcp_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *srv_address, + uint32_t flags) +{ + int ret; + + if (my_address && my_address->sockaddr) { + ret = bind(sock->fd, my_address->sockaddr, my_address->sockaddrlen); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } else if (my_address) { + struct in6_addr my_ip; + my_ip = interpret_addr6(my_address->addr); + + if (memcmp(&my_ip, &in6addr_any, sizeof(my_ip)) || my_address->port != 0) { + struct sockaddr_in6 my_addr; + ZERO_STRUCT(my_addr); + my_addr.sin6_addr = my_ip; + my_addr.sin6_port = htons(my_address->port); + my_addr.sin6_family = PF_INET6; + + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + } + + if (srv_address->sockaddr) { + ret = connect(sock->fd, srv_address->sockaddr, srv_address->sockaddrlen); + } else { + struct in6_addr srv_ip; + struct sockaddr_in6 srv_addr; + srv_ip = interpret_addr6(srv_address->addr); + if (memcmp(&srv_ip, &in6addr_any, sizeof(srv_ip)) == 0) { + return NT_STATUS_BAD_NETWORK_NAME; + } + + ZERO_STRUCT(srv_addr); + srv_addr.sin6_addr = srv_ip; + srv_addr.sin6_port = htons(srv_address->port); + srv_addr.sin6_family = PF_INET6; + + ret = connect(sock->fd, (const struct sockaddr *)&srv_addr, sizeof(srv_addr)); + } + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + return ip_connect_complete(sock, flags); +} + +/* + fix the sin6_scope_id based on the address interface + */ +static void fix_scope_id(struct sockaddr_in6 *in6, + const char *address) +{ + const char *p = strchr(address, '%'); + if (p != NULL) { + in6->sin6_scope_id = if_nametoindex(p+1); + } +} + + +static NTSTATUS ipv6_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags) +{ + struct sockaddr_in6 my_addr; + struct in6_addr ip_addr; + int ret; + + socket_set_option(sock, "SO_REUSEADDR=1", NULL); + + if (my_address->sockaddr) { + ret = bind(sock->fd, my_address->sockaddr, my_address->sockaddrlen); + } else { + int one = 1; + ip_addr = interpret_addr6(my_address->addr); + + ZERO_STRUCT(my_addr); + my_addr.sin6_addr = ip_addr; + my_addr.sin6_port = htons(my_address->port); + my_addr.sin6_family = PF_INET6; + fix_scope_id(&my_addr, my_address->addr); + + /* when binding on ipv6 we always want to only bind on v6 */ + ret = setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, + (const void *)&one, sizeof(one)); + if (ret != -1) { + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + } + } + + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + if (sock->type == SOCKET_TYPE_STREAM) { + ret = listen(sock->fd, queue_size); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + sock->state= SOCKET_STATE_SERVER_LISTEN; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_tcp_accept(struct socket_context *sock, struct socket_context **new_sock) +{ + struct sockaddr_in6 cli_addr; + socklen_t cli_addr_len = sizeof(cli_addr); + int new_fd, ret; + + if (sock->type != SOCKET_TYPE_STREAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + new_fd = accept(sock->fd, (struct sockaddr *)&cli_addr, &cli_addr_len); + if (new_fd == -1) { + return map_nt_error_from_unix_common(errno); + } + + ret = set_blocking(new_fd, false); + if (ret == -1) { + close(new_fd); + return map_nt_error_from_unix_common(errno); + } + smb_set_close_on_exec(new_fd); + + /* TODO: we could add a 'accept_check' hook here + * which get the black/white lists via socket_set_accept_filter() + * or something like that + * --metze + */ + + (*new_sock) = talloc(NULL, struct socket_context); + if (!(*new_sock)) { + close(new_fd); + return NT_STATUS_NO_MEMORY; + } + + /* copy the socket_context */ + (*new_sock)->type = sock->type; + (*new_sock)->state = SOCKET_STATE_SERVER_CONNECTED; + (*new_sock)->flags = sock->flags; + + (*new_sock)->fd = new_fd; + + (*new_sock)->private_data = NULL; + (*new_sock)->ops = sock->ops; + (*new_sock)->backend_name = sock->backend_name; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_recvfrom(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread, + TALLOC_CTX *addr_ctx, struct socket_address **_src) +{ + ssize_t gotlen; + struct sockaddr_in6 *from_addr; + socklen_t from_len = sizeof(*from_addr); + struct socket_address *src; + char addrstring[INET6_ADDRSTRLEN]; + + src = talloc(addr_ctx, struct socket_address); + if (!src) { + return NT_STATUS_NO_MEMORY; + } + + src->family = sock->backend_name; + + from_addr = talloc(src, struct sockaddr_in6); + if (!from_addr) { + talloc_free(src); + return NT_STATUS_NO_MEMORY; + } + + src->sockaddr = (struct sockaddr *)from_addr; + + *nread = 0; + + gotlen = recvfrom(sock->fd, buf, wantlen, 0, + src->sockaddr, &from_len); + if (gotlen == 0) { + talloc_free(src); + return NT_STATUS_END_OF_FILE; + } else if (gotlen == -1) { + talloc_free(src); + return map_nt_error_from_unix_common(errno); + } + + src->sockaddrlen = from_len; + + if (inet_ntop(AF_INET6, &from_addr->sin6_addr, addrstring, sizeof(addrstring)) == NULL) { + DEBUG(0, ("Unable to convert address to string: %s\n", strerror(errno))); + talloc_free(src); + return NT_STATUS_INTERNAL_ERROR; + } + + src->addr = talloc_strdup(src, addrstring); + if (src->addr == NULL) { + talloc_free(src); + return NT_STATUS_NO_MEMORY; + } + src->port = ntohs(from_addr->sin6_port); + + *nread = gotlen; + *_src = src; + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest_addr) +{ + ssize_t len; + + if (dest_addr->sockaddr) { + len = sendto(sock->fd, blob->data, blob->length, 0, + dest_addr->sockaddr, dest_addr->sockaddrlen); + } else { + struct sockaddr_in6 srv_addr; + struct in6_addr addr; + + ZERO_STRUCT(srv_addr); + addr = interpret_addr6(dest_addr->addr); + if (memcmp(&addr.s6_addr, &in6addr_any, + sizeof(addr.s6_addr)) == 0) { + return NT_STATUS_HOST_UNREACHABLE; + } + srv_addr.sin6_addr = addr; + srv_addr.sin6_port = htons(dest_addr->port); + srv_addr.sin6_family = PF_INET6; + + *sendlen = 0; + + len = sendto(sock->fd, blob->data, blob->length, 0, + (struct sockaddr *)&srv_addr, sizeof(srv_addr)); + } + if (len == -1) { + return map_nt_error_from_unix_common(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + +static NTSTATUS ipv6_set_option(struct socket_context *sock, const char *option, const char *val) +{ + set_socket_options(sock->fd, option); + return NT_STATUS_OK; +} + +static char *ipv6_tcp_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in6 peer_addr; + socklen_t len = sizeof(peer_addr); + struct hostent *he; + int ret; + + ret = getpeername(sock->fd, (struct sockaddr *)&peer_addr, &len); + if (ret == -1) { + return NULL; + } + + he = gethostbyaddr((char *)&peer_addr.sin6_addr, sizeof(peer_addr.sin6_addr), AF_INET6); + if (he == NULL) { + return NULL; + } + + return talloc_strdup(mem_ctx, he->h_name); +} + +static struct socket_address *ipv6_tcp_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in6 *peer_addr; + socklen_t len = sizeof(*peer_addr); + struct socket_address *peer; + int ret; + char addr[128]; + const char *addr_ret; + + peer = talloc(mem_ctx, struct socket_address); + if (!peer) { + return NULL; + } + + peer->family = sock->backend_name; + peer_addr = talloc(peer, struct sockaddr_in6); + if (!peer_addr) { + talloc_free(peer); + return NULL; + } + + peer->sockaddr = (struct sockaddr *)peer_addr; + + ret = getpeername(sock->fd, peer->sockaddr, &len); + if (ret == -1) { + talloc_free(peer); + return NULL; + } + + peer->sockaddrlen = len; + + addr_ret = inet_ntop(AF_INET6, &peer_addr->sin6_addr, addr, sizeof(addr)); + if (addr_ret == NULL) { + talloc_free(peer); + return NULL; + } + + peer->addr = talloc_strdup(peer, addr_ret); + if (peer->addr == NULL) { + talloc_free(peer); + return NULL; + } + + peer->port = ntohs(peer_addr->sin6_port); + + return peer; +} + +static struct socket_address *ipv6_tcp_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_in6 *local_addr; + socklen_t len = sizeof(*local_addr); + struct socket_address *local; + int ret; + char addrstring[INET6_ADDRSTRLEN]; + + local = talloc(mem_ctx, struct socket_address); + if (!local) { + return NULL; + } + + local->family = sock->backend_name; + local_addr = talloc(local, struct sockaddr_in6); + if (!local_addr) { + talloc_free(local); + return NULL; + } + + local->sockaddr = (struct sockaddr *)local_addr; + + ret = getsockname(sock->fd, local->sockaddr, &len); + if (ret == -1) { + talloc_free(local); + return NULL; + } + + local->sockaddrlen = len; + + if (inet_ntop(AF_INET6, &local_addr->sin6_addr, addrstring, + sizeof(addrstring)) == NULL) { + DEBUG(0, ("Unable to convert address to string: %s\n", + strerror(errno))); + talloc_free(local); + return NULL; + } + + local->addr = talloc_strdup(local, addrstring); + if (!local->addr) { + talloc_free(local); + return NULL; + } + local->port = ntohs(local_addr->sin6_port); + + return local; +} + +static const struct socket_ops ipv6_tcp_ops = { + .name = "ipv6", + .fn_init = ipv6_init, + .fn_connect = ipv6_tcp_connect, + .fn_connect_complete = ip_connect_complete, + .fn_listen = ipv6_listen, + .fn_accept = ipv6_tcp_accept, + .fn_recv = ip_recv, + .fn_recvfrom = ipv6_recvfrom, + .fn_send = ip_send, + .fn_sendto = ipv6_sendto, + .fn_pending = ip_pending, + .fn_close = ip_close, + + .fn_set_option = ipv6_set_option, + + .fn_get_peer_name = ipv6_tcp_get_peer_name, + .fn_get_peer_addr = ipv6_tcp_get_peer_addr, + .fn_get_my_addr = ipv6_tcp_get_my_addr, + + .fn_get_fd = ip_get_fd +}; + +_PUBLIC_ const struct socket_ops *socket_ipv6_ops(enum socket_type type) +{ + return &ipv6_tcp_ops; +} + +#endif diff --git a/source4/lib/socket/socket_unix.c b/source4/lib/socket/socket_unix.c new file mode 100644 index 0000000..d4946d8 --- /dev/null +++ b/source4/lib/socket/socket_unix.c @@ -0,0 +1,436 @@ +/* + Unix SMB/CIFS implementation. + + unix domain socket functions + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Tridgell 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "system/network.h" +#include "system/filesys.h" + +_PUBLIC_ const struct socket_ops *socket_unixdom_ops(enum socket_type type); + + +/* + approximate errno mapping +*/ +static NTSTATUS unixdom_error(int ernum) +{ + return map_nt_error_from_unix_common(ernum); +} + +static NTSTATUS unixdom_init(struct socket_context *sock) +{ + int type; + + switch (sock->type) { + case SOCKET_TYPE_STREAM: + type = SOCK_STREAM; + break; + case SOCKET_TYPE_DGRAM: + type = SOCK_DGRAM; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + sock->fd = socket(PF_UNIX, type, 0); + if (sock->fd == -1) { + return map_nt_error_from_unix_common(errno); + } + sock->private_data = NULL; + + sock->backend_name = "unix"; + + smb_set_close_on_exec(sock->fd); + + return NT_STATUS_OK; +} + +static void unixdom_close(struct socket_context *sock) +{ + close(sock->fd); +} + +static NTSTATUS unixdom_connect_complete(struct socket_context *sock, uint32_t flags) +{ + int error=0, ret; + socklen_t len = sizeof(error); + + /* check for any errors that may have occurred - this is needed + for non-blocking connect */ + ret = getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &error, &len); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + if (error != 0) { + return map_nt_error_from_unix_common(error); + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return map_nt_error_from_unix_common(errno); + } + + sock->state = SOCKET_STATE_CLIENT_CONNECTED; + + return NT_STATUS_OK; +} + +static NTSTATUS unixdom_connect(struct socket_context *sock, + const struct socket_address *my_address, + const struct socket_address *srv_address, + uint32_t flags) +{ + int ret; + + if (srv_address->sockaddr) { + ret = connect(sock->fd, srv_address->sockaddr, srv_address->sockaddrlen); + } else { + struct sockaddr_un srv_addr; + if (strlen(srv_address->addr)+1 > sizeof(srv_addr.sun_path)) { + return NT_STATUS_OBJECT_PATH_INVALID; + } + + ZERO_STRUCT(srv_addr); + srv_addr.sun_family = AF_UNIX; + snprintf(srv_addr.sun_path, sizeof(srv_addr.sun_path), "%s", srv_address->addr); + + ret = connect(sock->fd, (const struct sockaddr *)&srv_addr, sizeof(srv_addr)); + } + if (ret == -1) { + return unixdom_error(errno); + } + + return unixdom_connect_complete(sock, flags); +} + +static NTSTATUS unixdom_listen(struct socket_context *sock, + const struct socket_address *my_address, + int queue_size, uint32_t flags) +{ + struct sockaddr_un my_addr; + int ret; + + /* delete if it already exists */ + if (my_address->addr) { + unlink(my_address->addr); + } + + if (my_address->sockaddr) { + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + } else if (my_address->addr == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } else { + if (strlen(my_address->addr)+1 > sizeof(my_addr.sun_path)) { + return NT_STATUS_OBJECT_PATH_INVALID; + } + + + ZERO_STRUCT(my_addr); + my_addr.sun_family = AF_UNIX; + snprintf(my_addr.sun_path, sizeof(my_addr.sun_path), "%s", my_address->addr); + + ret = bind(sock->fd, (struct sockaddr *)&my_addr, sizeof(my_addr)); + } + if (ret == -1) { + return unixdom_error(errno); + } + + if (sock->type == SOCKET_TYPE_STREAM) { + ret = listen(sock->fd, queue_size); + if (ret == -1) { + return unixdom_error(errno); + } + } + + ret = set_blocking(sock->fd, false); + if (ret == -1) { + return unixdom_error(errno); + } + + sock->state = SOCKET_STATE_SERVER_LISTEN; + sock->private_data = (void *)talloc_strdup(sock, my_address->addr); + + return NT_STATUS_OK; +} + +static NTSTATUS unixdom_accept(struct socket_context *sock, + struct socket_context **new_sock) +{ + struct sockaddr_un cli_addr; + socklen_t cli_addr_len = sizeof(cli_addr); + int new_fd, ret; + + if (sock->type != SOCKET_TYPE_STREAM) { + return NT_STATUS_INVALID_PARAMETER; + } + + new_fd = accept(sock->fd, (struct sockaddr *)&cli_addr, &cli_addr_len); + if (new_fd == -1) { + return unixdom_error(errno); + } + + ret = set_blocking(new_fd, false); + if (ret == -1) { + close(new_fd); + return map_nt_error_from_unix_common(errno); + } + + smb_set_close_on_exec(new_fd); + + (*new_sock) = talloc(NULL, struct socket_context); + if (!(*new_sock)) { + close(new_fd); + return NT_STATUS_NO_MEMORY; + } + + /* copy the socket_context */ + (*new_sock)->type = sock->type; + (*new_sock)->state = SOCKET_STATE_SERVER_CONNECTED; + (*new_sock)->flags = sock->flags; + + (*new_sock)->fd = new_fd; + + (*new_sock)->private_data = NULL; + (*new_sock)->ops = sock->ops; + (*new_sock)->backend_name = sock->backend_name; + + return NT_STATUS_OK; +} + +static NTSTATUS unixdom_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread) +{ + ssize_t gotlen; + + *nread = 0; + + gotlen = recv(sock->fd, buf, wantlen, 0); + if (gotlen == 0) { + return NT_STATUS_END_OF_FILE; + } else if (gotlen == -1) { + return unixdom_error(errno); + } + + *nread = gotlen; + + return NT_STATUS_OK; +} + +static NTSTATUS unixdom_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen) +{ + ssize_t len; + + *sendlen = 0; + + len = send(sock->fd, blob->data, blob->length, 0); + if (len == -1) { + return unixdom_error(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + + +static NTSTATUS unixdom_sendto(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen, + const struct socket_address *dest) +{ + struct sockaddr_un srv_addr; + const struct sockaddr *sa; + socklen_t sa_len; + ssize_t len; + + *sendlen = 0; + + if (dest->sockaddr) { + sa = dest->sockaddr; + sa_len = dest->sockaddrlen; + } else { + if (strlen(dest->addr)+1 > sizeof(srv_addr.sun_path)) { + return NT_STATUS_OBJECT_PATH_INVALID; + } + + ZERO_STRUCT(srv_addr); + srv_addr.sun_family = AF_UNIX; + snprintf(srv_addr.sun_path, sizeof(srv_addr.sun_path), "%s", + dest->addr); + sa = (struct sockaddr *) &srv_addr; + sa_len = sizeof(srv_addr); + } + + len = sendto(sock->fd, blob->data, blob->length, 0, sa, sa_len); + + /* retry once */ + if (len == -1 && errno == EMSGSIZE) { + /* round up in 1K increments */ + int bufsize = ((blob->length + 1023) & (~1023)); + if (setsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF, &bufsize, + sizeof(bufsize)) == -1) + { + return map_nt_error_from_unix_common(EMSGSIZE); + } + len = sendto(sock->fd, blob->data, blob->length, 0, sa, sa_len); + } + + if (len == -1) { + return map_nt_error_from_unix_common(errno); + } + + *sendlen = len; + + return NT_STATUS_OK; +} + + +static NTSTATUS unixdom_set_option(struct socket_context *sock, + const char *option, const char *val) +{ + return NT_STATUS_OK; +} + +static char *unixdom_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + return talloc_strdup(mem_ctx, "LOCAL/unixdom"); +} + +static struct socket_address *unixdom_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_un *peer_addr; + socklen_t len = sizeof(*peer_addr); + struct socket_address *peer; + int ret; + + peer = talloc(mem_ctx, struct socket_address); + if (!peer) { + return NULL; + } + + peer->family = sock->backend_name; + peer_addr = talloc(peer, struct sockaddr_un); + if (!peer_addr) { + talloc_free(peer); + return NULL; + } + + peer->sockaddr = (struct sockaddr *)peer_addr; + + ret = getpeername(sock->fd, peer->sockaddr, &len); + if (ret == -1) { + talloc_free(peer); + return NULL; + } + + peer->sockaddrlen = len; + + peer->port = 0; + peer->addr = talloc_strdup(peer, "LOCAL/unixdom"); + if (!peer->addr) { + talloc_free(peer); + return NULL; + } + + return peer; +} + +static struct socket_address *unixdom_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct sockaddr_un *local_addr; + socklen_t len = sizeof(*local_addr); + struct socket_address *local; + int ret; + + local = talloc(mem_ctx, struct socket_address); + if (!local) { + return NULL; + } + + local->family = sock->backend_name; + local_addr = talloc(local, struct sockaddr_un); + if (!local_addr) { + talloc_free(local); + return NULL; + } + + local->sockaddr = (struct sockaddr *)local_addr; + + ret = getsockname(sock->fd, local->sockaddr, &len); + if (ret == -1) { + talloc_free(local); + return NULL; + } + + local->sockaddrlen = len; + + local->port = 0; + local->addr = talloc_strdup(local, "LOCAL/unixdom"); + if (!local->addr) { + talloc_free(local); + return NULL; + } + + return local; +} + +static int unixdom_get_fd(struct socket_context *sock) +{ + return sock->fd; +} + +static NTSTATUS unixdom_pending(struct socket_context *sock, size_t *npending) +{ + int value = 0; + if (ioctl(sock->fd, FIONREAD, &value) == 0) { + *npending = value; + return NT_STATUS_OK; + } + return map_nt_error_from_unix_common(errno); +} + +static const struct socket_ops unixdom_ops = { + .name = "unix", + .fn_init = unixdom_init, + .fn_connect = unixdom_connect, + .fn_connect_complete = unixdom_connect_complete, + .fn_listen = unixdom_listen, + .fn_accept = unixdom_accept, + .fn_recv = unixdom_recv, + .fn_send = unixdom_send, + .fn_sendto = unixdom_sendto, + .fn_close = unixdom_close, + .fn_pending = unixdom_pending, + + .fn_set_option = unixdom_set_option, + + .fn_get_peer_name = unixdom_get_peer_name, + .fn_get_peer_addr = unixdom_get_peer_addr, + .fn_get_my_addr = unixdom_get_my_addr, + + .fn_get_fd = unixdom_get_fd +}; + +_PUBLIC_ const struct socket_ops *socket_unixdom_ops(enum socket_type type) +{ + return &unixdom_ops; +} diff --git a/source4/lib/socket/testsuite.c b/source4/lib/socket/testsuite.c new file mode 100644 index 0000000..1df96e3 --- /dev/null +++ b/source4/lib/socket/testsuite.c @@ -0,0 +1,194 @@ +/* + Unix SMB/CIFS implementation. + + local testing of socket routines. + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/socket/socket.h" +#include "lib/events/events.h" +#include "system/network.h" +#include "lib/socket/netif.h" +#include "torture/torture.h" +#include "torture/local/proto.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" + +/** + basic testing of udp routines +*/ +static bool test_udp(struct torture_context *tctx) +{ + struct socket_context *sock1, *sock2; + NTSTATUS status; + struct socket_address *srv_addr, *from_addr, *localhost; + size_t size = 100 + (random() % 100); + DATA_BLOB blob, blob2; + size_t sent, nread; + TALLOC_CTX *mem_ctx = tctx; + struct interface *ifaces; + + load_interface_list(tctx, tctx->lp_ctx, &ifaces); + + status = socket_create(mem_ctx, "ip", SOCKET_TYPE_DGRAM, &sock1, 0); + torture_assert_ntstatus_ok(tctx, status, "creating DGRAM IP socket 1"); + + status = socket_create(mem_ctx, "ip", SOCKET_TYPE_DGRAM, &sock2, 0); + torture_assert_ntstatus_ok(tctx, status, "creating DGRAM IP socket 1"); + + localhost = socket_address_from_strings(sock1, sock1->backend_name, + iface_list_best_ip(ifaces, "127.0.0.1"), 0); + + torture_assert(tctx, localhost, "Localhost not found"); + + status = socket_listen(sock1, localhost, 0, 0); + torture_assert_ntstatus_ok(tctx, status, "listen on socket 1"); + + srv_addr = socket_get_my_addr(sock1, mem_ctx); + torture_assert(tctx, srv_addr != NULL && + strcmp(srv_addr->addr, iface_list_best_ip(ifaces, "127.0.0.1")) == 0, + talloc_asprintf(tctx, + "Expected server address of %s but got %s", + iface_list_best_ip(ifaces, "127.0.0.1"), srv_addr ? srv_addr->addr : NULL)); + + torture_comment(tctx, "server port is %d\n", srv_addr->port); + + blob = data_blob_talloc(mem_ctx, NULL, size); + blob2 = data_blob_talloc(mem_ctx, NULL, size); + generate_random_buffer(blob.data, blob.length); + + sent = size; + status = socket_sendto(sock2, &blob, &sent, srv_addr); + torture_assert_ntstatus_ok(tctx, status, "sendto() on socket 2"); + + status = socket_recvfrom(sock1, blob2.data, size, &nread, + sock1, &from_addr); + torture_assert_ntstatus_ok(tctx, status, "recvfrom() on socket 1"); + + torture_assert_str_equal(tctx, from_addr->addr, srv_addr->addr, + "different address"); + + torture_assert_int_equal(tctx, nread, size, "Unexpected recvfrom size"); + + torture_assert_mem_equal(tctx, blob2.data, blob.data, size, + "Bad data in recvfrom"); + + generate_random_buffer(blob.data, blob.length); + status = socket_sendto(sock1, &blob, &sent, from_addr); + torture_assert_ntstatus_ok(tctx, status, "sendto() on socket 1"); + + status = socket_recvfrom(sock2, blob2.data, size, &nread, + sock2, &from_addr); + torture_assert_ntstatus_ok(tctx, status, "recvfrom() on socket 2"); + torture_assert_str_equal(tctx, from_addr->addr, srv_addr->addr, + "Unexpected recvfrom addr"); + + torture_assert_int_equal(tctx, nread, size, "Unexpected recvfrom size"); + + torture_assert_int_equal(tctx, from_addr->port, srv_addr->port, + "Unexpected recvfrom port"); + + torture_assert_mem_equal(tctx, blob2.data, blob.data, size, + "Bad data in recvfrom"); + + talloc_free(sock1); + talloc_free(sock2); + return true; +} + +/* + basic testing of tcp routines +*/ +static bool test_tcp(struct torture_context *tctx) +{ + struct socket_context *sock1, *sock2, *sock3; + NTSTATUS status; + struct socket_address *srv_addr, *from_addr, *localhost; + size_t size = 100 + (random() % 100); + DATA_BLOB blob, blob2; + size_t sent, nread; + TALLOC_CTX *mem_ctx = tctx; + struct tevent_context *ev = tctx->ev; + struct interface *ifaces; + + status = socket_create(mem_ctx, "ip", SOCKET_TYPE_STREAM, &sock1, 0); + torture_assert_ntstatus_ok(tctx, status, "creating IP stream socket 1"); + + status = socket_create(mem_ctx, "ip", SOCKET_TYPE_STREAM, &sock2, 0); + torture_assert_ntstatus_ok(tctx, status, "creating IP stream socket 1"); + + load_interface_list(tctx, tctx->lp_ctx, &ifaces); + localhost = socket_address_from_strings(sock1, sock1->backend_name, + iface_list_best_ip(ifaces, "127.0.0.1"), 0); + torture_assert(tctx, localhost, "Localhost not found"); + + status = socket_listen(sock1, localhost, 0, 0); + torture_assert_ntstatus_ok(tctx, status, "listen on socket 1"); + + srv_addr = socket_get_my_addr(sock1, mem_ctx); + torture_assert(tctx, srv_addr && srv_addr->addr, + "Unexpected socket_get_my_addr NULL\n"); + + torture_assert_str_equal(tctx, srv_addr->addr, iface_list_best_ip(ifaces, "127.0.0.1"), + "Unexpected server address"); + + torture_comment(tctx, "server port is %d\n", srv_addr->port); + + status = socket_connect_ev(sock2, NULL, srv_addr, 0, ev); + torture_assert_ntstatus_ok(tctx, status, "connect() on socket 2"); + + status = socket_accept(sock1, &sock3); + torture_assert_ntstatus_ok(tctx, status, "accept() on socket 1"); + talloc_steal(mem_ctx, sock3); + talloc_free(sock1); + + blob = data_blob_talloc(mem_ctx, NULL, size); + blob2 = data_blob_talloc(mem_ctx, NULL, size); + generate_random_buffer(blob.data, blob.length); + + sent = size; + status = socket_send(sock2, &blob, &sent); + torture_assert_ntstatus_ok(tctx, status, "send() on socket 2"); + + status = socket_recv(sock3, blob2.data, size, &nread); + torture_assert_ntstatus_ok(tctx, status, "recv() on socket 3"); + + from_addr = socket_get_peer_addr(sock3, mem_ctx); + + torture_assert(tctx, from_addr && from_addr->addr, + "Unexpected recvfrom addr NULL"); + + torture_assert_str_equal(tctx, from_addr->addr, srv_addr->addr, + "Unexpected recvfrom addr"); + + torture_assert_int_equal(tctx, nread, size, "Unexpected recvfrom size"); + + torture_assert_mem_equal(tctx, blob2.data, blob.data, size, + "Bad data in recv"); + return true; +} + +struct torture_suite *torture_local_socket(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "socket"); + + torture_suite_add_simple_test(suite, "udp", test_udp); + torture_suite_add_simple_test(suite, "tcp", test_tcp); + + return suite; +} diff --git a/source4/lib/socket/wscript_build b/source4/lib/socket/wscript_build new file mode 100644 index 0000000..e243824 --- /dev/null +++ b/source4/lib/socket/wscript_build @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +bld.SAMBA_LIBRARY('netif', + source='interface.c', + deps='samba-util interfaces samba-hostconfig', + private_library=True, + autoproto='netif_proto.h' + ) + +bld.SAMBA_MODULE('socket_ip', + source='socket_ip.c', + subsystem='samba_socket', + deps='samba-errors', + internal_module=True + ) + +bld.SAMBA_MODULE('socket_unix', + source='socket_unix.c', + subsystem='samba_socket', + deps='talloc', + internal_module=True + ) + +bld.SAMBA_SUBSYSTEM('samba_socket', + source='socket.c access.c connect_multi.c connect.c', + public_deps='talloc LIBTSOCKET', + deps='cli_composite LIBCLI_RESOLVE socket_ip socket_unix access' + ) + |