#include <sys/param.h>
#include <sys/t_lock.h>
#include <sys/user.h>
#include <sys/vnode.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#include <sys/socket.h>
#include <sys/tihdr.h>
#include <sys/timod.h>
#include <sys/tiuser.h>
#include <sys/systm.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/sdt.h>
#include <netinet/in.h>
#include <rpc/types.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <rpc/rpcb_prot.h>
#include <rpc/pmap_prot.h>
static int strtoi(char *, char **);
static void grow_netbuf(struct netbuf *, size_t);
static void loopb_u2t(const char *, struct netbuf *);
#define RPC_PMAP_TIMEOUT 5
#define RPC_MAX_IP_LENGTH 64
uint_t rpclog = 0;
void
rpc_poptimod(vnode_t *vp)
{
int error, isfound, ret;
error = strioctl(vp, I_FIND, (intptr_t)"timod", 0, K_TO_K, kcred,
&isfound);
if (error) {
RPCLOG(1, "rpc_poptimod: I_FIND strioctl error %d\n", error);
return;
}
if (isfound) {
error = strioctl(vp, I_POP, 0, 0, K_TO_K, kcred, &ret);
if (error) {
RPCLOG(1, "rpc_poptimod: I_POP strioctl error %d\n",
error);
return;
}
}
}
int
rpc_iptype(
char *ipaddr,
int *typeval)
{
char *cp;
int chcnt = 0;
int coloncnt = 0;
int dotcnt = 0;
int numcnt = 0;
int hexnumcnt = 0;
int othercnt = 0;
cp = ipaddr;
while ((*cp != '\0') && (chcnt < RPC_MAX_IP_LENGTH)) {
switch (*cp) {
case ':':
coloncnt++;
break;
case '.':
dotcnt++;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
numcnt++;
break;
case 'a':
case 'A':
case 'b':
case 'B':
case 'c':
case 'C':
case 'd':
case 'D':
case 'e':
case 'E':
case 'f':
case 'F':
hexnumcnt++;
break;
default:
othercnt++;
break;
}
chcnt++;
cp++;
}
if ((chcnt == RPC_MAX_IP_LENGTH) || (othercnt))
return (-1);
if (coloncnt) {
if ((coloncnt < 2) || (coloncnt > 7))
return (-1);
*typeval = AF_INET6;
} else {
if ((hexnumcnt) || (dotcnt != 5))
return (-1);
*typeval = AF_INET;
}
return (0);
}
int
rpc_uaddr2port(int af, char *addr)
{
int p1;
int p2;
char *next, *p;
if (af == AF_INET) {
(void) strtoi(addr, &next);
(void) strtoi(next, &next);
(void) strtoi(next, &next);
(void) strtoi(next, &next);
p1 = strtoi(next, &next);
p2 = strtoi(next, &next);
} else if (af == AF_INET6) {
next = addr;
while (next = strchr(next, '.')) {
p = ++next;
next = strchr(next, '.');
next++;
}
p1 = strtoi(p, &p);
p2 = strtoi(p, &p);
RPCLOG(1, "rpc_uaddr2port: IPv6 port %d\n", ((p1 << 8) + p2));
}
return ((p1 << 8) + p2);
}
static int
strtoi(char *str, char **ptr)
{
int c;
int val;
for (val = 0, c = *str++; c >= '0' && c <= '9'; c = *str++) {
val *= 10;
val += c - '0';
}
*ptr = str;
return (val);
}
void
put_inet_port(struct netbuf *addr, ushort_t port)
{
((struct sockaddr_in *)(addr->buf))->sin_port = port;
}
void
put_inet6_port(struct netbuf *addr, ushort_t port)
{
((struct sockaddr_in6 *)(addr->buf))->sin6_port = port;
}
void
put_loopback_port(struct netbuf *addr, char *port)
{
char *dot;
char *newbuf;
int newlen;
if ((dot = strnrchr(addr->buf, '.', addr->len)) == (char *)NULL)
return;
newlen = (int)((dot - addr->buf + 1) + strlen(port));
if (newlen > addr->maxlen) {
newbuf = kmem_zalloc(newlen, KM_SLEEP);
bcopy(addr->buf, newbuf, addr->len);
kmem_free(addr->buf, addr->maxlen);
addr->buf = newbuf;
addr->len = addr->maxlen = newlen;
dot = strnrchr(addr->buf, '.', addr->len);
} else {
addr->len = newlen;
}
(void) strncpy(++dot, port, strlen(port));
}
static void
loopb_u2t(const char *ua, struct netbuf *addr)
{
size_t stringlen = strlen(ua) + 1;
const char *univp;
char *transp;
if (addr->maxlen < stringlen) {
grow_netbuf(addr, stringlen);
}
univp = ua;
transp = addr->buf;
while (*univp != '\0') {
if (*univp == '\\' && *(univp+1) == '\\') {
*transp = '\\';
univp += 2;
} else if (*univp == '\\') {
*transp = (((*(univp+1) - '0') & 3) << 6) +
(((*(univp+2) - '0') & 7) << 3) +
((*(univp+3) - '0') & 7);
univp += 4;
} else {
*transp = *univp;
univp++;
}
transp++;
}
addr->len = (unsigned int)(transp - addr->buf);
ASSERT(addr->len <= addr->maxlen);
}
static void
grow_netbuf(struct netbuf *nb, size_t length)
{
char *newbuf;
if (nb->maxlen >= length)
return;
newbuf = kmem_zalloc(length, KM_SLEEP);
bcopy(nb->buf, newbuf, nb->len);
kmem_free(nb->buf, nb->maxlen);
nb->buf = newbuf;
nb->maxlen = (unsigned int)length;
}
bool_t
xdr_pmap(XDR *xdrs, PMAP *objp)
{
if (!xdr_rpcprog(xdrs, &objp->pm_prog))
return (FALSE);
if (!xdr_rpcvers(xdrs, &objp->pm_vers))
return (FALSE);
if (!xdr_rpcprot(xdrs, &objp->pm_prot))
return (FALSE);
if (!xdr_u_int(xdrs, &objp->pm_port))
return (FALSE);
return (TRUE);
}
static enum clnt_stat
portmap_getport(struct knetconfig *config, rpcprog_t prog, rpcvers_t vers,
struct netbuf *addr, struct timeval tmo)
{
enum clnt_stat status;
CLIENT *client = NULL;
k_sigset_t oldmask;
k_sigset_t newmask;
ushort_t port = 0;
struct pmap parms;
ASSERT(strcmp(config->knc_protofmly, NC_INET) == 0);
bzero(&parms, sizeof (parms));
parms.pm_prog = prog;
parms.pm_vers = vers;
if (strcmp(config->knc_proto, NC_TCP) == 0) {
parms.pm_prot = IPPROTO_TCP;
} else {
parms.pm_prot = IPPROTO_UDP;
}
sigfillset(&newmask);
sigreplace(&newmask, &oldmask);
if (clnt_tli_kcreate(config, addr, PMAPPROG,
PMAPVERS, 0, 0, CRED(), &client)) {
sigreplace(&oldmask, (k_sigset_t *)NULL);
return (RPC_TLIERROR);
}
client->cl_nosignal = 1;
status = CLNT_CALL(client, PMAPPROC_GETPORT,
xdr_pmap, (char *)&parms,
xdr_u_short, (char *)&port, tmo);
sigreplace(&oldmask, (k_sigset_t *)NULL);
if (status != RPC_SUCCESS)
goto out;
if (port == 0) {
status = RPC_PROGNOTREGISTERED;
goto out;
}
put_inet_port(addr, ntohs(port));
out:
auth_destroy(client->cl_auth);
clnt_destroy(client);
return (status);
}
enum clnt_stat
rpcbind_getaddr(struct knetconfig *config, rpcprog_t prog, rpcvers_t vers,
struct netbuf *addr)
{
return (rpcbind_getaddr5(config, prog, vers, addr, NULL));
}
enum clnt_stat
rpcbind_getaddr5(struct knetconfig *config, rpcprog_t prog, rpcvers_t vers,
struct netbuf *addr, struct netbuf *laddr)
{
char *ua = NULL;
enum clnt_stat status;
RPCB parms;
struct timeval tmo;
k_sigset_t oldmask;
k_sigset_t newmask;
ushort_t port;
int iptype;
rpcvers_t rpcbv;
tmo.tv_sec = RPC_PMAP_TIMEOUT;
tmo.tv_usec = 0;
parms.r_prog = prog;
parms.r_vers = vers;
parms.r_addr = parms.r_owner = "";
if (strcmp(config->knc_protofmly, NC_INET) == 0) {
put_inet_port(addr, htons(PMAPPORT));
if (strcmp(config->knc_proto, NC_TCP) == 0)
parms.r_netid = "tcp";
else
parms.r_netid = "udp";
} else if (strcmp(config->knc_protofmly, NC_INET6) == 0) {
if (strcmp(config->knc_proto, NC_TCP) == 0)
parms.r_netid = "tcp6";
else
parms.r_netid = "udp6";
put_inet6_port(addr, htons(PMAPPORT));
} else if (strcmp(config->knc_protofmly, NC_LOOPBACK) == 0) {
ASSERT(strnrchr(addr->buf, '.', addr->len) != NULL);
if (config->knc_semantics == NC_TPI_COTS_ORD)
parms.r_netid = "ticotsord";
else if (config->knc_semantics == NC_TPI_COTS)
parms.r_netid = "ticots";
else
parms.r_netid = "ticlts";
put_loopback_port(addr, "rpc");
} else {
status = RPC_UNKNOWNPROTO;
goto out;
}
for (rpcbv = RPCBVERS4; rpcbv >= RPCBVERS; rpcbv--) {
CLIENT *client = NULL;
if (ua != NULL) {
xdr_free(xdr_wrapstring, (char *)&ua);
ua = NULL;
}
sigfillset(&newmask);
sigreplace(&newmask, &oldmask);
if (clnt_tli_kcreate(config, addr, RPCBPROG,
rpcbv, 0, 0, CRED(), &client)) {
status = RPC_TLIERROR;
sigreplace(&oldmask, (k_sigset_t *)NULL);
continue;
}
if (laddr != NULL) {
if (!clnt_control(client, CLSET_BINDSRCADDR,
(char *)laddr)) {
cmn_err(CE_WARN, "rpcbind_getaddr: "
"Unable to set CLSET_BINDSRCADDR\n");
}
}
client->cl_nosignal = 1;
status = CLNT_CALL(client, RPCBPROC_GETADDR,
xdr_rpcb, (char *)&parms,
xdr_wrapstring, (char *)&ua, tmo);
sigreplace(&oldmask, (k_sigset_t *)NULL);
auth_destroy(client->cl_auth);
clnt_destroy(client);
if (status == RPC_SUCCESS) {
if (ua == NULL || *ua == '\0') {
status = RPC_PROGNOTREGISTERED;
continue;
}
break;
}
}
if (status != RPC_SUCCESS)
goto try_portmap;
if (strcmp(config->knc_protofmly, NC_INET) == 0) {
if (rpc_iptype(ua, &iptype) != 0) {
status = RPC_UNKNOWNADDR;
goto try_portmap;
}
port = rpc_uaddr2port(iptype, ua);
put_inet_port(addr, ntohs(port));
} else if (strcmp(config->knc_protofmly, NC_INET6) == 0) {
if (rpc_iptype(ua, &iptype) != 0) {
status = RPC_UNKNOWNADDR;
goto try_portmap;
}
port = rpc_uaddr2port(iptype, ua);
put_inet6_port(addr, ntohs(port));
} else if (strcmp(config->knc_protofmly, NC_LOOPBACK) == 0) {
loopb_u2t(ua, addr);
} else {
cmn_err(CE_PANIC, "rpcbind_getaddr: bad protocol family");
}
try_portmap:
if (status != RPC_SUCCESS &&
strcmp(config->knc_protofmly, NC_INET) == 0) {
DTRACE_PROBE1(try__portmap, enum clnt_stat, status);
status = portmap_getport(config, prog, vers, addr, tmo);
}
out:
if (ua != NULL)
xdr_free(xdr_wrapstring, (char *)&ua);
return (status);
}
static const char *tpiprims[] = {
"T_CONN_REQ 0 connection request",
"T_CONN_RES 1 connection response",
"T_DISCON_REQ 2 disconnect request",
"T_DATA_REQ 3 data request",
"T_EXDATA_REQ 4 expedited data request",
"T_INFO_REQ 5 information request",
"T_BIND_REQ 6 bind request",
"T_UNBIND_REQ 7 unbind request",
"T_UNITDATA_REQ 8 unitdata request",
"T_OPTMGMT_REQ 9 manage options req",
"T_ORDREL_REQ 10 orderly release req",
"T_CONN_IND 11 connection indication",
"T_CONN_CON 12 connection confirmation",
"T_DISCON_IND 13 disconnect indication",
"T_DATA_IND 14 data indication",
"T_EXDATA_IND 15 expeditied data indication",
"T_INFO_ACK 16 information acknowledgment",
"T_BIND_ACK 17 bind acknowledment",
"T_ERROR_ACK 18 error acknowledgment",
"T_OK_ACK 19 ok acknowledgment",
"T_UNITDATA_IND 20 unitdata indication",
"T_UDERROR_IND 21 unitdata error indication",
"T_OPTMGMT_ACK 22 manage options ack",
"T_ORDREL_IND 23 orderly release ind"
};
const char *
rpc_tpiprim2name(uint_t prim)
{
if (prim > (sizeof (tpiprims) / sizeof (tpiprims[0]) - 1))
return ("unknown primitive");
return (tpiprims[prim]);
}
static const char *tpierrs[] = {
"error zero 0",
"TBADADDR 1 incorrect addr format",
"TBADOPT 2 incorrect option format",
"TACCES 3 incorrect permissions",
"TBADF 4 illegal transport fd",
"TNOADDR 5 couldn't allocate addr",
"TOUTSTATE 6 out of state",
"TBADSEQ 7 bad call sequnce number",
"TSYSERR 8 system error",
"TLOOK 9 event requires attention",
"TBADDATA 10 illegal amount of data",
"TBUFOVFLW 11 buffer not large enough",
"TFLOW 12 flow control",
"TNODATA 13 no data",
"TNODIS 14 discon_ind not found on q",
"TNOUDERR 15 unitdata error not found",
"TBADFLAG 16 bad flags",
"TNOREL 17 no ord rel found on q",
"TNOTSUPPORT 18 primitive not supported",
"TSTATECHNG 19 state is in process of changing"
};
const char *
rpc_tpierr2name(uint_t err)
{
if (err > (sizeof (tpierrs) / sizeof (tpierrs[0]) - 1))
return ("unknown error");
return (tpierrs[err]);
}
#define INADDRSZ 4
#define IN6ADDRSZ 16
#define INT16SZ 2
const char *
kinet_ntop6(uchar_t *src, char *dst, size_t size)
{
char tmp[sizeof ("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
char *tp;
struct { int base, len; } best, cur;
uint_t words[IN6ADDRSZ / INT16SZ];
int i;
size_t len;
bzero(words, sizeof (words));
for (i = 0; i < IN6ADDRSZ; i++)
words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
best.base = -1;
cur.base = -1;
for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
if (words[i] == 0) {
if (cur.base == -1)
cur.base = i, cur.len = 1;
else
cur.len++;
} else {
if (cur.base != -1) {
if (best.base == -1 || cur.len > best.len)
best = cur;
cur.base = -1;
}
}
}
if (cur.base != -1) {
if (best.base == -1 || cur.len > best.len)
best = cur;
}
if (best.base != -1 && best.len < 2)
best.base = -1;
tp = tmp;
for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
if (best.base != -1 && i >= best.base &&
i < (best.base + best.len)) {
if (i == best.base)
*tp++ = ':';
continue;
}
if (i != 0)
*tp++ = ':';
(void) sprintf(tp, "%x", words[i]);
len = strlen(tp);
tp += len;
}
if (best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ))
*tp++ = ':';
*tp++ = '\0';
if ((int)(tp - tmp) > size) {
return (NULL);
}
(void) strcpy(dst, tmp);
return (dst);
}