/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. * Copyright 2017 Joyent Inc */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * rpcb_svc_com.c * The commom server procedure for the rpcbind. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PORTMAP #include #include #else #define PMAPVERS 2 #endif /* PORTMAP */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rpcbind.h" #include static struct finfo *forward_register(ulong_t, struct netbuf *, int, char *); static void forward_destroy(struct finfo *); static void handle_reply(svc_input_id_t, int, unsigned int, void *); static int netbufcmp(struct netbuf *, struct netbuf *); static void netbuffree(struct netbuf *); static struct netbuf *netbufdup(struct netbuf *); static void find_versions(rpcprog_t, char *, rpcvers_t *, rpcvers_t *); static rpcblist_ptr find_service(ulong_t, ulong_t, char *); #ifdef PORTMAP static int add_pmaplist(RPCB *); #endif zoneid_t myzone; /* * Set a mapping of program, version, netid */ bool_t rpcbproc_set_com(RPCB *regp, bool_t *result, struct svc_req *rqstp, int rpcbversnum) { char owner[64]; *result = map_set(regp, getowner(rqstp->rq_xprt, owner)); rpcbs_set(rpcbversnum - PMAPVERS, *result); return (TRUE); } bool_t map_set(RPCB *regp, char *owner) { RPCB *a; rpcblist_ptr rbl, fnd; /* * check to see if already used * find_service returns a hit even if * the versions don't match, so check for it */ (void) rw_wrlock(&list_rbl_lock); #ifdef PORTMAP (void) rw_wrlock(&list_pml_lock); #endif /* PORTMAP */ fnd = find_service(regp->r_prog, regp->r_vers, regp->r_netid); if (fnd && (fnd->rpcb_map.r_vers == regp->r_vers)) { if (strcmp(fnd->rpcb_map.r_addr, regp->r_addr) == 0) { /* * if these match then it is already * registered so just say "OK". */ #ifdef PORTMAP (void) rw_unlock(&list_pml_lock); #endif /* PORTMAP */ (void) rw_unlock(&list_rbl_lock); return (TRUE); } else { /* * Check if server is up. If so, return FALSE. * If not, cleanup old registrations for the * program and register the new server. */ if (is_bound(fnd->rpcb_map.r_netid, fnd->rpcb_map.r_addr)) { #ifdef PORTMAP (void) rw_unlock(&list_pml_lock); #endif /* PORTMAP */ (void) rw_unlock(&list_rbl_lock); return (FALSE); } delete_prog(regp->r_prog); fnd = NULL; } } #ifdef PORTMAP (void) rw_unlock(&list_pml_lock); #endif /* PORTMAP */ /* * add to the end of the list */ rbl = malloc(sizeof (RPCBLIST)); if (rbl == NULL) { (void) rw_unlock(&list_rbl_lock); return (FALSE); } a = &rbl->rpcb_map; a->r_prog = regp->r_prog; a->r_vers = regp->r_vers; a->r_netid = strdup(regp->r_netid); a->r_addr = strdup(regp->r_addr); a->r_owner = strdup(owner); if (a->r_addr == NULL || a->r_netid == NULL|| a->r_owner == NULL) { (void) rw_unlock(&list_rbl_lock); delete_rbl(rbl); return (FALSE); } rbl->rpcb_next = NULL; if (list_rbl == NULL) { list_rbl = rbl; } else { for (fnd = list_rbl; fnd->rpcb_next; fnd = fnd->rpcb_next) ; fnd->rpcb_next = rbl; } #ifdef PORTMAP (void) add_pmaplist(regp); #endif (void) rw_unlock(&list_rbl_lock); return (TRUE); } /* * Unset a mapping of program, version, netid */ bool_t rpcbproc_unset_com(RPCB *regp, bool_t *result, struct svc_req *rqstp, int rpcbversnum) { char owner[64]; *result = map_unset(regp, getowner(rqstp->rq_xprt, owner)); rpcbs_unset(rpcbversnum - PMAPVERS, *result); return (TRUE); } bool_t map_unset(RPCB *regp, char *owner) { #ifdef PORTMAP int ans = 0; #endif rpcblist_ptr rbl, next, prev = NULL; if (owner == NULL) return (0); (void) rw_wrlock(&list_rbl_lock); for (rbl = list_rbl; rbl != NULL; rbl = next) { next = rbl->rpcb_next; if ((rbl->rpcb_map.r_prog != regp->r_prog) || (rbl->rpcb_map.r_vers != regp->r_vers) || (regp->r_netid[0] && strcasecmp(regp->r_netid, rbl->rpcb_map.r_netid))) { /* prev moves forwards */ prev = rbl; continue; } /* * Check whether appropriate uid. Unset only * if superuser or the owner itself. */ if (strcmp(owner, "superuser") && strcmp(rbl->rpcb_map.r_owner, owner)) { (void) rw_unlock(&list_rbl_lock); return (0); } /* prev stays */ #ifdef PORTMAP ans = 1; #endif delete_rbl(rbl); if (prev == NULL) list_rbl = next; else prev->rpcb_next = next; } #ifdef PORTMAP if (ans != 0) { (void) rw_wrlock(&list_pml_lock); (void) del_pmaplist(regp); (void) rw_unlock(&list_pml_lock); } #endif (void) rw_unlock(&list_rbl_lock); /* * We return 1 either when the entry was not there or it * was able to unset it. It can come to this point only if * at least one of the conditions is true. */ return (1); } void delete_rbl(rpcblist_ptr rbl) { free(rbl->rpcb_map.r_addr); free(rbl->rpcb_map.r_netid); free(rbl->rpcb_map.r_owner); free(rbl); } void delete_prog(rpcprog_t prog) { rpcblist_ptr rbl, next, prev = NULL; assert(RW_WRITE_HELD(&list_rbl_lock)); for (rbl = list_rbl; rbl != NULL; rbl = next) { next = rbl->rpcb_next; if (rbl->rpcb_map.r_prog != prog || is_bound(rbl->rpcb_map.r_netid, rbl->rpcb_map.r_addr)) { prev = rbl; continue; } #ifdef PORTMAP (void) del_pmaplist(&rbl->rpcb_map); #endif delete_rbl(rbl); if (prev == NULL) list_rbl = next; else prev->rpcb_next = next; } } /* * Lookup the mapping for a program, version and return its * address. Assuming that the caller wants the address of the * server running on the transport on which the request came. * * For RPCBPROC_GETVERSADDR it will return a service with the exact version * number only. * * Otherwise, even if a service with a different version number is available, * it will return that address. The client should check with an * clnt_call to verify whether the service is the one that is desired. * * We also try to resolve the universal address in terms of * address of the caller. */ bool_t rpcbproc_getaddr_com(RPCB *regp, char **result, struct svc_req *rqstp, ulong_t rpcbversnum) { char *saddr = NULL; rpcblist_ptr fnd; struct netconfig *trans_conf; /* transport netconfig */ SVCXPRT *transp = rqstp->rq_xprt; int verstype = rqstp->rq_proc == RPCBPROC_GETVERSADDR ? RPCB_ONEVERS : RPCB_ALLVERS; bool_t pml_locked = FALSE; /* * There is a potential window at startup during which rpcbind * service has been established over IPv6 but not over IPv4. If an * IPv4 request comes in during that window, the IP code will map * it into IPv6. We could patch up the request so that it looks * like IPv4 (so that rpcbind returns an IPv4 uaddr to the caller), * but that requires some non-trivial code and it's hard to test. * Instead, drop the request on the floor and force the caller to * retransmit. By the time rpcbind sees the retransmission, IPv4 * service should be in place and it should see the request as * IPv4, as desired. */ trans_conf = rpcbind_get_conf(transp->xp_netid); if (strcmp(trans_conf->nc_protofmly, NC_INET6) == 0) { struct sockaddr_in6 *rmtaddr; rmtaddr = (struct sockaddr_in6 *)transp->xp_rtaddr.buf; if (IN6_IS_ADDR_V4MAPPED(&rmtaddr->sin6_addr)) { syslog(LOG_DEBUG, "IPv4 GETADDR request mapped to IPv6: ignoring"); *result = NULL; return (FALSE); } } (void) rw_rdlock(&list_rbl_lock); retry: fnd = find_service(regp->r_prog, regp->r_vers, transp->xp_netid); if (fnd && ((verstype == RPCB_ALLVERS) || (regp->r_vers == fnd->rpcb_map.r_vers))) { if (*(regp->r_addr) != '\0') { /* may contain a hint about */ saddr = regp->r_addr; /* the interface that we */ } /* should use */ if (!(*result = mergeaddr(transp, transp->xp_netid, fnd->rpcb_map.r_addr, saddr))) { /* Try whatever we have */ *result = strdup(fnd->rpcb_map.r_addr); } else if (!(*result)[0]) { if (!pml_locked) { (void) rw_unlock(&list_rbl_lock); (void) rw_wrlock(&list_rbl_lock); #ifdef PORTMAP (void) rw_wrlock(&list_pml_lock); #endif /* PORTMAP */ pml_locked = TRUE; goto retry; } /* * The server died. Unset all versions of this prog. */ delete_prog(regp->r_prog); *result = NULL; } } else { *result = NULL; } #ifdef PORTMAP if (pml_locked) (void) rw_unlock(&list_pml_lock); #endif /* PORTMAP */ (void) rw_unlock(&list_rbl_lock); rpcbs_getaddr(rpcbversnum - PMAPVERS, regp->r_prog, regp->r_vers, transp->xp_netid, *result); return (TRUE); } /* ARGSUSED */ bool_t rpcbproc_dump_com(void *argp, rpcblist_ptr **result) { /* * list_rbl_lock is unlocked in xdr_rpcblist_ptr_ptr() */ (void) rw_rdlock(&list_rbl_lock); *result = &list_rbl; return (TRUE); } bool_t xdr_rpcblist_ptr_ptr(XDR *xdrs, rpcblist_ptr **objp) { if (xdrs->x_op == XDR_FREE) { /* * list_rbl_lock is locked in rpcbproc_dump_com() */ rw_unlock(&list_rbl_lock); return (TRUE); } return (xdr_rpcblist_ptr(xdrs, *objp)); } /* ARGSUSED */ bool_t rpcbproc_gettime_com(void *argp, ulong_t *result) { (void) time((time_t *)result); return (TRUE); } /* * Convert uaddr to taddr. Should be used only by * local servers/clients. (kernel level stuff only) */ bool_t rpcbproc_uaddr2taddr_com(char **uaddrp, struct netbuf *result, struct svc_req *rqstp) { struct netconfig *nconf; struct netbuf *taddr; if (((nconf = rpcbind_get_conf(rqstp->rq_xprt->xp_netid)) == NULL) || ((taddr = uaddr2taddr(nconf, *uaddrp)) == NULL)) { (void) memset(result, 0, sizeof (*result)); return (TRUE); } memcpy(result, taddr, sizeof (*result)); free(taddr); return (TRUE); } /* * Convert taddr to uaddr. Should be used only by * local servers/clients. (kernel level stuff only) */ bool_t rpcbproc_taddr2uaddr_com(struct netbuf *taddr, char **result, struct svc_req *rqstp) { struct netconfig *nconf; if ((nconf = rpcbind_get_conf(rqstp->rq_xprt->xp_netid)) == NULL) *result = NULL; else *result = taddr2uaddr(nconf, taddr); return (TRUE); } /* * Stuff for the rmtcall service */ bool_t xdr_rpcb_rmtcallargs(XDR *xdrs, rpcb_rmtcallargs *objp) { if (!xdr_u_long(xdrs, &objp->prog)) return (FALSE); if (!xdr_u_long(xdrs, &objp->vers)) return (FALSE); if (!xdr_u_long(xdrs, &objp->proc)) return (FALSE); if (!xdr_bytes(xdrs, (char **)&objp->args.args_val, (uint_t *)&objp->args.args_len, ~0)) return (FALSE); return (TRUE); } #ifdef PORTMAP bool_t xdr_rmtcallres(XDR *xdrs, rmtcallres *objp) { if (!xdr_u_long(xdrs, &objp->port)) return (FALSE); if (!xdr_bytes(xdrs, (char **)&objp->res.res_val, (uint_t *)&objp->res.res_len, ~0)) return (FALSE); return (TRUE); } #endif bool_t xdr_rpcb_rmtcallres(XDR *xdrs, rpcb_rmtcallres *objp) { if (!xdr_string(xdrs, &objp->addr, ~0)) return (FALSE); if (!xdr_bytes(xdrs, (char **)&objp->results.results_val, (uint_t *)&objp->results.results_len, ~0)) return (FALSE); return (TRUE); } struct rmtcallfd_list { int fd; char *netid; struct rmtcallfd_list *next; }; static struct rmtcallfd_list *rmthead; static struct rmtcallfd_list *rmttail; #define MASKVAL (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND) int create_rmtcall_fd(struct netconfig *nconf) { int fd; struct rmtcallfd_list *rmt; if ((fd = t_open(nconf->nc_device, O_RDWR, NULL)) == -1) { if (debugging) fprintf(stderr, "create_rmtcall_fd: couldn't open " "\"%s\" (errno %d, t_errno %d)\n", nconf->nc_device, errno, t_errno); return (-1); } if (t_bind(fd, NULL, NULL) == -1) { if (debugging) fprintf(stderr, "create_rmtcall_fd: couldn't bind to " "fd for \"%s\" (errno %d, t_errno %d)\n", nconf->nc_device, errno, t_errno); return (-1); } rmt = malloc(sizeof (struct rmtcallfd_list)); if (rmt == NULL) { syslog(LOG_ERR, "create_rmtcall_fd: no memory!"); return (-1); } rmt->netid = strdup(nconf->nc_netid); if (rmt->netid == NULL) { free(rmt); syslog(LOG_ERR, "create_rmtcall_fd: no memory!"); return (-1); } if (svc_add_input(fd, MASKVAL, handle_reply, rmt->netid) == -1) { free(rmt->netid); free(rmt); syslog(LOG_ERR, "create_rmtcall_fd: svc_add_input() failed!"); return (-1); } rmt->fd = fd; rmt->next = NULL; if (rmthead == NULL) { rmthead = rmt; rmttail = rmt; } else { rmttail->next = rmt; rmttail = rmt; } return (fd); } static int find_rmtcallfd_by_netid(char *netid) { struct rmtcallfd_list *rmt; for (rmt = rmthead; rmt != NULL; rmt = rmt->next) { if (strcmp(netid, rmt->netid) == 0) { return (rmt->fd); } } return (-1); } #define MAXTIME_OFF 300 /* 5 minutes timeout for rmtcalls */ struct finfo { struct finfo *prev; struct finfo *next; int flag; #define FINFO_ACTIVE 0x1 ulong_t caller_xid; struct netbuf *caller_addr; ulong_t forward_xid; int forward_fd; char *uaddr; struct t_unitdata *reply_data; struct rpc_err reply_error; uint_t res_len; void *res_val; cond_t cv; }; /* * finfo_lock protects rpcb_rmtcalls, rpcb_rmtcalls_max, lastxid, * fihead, and fitail. */ static mutex_t finfo_lock = DEFAULTMUTEX; static int rpcb_rmtcalls; static int rpcb_rmtcalls_max; static ulong_t lastxid; static struct finfo *fihead; static struct finfo *fitail; void set_rpcb_rmtcalls_max(int max) { (void) mutex_lock(&finfo_lock); rpcb_rmtcalls_max = max; if (rpcb_rmtcalls > rpcb_rmtcalls_max) { assert(fitail != NULL); (void) cond_signal(&fitail->cv); } (void) mutex_unlock(&finfo_lock); } /* * Call a remote procedure service. This procedure is very quiet when things * go wrong. The proc is written to support broadcast rpc. In the broadcast * case, a machine should shut-up instead of complain, lest the requestor be * overrun with complaints at the expense of not hearing a valid reply. * When receiving a request and verifying that the service exists, we * * receive the request * * open a new TLI endpoint on the same transport on which we received * the original request * * remember the original request's XID (which requires knowing the format * of the svc_dg_data structure) * * forward the request, with a new XID, to the requested service, * remembering the XID used to send this request (for later use in * reassociating the answer with the original request), the requestor's * address, the file descriptor on which the forwarded request is * made and the service's address * * wait for either the timeout or the condition variable is signalled from * handle_reply(). * * At some time in the future, a reply will be received from the service to * which we forwarded the request. At that time, svc_run() detect that the * socket used was for forwarding and call handle_reply() to * * receive the reply * * bundle the reply, along with the service's universal address * * put the reply into the particular finfo * * signal the condition variable. */ #define RPC_BUF_MAX 65536 /* can be raised if required */ /* * This is from ../ypcmd/yp_b.h * It does not appear in */ #define YPBINDPROG ((ulong_t)100007) #define YPBINDPROC_SETDOM ((ulong_t)2) /* * reply_type - which proc number * versnum - which vers was called */ void rpcbproc_callit_com(struct svc_req *rqstp, SVCXPRT *transp, ulong_t reply_type, int versnum) { struct t_info tinfo; uint_t sendsz; rpcb_rmtcallargs arg; rpcblist_ptr rbl; struct netconfig *nconf; struct netbuf *caller; struct nd_mergearg ma; int stat; int fd; struct svc_dg_data *bd; struct finfo *fi; struct rpc_msg call_msg; char outbuf[RPC_BUF_MAX]; char *outbuf_alloc = NULL; XDR outxdr; bool_t outxdr_created = FALSE; AUTH *auth; struct t_unitdata tu_data; struct netbuf *na; timestruc_t to; (void) mutex_lock(&finfo_lock); if (!allow_indirect || rpcb_rmtcalls_max == 0) { (void) mutex_unlock(&finfo_lock); return; } (void) mutex_unlock(&finfo_lock); if (t_getinfo(transp->xp_fd, &tinfo) == -1) { if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); return; } if (tinfo.servtype != T_CLTS) return; /* Only datagram type accepted */ sendsz = __rpc_get_t_size(0, tinfo.tsdu); if (sendsz == 0) { /* data transfer not supported */ if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); return; } /* * Should be multiple of 4 for XDR. */ sendsz = ((sendsz + 3) / 4) * 4; (void) memset((char *)&arg, 0, sizeof (arg)); if (!svc_getargs(transp, xdr_rpcb_rmtcallargs, (char *)&arg)) { if (reply_type == RPCBPROC_INDIRECT) svcerr_decode(transp); if (debugging) fprintf(stderr, "rpcbproc_callit_com: svc_getargs failed\n"); goto error; } /* * Disallow calling rpcbind for certain procedures. * Allow calling NULLPROC - per man page on rpcb_rmtcall(). * switch is in alphabetical order. */ if (arg.proc != NULLPROC) { switch (arg.prog) { case KEY_PROG: if (debugging) fprintf(stderr, "rpcbind: rejecting KEY_PROG(%d)\n", arg.proc); goto error; case MOUNTPROG: if (arg.proc != MOUNTPROC_MNT) break; /* * In Solaris 2.6, the host-based accesss control * is done by the NFS server on each request. * Prior to 2.6 we rely on mountd. */ if (debugging) fprintf(stderr, "rpcbind: rejecting MOUNTPROG(%d)\n", arg.proc); goto error; case NFS_ACL_PROGRAM: if (debugging) fprintf(stderr, "rpcbind: rejecting NFS_ACL_PROGRAM(%d)\n", arg.proc); goto error; case NFS_PROGRAM: /* also NFS3_PROGRAM */ if (debugging) fprintf(stderr, "rpcbind: rejecting NFS_PROGRAM(%d)\n", arg.proc); goto error; case RPCBPROG: /* * Disallow calling rpcbind for certain procedures. * Luckily Portmap set/unset/callit also have same * procedure numbers. So, will not check for those. */ switch (arg.proc) { case RPCBPROC_SET: case RPCBPROC_UNSET: case RPCBPROC_CALLIT: case RPCBPROC_INDIRECT: if (reply_type == RPCBPROC_INDIRECT) svcerr_weakauth(transp); /* XXX */ if (debugging) fprintf(stderr, "rpcbproc_callit_com: " "calling RPCBPROG procs SET, " "UNSET, CALLIT, or INDIRECT not " "allowed\n"); goto error; default: /* * Ideally, we should have called rpcb_service() * or pmap_service() with appropriate parameters * instead of going about in a roundabout * manner. Hopefully, this case should happen * rarely. */ break; } break; case RQUOTAPROG: if (debugging) fprintf(stderr, "rpcbind: rejecting RQUOTAPROG(%d)\n", arg.proc); goto error; case YPPASSWDPROG: if (debugging) fprintf(stderr, "rpcbind: rejecting YPPASSWDPROG(%d)\n", arg.proc); goto error; case YPU_PROG: if (debugging) fprintf(stderr, "rpcbind: rejecting YPU_PROG(%d)\n", arg.proc); goto error; case YPBINDPROG: if (arg.proc != YPBINDPROC_SETDOM) break; if (debugging) fprintf(stderr, "rpcbind: rejecting YPBINDPROG(%d)\n", arg.proc); goto error; case YPPROG: switch (arg.proc) { case YPPROC_FIRST: case YPPROC_NEXT: case YPPROC_MATCH: case YPPROC_ALL: if (debugging) fprintf(stderr, "rpcbind: rejecting YPPROG(%d)\n", arg.proc); goto error; default: break; } break; default: break; } } (void) rw_rdlock(&list_rbl_lock); rbl = find_service(arg.prog, arg.vers, transp->xp_netid); rpcbs_rmtcall(versnum - PMAPVERS, reply_type, arg.prog, arg.vers, arg.proc, transp->xp_netid, rbl); if (rbl == NULL) { (void) rw_unlock(&list_rbl_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_noprog(transp); goto error; } if (rbl->rpcb_map.r_vers != arg.vers) { if (reply_type == RPCBPROC_INDIRECT) { ulong_t vers_low, vers_high; find_versions(arg.prog, transp->xp_netid, &vers_low, &vers_high); (void) rw_unlock(&list_rbl_lock); svcerr_progvers(transp, vers_low, vers_high); } else { (void) rw_unlock(&list_rbl_lock); } goto error; } /* * Check whether this entry is valid and a server is present * Mergeaddr() returns NULL if no such entry is present, and * returns "" if the entry was present but the server is not * present (i.e., it crashed). */ if (reply_type == RPCBPROC_INDIRECT) { char *uaddr = mergeaddr(transp, transp->xp_netid, rbl->rpcb_map.r_addr, NULL); if ((uaddr == (char *)NULL) || uaddr[0] == '\0') { (void) rw_unlock(&list_rbl_lock); svcerr_noprog(transp); goto error; } else { free(uaddr); } } nconf = rpcbind_get_conf(transp->xp_netid); if (nconf == NULL) { (void) rw_unlock(&list_rbl_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) fprintf(stderr, "rpcbproc_callit_com: rpcbind_get_conf failed\n"); goto error; } caller = svc_getrpccaller(transp); ma.c_uaddr = taddr2uaddr(nconf, caller); ma.s_uaddr = rbl->rpcb_map.r_addr; /* * A mergeaddr operation allocates a string, which it stores in * ma.m_uaddr. It's passed to forward_register() and is * eventually freed by forward_destroy(). */ stat = netdir_options(nconf, ND_MERGEADDR, 0, (char *)&ma); (void) rw_unlock(&list_rbl_lock); free(ma.c_uaddr); if (stat) (void) syslog(LOG_ERR, "netdir_merge failed for %s: %s", nconf->nc_netid, netdir_sperror()); if ((fd = find_rmtcallfd_by_netid(nconf->nc_netid)) == -1) { if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); free(ma.m_uaddr); goto error; } bd = get_svc_dg_data(transp); assert(!MUTEX_HELD(&finfo_lock)); fi = forward_register(bd->su_xid, caller, fd, ma.m_uaddr); if (fi == NULL) { /* forward_register failed. Perhaps no memory. */ free(ma.m_uaddr); if (debugging) fprintf(stderr, "rpcbproc_callit_com: forward_register failed\n"); assert(!MUTEX_HELD(&finfo_lock)); goto error; } /* forward_register() returns with finfo_lock held when successful */ assert(MUTEX_HELD(&finfo_lock)); if (fi->flag & FINFO_ACTIVE) { /* * A duplicate request for the slow server. Let's not * beat on it any more. */ (void) mutex_unlock(&finfo_lock); free(ma.m_uaddr); if (debugging) fprintf(stderr, "rpcbproc_callit_com: duplicate request\n"); goto error; } fi->flag |= FINFO_ACTIVE; call_msg.rm_xid = fi->forward_xid; call_msg.rm_direction = CALL; call_msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; call_msg.rm_call.cb_prog = arg.prog; call_msg.rm_call.cb_vers = arg.vers; if (sendsz > RPC_BUF_MAX) { outbuf_alloc = malloc(sendsz); if (outbuf_alloc == NULL) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) fprintf(stderr, "rpcbproc_callit_com: No memory!\n"); goto error; } xdrmem_create(&outxdr, outbuf_alloc, sendsz, XDR_ENCODE); } else { xdrmem_create(&outxdr, outbuf, sendsz, XDR_ENCODE); } outxdr_created = TRUE; if (!xdr_callhdr(&outxdr, &call_msg)) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) fprintf(stderr, "rpcbproc_callit_com: xdr_callhdr failed\n"); goto error; } if (!xdr_u_long(&outxdr, &arg.proc)) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) fprintf(stderr, "rpcbproc_callit_com: xdr_u_long failed\n"); goto error; } if (rqstp->rq_cred.oa_flavor == AUTH_NULL) { auth = authnone_create(); } else if (rqstp->rq_cred.oa_flavor == AUTH_SYS) { struct authsys_parms *au; CTASSERT(sizeof (struct authsys_parms) <= RQCRED_SIZE); au = (struct authsys_parms *)rqstp->rq_clntcred; auth = authsys_create(au->aup_machname, au->aup_uid, au->aup_gid, au->aup_len, au->aup_gids); if (auth == NULL) /* fall back */ auth = authnone_create(); } else { /* we do not support any other authentication scheme */ forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_weakauth(transp); /* XXX too strong.. */ if (debugging) fprintf(stderr, "rpcbproc_callit_com: oa_flavor != " "AUTH_NONE and oa_flavor != AUTH_SYS\n"); goto error; } if (auth == NULL) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) fprintf(stderr, "rpcbproc_callit_com: " "authwhatever_create returned NULL\n"); goto error; } if (!AUTH_MARSHALL(auth, &outxdr)) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); AUTH_DESTROY(auth); if (debugging) fprintf(stderr, "rpcbproc_callit_com: AUTH_MARSHALL failed\n"); goto error; } AUTH_DESTROY(auth); if (!xdr_opaque(&outxdr, arg.args.args_val, arg.args.args_len)) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) fprintf(stderr, "rpcbproc_callit_com: xdr_opaque failed\n"); goto error; } tu_data.udata.len = XDR_GETPOS(&outxdr); if (outbuf_alloc) tu_data.udata.buf = outbuf_alloc; else tu_data.udata.buf = outbuf; tu_data.opt.len = 0; na = uaddr2taddr(nconf, ma.m_uaddr); if (!na) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); goto error; } tu_data.addr = *na; if (t_sndudata(fd, &tu_data) == -1) { int err = errno; forward_destroy(fi); (void) mutex_unlock(&finfo_lock); netdir_free((char *)na, ND_ADDR); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) fprintf(stderr, "rpcbproc_callit_com: t_sndudata failed: " "t_errno %d, errno %d\n", t_errno, err); goto error; } netdir_free((char *)na, ND_ADDR); xdr_destroy(&outxdr); outxdr_created = FALSE; if (outbuf_alloc != NULL) { free(outbuf_alloc); outbuf_alloc = NULL; } svc_freeargs(transp, xdr_rpcb_rmtcallargs, (char *)&arg); to.tv_sec = time(NULL) + MAXTIME_OFF; to.tv_nsec = 0; while (fi->reply_data == NULL && cond_timedwait(&fi->cv, &finfo_lock, &to) != ETIME) ; if (fi->reply_data == NULL) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) (void) fprintf(stderr, "rpcbproc_callit_com: timeout\n"); return; } if (fi->reply_error.re_status != RPC_SUCCESS) { forward_destroy(fi); (void) mutex_unlock(&finfo_lock); if (reply_type == RPCBPROC_INDIRECT) svcerr_systemerr(transp); if (debugging) (void) fprintf(stderr, "rpcbproc_callit_com: error in reply: %s\n", clnt_sperrno(fi->reply_error.re_status)); return; } switch (versnum) { #ifdef PORTMAP case PMAPVERS: { rmtcallres result; int h1, h2, h3, h4, p1, p2; /* interpret the universal address for TCP/IP */ if (sscanf(fi->uaddr, "%d.%d.%d.%d.%d.%d", &h1, &h2, &h3, &h4, &p1, &p2) != 6) break; result.port = ((p1 & 0xff) << 8) + (p2 & 0xff); result.res.res_len = fi->res_len; result.res.res_val = fi->res_val; svc_sendreply(transp, xdr_rmtcallres, (char *)&result); } break; #endif case RPCBVERS: case RPCBVERS4: { rpcb_rmtcallres result; result.addr = fi->uaddr; result.results.results_len = fi->res_len; result.results.results_val = fi->res_val; svc_sendreply(transp, xdr_rpcb_rmtcallres, (char *)&result); } break; } forward_destroy(fi); (void) mutex_unlock(&finfo_lock); return; error: if (outxdr_created) xdr_destroy(&outxdr); free(outbuf_alloc); svc_freeargs(transp, xdr_rpcb_rmtcallargs, (char *)&arg); } static struct finfo *forward_find(ulong_t, char *); /* * Adds an entry into the finfo list for the given request. Returns the finfo * and finfo_lock is left held. If duplicate request, returns finfo with * FINFO_ACTIVE, else returns finfo without FINFO_ACTIVE. * If failed, returns NULL and finfo_lock is left unheld. */ static struct finfo * forward_register(ulong_t caller_xid, struct netbuf *caller_addr, int forward_fd, char *uaddr) { struct finfo *fi; (void) mutex_lock(&finfo_lock); if (rpcb_rmtcalls_max == 0) { (void) mutex_unlock(&finfo_lock); return (NULL); } /* * initialization: once this has happened, lastxid will * never be 0 again, when entering or returning from this function. */ if (lastxid == 0) lastxid = time(NULL); /* * Check if it is an duplicate entry */ for (fi = fihead; fi != NULL; fi = fi->next) { if (fi->caller_xid == caller_xid && netbufcmp(fi->caller_addr, caller_addr)) { assert(fi->flag & FINFO_ACTIVE); return (fi); } } fi = malloc(sizeof (*fi)); if (fi == NULL) { (void) mutex_unlock(&finfo_lock); return (NULL); } if ((fi->caller_addr = netbufdup(caller_addr)) == NULL) { (void) mutex_unlock(&finfo_lock); free(fi); return (NULL); } /* * Generate new xid and make sure it is unique. */ do { lastxid++; /* avoid lastxid wraparound to 0 */ if (lastxid == 0) lastxid = 1; } while (forward_find(lastxid, uaddr) != NULL); fi->prev = NULL; fi->next = fihead; if (fihead != NULL) fihead->prev = fi; fihead = fi; if (fitail == NULL) fitail = fi; fi->flag = 0; fi->caller_xid = caller_xid; fi->forward_xid = lastxid; fi->forward_fd = forward_fd; /* * Though uaddr is not allocated here, it will still be freed * from forward_destroy(). */ fi->uaddr = uaddr; fi->reply_data = NULL; (void) cond_init(&fi->cv, USYNC_THREAD, NULL); rpcb_rmtcalls++; if (rpcb_rmtcalls > rpcb_rmtcalls_max) { assert(fitail != fi); (void) cond_signal(&fitail->cv); } return (fi); } static void forward_destroy(struct finfo *fi) { assert(MUTEX_HELD(&finfo_lock)); assert(fi->flag & FINFO_ACTIVE); if (fihead == fi) { assert(fi->prev == NULL); fihead = fi->next; } else { fi->prev->next = fi->next; } if (fitail == fi) { assert(fi->next == NULL); fitail = fi->prev; } else { fi->next->prev = fi->prev; } netbuffree(fi->caller_addr); free(fi->uaddr); if (fi->reply_data != NULL) t_free((char *)fi->reply_data, T_UNITDATA); (void) cond_destroy(&fi->cv); free(fi); rpcb_rmtcalls--; if (rpcb_rmtcalls > rpcb_rmtcalls_max) { assert(fitail != NULL); (void) cond_signal(&fitail->cv); } } static struct finfo * forward_find(ulong_t reply_xid, char *uaddr) { struct finfo *fi; assert(MUTEX_HELD(&finfo_lock)); for (fi = fihead; fi != NULL; fi = fi->next) { if (fi->forward_xid == reply_xid && strcmp(fi->uaddr, uaddr) == 0) return (fi); } return (NULL); } static int netbufcmp(struct netbuf *n1, struct netbuf *n2) { return ((n1->len != n2->len) || memcmp(n1->buf, n2->buf, n1->len)); } static struct netbuf * netbufdup(struct netbuf *ap) { struct netbuf *np; np = malloc(sizeof (struct netbuf) + ap->len); if (np) { np->maxlen = np->len = ap->len; np->buf = ((char *)np) + sizeof (struct netbuf); (void) memcpy(np->buf, ap->buf, ap->len); } return (np); } static void netbuffree(struct netbuf *ap) { free(ap); } static void handle_reply(svc_input_id_t id, int fd, unsigned int events, void *cookie) { struct t_unitdata *tr_data; int res; unsigned int inlen; char *buffer; XDR reply_xdrs; struct rpc_msg reply_msg; unsigned int pos; unsigned int len; struct netconfig *nconf; char *uaddr = NULL; struct finfo *fi; tr_data = (struct t_unitdata *)t_alloc(fd, T_UNITDATA, T_ADDR | T_UDATA); if (tr_data == NULL) { syslog(LOG_ERR, "handle_reply: t_alloc failed!"); return; } do { int moreflag = 0; if (errno == EINTR) errno = 0; res = t_rcvudata(fd, tr_data, &moreflag); if (moreflag & T_MORE) { /* Drop this packet - we have no more space. */ if (debugging) fprintf(stderr, "handle_reply: recvd packet " "with T_MORE flag set\n"); goto done; } } while (res < 0 && t_errno == TSYSERR && errno == EINTR); if (res < 0) { if (debugging) fprintf(stderr, "handle_reply: t_rcvudata returned " "%d, t_errno %d, errno %d\n", res, t_errno, errno); if (t_errno == TLOOK) (void) t_rcvuderr(fd, NULL); goto done; } inlen = tr_data->udata.len; buffer = tr_data->udata.buf; assert(buffer != NULL); xdrmem_create(&reply_xdrs, buffer, inlen, XDR_DECODE); reply_msg.acpted_rply.ar_verf = _null_auth; reply_msg.acpted_rply.ar_results.where = 0; reply_msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void; if (!xdr_replymsg(&reply_xdrs, &reply_msg)) { xdr_destroy(&reply_xdrs); if (debugging) (void) fprintf(stderr, "handle_reply: xdr_replymsg failed\n"); goto done; } pos = XDR_GETPOS(&reply_xdrs); xdr_destroy(&reply_xdrs); len = inlen - pos; nconf = rpcbind_get_conf((char *)cookie); if (nconf == NULL) { syslog(LOG_ERR, "handle_reply: rpcbind_get_conf failed!"); goto done; } uaddr = taddr2uaddr(nconf, &tr_data->addr); if (uaddr == NULL) { syslog(LOG_ERR, "handle_reply: taddr2uaddr failed!"); goto done; } (void) mutex_lock(&finfo_lock); fi = forward_find(reply_msg.rm_xid, uaddr); if (fi == NULL) { (void) mutex_unlock(&finfo_lock); goto done; } fi->reply_data = tr_data; tr_data = NULL; __seterr_reply(&reply_msg, &fi->reply_error); fi->res_len = len; fi->res_val = &buffer[pos]; (void) cond_signal(&fi->cv); (void) mutex_unlock(&finfo_lock); done: free(uaddr); if (tr_data) t_free((char *)tr_data, T_UNITDATA); } /* * prog: Program Number * netid: Transport Provider token * lowvp: Low version number * highvp: High version number */ static void find_versions(rpcprog_t prog, char *netid, rpcvers_t *lowvp, rpcvers_t *highvp) { rpcblist_ptr rbl; rpcvers_t lowv = 0; rpcvers_t highv = 0; assert(RW_LOCK_HELD(&list_rbl_lock)); for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) { if ((rbl->rpcb_map.r_prog != prog) || (strcasecmp(rbl->rpcb_map.r_netid, netid) != 0)) continue; if (lowv == 0) { highv = rbl->rpcb_map.r_vers; lowv = highv; } else if (rbl->rpcb_map.r_vers < lowv) { lowv = rbl->rpcb_map.r_vers; } else if (rbl->rpcb_map.r_vers > highv) { highv = rbl->rpcb_map.r_vers; } } *lowvp = lowv; *highvp = highv; } /* * returns the item with the given program, version number and netid. * If that version number is not found, it returns the item with that * program number, so that address is now returned to the caller. The * caller when makes a call to this program, version number, the call * will fail and it will return with PROGVERS_MISMATCH. The user can * then determine the highest and the lowest version number for this * program using clnt_geterr() and use those program version numbers. * * Returns the RPCBLIST for the given prog, vers and netid * * prog: Program Number * vers: Version Number * netid: Transport Provider token */ static rpcblist_ptr find_service(rpcprog_t prog, rpcvers_t vers, char *netid) { rpcblist_ptr hit = NULL; rpcblist_ptr rbl; assert(RW_LOCK_HELD(&list_rbl_lock)); for (rbl = list_rbl; rbl != NULL; rbl = rbl->rpcb_next) { if ((rbl->rpcb_map.r_prog != prog) || (strcasecmp(rbl->rpcb_map.r_netid, netid) != 0)) continue; hit = rbl; if (rbl->rpcb_map.r_vers == vers) break; } return (hit); } /* * If the caller is from our zone and we know * who it is, we return the uid. */ uid_t rpcb_caller_uid(SVCXPRT *transp) { ucred_t *uc = alloca(ucred_size()); if (svc_getcallerucred(transp, &uc) != 0 || (ucred_getzoneid(uc)) != myzone) { return (-1); } else { return (ucred_geteuid(uc)); } } /* * Copies the name associated with the uid of the caller and returns * a pointer to it. Similar to getwd(). */ char * getowner(SVCXPRT *transp, char *owner) { uid_t uid = rpcb_caller_uid(transp); switch (uid) { case -1: return (strcpy(owner, "unknown")); case 0: return (strcpy(owner, "superuser")); default: (void) sprintf(owner, "%u", uid); return (owner); } } #ifdef PORTMAP /* * Add this to the pmap list only if it is UDP or TCP. */ static int add_pmaplist(RPCB *arg) { pmap pmap; pmaplist *pml; int h1, h2, h3, h4, p1, p2; if (strcmp(arg->r_netid, udptrans) == 0) { /* It is UDP! */ pmap.pm_prot = IPPROTO_UDP; } else if (strcmp(arg->r_netid, tcptrans) == 0) { /* It is TCP */ pmap.pm_prot = IPPROTO_TCP; } else /* Not a IP protocol */ return (0); /* interpret the universal address for TCP/IP */ if (sscanf(arg->r_addr, "%d.%d.%d.%d.%d.%d", &h1, &h2, &h3, &h4, &p1, &p2) != 6) return (0); pmap.pm_port = ((p1 & 0xff) << 8) + (p2 & 0xff); pmap.pm_prog = arg->r_prog; pmap.pm_vers = arg->r_vers; /* * add to END of list */ pml = (pmaplist *) malloc((uint_t)sizeof (pmaplist)); if (pml == NULL) { (void) syslog(LOG_ERR, "rpcbind: no memory!\n"); return (1); } pml->pml_map = pmap; pml->pml_next = NULL; (void) rw_wrlock(&list_pml_lock); if (list_pml == NULL) { list_pml = pml; } else { pmaplist *fnd; /* Attach to the end of the list */ for (fnd = list_pml; fnd->pml_next; fnd = fnd->pml_next) ; fnd->pml_next = pml; } (void) rw_unlock(&list_pml_lock); return (0); } /* * Delete this from the pmap list only if it is UDP or TCP. */ int del_pmaplist(RPCB *arg) { pmaplist *pml; pmaplist *prevpml, *fnd; rpcport_t prot; if (strcmp(arg->r_netid, udptrans) == 0) { /* It is UDP! */ prot = IPPROTO_UDP; } else if (strcmp(arg->r_netid, tcptrans) == 0) { /* It is TCP */ prot = IPPROTO_TCP; } else if (arg->r_netid[0] == '\0') { prot = 0; /* Remove all occurrences */ } else { /* Not a IP protocol */ return (0); } assert(RW_WRITE_HELD(&list_pml_lock)); for (prevpml = NULL, pml = list_pml; pml; /* cstyle */) { if ((pml->pml_map.pm_prog != arg->r_prog) || (pml->pml_map.pm_vers != arg->r_vers) || (prot && (pml->pml_map.pm_prot != prot))) { /* both pml & prevpml move forwards */ prevpml = pml; pml = pml->pml_next; continue; } /* found it; pml moves forward, prevpml stays */ fnd = pml; pml = pml->pml_next; if (prevpml == NULL) list_pml = pml; else prevpml->pml_next = pml; free(fnd); } return (0); } #endif /* PORTMAP */