summaryrefslogtreecommitdiffstats
path: root/source4/lib/socket/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/lib/socket/socket.c')
-rw-r--r--source4/lib/socket/socket.c640
1 files changed, 640 insertions, 0 deletions
diff --git a/source4/lib/socket/socket.c b/source4/lib/socket/socket.c
new file mode 100644
index 0000000..ef54029
--- /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 consistent */
+ 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 maximum 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;
+}