/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This file contains the routines that maintain a linked list of known
* program to udp port mappings. There are three static members initialized
* by default, one for the portmapper itself (of course), one for rpcbind,
* and one for nfs. If a program number is not in the list, then routines
* in this file contact the portmapper on the server, and dynamically add
* new members to this list.
*
* This file also contains bpmap_rmtcall() - which lets one get the port
* number AND run the rpc call in one step. Only the server that successfully
* completes the rpc call will return a result.
*
* NOTE: Because we will end up caching the port entries we need
* before the kernel begins running, we can use dynamic allocation here.
* boot_memfree() calls bpmap_memfree() to free up any dynamically
* allocated entries when the boot program has finished its job.
*/
#include <sys/types.h>
#include <rpc/types.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <rpc/xdr.h>
#include <rpc/auth.h>
#include <sys/t_lock.h>
#include "clnt.h"
#include <rpc/pmap_prot.h>
#include <rpc/pmap_rmt.h>
#include <rpc/rpc.h>
#include "brpc.h"
#include "pmap.h"
#include "nfs_inet.h"
#include <rpcsvc/nfs_prot.h>
#include <rpc/rpcb_prot.h>
#include <sys/salib.h>
#include "socket_inet.h"
#include <sys/promif.h>
#include <sys/bootdebug.h>
/* portmap structure */
#define PMAP_STATIC (3) /* last statically allocated list entry */
struct pmaplist pre_init[PMAP_STATIC + 1] = {
{ {PMAPPROG, PMAPVERS, IPPROTO_UDP, PMAPPORT}, &pre_init[1] },
/* SVR4 rpcbind listens to old portmapper port */
{ {RPCBPROG, RPCBVERS, IPPROTO_UDP, PMAPPORT}, &pre_init[2] },
{ {NFS_PROGRAM, NFS_VERSION, IPPROTO_UDP, NFS_PORT}, &pre_init[3] },
{ {NFS_PROGRAM, NFS_V3, IPPROTO_UDP, NFS_PORT}, NULL }
};
struct pmaplist *map_head = &pre_init[0];
struct pmaplist *map_tail = &pre_init[PMAP_STATIC];
#define dprintf if (boothowto & RB_DEBUG) printf
/*
* bpmap_addport: adds a new entry on to the end of the pmap cache.
* Items are kept in host order.
*/
static void
bpmap_addport(rpcprog_t prog, rpcvers_t vers, rpcport_t port)
{
struct pmaplist *newp;
/* allocate new pmaplist */
newp = (struct pmaplist *)bkmem_alloc(sizeof (struct pmaplist));
if (newp == NULL)
return; /* not fatal here, we'll just throw out the entry */
newp->pml_map.pm_prog = prog;
newp->pml_map.pm_vers = vers;
newp->pml_map.pm_prot = (rpcprot_t)IPPROTO_UDP;
newp->pml_map.pm_port = port;
map_tail->pml_next = newp;
newp->pml_next = NULL;
map_tail = newp;
}
/*
* bpmap_delport: deletes an existing entry from the list. Caution - don't
* call this function to delete statically allocated entries. Why would
* you want to, anyway? Only IPPROTO_UDP is supported, of course.
*/
static void
bpmap_delport(rpcprog_t prog, rpcvers_t vers)
{
struct pmaplist *tmp, *prev;
prev = map_head;
for (tmp = map_head; tmp != NULL; tmp = tmp->pml_next) {
if ((tmp->pml_map.pm_prog == prog) &&
(tmp->pml_map.pm_vers == vers)) {
if (tmp == map_head)
map_head = tmp->pml_next; /* new head */
else if (tmp == map_tail) {
map_tail = prev; /* new tail */
map_tail->pml_next = NULL;
} else {
/* internal delete */
prev->pml_next = tmp->pml_next;
}
#ifdef DEBUG
printf("bpmap_delport: prog: %x, vers: %x\n", prog,
vers);
#endif /* DEBUG */
bkmem_free((caddr_t)tmp, sizeof (struct pmaplist));
break;
} else
prev = tmp;
}
}
/*
* Modified strtol(3).
*/
static int
strtoi(char *str, char **ptr)
{
int c, val;
for (val = 0, c = *str++; c >= '0' && c <= '9'; c = *str++) {
val *= 10;
val += c - '0';
}
*ptr = str;
return (val);
}
/*
* (from dlboot_inet.c) (kernel)
* Convert a port number from a sockaddr_in expressed
* in universal address format.
*/
static int
uaddr2port(char *addr)
{
int p1, p2;
char *next;
/*
* A struct sockaddr_in expressed in universal address
* format looks like:
*
* "IP.IP.IP.IP.PORT[top byte].PORT[bot. byte]"
*
* Where each component expresses as a charactor,
* the corresponding part of the IP address
* and port number.
* Thus 127.0.0.1, port 2345 looks like:
*
* 49 50 55 46 48 46 48 46 49 46 57 46 52 49
* 1 2 7 . 0 . 0 . 1 . 9 . 4 1
*
* 2345 = 929base16 = 9.32+9 = 9.41
*/
(void) strtoi(addr, &next);
(void) strtoi(next, &next);
(void) strtoi(next, &next);
(void) strtoi(next, &next);
p1 = strtoi(next, &next);
p2 = strtoi(next, &next);
return ((p1 << 8) + p2);
}
/*
* Xdr routines used for calling portmapper/rpcbind.
*/
bool_t
xdr_pmap(XDR *xdrs, struct pmap *regs)
{
if (xdr_rpcprog(xdrs, &regs->pm_prog) &&
xdr_rpcvers(xdrs, &regs->pm_vers) &&
xdr_rpcprot(xdrs, &regs->pm_prot))
return (xdr_rpcprot(xdrs, &regs->pm_port));
return (FALSE);
}
bool_t
xdr_rpcb(XDR *xdrs, RPCB *objp)
{
if (!xdr_rpcprog(xdrs, &objp->r_prog))
return (FALSE);
if (!xdr_rpcvers(xdrs, &objp->r_vers))
return (FALSE);
if (!xdr_string(xdrs, &objp->r_netid, ~0))
return (FALSE);
if (!xdr_string(xdrs, &objp->r_addr, ~0))
return (FALSE);
if (!xdr_string(xdrs, &objp->r_owner, ~0))
return (FALSE);
return (TRUE);
}
/*
* XDR remote call arguments
* written for XDR_ENCODE direction only
*/
bool_t
xdr_rmtcall_args(XDR *xdrs, struct rmtcallargs *cap)
{
uint_t lenposition, argposition, position;
if (xdr_rpcprog(xdrs, &(cap->prog)) &&
xdr_rpcvers(xdrs, &(cap->vers)) &&
xdr_rpcproc(xdrs, &(cap->proc))) {
lenposition = XDR_GETPOS(xdrs);
if (!xdr_u_int(xdrs, &(cap->arglen)))
return (FALSE);
argposition = XDR_GETPOS(xdrs);
if (!(*(cap->xdr_args))(xdrs, cap->args_ptr))
return (FALSE);
position = XDR_GETPOS(xdrs);
cap->arglen = position - argposition;
XDR_SETPOS(xdrs, lenposition);
if (!xdr_u_int(xdrs, &(cap->arglen)))
return (FALSE);
XDR_SETPOS(xdrs, position);
return (TRUE);
}
return (FALSE);
}
/*
* XDR remote call results
* written for XDR_DECODE direction only
*/
bool_t
xdr_rmtcallres(XDR *xdrs, struct rmtcallres *crp)
{
caddr_t port_ptr;
port_ptr = (caddr_t)crp->port_ptr;
if (xdr_reference(xdrs, &port_ptr, sizeof (uint_t), xdr_u_int) &&
xdr_u_int(xdrs, &crp->resultslen)) {
crp->port_ptr = (rpcport_t *)port_ptr;
return ((*(crp->xdr_results))(xdrs, crp->results_ptr));
}
return (FALSE);
}
/*
* XDR remote call arguments
* written for XDR_ENCODE direction only
*/
bool_t
xdr_rpcb_rmtcallargs(XDR *xdrs, struct rpcb_rmtcallargs *objp)
{
uint_t lenposition, argposition, position;
if (!xdr_rpcprog(xdrs, &objp->prog))
return (FALSE);
if (!xdr_rpcvers(xdrs, &objp->vers))
return (FALSE);
if (!xdr_rpcproc(xdrs, &objp->proc))
return (FALSE);
/*
* All the jugglery for just getting the size of the arguments
*/
lenposition = XDR_GETPOS(xdrs);
if (!xdr_u_int(xdrs, &(objp->arglen)))
return (FALSE);
argposition = XDR_GETPOS(xdrs);
if (!(*(objp->xdr_args))(xdrs, objp->args_ptr))
return (FALSE);
position = XDR_GETPOS(xdrs);
objp->arglen = position - argposition;
XDR_SETPOS(xdrs, lenposition);
if (!xdr_u_int(xdrs, &(objp->arglen)))
return (FALSE);
XDR_SETPOS(xdrs, position);
return (TRUE);
}
/*
* XDR remote call results
* written for XDR_DECODE direction only
*/
bool_t
xdr_rpcb_rmtcallres(XDR *xdrs, struct rpcb_rmtcallres *objp)
{
if (!xdr_string(xdrs, &objp->addr_ptr, ~0))
return (FALSE);
if (!xdr_u_int(xdrs, &objp->resultslen))
return (FALSE);
return ((*(objp->xdr_results))(xdrs, objp->results_ptr));
}
/*
* bpmap_rmtcall: does PMAPPROC_CALLIT broadcasts w/ rpc_call requests.
* Lets one do a PMAPGETPORT/RPC PROC call in one easy step. sockaddr_in args
* are taken as network order.
*
* Code adapted from bpmap_rmtcall() in dlboot_inet.c (kernel)
*/
/*ARGSUSED*/
enum clnt_stat
bpmap_rmtcall(
rpcprog_t prog, /* rpc program number to call. */
rpcvers_t vers, /* rpc program version */
rpcproc_t proc, /* rpc procedure to call */
xdrproc_t in_xdr, /* routine to serialize arguments */
caddr_t args, /* arg vector for remote call */
xdrproc_t out_xdr, /* routine to deserialize results */
caddr_t ret, /* addr of buf to place results in */
int rexmit, /* retransmission interval (secs) */
int wait, /* how long (secs) to wait for a resp */
struct sockaddr_in *to, /* destination */
struct sockaddr_in *from, /* filled in w/ responder's port/addr */
uint_t auth) /* type of authentication wanted. */
{
enum clnt_stat status; /* rpc_call status */
rpcport_t port = 0; /* returned port # */
struct rmtcallargs pmap_a; /* args for pmap call */
struct rmtcallres pmap_r; /* results from pmap call */
struct rpcb_rmtcallargs rpcb_a; /* args for rpcb call */
struct rpcb_rmtcallres rpcb_r; /* results from rpcb call */
char ua[UA_SIZE]; /* universal addr buffer */
/* initialize pmap */
pmap_a.prog = prog;
pmap_a.vers = vers;
pmap_a.proc = proc;
pmap_a.args_ptr = args;
pmap_a.xdr_args = in_xdr;
pmap_r.port_ptr = &port;
pmap_r.results_ptr = ret;
pmap_r.xdr_results = out_xdr;
status = brpc_call((rpcprog_t)PMAPPROG, (rpcvers_t)PMAPVERS,
(rpcproc_t)PMAPPROC_CALLIT, xdr_rmtcall_args, (caddr_t)&pmap_a,
xdr_rmtcallres, (caddr_t)&pmap_r, rexmit, wait, to, from,
AUTH_NONE);
if (status != RPC_PROGUNAVAIL) {
if (status == RPC_SUCCESS) {
/* delete old port mapping, if it exists */
bpmap_delport(prog, vers);
/* save the new port mapping */
bpmap_addport(prog, vers, port);
}
return (status);
}
/*
* PMAP is unavailable. Maybe there's a SVR4 machine, with rpcbind.
*/
bzero(ua, sizeof (ua));
/* initialize rpcb */
rpcb_a.prog = prog;
rpcb_a.vers = vers;
rpcb_a.proc = proc;
rpcb_a.args_ptr = args;
rpcb_a.xdr_args = in_xdr;
rpcb_r.addr_ptr = ua;
rpcb_r.results_ptr = ret;
rpcb_r.xdr_results = out_xdr;
status = brpc_call((rpcprog_t)RPCBPROG, (rpcvers_t)RPCBVERS,
(rpcproc_t)RPCBPROC_CALLIT, xdr_rpcb_rmtcallargs, (caddr_t)&rpcb_a,
xdr_rpcb_rmtcallres, (caddr_t)&rpcb_r, rexmit, wait, to, from,
AUTH_NONE);
if (status == RPC_SUCCESS) {
/* delete old port mapping, if it exists */
bpmap_delport(prog, vers);
/* save the new port mapping */
port = ntohs(uaddr2port(ua));
bpmap_addport(prog, vers, port);
}
return (status);
}
/*
* bpmap_getport: Queries current list of cached pmap_list entries,
* returns the port number of the entry found. If the port number
* is not cached, then getport makes a rpc call first to the portmapper,
* and then to rpcbind (SVR4) if the portmapper does not respond. The
* returned port is then added to the cache, and the port number is
* returned. If both portmapper and rpc bind fail to give us the necessary
* port, we return 0 to signal we hit an error, and set rpc_stat to
* the appropriate RPC error code. Only IPPROTO_UDP protocol is supported.
*
* Port and sockaddr_in arguments taken in network order. rpcport_t is returned
* in host order.
*/
rpcport_t
bpmap_getport(rpcprog_t prog, rpcvers_t vers, enum clnt_stat *rpc_stat,
struct sockaddr_in *to, struct sockaddr_in *from)
{
struct pmaplist *walk;
struct pmap pmap_send; /* portmap */
in_port_t pmap_port;
rpcport_t dport;
#ifdef DEBUG
printf("bpmap_getport: called with: prog: %d, vers: %d\n", prog, vers);
#endif /* DEBUG */
for (walk = map_head; walk != 0; walk = walk->pml_next) {
if ((walk->pml_map.pm_prog == prog) &&
(walk->pml_map.pm_vers == vers) &&
(walk->pml_map.pm_prot == (rpcprot_t)IPPROTO_UDP)) {
#ifdef DEBUG
printf("bpmap_getport: Found in cache. returning: %d\n",
walk->pml_map.pm_port);
#endif /* DEBUG */
return (walk->pml_map.pm_port);
}
}
/*
* Not in the cache. First try the portmapper (SunOS server?) and
* if that fails, try rpcbind (SVR4 server).
*/
pmap_send.pm_prog = prog;
pmap_send.pm_vers = vers;
pmap_send.pm_prot = (rpcprot_t)IPPROTO_UDP;
pmap_send.pm_port = 0; /* what we're after */
*rpc_stat = brpc_call(PMAPPROG, PMAPVERS, PMAPPROC_GETPORT,
xdr_pmap, (caddr_t)&pmap_send, xdr_u_short,
(caddr_t)&pmap_port, 0, 0, to, from, AUTH_NONE);
if (*rpc_stat == RPC_PROGUNAVAIL) {
/*
* The portmapper isn't available. Try rpcbind.
* Maybe the server is a SVR4 server.
*/
char *ua; /* universal address */
char ua_buf[UA_SIZE]; /* and its buffer */
RPCB rpcb_send;
rpcb_send.r_prog = prog;
rpcb_send.r_vers = vers;
rpcb_send.r_netid = NULL;
rpcb_send.r_addr = NULL;
rpcb_send.r_owner = NULL;
bzero(ua_buf, UA_SIZE);
ua = ua_buf;
/*
* Again, default # of retries. xdr_wrapstring()
* wants a char **.
*/
*rpc_stat = brpc_call(RPCBPROG, RPCBVERS, RPCBPROC_GETADDR,
xdr_rpcb, (caddr_t)&rpcb_send, xdr_wrapstring,
(char *)&ua, 0, 0, to, from, AUTH_NONE);
if (*rpc_stat == RPC_SUCCESS) {
if (ua[0] != '\0')
dport = ntohs(uaddr2port(ua));
else
return (0); /* Address unknown */
}
} else {
/*
* Why are rpcport_t's uint32_t? port numbers are uint16_t
* for ipv4 AND ipv6.... XXXX
*/
dport = (rpcport_t)pmap_port;
}
if (*rpc_stat != RPC_SUCCESS) {
dprintf("pmap_getport: Failed getting port.\n");
return (0); /* we failed. */
}
#ifdef DEBUG
printf("bpmap_getport: prog: %d, vers: %d; returning port: %d.\n",
prog, vers, dport);
#endif /* DEBUG */
bpmap_addport(prog, vers, dport);
return (dport);
}
/*
* bpmap_memfree: frees up any dynamically allocated entries.
*/
void
bpmap_memfree(void)
{
struct pmaplist *current, *tmp;
if (map_tail == &pre_init[PMAP_STATIC])
return; /* no dynamic entries */
/* free from head of the list to the tail. */
current = pre_init[PMAP_STATIC].pml_next;
while (current != NULL) {
tmp = current->pml_next;
bkmem_free((caddr_t)current, sizeof (struct pmaplist));
current = tmp;
}
}