root/sbin/hastd/proto_uds.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2009-2010 The FreeBSD Foundation
 *
 * This software was developed by Pawel Jakub Dawidek under sponsorship from
 * the FreeBSD Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
/* UDS - UNIX Domain Socket */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "pjdlog.h"
#include "proto_impl.h"

#define UDS_CTX_MAGIC   0xd541c
struct uds_ctx {
        int                     uc_magic;
        struct sockaddr_un      uc_sun;
        int                     uc_fd;
        int                     uc_side;
#define UDS_SIDE_CLIENT         0
#define UDS_SIDE_SERVER_LISTEN  1
#define UDS_SIDE_SERVER_WORK    2
        pid_t                   uc_owner;
};

static void uds_close(void *ctx);

static int
uds_addr(const char *addr, struct sockaddr_un *sunp)
{

        if (addr == NULL)
                return (-1);

        if (strncasecmp(addr, "uds://", 6) == 0)
                addr += 6;
        else if (strncasecmp(addr, "unix://", 7) == 0)
                addr += 7;
        else if (addr[0] == '/' &&      /* If it starts from /... */
            strstr(addr, "://") == NULL)/* ...and there is no prefix... */
                ;                       /* ...we assume its us. */
        else
                return (-1);

        sunp->sun_family = AF_UNIX;
        if (strlcpy(sunp->sun_path, addr, sizeof(sunp->sun_path)) >=
            sizeof(sunp->sun_path)) {
                return (ENAMETOOLONG);
        }
        sunp->sun_len = SUN_LEN(sunp);

        return (0);
}

static int
uds_common_setup(const char *addr, void **ctxp, int side)
{
        struct uds_ctx *uctx;
        int ret;

        uctx = malloc(sizeof(*uctx));
        if (uctx == NULL)
                return (errno);

        /* Parse given address. */
        if ((ret = uds_addr(addr, &uctx->uc_sun)) != 0) {
                free(uctx);
                return (ret);
        }

        uctx->uc_fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (uctx->uc_fd == -1) {
                ret = errno;
                free(uctx);
                return (ret);
        }

        uctx->uc_side = side;
        uctx->uc_owner = 0;
        uctx->uc_magic = UDS_CTX_MAGIC;
        *ctxp = uctx;

        return (0);
}

static int
uds_client(const char *srcaddr, const char *dstaddr, void **ctxp)
{
        int ret;

        ret = uds_common_setup(dstaddr, ctxp, UDS_SIDE_CLIENT);
        if (ret != 0)
                return (ret);

        PJDLOG_ASSERT(srcaddr == NULL);

        return (0);
}

static int
uds_connect(void *ctx, int timeout)
{
        struct uds_ctx *uctx = ctx;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
        PJDLOG_ASSERT(uctx->uc_side == UDS_SIDE_CLIENT);
        PJDLOG_ASSERT(uctx->uc_fd >= 0);
        PJDLOG_ASSERT(timeout >= -1);

        if (connect(uctx->uc_fd, (struct sockaddr *)&uctx->uc_sun,
            sizeof(uctx->uc_sun)) == -1) {
                return (errno);
        }

        return (0);
}

static int
uds_connect_wait(void *ctx, int timeout)
{
        struct uds_ctx *uctx = ctx;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
        PJDLOG_ASSERT(uctx->uc_side == UDS_SIDE_CLIENT);
        PJDLOG_ASSERT(uctx->uc_fd >= 0);
        PJDLOG_ASSERT(timeout >= 0);

        return (0);
}

static int
uds_server(const char *addr, void **ctxp)
{
        struct uds_ctx *uctx;
        int ret;

        ret = uds_common_setup(addr, ctxp, UDS_SIDE_SERVER_LISTEN);
        if (ret != 0)
                return (ret);

        uctx = *ctxp;

        (void)unlink(uctx->uc_sun.sun_path);
        if (bind(uctx->uc_fd, (struct sockaddr *)&uctx->uc_sun,
            sizeof(uctx->uc_sun)) == -1) {
                ret = errno;
                uds_close(uctx);
                return (ret);
        }
        uctx->uc_owner = getpid();
        if (listen(uctx->uc_fd, 8) == -1) {
                ret = errno;
                uds_close(uctx);
                return (ret);
        }

        return (0);
}

