/* * Copyright (C) 2002-2003 by Ryan Beasley * * See the IPFILTER.LICENCE file for details on licencing. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Overview: * This is an in-kernel application proxy for Sun's RPCBIND (nee portmap) * protocol as defined in RFC1833. It is far from complete, mostly * lacking in less-likely corner cases, but it's definitely functional. * * Invocation: * rdr /32 port -> port udp proxy rpcbu * * If the host running IP Filter is the same as the RPC server, it's * perfectly legal for both the internal and external addresses and ports * to match. * * When triggered by appropriate IP NAT rules, this proxy works by * examining data contained in received packets. Requests and replies are * modified, NAT and state table entries created, etc., as necessary. */ /* * TODO / NOTES * * o Must implement locking to protect proxy session data. * o Fragmentation isn't supported. * o Only supports UDP. * o Doesn't support multiple RPC records in a single request. * o Errors should be more fine-grained. (e.g., malloc failure vs. * illegal RPCB request / reply) * o Even with the limit on the total amount of recorded transactions, * should there be a timeout on transaction removal? * o There is a potential collision between cloning, wildcard NAT and * state entries. There should be an appr_getport routine for * to avoid this. * o The enclosed hack of STREAMS support is pretty sick and most likely * broken. * * $Id: ip_rpcb_pxy.c,v 2.25.2.3 2005/02/04 10:22:56 darrenr Exp $ */ #define IPF_RPCB_PROXY typedef struct ifs_rpcbpxy { frentry_t rpcbfr; /* Skeleton rule for reference by entities this proxy creates. */ int rpcbcnt;/* Upper bound of allocated RPCB sessions. */ /* XXX rpcbcnt still requires locking. */ int rpcb_proxy_init; } ifs_rpcbpxy_t; /* * Function prototypes */ int ippr_rpcb_init __P((void **, ipf_stack_t *)); void ippr_rpcb_fini __P((void **, ipf_stack_t *)); int ippr_rpcb_new __P((fr_info_t *, ap_session_t *, nat_t *, void *)); void ippr_rpcb_del __P((ap_session_t *, void *, ipf_stack_t *)); int ippr_rpcb_in __P((fr_info_t *, ap_session_t *, nat_t *, void *)); int ippr_rpcb_out __P((fr_info_t *, ap_session_t *, nat_t *, void *)); static void ippr_rpcb_flush __P((rpcb_session_t *)); static int ippr_rpcb_decodereq __P((fr_info_t *, nat_t *, rpcb_session_t *, rpc_msg_t *, ifs_rpcbpxy_t *)); static int ippr_rpcb_skipauth __P((rpc_msg_t *, xdr_auth_t *, u_32_t **)); static int ippr_rpcb_insert __P((rpcb_session_t *, rpcb_xact_t *, ifs_rpcbpxy_t *)); static int ippr_rpcb_xdrrpcb __P((rpc_msg_t *, u_32_t *, rpcb_args_t *)); static int ippr_rpcb_getuaddr __P((rpc_msg_t *, xdr_uaddr_t *, u_32_t **)); static u_int ippr_rpcb_atoi __P((char *)); static int ippr_rpcb_modreq __P((fr_info_t *, nat_t *, rpc_msg_t *, mb_t *, u_int)); static int ippr_rpcb_decoderep __P((fr_info_t *, nat_t *, rpcb_session_t *, rpc_msg_t *, rpcb_xact_t **, ifs_rpcbpxy_t *)); static rpcb_xact_t * ippr_rpcb_lookup __P((rpcb_session_t *, u_32_t)); static void ippr_rpcb_deref __P((rpcb_session_t *, rpcb_xact_t *, ifs_rpcbpxy_t *)); static int ippr_rpcb_getproto __P((rpc_msg_t *, xdr_proto_t *, u_32_t **)); static int ippr_rpcb_getnat __P((fr_info_t *, nat_t *, u_int, u_int, ifs_rpcbpxy_t *)); static int ippr_rpcb_modv3 __P((fr_info_t *, nat_t *, rpc_msg_t *, mb_t *, u_int)); static int ippr_rpcb_modv4 __P((fr_info_t *, nat_t *, rpc_msg_t *, mb_t *, u_int)); static void ippr_rpcb_fixlen __P((fr_info_t *, int)); /* * Since rpc_msg contains only pointers, one should use this macro as a * handy way to get to the goods. (In case you're wondering about the name, * this started as BYTEREF -> BREF -> B.) */ #define B(r) (u_32_t)ntohl(*(r)) /* * Public subroutines */ /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_init */ /* Returns: int - 0 == success */ /* Parameters: (void) */ /* */ /* Initialize the filter rule entry and session limiter. */ /* -------------------------------------------------------------------- */ /*ARGSUSED*/ int ippr_rpcb_init(private, ifs) void **private; ipf_stack_t *ifs; { ifs_rpcbpxy_t *ifsrpcb; KMALLOC(ifsrpcb, ifs_rpcbpxy_t *); if (ifsrpcb == NULL) return -1; ifsrpcb->rpcbcnt = 0; bzero((char *)&ifsrpcb->rpcbfr, sizeof(ifsrpcb->rpcbfr)); ifsrpcb->rpcbfr.fr_ref = 1; ifsrpcb->rpcbfr.fr_flags = FR_PASS|FR_QUICK|FR_KEEPSTATE; MUTEX_INIT(&ifsrpcb->rpcbfr.fr_lock, "ipf Sun RPCB proxy rule lock"); ifsrpcb->rpcb_proxy_init = 1; *private = (void *)ifsrpcb; return(0); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_fini */ /* Returns: void */ /* Parameters: (void) */ /* */ /* Destroy rpcbfr's mutex to avoid a lock leak. */ /* -------------------------------------------------------------------- */ /*ARGSUSED*/ void ippr_rpcb_fini(private, ifs) void **private; ipf_stack_t *ifs; { ifs_rpcbpxy_t *ifsrpcb = *((ifs_rpcbpxy_t **)private); if (ifsrpcb->rpcb_proxy_init == 1) { MUTEX_DESTROY(&ifsrpcb->rpcbfr.fr_lock); ifsrpcb->rpcb_proxy_init = 0; } KFREE(ifsrpcb); *private = NULL; } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_new */ /* Returns: int - -1 == failure, 0 == success */ /* Parameters: fin(I) - pointer to packet information */ /* aps(I) - pointer to proxy session structure */ /* nat(I) - pointer to NAT session structure */ /* */ /* Allocate resources for per-session proxy structures. */ /* -------------------------------------------------------------------- */ /*ARGSUSED*/ int ippr_rpcb_new(fin, aps, nat, private) fr_info_t *fin; ap_session_t *aps; nat_t *nat; void *private; { rpcb_session_t *rs; fin = fin; /* LINT */ nat = nat; /* LINT */ KMALLOC(rs, rpcb_session_t *); if (rs == NULL) return(-1); bzero((char *)rs, sizeof(*rs)); MUTEX_INIT(&rs->rs_rxlock, "ipf Sun RPCB proxy session lock"); aps->aps_data = rs; return(0); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_del */ /* Returns: void */ /* Parameters: aps(I) - pointer to proxy session structure */ /* */ /* Free up a session's list of RPCB requests. */ /* -------------------------------------------------------------------- */ /*ARGSUSED*/ void ippr_rpcb_del(aps, private, ifs) ap_session_t *aps; void *private; ipf_stack_t *ifs; { rpcb_session_t *rs; rs = (rpcb_session_t *)aps->aps_data; ifs = ifs; /* LINT */ MUTEX_ENTER(&rs->rs_rxlock); ippr_rpcb_flush(rs); MUTEX_EXIT(&rs->rs_rxlock); MUTEX_DESTROY(&rs->rs_rxlock); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_in */ /* Returns: int - APR_ERR(1) == drop the packet, */ /* APR_ERR(2) == kill the proxy session, */ /* else change in packet length (in bytes) */ /* Parameters: fin(I) - pointer to packet information */ /* ip(I) - pointer to packet header */ /* aps(I) - pointer to proxy session structure */ /* nat(I) - pointer to NAT session structure */ /* */ /* Given a presumed RPCB request, perform some minor tests and pass off */ /* for decoding. Also pass packet off for a rewrite if necessary. */ /* -------------------------------------------------------------------- */ int ippr_rpcb_in(fin, aps, nat, private) fr_info_t *fin; ap_session_t *aps; nat_t *nat; void *private; { rpc_msg_t rpcmsg, *rm; rpcb_session_t *rs; u_int off, dlen; mb_t *m; int rv; /* Disallow fragmented or illegally short packets. */ if ((fin->fin_flx & (FI_FRAG|FI_SHORT)) != 0) return(APR_ERR(1)); /* Perform basic variable initialization. */ rs = (rpcb_session_t *)aps->aps_data; m = fin->fin_m; off = (char *)fin->fin_dp - (char *)fin->fin_ip; off += sizeof(udphdr_t) + fin->fin_ipoff; dlen = fin->fin_dlen - sizeof(udphdr_t); /* Disallow packets outside legal range for supported requests. */ if ((dlen < RPCB_REQMIN) || (dlen > RPCB_REQMAX)) return(APR_ERR(1)); /* Copy packet over to convenience buffer. */ rm = &rpcmsg; bzero((char *)rm, sizeof(*rm)); COPYDATA(m, off, dlen, (caddr_t)&rm->rm_msgbuf); rm->rm_buflen = dlen; /* Send off to decode request. */ rv = ippr_rpcb_decodereq(fin, nat, rs, rm, (ifs_rpcbpxy_t *)private); switch(rv) { case -1: return(APR_ERR(1)); case 0: break; case 1: rv = ippr_rpcb_modreq(fin, nat, rm, m, off); break; default: /*CONSTANTCONDITION*/ IPF_PANIC(1, ("illegal rv %d (ippr_rpcb_req)", rv)); } return(rv); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_out */ /* Returns: int - APR_ERR(1) == drop the packet, */ /* APR_ERR(2) == kill the proxy session, */ /* else change in packet length (in bytes) */ /* Parameters: fin(I) - pointer to packet information */ /* ip(I) - pointer to packet header */ /* aps(I) - pointer to proxy session structure */ /* nat(I) - pointer to NAT session structure */ /* */ /* Given a presumed RPCB reply, perform some minor tests and pass off */ /* for decoding. If the message indicates a successful request with */ /* valid addressing information, create NAT and state structures to */ /* allow direct communication between RPC client and server. */ /* -------------------------------------------------------------------- */ int ippr_rpcb_out(fin, aps, nat, private) fr_info_t *fin; ap_session_t *aps; nat_t *nat; void *private; { rpc_msg_t rpcmsg, *rm; rpcb_session_t *rs; rpcb_xact_t *rx; u_int off, dlen; int rv, diff; mb_t *m; ifs_rpcbpxy_t *ifsrpcb = (ifs_rpcbpxy_t *)private; /* Disallow fragmented or illegally short packets. */ if ((fin->fin_flx & (FI_FRAG|FI_SHORT)) != 0) return(APR_ERR(1)); /* Perform basic variable initialization. */ rs = (rpcb_session_t *)aps->aps_data; m = fin->fin_m; off = (char *)fin->fin_dp - (char *)fin->fin_ip; off += sizeof(udphdr_t) + fin->fin_ipoff; dlen = fin->fin_dlen - sizeof(udphdr_t); diff = 0; /* Disallow packets outside legal range for supported requests. */ if ((dlen < RPCB_REPMIN) || (dlen > RPCB_REPMAX)) return(APR_ERR(1)); /* Copy packet over to convenience buffer. */ rm = &rpcmsg; bzero((char *)rm, sizeof(*rm)); COPYDATA(m, off, dlen, (caddr_t)&rm->rm_msgbuf); rm->rm_buflen = dlen; /* Send off to decode reply. */ rv = ippr_rpcb_decoderep(fin, nat, rs, rm, &rx, ifsrpcb); switch(rv) { case -1: /* Bad packet */ if (rx != NULL) { MUTEX_ENTER(&rs->rs_rxlock); ippr_rpcb_deref(rs, rx, ifsrpcb); MUTEX_EXIT(&rs->rs_rxlock); } return(APR_ERR(1)); case 0: /* Negative reply / request rejected */ break; case 1: /* Positive reply */ /* * With the IP address embedded in a GETADDR(LIST) reply, * we'll need to rewrite the packet in the very possible * event that the internal & external addresses aren't the * same. (i.e., this box is either a router or rpcbind * only listens on loopback.) */ if (nat->nat_inip.s_addr != nat->nat_outip.s_addr) { if (rx->rx_type == RPCB_RES_STRING) diff = ippr_rpcb_modv3(fin, nat, rm, m, off); else if (rx->rx_type == RPCB_RES_LIST) diff = ippr_rpcb_modv4(fin, nat, rm, m, off); } break; default: /*CONSTANTCONDITION*/ IPF_PANIC(1, ("illegal rv %d (ippr_rpcb_decoderep)", rv)); } if (rx != NULL) { MUTEX_ENTER(&rs->rs_rxlock); /* XXX Gross hack - I'm overloading the reference * counter to deal with both threads and retransmitted * requests. One deref signals that this thread is * finished with rx, and the other signals that we've * processed its reply. */ ippr_rpcb_deref(rs, rx, ifsrpcb); ippr_rpcb_deref(rs, rx, ifsrpcb); MUTEX_EXIT(&rs->rs_rxlock); } return(diff); } /* * Private support subroutines */ /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_flush */ /* Returns: void */ /* Parameters: rs(I) - pointer to RPCB session structure */ /* */ /* Simply flushes the list of outstanding transactions, if any. */ /* -------------------------------------------------------------------- */ static void ippr_rpcb_flush(rs) rpcb_session_t *rs; { rpcb_xact_t *r1, *r2; r1 = rs->rs_rxlist; if (r1 == NULL) return; while (r1 != NULL) { r2 = r1; r1 = r1->rx_next; KFREE(r2); } } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_decodereq */ /* Returns: int - -1 == bad request or critical failure, */ /* 0 == request successfully decoded, */ /* 1 == request successfully decoded; requires */ /* address rewrite/modification */ /* Parameters: fin(I) - pointer to packet information */ /* nat(I) - pointer to NAT session structure */ /* rs(I) - pointer to RPCB session structure */ /* rm(I) - pointer to RPC message structure */ /* */ /* Take a presumed RPCB request, decode it, and store the results in */ /* the transaction list. If the internal target address needs to be */ /* modified, store its location in ptr. */ /* WARNING: It's the responsibility of the caller to make sure there */ /* is enough room in rs_buf for the basic RPC message "preamble". */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_decodereq(fin, nat, rs, rm, ifsrpcb) fr_info_t *fin; nat_t *nat; rpcb_session_t *rs; rpc_msg_t *rm; ifs_rpcbpxy_t *ifsrpcb; { rpcb_args_t *ra; u_32_t xdr, *p; rpc_call_t *rc; rpcb_xact_t rx; int mod; p = (u_32_t *)rm->rm_msgbuf; mod = 0; bzero((char *)&rx, sizeof(rx)); rc = &rm->rm_call; rm->rm_xid = p; rx.rx_xid = B(p++); /* Record this message's XID. */ /* Parse out and test the RPC header. */ if ((B(p++) != RPCB_CALL) || (B(p++) != RPCB_MSG_VERSION) || (B(p++) != RPCB_PROG)) return(-1); /* Record the RPCB version and procedure. */ rc->rc_vers = p++; rc->rc_proc = p++; /* Bypass RPC authentication stuff. */ if (ippr_rpcb_skipauth(rm, &rc->rc_authcred, &p) != 0) return(-1); if (ippr_rpcb_skipauth(rm, &rc->rc_authverf, &p) != 0) return(-1); /* Compare RPCB version and procedure numbers. */ switch(B(rc->rc_vers)) { case 2: /* This proxy only supports PMAP_GETPORT. */ if (B(rc->rc_proc) != RPCB_GETPORT) return(-1); /* Portmap requests contain four 4 byte parameters. */ if (RPCB_BUF_EQ(rm, p, 16) == 0) return(-1); p += 2; /* Skip requested program and version numbers. */ /* Sanity check the requested protocol. */ xdr = B(p); if (!(xdr == IPPROTO_UDP || xdr == IPPROTO_TCP)) return(-1); rx.rx_type = RPCB_RES_PMAP; rx.rx_proto = xdr; break; case 3: case 4: /* GETADDRLIST is exclusive to v4; GETADDR for v3 & v4 */ switch(B(rc->rc_proc)) { case RPCB_GETADDR: rx.rx_type = RPCB_RES_STRING; rx.rx_proto = (u_int)fin->fin_p; break; case RPCB_GETADDRLIST: if (B(rc->rc_vers) != 4) return(-1); rx.rx_type = RPCB_RES_LIST; break; default: return(-1); } ra = &rc->rc_rpcbargs; /* Decode the 'struct rpcb' request. */ if (ippr_rpcb_xdrrpcb(rm, p, ra) != 0) return(-1); /* Are the target address & port valid? */ if ((ra->ra_maddr.xu_ip != nat->nat_outip.s_addr) || (ra->ra_maddr.xu_port != nat->nat_outport)) return(-1); /* Do we need to rewrite this packet? */ if ((nat->nat_outip.s_addr != nat->nat_inip.s_addr) || (nat->nat_outport != nat->nat_inport)) mod = 1; break; default: return(-1); } MUTEX_ENTER(&rs->rs_rxlock); if (ippr_rpcb_insert(rs, &rx, ifsrpcb) != 0) { MUTEX_EXIT(&rs->rs_rxlock); return(-1); } MUTEX_EXIT(&rs->rs_rxlock); return(mod); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_skipauth */ /* Returns: int -- -1 == illegal auth parameters (lengths) */ /* 0 == valid parameters, pointer advanced */ /* Parameters: rm(I) - pointer to RPC message structure */ /* auth(I) - pointer to RPC auth structure */ /* buf(IO) - pointer to location within convenience buffer */ /* */ /* Record auth data length & location of auth data, then advance past */ /* it. */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_skipauth(rm, auth, buf) rpc_msg_t *rm; xdr_auth_t *auth; u_32_t **buf; { u_32_t *p, xdr; p = *buf; /* Make sure we have enough space for expected fixed auth parms. */ if (RPCB_BUF_GEQ(rm, p, 8) == 0) return(-1); p++; /* We don't care about auth_flavor. */ auth->xa_string.xs_len = p; xdr = B(p++); /* Length of auth_data */ /* Test for absurdity / illegality of auth_data length. */ if ((XDRALIGN(xdr) < xdr) || (RPCB_BUF_GEQ(rm, p, XDRALIGN(xdr)) == 0)) return(-1); auth->xa_string.xs_str = (char *)p; p += XDRALIGN(xdr); /* Advance our location. */ *buf = (u_32_t *)p; return(0); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_insert */ /* Returns: int -- -1 == list insertion failed, */ /* 0 == item successfully added */ /* Parameters: rs(I) - pointer to RPCB session structure */ /* rx(I) - pointer to RPCB transaction structure */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_insert(rs, rx, ifsrpcb) rpcb_session_t *rs; rpcb_xact_t *rx; ifs_rpcbpxy_t *ifsrpcb; { rpcb_xact_t *rxp; rxp = ippr_rpcb_lookup(rs, rx->rx_xid); if (rxp != NULL) { ++rxp->rx_ref; return(0); } if (ifsrpcb->rpcbcnt == RPCB_MAXREQS) return(-1); KMALLOC(rxp, rpcb_xact_t *); if (rxp == NULL) return(-1); bcopy((char *)rx, (char *)rxp, sizeof(*rx)); if (rs->rs_rxlist != NULL) rs->rs_rxlist->rx_pnext = &rxp->rx_next; rxp->rx_pnext = &rs->rs_rxlist; rxp->rx_next = rs->rs_rxlist; rs->rs_rxlist = rxp; rxp->rx_ref = 1; ++ifsrpcb->rpcbcnt; return(0); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_xdrrpcb */ /* Returns: int -- -1 == failure to properly decode the request */ /* 0 == rpcb successfully decoded */ /* Parameters: rs(I) - pointer to RPCB session structure */ /* p(I) - pointer to location within session buffer */ /* rpcb(O) - pointer to rpcb (xdr type) structure */ /* */ /* Decode a XDR encoded rpcb structure and record its contents in rpcb */ /* within only the context of TCP/UDP over IP networks. */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_xdrrpcb(rm, p, ra) rpc_msg_t *rm; u_32_t *p; rpcb_args_t *ra; { if (!RPCB_BUF_GEQ(rm, p, 20)) return(-1); /* Bypass target program & version. */ p += 2; /* Decode r_netid. Must be "tcp" or "udp". */ if (ippr_rpcb_getproto(rm, &ra->ra_netid, &p) != 0) return(-1); /* Decode r_maddr. */ if (ippr_rpcb_getuaddr(rm, &ra->ra_maddr, &p) != 0) return(-1); /* Advance to r_owner and make sure it's empty. */ if (!RPCB_BUF_EQ(rm, p, 4) || (B(p) != 0)) return(-1); return(0); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_getuaddr */ /* Returns: int -- -1 == illegal string, */ /* 0 == string parsed; contents recorded */ /* Parameters: rm(I) - pointer to RPC message structure */ /* xu(I) - pointer to universal address structure */ /* p(IO) - pointer to location within message buffer */ /* */ /* Decode the IP address / port at p and record them in xu. */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_getuaddr(rm, xu, p) rpc_msg_t *rm; xdr_uaddr_t *xu; u_32_t **p; { char *c, *i, *b, *pp; u_int d, dd, l, t; char uastr[24]; /* Test for string length. */ if (!RPCB_BUF_GEQ(rm, *p, 4)) return(-1); xu->xu_xslen = (*p)++; xu->xu_xsstr = (char *)*p; /* Length check */ l = B(xu->xu_xslen); if (l < 11 || l > 23 || !RPCB_BUF_GEQ(rm, *p, XDRALIGN(l))) return(-1); /* Advance p */ *(char **)p += XDRALIGN(l); /* Copy string to local buffer & terminate C style */ bcopy(xu->xu_xsstr, uastr, l); uastr[l] = '\0'; i = (char *)&xu->xu_ip; pp = (char *)&xu->xu_port; /* * Expected format: a.b.c.d.e.f where [a-d] correspond to bytes of * an IP address and [ef] are the bytes of a L4 port. */ if (!(ISDIGIT(uastr[0]) && ISDIGIT(uastr[l-1]))) return(-1); b = uastr; for (c = &uastr[1], d = 0, dd = 0; c < &uastr[l-1]; c++) { if (ISDIGIT(*c)) { dd = 0; continue; } if (*c == '.') { if (dd != 0) return(-1); /* Check for ASCII byte. */ *c = '\0'; t = ippr_rpcb_atoi(b); if (t > 255) return(-1); /* Aim b at beginning of the next byte. */ b = c + 1; /* Switch off IP addr vs port parsing. */ if (d < 4) i[d++] = t & 0xff; else pp[d++ - 4] = t & 0xff; dd = 1; continue; } return(-1); } if (d != 5) /* String must contain exactly 5 periods. */ return(-1); /* Handle the last byte (port low byte) */ t = ippr_rpcb_atoi(b); if (t > 255) return(-1); pp[d - 4] = t & 0xff; return(0); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_atoi (XXX should be generic for all proxies) */ /* Returns: int -- integer representation of supplied string */ /* Parameters: ptr(I) - input string */ /* */ /* Simple version of atoi(3) ripped from ip_rcmd_pxy.c. */ /* -------------------------------------------------------------------- */ static u_int ippr_rpcb_atoi(ptr) char *ptr; { register char *s = ptr, c; register u_int i = 0; while (((c = *s++) != '\0') && ISDIGIT(c)) { i *= 10; i += c - '0'; } return i; } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_modreq */ /* Returns: int -- change in datagram length */ /* APR_ERR(2) - critical failure */ /* Parameters: fin(I) - pointer to packet information */ /* nat(I) - pointer to NAT session */ /* rm(I) - pointer to RPC message structure */ /* m(I) - pointer to mbuf chain */ /* off(I) - current offset within mbuf chain */ /* */ /* When external and internal addresses differ, we rewrite the former */ /* with the latter. (This is exclusive to protocol versions 3 & 4). */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_modreq(fin, nat, rm, m, off) fr_info_t *fin; nat_t *nat; rpc_msg_t *rm; mb_t *m; u_int off; { u_int len, xlen, pos, bogo; rpcb_args_t *ra; char uaddr[24]; udphdr_t *udp; char *i, *p; int diff; ra = &rm->rm_call.rc_rpcbargs; i = (char *)&nat->nat_inip.s_addr; p = (char *)&nat->nat_inport; /* Form new string. */ bzero(uaddr, sizeof(uaddr)); /* Just in case we need padding. */ #if defined(SNPRINTF) && defined(_KERNEL) (void) SNPRINTF(uaddr, sizeof(uaddr), #else (void) sprintf(uaddr, #endif "%u.%u.%u.%u.%u.%u", i[0] & 0xff, i[1] & 0xff, i[2] & 0xff, i[3] & 0xff, p[0] & 0xff, p[1] & 0xff); len = strlen(uaddr); xlen = XDRALIGN(len); /* Determine mbuf offset to start writing to. */ pos = (char *)ra->ra_maddr.xu_xslen - rm->rm_msgbuf; off += pos; /* Write new string length. */ bogo = htonl(len); COPYBACK(m, off, 4, (caddr_t)&bogo); off += 4; /* Write new string. */ COPYBACK(m, off, xlen, uaddr); off += xlen; /* Write in zero r_owner. */ bogo = 0; COPYBACK(m, off, 4, (caddr_t)&bogo); /* Determine difference in data lengths. */ diff = xlen - XDRALIGN(B(ra->ra_maddr.xu_xslen)); /* * If our new string has a different length, make necessary * adjustments. */ if (diff != 0) { udp = fin->fin_dp; udp->uh_ulen = htons(ntohs(udp->uh_ulen) + diff); fin->fin_ip->ip_len += diff; fin->fin_dlen += diff; fin->fin_plen += diff; /* XXX Storage lengths. */ } return(diff); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_decoderep */ /* Returns: int - -1 == bad request or critical failure, */ /* 0 == valid, negative reply */ /* 1 == vaddlid, positive reply; needs no changes */ /* Parameters: fin(I) - pointer to packet information */ /* nat(I) - pointer to NAT session structure */ /* rs(I) - pointer to RPCB session structure */ /* rm(I) - pointer to RPC message structure */ /* rxp(O) - pointer to RPCB transaction structure */ /* */ /* Take a presumed RPCB reply, extract the XID, search for the original */ /* request information, and determine whether the request was accepted */ /* or rejected. With a valid accepted reply, go ahead and create NAT */ /* and state entries, and finish up by rewriting the packet as */ /* required. */ /* */ /* WARNING: It's the responsibility of the caller to make sure there */ /* is enough room in rs_buf for the basic RPC message "preamble". */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_decoderep(fin, nat, rs, rm, rxp, ifsrpcb) fr_info_t *fin; nat_t *nat; rpcb_session_t *rs; rpc_msg_t *rm; rpcb_xact_t **rxp; ifs_rpcbpxy_t *ifsrpcb; { rpcb_listp_t *rl; rpcb_entry_t *re; rpcb_xact_t *rx; u_32_t xdr, *p; rpc_resp_t *rr; int rv, cnt; p = (u_32_t *)rm->rm_msgbuf; bzero((char *)&rx, sizeof(rx)); rr = &rm->rm_resp; rm->rm_xid = p; xdr = B(p++); /* Record this message's XID. */ /* Lookup XID */ MUTEX_ENTER(&rs->rs_rxlock); if ((rx = ippr_rpcb_lookup(rs, xdr)) == NULL) { MUTEX_EXIT(&rs->rs_rxlock); return(-1); } ++rx->rx_ref; /* per thread reference */ MUTEX_EXIT(&rs->rs_rxlock); *rxp = rx; /* Test call vs reply */ if (B(p++) != RPCB_REPLY) return(-1); /* Test reply_stat */ switch(B(p++)) { case RPCB_MSG_DENIED: return(0); case RPCB_MSG_ACCEPTED: break; default: return(-1); } /* Bypass RPC authentication stuff. */ if (ippr_rpcb_skipauth(rm, &rr->rr_authverf, &p) != 0) return(-1); /* Test accept status */ if (!RPCB_BUF_GEQ(rm, p, 4)) return(-1); if (B(p++) != 0) return(0); /* Parse out the expected reply */ switch(rx->rx_type) { case RPCB_RES_PMAP: /* There must be only one 4 byte argument. */ if (!RPCB_BUF_EQ(rm, p, 4)) return(-1); rr->rr_v2 = p; xdr = B(rr->rr_v2); /* Reply w/ a 0 port indicates service isn't registered */ if (xdr == 0) return(0); /* Is the value sane? */ if (xdr > 65535) return(-1); /* Create NAT & state table entries. */ if (ippr_rpcb_getnat(fin, nat, rx->rx_proto, (u_int)xdr, ifsrpcb) != 0) return(-1); break; case RPCB_RES_STRING: /* Expecting a XDR string; need 4 bytes for length */ if (!RPCB_BUF_GEQ(rm, p, 4)) return(-1); rr->rr_v3.xu_str.xs_len = p++; rr->rr_v3.xu_str.xs_str = (char *)p; xdr = B(rr->rr_v3.xu_xslen); /* A null string indicates an unregistered service */ if ((xdr == 0) && RPCB_BUF_EQ(rm, p, 0)) return(0); /* Decode the target IP address / port. */ if (ippr_rpcb_getuaddr(rm, &rr->rr_v3, &p) != 0) return(-1); /* Validate the IP address and port contained. */ if (nat->nat_inip.s_addr != rr->rr_v3.xu_ip) return(-1); /* Create NAT & state table entries. */ if (ippr_rpcb_getnat(fin, nat, rx->rx_proto, (u_int)rr->rr_v3.xu_port, ifsrpcb) != 0) return(-1); break; case RPCB_RES_LIST: if (!RPCB_BUF_GEQ(rm, p, 4)) return(-1); /* rpcb_entry_list_ptr */ switch(B(p)) { case 0: return(0); case 1: break; default: return(-1); } rl = &rr->rr_v4; rl->rl_list = p++; cnt = 0; for(;;) { re = &rl->rl_entries[rl->rl_cnt]; if (ippr_rpcb_getuaddr(rm, &re->re_maddr, &p) != 0) return(-1); if (ippr_rpcb_getproto(rm, &re->re_netid, &p) != 0) return(-1); /* re_semantics & re_pfamily length */ if (!RPCB_BUF_GEQ(rm, p, 12)) return(-1); p++; /* Skipping re_semantics. */ xdr = B(p++); if ((xdr != 4) || strncmp((char *)p, "inet", 4)) return(-1); p++; if (ippr_rpcb_getproto(rm, &re->re_proto, &p) != 0) return(-1); if (!RPCB_BUF_GEQ(rm, p, 4)) return(-1); re->re_more = p; if (B(re->re_more) > 1) /* 0,1 only legal values */ return(-1); ++rl->rl_cnt; ++cnt; if (B(re->re_more) == 0) break; /* Replies in max out at 2; TCP and/or UDP */ if (cnt > 2) return(-1); p++; } for(rl->rl_cnt = 0; rl->rl_cnt < cnt; rl->rl_cnt++) { re = &rl->rl_entries[rl->rl_cnt]; rv = ippr_rpcb_getnat(fin, nat, re->re_proto.xp_proto, (u_int)re->re_maddr.xu_port, ifsrpcb); if (rv != 0) return(-1); } break; default: /*CONSTANTCONDITION*/ IPF_PANIC(1, ("illegal rx_type %d", rx->rx_type)); } return(1); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_lookup */ /* Returns: rpcb_xact_t * - NULL == no matching record, */ /* else pointer to relevant entry */ /* Parameters: rs(I) - pointer to RPCB session */ /* xid(I) - XID to look for */ /* -------------------------------------------------------------------- */ static rpcb_xact_t * ippr_rpcb_lookup(rs, xid) rpcb_session_t *rs; u_32_t xid; { rpcb_xact_t *rx; if (rs->rs_rxlist == NULL) return(NULL); for (rx = rs->rs_rxlist; rx != NULL; rx = rx->rx_next) if (rx->rx_xid == xid) break; return(rx); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_deref */ /* Returns: (void) */ /* Parameters: rs(I) - pointer to RPCB session */ /* rx(I) - pointer to RPC transaction struct to remove */ /* force(I) - indicates to delete entry regardless of */ /* reference count */ /* Locking: rs->rs_rxlock must be held write only */ /* */ /* Free the RPCB transaction record rx from the chain of entries. */ /* -------------------------------------------------------------------- */ static void ippr_rpcb_deref(rs, rx, ifsrpcb) rpcb_session_t *rs; rpcb_xact_t *rx; ifs_rpcbpxy_t *ifsrpcb; { rs = rs; /* LINT */ if (rx == NULL) return; if (--rx->rx_ref != 0) return; if (rx->rx_next != NULL) rx->rx_next->rx_pnext = rx->rx_pnext; *rx->rx_pnext = rx->rx_next; KFREE(rx); --ifsrpcb->rpcbcnt; } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_getproto */ /* Returns: int - -1 == illegal protocol/netid, */ /* 0 == legal protocol/netid */ /* Parameters: rm(I) - pointer to RPC message structure */ /* xp(I) - pointer to netid structure */ /* p(IO) - pointer to location within packet buffer */ /* */ /* Decode netid/proto stored at p and record its numeric value. */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_getproto(rm, xp, p) rpc_msg_t *rm; xdr_proto_t *xp; u_32_t **p; { u_int len; /* Must have 4 bytes for length & 4 bytes for "tcp" or "udp". */ if (!RPCB_BUF_GEQ(rm, p, 8)) return(-1); xp->xp_xslen = (*p)++; xp->xp_xsstr = (char *)*p; /* Test the string length. */ len = B(xp->xp_xslen); if (len != 3) return(-1); /* Test the actual string & record the protocol accordingly. */ if (!strncmp((char *)xp->xp_xsstr, "tcp\0", 4)) xp->xp_proto = IPPROTO_TCP; else if (!strncmp((char *)xp->xp_xsstr, "udp\0", 4)) xp->xp_proto = IPPROTO_UDP; else { return(-1); } /* Advance past the string. */ (*p)++; return(0); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_getnat */ /* Returns: int -- -1 == failed to create table entries, */ /* 0 == success */ /* Parameters: fin(I) - pointer to packet information */ /* nat(I) - pointer to NAT table entry */ /* proto(I) - transport protocol for new entries */ /* port(I) - new port to use w/ wildcard table entries */ /* */ /* Create state and NAT entries to handle an anticipated connection */ /* attempt between RPC client and server. */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_getnat(fin, nat, proto, port, ifsrpcb) fr_info_t *fin; nat_t *nat; u_int proto; u_int port; ifs_rpcbpxy_t *ifsrpcb; { ipnat_t *ipn, ipnat; tcphdr_t tcp; ipstate_t *is; fr_info_t fi; nat_t *natl; int nflags; ipf_stack_t *ifs = fin->fin_ifs; ipn = nat->nat_ptr; /* Generate dummy fr_info */ bcopy((char *)fin, (char *)&fi, sizeof(fi)); fi.fin_out = 0; fi.fin_src = fin->fin_dst; fi.fin_dst = nat->nat_outip; fi.fin_p = proto; fi.fin_sport = 0; fi.fin_dport = port & 0xffff; fi.fin_flx |= FI_IGNORE; bzero((char *)&tcp, sizeof(tcp)); tcp.th_dport = htons(port); if (proto == IPPROTO_TCP) { tcp.th_win = htons(8192); TCP_OFF_A(&tcp, sizeof(tcphdr_t) >> 2); fi.fin_dlen = sizeof(tcphdr_t); tcp.th_flags = TH_SYN; nflags = NAT_TCP; } else { fi.fin_dlen = sizeof(udphdr_t); nflags = NAT_UDP; } nflags |= SI_W_SPORT|NAT_SEARCH; fi.fin_dp = &tcp; fi.fin_plen = fi.fin_hlen + fi.fin_dlen; /* * Search for existing NAT & state entries. Pay close attention to * mutexes / locks grabbed from lookup routines, as not doing so could * lead to bad things. * * If successful, fr_stlookup returns with ipf_state locked. We have * no use for this lock, so simply unlock it if necessary. */ is = fr_stlookup(&fi, &tcp, NULL); if (is != NULL) RWLOCK_EXIT(&ifs->ifs_ipf_state); RWLOCK_EXIT(&ifs->ifs_ipf_nat); WRITE_ENTER(&ifs->ifs_ipf_nat); natl = nat_inlookup(&fi, nflags, proto, fi.fin_src, fi.fin_dst); if ((natl != NULL) && (is != NULL)) { MUTEX_DOWNGRADE(&ifs->ifs_ipf_nat); return(0); } /* Slightly modify the following structures for actual use in creating * NAT and/or state entries. We're primarily concerned with stripping * flags that may be detrimental to the creation process or simply * shouldn't be associated with a table entry. */ fi.fin_fr = &ifsrpcb->rpcbfr; fi.fin_flx &= ~FI_IGNORE; nflags &= ~NAT_SEARCH; if (natl == NULL) { /* XXX Since we're just copying the original ipn contents * back, would we be better off just sending a pointer to * the 'temp' copy off to nat_new instead? */ /* Generate template/bogus NAT rule. */ bcopy((char *)ipn, (char *)&ipnat, sizeof(ipnat)); ipn->in_flags = nflags & IPN_TCPUDP; ipn->in_apr = NULL; ipn->in_p = proto; ipn->in_pmin = htons(fi.fin_dport); ipn->in_pmax = htons(fi.fin_dport); ipn->in_pnext = htons(fi.fin_dport); ipn->in_space = 1; ipn->in_ippip = 1; if (ipn->in_flags & IPN_FILTER) { ipn->in_scmp = 0; ipn->in_dcmp = 0; } *ipn->in_plabel = '\0'; /* Create NAT entry. return NULL if this fails. */ natl = nat_new(&fi, ipn, NULL, nflags|SI_CLONE|NAT_SLAVE, NAT_INBOUND); bcopy((char *)&ipnat, (char *)ipn, sizeof(ipnat)); if (natl == NULL) { MUTEX_DOWNGRADE(&ifs->ifs_ipf_nat); return(-1); } ipn->in_use++; (void) nat_proto(&fi, natl, nflags); nat_update(&fi, natl, natl->nat_ptr); } MUTEX_DOWNGRADE(&ifs->ifs_ipf_nat); if (is == NULL) { /* Create state entry. Return NULL if this fails. */ fi.fin_dst = nat->nat_inip; fi.fin_flx |= FI_NATED; fi.fin_flx &= ~FI_STATE; nflags &= NAT_TCPUDP; nflags |= SI_W_SPORT|SI_CLONE; is = fr_addstate(&fi, NULL, nflags); if (is == NULL) { /* * XXX nat_delete is private to ip_nat.c. Should * check w/ Darren about this one. * * nat_delete(natl, NL_EXPIRE, ifs); */ return(-1); } } return(0); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_modv3 */ /* Returns: int -- change in packet length */ /* Parameters: fin(I) - pointer to packet information */ /* nat(I) - pointer to NAT session */ /* rm(I) - pointer to RPC message structure */ /* m(I) - pointer to mbuf chain */ /* off(I) - offset within mbuf chain */ /* */ /* Write a new universal address string to this packet, adjusting */ /* lengths as necessary. */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_modv3(fin, nat, rm, m, off) fr_info_t *fin; nat_t *nat; rpc_msg_t *rm; mb_t *m; u_int off; { u_int len, xlen, pos, bogo; rpc_resp_t *rr; char uaddr[24]; char *i, *p; int diff; rr = &rm->rm_resp; i = (char *)&nat->nat_outip.s_addr; p = (char *)&rr->rr_v3.xu_port; /* Form new string. */ bzero(uaddr, sizeof(uaddr)); /* Just in case we need padding. */ #if defined(SNPRINTF) && defined(_KERNEL) (void) SNPRINTF(uaddr, sizeof(uaddr), #else (void) sprintf(uaddr, #endif "%u.%u.%u.%u.%u.%u", i[0] & 0xff, i[1] & 0xff, i[2] & 0xff, i[3] & 0xff, p[0] & 0xff, p[1] & 0xff); len = strlen(uaddr); xlen = XDRALIGN(len); /* Determine mbuf offset to write to. */ pos = (char *)rr->rr_v3.xu_xslen - rm->rm_msgbuf; off += pos; /* Write new string length. */ bogo = htonl(len); COPYBACK(m, off, 4, (caddr_t)&bogo); off += 4; /* Write new string. */ COPYBACK(m, off, xlen, uaddr); /* Determine difference in data lengths. */ diff = xlen - XDRALIGN(B(rr->rr_v3.xu_xslen)); /* * If our new string has a different length, make necessary * adjustments. */ if (diff != 0) ippr_rpcb_fixlen(fin, diff); return(diff); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_modv4 */ /* Returns: int -- change in packet length */ /* Parameters: fin(I) - pointer to packet information */ /* nat(I) - pointer to NAT session */ /* rm(I) - pointer to RPC message structure */ /* m(I) - pointer to mbuf chain */ /* off(I) - offset within mbuf chain */ /* */ /* Write new rpcb_entry list, adjusting lengths as necessary. */ /* -------------------------------------------------------------------- */ static int ippr_rpcb_modv4(fin, nat, rm, m, off) fr_info_t *fin; nat_t *nat; rpc_msg_t *rm; mb_t *m; u_int off; { u_int len, xlen, pos, bogo; rpcb_listp_t *rl; rpcb_entry_t *re; rpc_resp_t *rr; char uaddr[24]; int diff, cnt; char *i, *p; diff = 0; rr = &rm->rm_resp; rl = &rr->rr_v4; i = (char *)&nat->nat_outip.s_addr; /* Determine mbuf offset to write to. */ re = &rl->rl_entries[0]; pos = (char *)re->re_maddr.xu_xslen - rm->rm_msgbuf; off += pos; for (cnt = 0; cnt < rl->rl_cnt; cnt++) { re = &rl->rl_entries[cnt]; p = (char *)&re->re_maddr.xu_port; /* Form new string. */ bzero(uaddr, sizeof(uaddr)); /* Just in case we need padding. */ #if defined(SNPRINTF) && defined(_KERNEL) (void) SNPRINTF(uaddr, sizeof(uaddr), #else (void) sprintf(uaddr, #endif "%u.%u.%u.%u.%u.%u", i[0] & 0xff, i[1] & 0xff, i[2] & 0xff, i[3] & 0xff, p[0] & 0xff, p[1] & 0xff); len = strlen(uaddr); xlen = XDRALIGN(len); /* Write new string length. */ bogo = htonl(len); COPYBACK(m, off, 4, (caddr_t)&bogo); off += 4; /* Write new string. */ COPYBACK(m, off, xlen, uaddr); off += xlen; /* Record any change in length. */ diff += xlen - XDRALIGN(B(re->re_maddr.xu_xslen)); /* If the length changed, copy back the rest of this entry. */ len = ((char *)re->re_more + 4) - (char *)re->re_netid.xp_xslen; if (diff != 0) { COPYBACK(m, off, len, (caddr_t)re->re_netid.xp_xslen); } off += len; } /* * If our new string has a different length, make necessary * adjustments. */ if (diff != 0) ippr_rpcb_fixlen(fin, diff); return(diff); } /* -------------------------------------------------------------------- */ /* Function: ippr_rpcb_fixlen */ /* Returns: (void) */ /* Parameters: fin(I) - pointer to packet information */ /* len(I) - change in packet length */ /* */ /* Adjust various packet related lengths held in structure and packet */ /* header fields. */ /* -------------------------------------------------------------------- */ static void ippr_rpcb_fixlen(fin, len) fr_info_t *fin; int len; { udphdr_t *udp; udp = fin->fin_dp; udp->uh_ulen = htons(ntohs(udp->uh_ulen) + len); fin->fin_ip->ip_len += len; fin->fin_dlen += len; fin->fin_plen += len; } #undef B