mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
783eff3f76
Signed-off-by: Andreas Schneider <asn@samba.org> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
641 lines
15 KiB
C
641 lines
15 KiB
C
/*
|
|
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;
|
|
}
|