/*      $NetBSD: check_bound.c,v 1.9 2020/06/17 00:16:22 kamil Exp $    */
/*      $FreeBSD: head/usr.sbin/rpcbind/check_bound.c 300942 2016-05-29 06:01:18Z ngie $ */

/*-
* Copyright (c) 2009, Sun Microsystems, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
*   this list of conditions and the following disclaimer.
* - 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.
* - Neither the name of Sun Microsystems, Inc. nor the names of its
*   contributors may be used to endorse or promote products derived
*   from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
*/
/*
* Copyright (c) 1986 - 1991 by Sun Microsystems, Inc.
*/

/* #ident       "@(#)check_bound.c      1.15    93/07/05 SMI" */

#if 0
#ifndef lint
static  char sccsid[] = "@(#)check_bound.c 1.11 89/04/21 Copyr 1989 Sun Micro";
#endif
#endif

/*
* check_bound.c
* Checks to see whether the program is still bound to the
* claimed address and returns the universal merged address
*
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <rpc/rpc.h>
#include <stdio.h>
#include <netconfig.h>
#include <syslog.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#ifdef RPCBIND_RUMP
#include <rump/rump.h>
#include <rump/rump_syscallshotgun.h>
#include <rump/rump_syscalls.h>
#endif

#include "rpcbind.h"

struct fdlist {
       int fd;
       struct netconfig *nconf;
       struct fdlist *next;
       int check_binding;
};

static struct fdlist *fdhead;   /* Link list of the check fd's */
static struct fdlist *fdtail;
static char nullstring[] = "";

static bool_t check_bound(struct fdlist *, const char *uaddr);

/*
* Returns 1 if the given address is bound for the given addr & transport
* For all error cases, we assume that the address is bound
* Returns 0 for success.
*/
static bool_t
check_bound(struct fdlist *fdl, const char *uaddr)
{
       int fd;
       struct netbuf *na;
       int ans;

       if (fdl->check_binding == FALSE)
               return (TRUE);

       na = uaddr2taddr(fdl->nconf, uaddr);
       if (!na)
               return (TRUE); /* punt, should never happen */

       fd = __rpc_nconf2fd(fdl->nconf);
       if (fd < 0) {
               free(na->buf);
               free(na);
               return (TRUE);
       }

       ans = bind(fd, (struct sockaddr *)na->buf, na->len);

#ifdef RPCBIND_RUMP
       rump_sys_close(fd);
#else
       close(fd);
#endif
       free(na->buf);
       free(na);

       return (ans == 0 ? FALSE : TRUE);
}

int
add_bndlist(struct netconfig *nconf, struct netbuf *baddr __unused)
{
       struct fdlist *fdl;
       struct netconfig *newnconf;

       newnconf = getnetconfigent(nconf->nc_netid);
       if (newnconf == NULL)
               return (-1);
       fdl = malloc(sizeof(*fdl));
       if (fdl == NULL) {
               freenetconfigent(newnconf);
               syslog(LOG_ERR, "no memory!");
               return (-1);
       }
       fdl->nconf = newnconf;
       fdl->next = NULL;
       if (fdhead == NULL) {
               fdhead = fdl;
               fdtail = fdl;
       } else {
               fdtail->next = fdl;
               fdtail = fdl;
       }
       /* XXX no bound checking for now */
       fdl->check_binding = FALSE;

       return 0;
}

bool_t
is_bound(const char *netid, const char *uaddr)
{
       struct fdlist *fdl;

       for (fdl = fdhead; fdl; fdl = fdl->next)
               if (strcmp(fdl->nconf->nc_netid, netid) == 0)
                       break;
       if (fdl == NULL)
               return (TRUE);
       return (check_bound(fdl, uaddr));
}

/*
* Returns NULL if there was some system error.
* Returns "" if the address was not bound, i.e the server crashed.
* Returns the merged address otherwise.
*/
char *
mergeaddr(SVCXPRT *xprt, char *netid, char *uaddr, char *saddr)
{
       struct fdlist *fdl;
       char *c_uaddr, *s_uaddr, *m_uaddr, *allocated_uaddr = NULL;

       for (fdl = fdhead; fdl; fdl = fdl->next)
               if (strcmp(fdl->nconf->nc_netid, netid) == 0)
                       break;
       if (fdl == NULL)
               return (NULL);
       if (check_bound(fdl, uaddr) == FALSE)
               /* that server died */
               return nullstring;
       /*
        * Try to determine the local address on which the client contacted us,
        * so we can send a reply from the same address.  If it's unknown, then
        * try to determine which address the client used, and pick a nearby
        * local address.
        *
        * If saddr is not NULL, the remote client may have included the
        * address by which it contacted us.  Use that for the "client" uaddr,
        * otherwise use the info from the SVCXPRT.
        */
       if (xprt->xp_rtaddr.buf != NULL) {
               c_uaddr = taddr2uaddr(fdl->nconf, &xprt->xp_rtaddr);
               allocated_uaddr = c_uaddr;
       } else if (saddr != NULL) {
               c_uaddr = saddr;
       } else {
               c_uaddr = taddr2uaddr(fdl->nconf, svc_getrpccaller(xprt));
               allocated_uaddr = c_uaddr;
       }
       if (c_uaddr == NULL) {
               syslog(LOG_ERR, "taddr2uaddr failed for %s",
                       fdl->nconf->nc_netid);
               return (NULL);
       }

#ifdef RPCBIND_DEBUG
       if (debugging) {
               if (saddr == NULL) {
                       fprintf(stderr, "mergeaddr: client uaddr = %s\n",
                           c_uaddr);
               } else {
                       fprintf(stderr, "mergeaddr: contact uaddr = %s\n",
                           c_uaddr);
               }
       }
#endif
       s_uaddr = uaddr;
       /*
        * This is all we should need for IP 4 and 6
        */
       m_uaddr = addrmerge(svc_getrpccaller(xprt), s_uaddr, c_uaddr, netid);
#ifdef RPCBIND_DEBUG
       if (debugging)
               fprintf(stderr, "mergeaddr: uaddr = %s, merged uaddr = %s\n",
                               uaddr, m_uaddr);
#endif
       free(allocated_uaddr);
       return (m_uaddr);
}

/*
* Returns a netconf structure from its internal list.  This
* structure should not be freed.
*/
struct netconfig *
rpcbind_get_conf(const char *netid)
{
       struct fdlist *fdl;

       for (fdl = fdhead; fdl; fdl = fdl->next)
               if (strcmp(fdl->nconf->nc_netid, netid) == 0)
                       break;
       if (fdl == NULL)
               return (NULL);
       return (fdl->nconf);
}