static int
uds_accept(void *ctx, void **newctxp)
{
        struct uds_ctx *uctx = ctx;
        struct uds_ctx *newuctx;
        socklen_t fromlen;
        int ret;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
        PJDLOG_ASSERT(uctx->uc_side == UDS_SIDE_SERVER_LISTEN);
        PJDLOG_ASSERT(uctx->uc_fd >= 0);

        newuctx = malloc(sizeof(*newuctx));
        if (newuctx == NULL)
                return (errno);

        fromlen = sizeof(newuctx->uc_sun);
        newuctx->uc_fd = accept(uctx->uc_fd,
            (struct sockaddr *)&newuctx->uc_sun, &fromlen);
        if (newuctx->uc_fd == -1) {
                ret = errno;
                free(newuctx);
                return (ret);
        }

        newuctx->uc_side = UDS_SIDE_SERVER_WORK;
        newuctx->uc_magic = UDS_CTX_MAGIC;
        *newctxp = newuctx;

        return (0);
}

static int
uds_send(void *ctx, const unsigned char *data, size_t size, int fd)
{
        struct uds_ctx *uctx = ctx;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
        PJDLOG_ASSERT(uctx->uc_fd >= 0);

        return (proto_common_send(uctx->uc_fd, data, size, fd));
}

static int
uds_recv(void *ctx, unsigned char *data, size_t size, int *fdp)
{
        struct uds_ctx *uctx = ctx;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
        PJDLOG_ASSERT(uctx->uc_fd >= 0);

        return (proto_common_recv(uctx->uc_fd, data, size, fdp));
}

static int
uds_descriptor(const void *ctx)
{
        const struct uds_ctx *uctx = ctx;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);

        return (uctx->uc_fd);
}

static void
uds_local_address(const void *ctx, char *addr, size_t size)
{
        const struct uds_ctx *uctx = ctx;
        struct sockaddr_un sun;
        socklen_t sunlen;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
        PJDLOG_ASSERT(addr != NULL);

        sunlen = sizeof(sun);
        if (getsockname(uctx->uc_fd, (struct sockaddr *)&sun, &sunlen) == -1) {
                PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
                return;
        }
        PJDLOG_ASSERT(sun.sun_family == AF_UNIX);
        if (sun.sun_path[0] == '\0') {
                PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
                return;
        }
        PJDLOG_VERIFY(snprintf(addr, size, "uds://%s", sun.sun_path) < (ssize_t)size);
}

static void
uds_remote_address(const void *ctx, char *addr, size_t size)
{
        const struct uds_ctx *uctx = ctx;
        struct sockaddr_un sun;
        socklen_t sunlen;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);
        PJDLOG_ASSERT(addr != NULL);

        sunlen = sizeof(sun);
        if (getpeername(uctx->uc_fd, (struct sockaddr *)&sun, &sunlen) == -1) {
                PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
                return;
        }
        PJDLOG_ASSERT(sun.sun_family == AF_UNIX);
        if (sun.sun_path[0] == '\0') {
                PJDLOG_VERIFY(strlcpy(addr, "N/A", size) < size);
                return;
        }
        snprintf(addr, size, "uds://%s", sun.sun_path);
}

static void
uds_close(void *ctx)
{
        struct uds_ctx *uctx = ctx;

        PJDLOG_ASSERT(uctx != NULL);
        PJDLOG_ASSERT(uctx->uc_magic == UDS_CTX_MAGIC);

        if (uctx->uc_fd >= 0)
                close(uctx->uc_fd);
        /*
         * Unlink the socket only if we are the owner and this is descriptor
         * we listen on.
         */
        if (uctx->uc_side == UDS_SIDE_SERVER_LISTEN &&
            uctx->uc_owner == getpid()) {
                PJDLOG_ASSERT(uctx->uc_sun.sun_path[0] != '\0');
                if (unlink(uctx->uc_sun.sun_path) == -1) {
                        pjdlog_errno(LOG_WARNING,
                            "Unable to unlink socket file %s",
                            uctx->uc_sun.sun_path);
                }
        }
        uctx->uc_owner = 0;
        uctx->uc_magic = 0;
        free(uctx);
}

static struct proto uds_proto = {
        .prt_name = "uds",
        .prt_client = uds_client,
        .prt_connect = uds_connect,
        .prt_connect_wait = uds_connect_wait,
        .prt_server = uds_server,
        .prt_accept = uds_accept,
        .prt_send = uds_send,
        .prt_recv = uds_recv,
        .prt_descriptor = uds_descriptor,
        .prt_local_address = uds_local_address,
        .prt_remote_address = uds_remote_address,
        .prt_close = uds_close
};

static __constructor void
uds_ctor(void)
{

        proto_register(&uds_proto, false);
}