17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * CDDL HEADER START
37c478bd9Sstevel@tonic-gate  *
47c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
545916cd2Sjpk  * Common Development and Distribution License (the "License").
645916cd2Sjpk  * You may not use this file except in compliance with the License.
77c478bd9Sstevel@tonic-gate  *
87c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
107c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
117c478bd9Sstevel@tonic-gate  * and limitations under the License.
127c478bd9Sstevel@tonic-gate  *
137c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
147c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
167c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
177c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
187c478bd9Sstevel@tonic-gate  *
197c478bd9Sstevel@tonic-gate  * CDDL HEADER END
207c478bd9Sstevel@tonic-gate  */
2161961e0fSrobinson 
227c478bd9Sstevel@tonic-gate /*
23*dec98d2aSGordon Ross  * Copyright 2016 Nexenta Systems, Inc.  All rights reserved.
249ff75adeSSurya Prakki  * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
257c478bd9Sstevel@tonic-gate  */
267c478bd9Sstevel@tonic-gate 
277c478bd9Sstevel@tonic-gate /*	Copyright (c) 1988 AT&T */
287c478bd9Sstevel@tonic-gate /*	All Rights Reserved   */
297c478bd9Sstevel@tonic-gate 
307c478bd9Sstevel@tonic-gate /*
317c478bd9Sstevel@tonic-gate  * svc_generic.c, Server side for RPC.
327c478bd9Sstevel@tonic-gate  *
337c478bd9Sstevel@tonic-gate  */
347c478bd9Sstevel@tonic-gate 
357c478bd9Sstevel@tonic-gate #include "mt.h"
3661961e0fSrobinson #include <stdlib.h>
377c478bd9Sstevel@tonic-gate #include <sys/socket.h>
387c478bd9Sstevel@tonic-gate #include <netinet/in.h>
397c478bd9Sstevel@tonic-gate #include <netinet/tcp.h>
407c478bd9Sstevel@tonic-gate #include <netinet/udp.h>
417c478bd9Sstevel@tonic-gate #include <inttypes.h>
427c478bd9Sstevel@tonic-gate #include "rpc_mt.h"
437c478bd9Sstevel@tonic-gate #include <stdio.h>
447c478bd9Sstevel@tonic-gate #include <rpc/rpc.h>
457c478bd9Sstevel@tonic-gate #include <sys/types.h>
467c478bd9Sstevel@tonic-gate #include <errno.h>
477c478bd9Sstevel@tonic-gate #include <syslog.h>
487c478bd9Sstevel@tonic-gate #include <rpc/nettype.h>
497c478bd9Sstevel@tonic-gate #include <malloc.h>
507c478bd9Sstevel@tonic-gate #include <string.h>
517c478bd9Sstevel@tonic-gate #include <stropts.h>
5245916cd2Sjpk #include <tsol/label.h>
5345916cd2Sjpk #include <nfs/nfs.h>
5445916cd2Sjpk #include <nfs/nfs_acl.h>
5545916cd2Sjpk #include <rpcsvc/mount.h>
5645916cd2Sjpk #include <rpcsvc/nsm_addr.h>
5745916cd2Sjpk #include <rpcsvc/rquota.h>
5845916cd2Sjpk #include <rpcsvc/sm_inter.h>
5945916cd2Sjpk #include <rpcsvc/nlm_prot.h>
607c478bd9Sstevel@tonic-gate 
6161961e0fSrobinson extern int __svc_vc_setflag(SVCXPRT *, int);
627c478bd9Sstevel@tonic-gate 
6361961e0fSrobinson extern SVCXPRT *svc_dg_create_private(int, uint_t, uint_t);
6461961e0fSrobinson extern SVCXPRT *svc_vc_create_private(int, uint_t, uint_t);
6561961e0fSrobinson extern SVCXPRT *svc_fd_create_private(int, uint_t, uint_t);
667c478bd9Sstevel@tonic-gate 
6761961e0fSrobinson extern bool_t __svc_add_to_xlist(SVCXPRT_LIST **, SVCXPRT *, mutex_t *);
6861961e0fSrobinson extern void __svc_free_xlist(SVCXPRT_LIST **, mutex_t *);
6961961e0fSrobinson 
7061961e0fSrobinson extern bool_t __rpc_try_doors(const char *, bool_t *);
717c478bd9Sstevel@tonic-gate 
727c478bd9Sstevel@tonic-gate /*
737c478bd9Sstevel@tonic-gate  * The highest level interface for server creation.
747c478bd9Sstevel@tonic-gate  * It tries for all the nettokens in that particular class of token
757c478bd9Sstevel@tonic-gate  * and returns the number of handles it can create and/or find.
767c478bd9Sstevel@tonic-gate  *
777c478bd9Sstevel@tonic-gate  * It creates a link list of all the handles it could create.
787c478bd9Sstevel@tonic-gate  * If svc_create() is called multiple times, it uses the handle
797c478bd9Sstevel@tonic-gate  * created earlier instead of creating a new handle every time.
807c478bd9Sstevel@tonic-gate  */
817c478bd9Sstevel@tonic-gate 
827c478bd9Sstevel@tonic-gate /* VARIABLES PROTECTED BY xprtlist_lock: xprtlist */
837c478bd9Sstevel@tonic-gate 
847c478bd9Sstevel@tonic-gate SVCXPRT_LIST *_svc_xprtlist = NULL;
857c478bd9Sstevel@tonic-gate extern mutex_t xprtlist_lock;
867c478bd9Sstevel@tonic-gate 
8745916cd2Sjpk static SVCXPRT * svc_tli_create_common(int, const struct netconfig *,
8845916cd2Sjpk     const struct t_bind *, uint_t, uint_t, boolean_t);
8945916cd2Sjpk 
90*dec98d2aSGordon Ross static SVCXPRT *svc_tp_create_bind(void (*dispatch)(),
91*dec98d2aSGordon Ross     const rpcprog_t, const rpcvers_t,
92*dec98d2aSGordon Ross     const struct netconfig *, const struct t_bind *);
93*dec98d2aSGordon Ross 
9445916cd2Sjpk boolean_t
is_multilevel(rpcprog_t prognum)9545916cd2Sjpk is_multilevel(rpcprog_t prognum)
9645916cd2Sjpk {
9745916cd2Sjpk 	/* This is a list of identified multilevel service provider */
9845916cd2Sjpk 	if ((prognum == MOUNTPROG) || (prognum == NFS_PROGRAM) ||
9945916cd2Sjpk 	    (prognum == NFS_ACL_PROGRAM) || (prognum == NLM_PROG) ||
10045916cd2Sjpk 	    (prognum == NSM_ADDR_PROGRAM) || (prognum == RQUOTAPROG) ||
10145916cd2Sjpk 	    (prognum == SM_PROG))
10245916cd2Sjpk 		return (B_TRUE);
10345916cd2Sjpk 
10445916cd2Sjpk 	return (B_FALSE);
10545916cd2Sjpk }
10645916cd2Sjpk 
1077c478bd9Sstevel@tonic-gate void
__svc_free_xprtlist(void)10861961e0fSrobinson __svc_free_xprtlist(void)
1097c478bd9Sstevel@tonic-gate {
1107c478bd9Sstevel@tonic-gate 	__svc_free_xlist(&_svc_xprtlist, &xprtlist_lock);
1117c478bd9Sstevel@tonic-gate }
1127c478bd9Sstevel@tonic-gate 
1137c478bd9Sstevel@tonic-gate int
svc_create(void (* dispatch)(),const rpcprog_t prognum,const rpcvers_t versnum,const char * nettype)11461961e0fSrobinson svc_create(void (*dispatch)(), const rpcprog_t prognum, const rpcvers_t versnum,
11561961e0fSrobinson 							const char *nettype)
1167c478bd9Sstevel@tonic-gate {
1177c478bd9Sstevel@tonic-gate 	SVCXPRT_LIST *l;
1187c478bd9Sstevel@tonic-gate 	int num = 0;
1197c478bd9Sstevel@tonic-gate 	SVCXPRT *xprt;
1207c478bd9Sstevel@tonic-gate 	struct netconfig *nconf;
1217c478bd9Sstevel@tonic-gate 	void *handle;
1227c478bd9Sstevel@tonic-gate 	bool_t try_others;
1237c478bd9Sstevel@tonic-gate 
1247c478bd9Sstevel@tonic-gate 	/*
1257c478bd9Sstevel@tonic-gate 	 * Check if service should register over doors transport.
1267c478bd9Sstevel@tonic-gate 	 */
1277c478bd9Sstevel@tonic-gate 	if (__rpc_try_doors(nettype, &try_others)) {
1287c478bd9Sstevel@tonic-gate 		if (svc_door_create(dispatch, prognum, versnum, 0) == NULL)
1297c478bd9Sstevel@tonic-gate 			(void) syslog(LOG_ERR,
130361f55a5SMarcel Telka 			    "svc_create: could not register over doors");
1317c478bd9Sstevel@tonic-gate 		else
1327c478bd9Sstevel@tonic-gate 			num++;
1337c478bd9Sstevel@tonic-gate 	}
1347c478bd9Sstevel@tonic-gate 	if (!try_others)
1357c478bd9Sstevel@tonic-gate 		return (num);
1367c478bd9Sstevel@tonic-gate 	if ((handle = __rpc_setconf((char *)nettype)) == NULL) {
1377c478bd9Sstevel@tonic-gate 		(void) syslog(LOG_ERR, "svc_create: unknown protocol");
1387c478bd9Sstevel@tonic-gate 		return (0);
1397c478bd9Sstevel@tonic-gate 	}
1407c478bd9Sstevel@tonic-gate 	while (nconf = __rpc_getconf(handle)) {
14161961e0fSrobinson 		(void) mutex_lock(&xprtlist_lock);
1427c478bd9Sstevel@tonic-gate 		for (l = _svc_xprtlist; l; l = l->next) {
1437c478bd9Sstevel@tonic-gate 			if (strcmp(l->xprt->xp_netid, nconf->nc_netid) == 0) {
1449ff75adeSSurya Prakki 				/* Found an old one, use it */
1459ff75adeSSurya Prakki 				(void) rpcb_unset(prognum, versnum, nconf);
1467c478bd9Sstevel@tonic-gate 				if (svc_reg(l->xprt, prognum, versnum,
147361f55a5SMarcel Telka 				    dispatch, nconf) == FALSE)
148361f55a5SMarcel Telka 					(void) syslog(LOG_ERR, "svc_create: "
149361f55a5SMarcel Telka 					    "could not register prog %d vers "
150361f55a5SMarcel Telka 					    "%d on %s",
151361f55a5SMarcel Telka 					    prognum, versnum, nconf->nc_netid);
1527c478bd9Sstevel@tonic-gate 				else
1537c478bd9Sstevel@tonic-gate 					num++;
1547c478bd9Sstevel@tonic-gate 				break;
1557c478bd9Sstevel@tonic-gate 			}
1567c478bd9Sstevel@tonic-gate 		}
15761961e0fSrobinson 		(void) mutex_unlock(&xprtlist_lock);
1587c478bd9Sstevel@tonic-gate 		if (l == NULL) {
1597c478bd9Sstevel@tonic-gate 			/* It was not found. Now create a new one */
1607c478bd9Sstevel@tonic-gate 			xprt = svc_tp_create(dispatch, prognum, versnum, nconf);
1617c478bd9Sstevel@tonic-gate 			if (xprt) {
1627c478bd9Sstevel@tonic-gate 				if (!__svc_add_to_xlist(&_svc_xprtlist, xprt,
163361f55a5SMarcel Telka 				    &xprtlist_lock)) {
1647c478bd9Sstevel@tonic-gate 					(void) syslog(LOG_ERR,
165361f55a5SMarcel Telka 					    "svc_create: no memory");
1667c478bd9Sstevel@tonic-gate 					return (0);
1677c478bd9Sstevel@tonic-gate 				}
1687c478bd9Sstevel@tonic-gate 				num++;
1697c478bd9Sstevel@tonic-gate 			}
1707c478bd9Sstevel@tonic-gate 		}
1717c478bd9Sstevel@tonic-gate 	}
1727c478bd9Sstevel@tonic-gate 	__rpc_endconf(handle);
1737c478bd9Sstevel@tonic-gate 	/*
1747c478bd9Sstevel@tonic-gate 	 * In case of num == 0; the error messages are generated by the
1757c478bd9Sstevel@tonic-gate 	 * underlying layers; and hence not needed here.
1767c478bd9Sstevel@tonic-gate 	 */
1777c478bd9Sstevel@tonic-gate 	return (num);
1787c478bd9Sstevel@tonic-gate }
1797c478bd9Sstevel@tonic-gate 
1807c478bd9Sstevel@tonic-gate /*
1817c478bd9Sstevel@tonic-gate  * The high level interface to svc_tli_create().
1827c478bd9Sstevel@tonic-gate  * It tries to create a server for "nconf" and registers the service
183*dec98d2aSGordon Ross  * with the rpcbind.
1847c478bd9Sstevel@tonic-gate  */
1857c478bd9Sstevel@tonic-gate SVCXPRT *
svc_tp_create(void (* dispatch)(),const rpcprog_t prognum,const rpcvers_t versnum,const struct netconfig * nconf)18661961e0fSrobinson svc_tp_create(void (*dispatch)(), const rpcprog_t prognum,
187*dec98d2aSGordon Ross     const rpcvers_t versnum, const struct netconfig *nconf)
188*dec98d2aSGordon Ross {
189*dec98d2aSGordon Ross 	return (svc_tp_create_bind(dispatch, prognum, versnum, nconf, NULL));
190*dec98d2aSGordon Ross }
191*dec98d2aSGordon Ross 
192*dec98d2aSGordon Ross /*
193*dec98d2aSGordon Ross  * svc_tp_create_addr()
194*dec98d2aSGordon Ross  * Variant of svc_tp_create() that allows specifying just the
195*dec98d2aSGordon Ross  * the binding address, for convenience.
196*dec98d2aSGordon Ross  */
197*dec98d2aSGordon Ross SVCXPRT *
svc_tp_create_addr(void (* dispatch)(),const rpcprog_t prognum,const rpcvers_t versnum,const struct netconfig * nconf,const struct netbuf * addr)198*dec98d2aSGordon Ross svc_tp_create_addr(void (*dispatch)(), const rpcprog_t prognum,
199*dec98d2aSGordon Ross     const rpcvers_t versnum, const struct netconfig *nconf,
200*dec98d2aSGordon Ross     const struct netbuf *addr)
201*dec98d2aSGordon Ross {
202*dec98d2aSGordon Ross 	struct t_bind bind;
203*dec98d2aSGordon Ross 	struct t_bind *bindp = NULL;
204*dec98d2aSGordon Ross 
205*dec98d2aSGordon Ross 	if (addr != NULL) {
206*dec98d2aSGordon Ross 
207*dec98d2aSGordon Ross 		bind.addr = *addr;
208*dec98d2aSGordon Ross 		if (!rpc_control(__RPC_SVC_LSTNBKLOG_GET, &bind.qlen)) {
209*dec98d2aSGordon Ross 			syslog(LOG_ERR,
210*dec98d2aSGordon Ross 			    "svc_tp_create: can't get listen backlog");
211*dec98d2aSGordon Ross 			return (NULL);
212*dec98d2aSGordon Ross 		}
213*dec98d2aSGordon Ross 		bindp = &bind;
214*dec98d2aSGordon Ross 	}
215*dec98d2aSGordon Ross 
216*dec98d2aSGordon Ross 	/*
217*dec98d2aSGordon Ross 	 * When bindp == NULL, this is the same as svc_tp_create().
218*dec98d2aSGordon Ross 	 */
219*dec98d2aSGordon Ross 	return (svc_tp_create_bind(dispatch, prognum, versnum,
220*dec98d2aSGordon Ross 	    nconf, bindp));
221*dec98d2aSGordon Ross }
222*dec98d2aSGordon Ross 
223*dec98d2aSGordon Ross static SVCXPRT *
svc_tp_create_bind(void (* dispatch)(),const rpcprog_t prognum,const rpcvers_t versnum,const struct netconfig * nconf,const struct t_bind * bindaddr)224*dec98d2aSGordon Ross svc_tp_create_bind(void (*dispatch)(), const rpcprog_t prognum,
225*dec98d2aSGordon Ross     const rpcvers_t versnum, const struct netconfig *nconf,
226*dec98d2aSGordon Ross     const struct t_bind *bindaddr)
2277c478bd9Sstevel@tonic-gate {
2287c478bd9Sstevel@tonic-gate 	SVCXPRT *xprt;
22945916cd2Sjpk 	boolean_t anon_mlp = B_FALSE;
2307c478bd9Sstevel@tonic-gate 
23161961e0fSrobinson 	if (nconf == NULL) {
232361f55a5SMarcel Telka 		(void) syslog(LOG_ERR, "svc_tp_create: invalid netconfig "
233361f55a5SMarcel Telka 		    "structure for prog %d vers %d", prognum, versnum);
23461961e0fSrobinson 		return (NULL);
2357c478bd9Sstevel@tonic-gate 	}
23645916cd2Sjpk 
23745916cd2Sjpk 	/* Some programs need to allocate MLP for multilevel services */
23845916cd2Sjpk 	if (is_system_labeled() && is_multilevel(prognum))
23945916cd2Sjpk 		anon_mlp = B_TRUE;
240*dec98d2aSGordon Ross 	xprt = svc_tli_create_common(RPC_ANYFD, nconf, bindaddr, 0, 0,
241*dec98d2aSGordon Ross 	    anon_mlp);
24261961e0fSrobinson 	if (xprt == NULL)
24361961e0fSrobinson 		return (NULL);
2449acbbeafSnn 
2459ff75adeSSurya Prakki 	(void) rpcb_unset(prognum, versnum, (struct netconfig *)nconf);
2467c478bd9Sstevel@tonic-gate 	if (svc_reg(xprt, prognum, versnum, dispatch, nconf) == FALSE) {
2477c478bd9Sstevel@tonic-gate 		(void) syslog(LOG_ERR,
248361f55a5SMarcel Telka 		    "svc_tp_create: Could not register prog %d vers %d on %s",
249361f55a5SMarcel Telka 		    prognum, versnum, nconf->nc_netid);
2507c478bd9Sstevel@tonic-gate 		SVC_DESTROY(xprt);
25161961e0fSrobinson 		return (NULL);
2527c478bd9Sstevel@tonic-gate 	}
2537c478bd9Sstevel@tonic-gate 	return (xprt);
2547c478bd9Sstevel@tonic-gate }
2557c478bd9Sstevel@tonic-gate 
25645916cd2Sjpk SVCXPRT *
svc_tli_create(const int fd,const struct netconfig * nconf,const struct t_bind * bindaddr,const uint_t sendsz,const uint_t recvsz)25745916cd2Sjpk svc_tli_create(const int fd, const struct netconfig *nconf,
25845916cd2Sjpk     const struct t_bind *bindaddr, const uint_t sendsz, const uint_t recvsz)
25945916cd2Sjpk {
26045916cd2Sjpk 	return (svc_tli_create_common(fd, nconf, bindaddr, sendsz, recvsz, 0));
26145916cd2Sjpk }
26245916cd2Sjpk 
2637c478bd9Sstevel@tonic-gate /*
2647c478bd9Sstevel@tonic-gate  * If fd is RPC_ANYFD, then it opens a fd for the given transport
2657c478bd9Sstevel@tonic-gate  * provider (nconf cannot be NULL then). If the t_state is T_UNBND and
2667c478bd9Sstevel@tonic-gate  * bindaddr is NON-NULL, it performs a t_bind using the bindaddr. For
2677c478bd9Sstevel@tonic-gate  * NULL bindadr and Connection oriented transports, the value of qlen
2687c478bd9Sstevel@tonic-gate  * is set arbitrarily.
2697c478bd9Sstevel@tonic-gate  *
2707c478bd9Sstevel@tonic-gate  * If sendsz or recvsz are zero, their default values are chosen.
2717c478bd9Sstevel@tonic-gate  */
2727c478bd9Sstevel@tonic-gate SVCXPRT *
svc_tli_create_common(const int ofd,const struct netconfig * nconf,const struct t_bind * bindaddr,const uint_t sendsz,const uint_t recvsz,boolean_t mlp_flag)27345916cd2Sjpk svc_tli_create_common(const int ofd, const struct netconfig *nconf,
27445916cd2Sjpk 	const struct t_bind *bindaddr, const uint_t sendsz,
27545916cd2Sjpk 	const uint_t recvsz, boolean_t mlp_flag)
2767c478bd9Sstevel@tonic-gate {
2777c478bd9Sstevel@tonic-gate 	SVCXPRT *xprt = NULL;		/* service handle */
2787c478bd9Sstevel@tonic-gate 	struct t_info tinfo;		/* transport info */
2797c478bd9Sstevel@tonic-gate 	struct t_bind *tres = NULL;	/* bind info */
2807c478bd9Sstevel@tonic-gate 	bool_t madefd = FALSE;		/* whether fd opened here  */
2817c478bd9Sstevel@tonic-gate 	int state;			/* state of the transport provider */
28261961e0fSrobinson 	int fd = ofd;
2837c478bd9Sstevel@tonic-gate 
2847c478bd9Sstevel@tonic-gate 	if (fd == RPC_ANYFD) {
28561961e0fSrobinson 		if (nconf == NULL) {
2867c478bd9Sstevel@tonic-gate 			(void) syslog(LOG_ERR,
2877c478bd9Sstevel@tonic-gate 			    "svc_tli_create: invalid netconfig");
28861961e0fSrobinson 			return (NULL);
2897c478bd9Sstevel@tonic-gate 		}
2907c478bd9Sstevel@tonic-gate 		fd = t_open(nconf->nc_device, O_RDWR, &tinfo);
2917c478bd9Sstevel@tonic-gate 		if (fd == -1) {
2927c478bd9Sstevel@tonic-gate 			char errorstr[100];
2937c478bd9Sstevel@tonic-gate 
2947c478bd9Sstevel@tonic-gate 			__tli_sys_strerror(errorstr, sizeof (errorstr),
295361f55a5SMarcel Telka 			    t_errno, errno);
296361f55a5SMarcel Telka 			(void) syslog(LOG_ERR, "svc_tli_create: could not open "
297361f55a5SMarcel Telka 			    "connection for %s: %s", nconf->nc_netid, errorstr);
29861961e0fSrobinson 			return (NULL);
2997c478bd9Sstevel@tonic-gate 		}
3007c478bd9Sstevel@tonic-gate 		madefd = TRUE;
3017c478bd9Sstevel@tonic-gate 		state = T_UNBND;
3027c478bd9Sstevel@tonic-gate 	} else {
3037c478bd9Sstevel@tonic-gate 		/*
3047c478bd9Sstevel@tonic-gate 		 * It is an open descriptor. Sync it & get the transport info.
3057c478bd9Sstevel@tonic-gate 		 */
3067c478bd9Sstevel@tonic-gate 		if ((state = t_sync(fd)) == -1) {
3077c478bd9Sstevel@tonic-gate 			char errorstr[100];
3087c478bd9Sstevel@tonic-gate 
3097c478bd9Sstevel@tonic-gate 			__tli_sys_strerror(errorstr, sizeof (errorstr),
310361f55a5SMarcel Telka 			    t_errno, errno);
3117c478bd9Sstevel@tonic-gate 			(void) syslog(LOG_ERR,
312361f55a5SMarcel Telka 			    "svc_tli_create: could not do t_sync: %s",
313361f55a5SMarcel Telka 			    errorstr);
31461961e0fSrobinson 			return (NULL);
3157c478bd9Sstevel@tonic-gate 		}
3167c478bd9Sstevel@tonic-gate 		if (t_getinfo(fd, &tinfo) == -1) {
3177c478bd9Sstevel@tonic-gate 			char errorstr[100];
3187c478bd9Sstevel@tonic-gate 
3197c478bd9Sstevel@tonic-gate 			__tli_sys_strerror(errorstr, sizeof (errorstr),
320361f55a5SMarcel Telka 			    t_errno, errno);
321361f55a5SMarcel Telka 			(void) syslog(LOG_ERR, "svc_tli_create: could not get "
322361f55a5SMarcel Telka 			    "transport information: %s", errorstr);
32361961e0fSrobinson 			return (NULL);
3247c478bd9Sstevel@tonic-gate 		}
3257c478bd9Sstevel@tonic-gate 		/* Enable options of returning the ip's for udp */
3267c478bd9Sstevel@tonic-gate 		if (nconf) {
327361f55a5SMarcel Telka 			int ret = 0;
328361f55a5SMarcel Telka 			if (strcmp(nconf->nc_netid, "udp6") == 0) {
329361f55a5SMarcel Telka 				ret = __rpc_tli_set_options(fd, IPPROTO_IPV6,
330361f55a5SMarcel Telka 				    IPV6_RECVPKTINFO, 1);
331361f55a5SMarcel Telka 				if (ret < 0) {
332361f55a5SMarcel Telka 					char errorstr[100];
333361f55a5SMarcel Telka 
334361f55a5SMarcel Telka 					__tli_sys_strerror(errorstr,
335361f55a5SMarcel Telka 					    sizeof (errorstr), t_errno, errno);
336361f55a5SMarcel Telka 					(void) syslog(LOG_ERR,
337361f55a5SMarcel Telka 					    "svc_tli_create: "
338361f55a5SMarcel Telka 					    "IPV6_RECVPKTINFO(1): %s",
339361f55a5SMarcel Telka 					    errorstr);
340361f55a5SMarcel Telka 					return (NULL);
341361f55a5SMarcel Telka 				}
342361f55a5SMarcel Telka 			} else if (strcmp(nconf->nc_netid, "udp") == 0) {
343361f55a5SMarcel Telka 				ret = __rpc_tli_set_options(fd, IPPROTO_IP,
344361f55a5SMarcel Telka 				    IP_RECVDSTADDR, 1);
345361f55a5SMarcel Telka 				if (ret < 0) {
346361f55a5SMarcel Telka 					char errorstr[100];
347361f55a5SMarcel Telka 
348361f55a5SMarcel Telka 					__tli_sys_strerror(errorstr,
349361f55a5SMarcel Telka 					    sizeof (errorstr), t_errno, errno);
350361f55a5SMarcel Telka 					(void) syslog(LOG_ERR,
351361f55a5SMarcel Telka 					    "svc_tli_create: "
352361f55a5SMarcel Telka 					    "IP_RECVDSTADDR(1): %s", errorstr);
353361f55a5SMarcel Telka 					return (NULL);
354361f55a5SMarcel Telka 				}
3557c478bd9Sstevel@tonic-gate 			}
3567c478bd9Sstevel@tonic-gate 		}
3577c478bd9Sstevel@tonic-gate 	}
3587c478bd9Sstevel@tonic-gate 
3597c478bd9Sstevel@tonic-gate 	/*
3607c478bd9Sstevel@tonic-gate 	 * If the fd is unbound, try to bind it.
3617c478bd9Sstevel@tonic-gate 	 * In any case, try to get its bound info in tres
3627c478bd9Sstevel@tonic-gate 	 */
3637c478bd9Sstevel@tonic-gate /* LINTED pointer alignment */
3647c478bd9Sstevel@tonic-gate 	tres = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR);
3657c478bd9Sstevel@tonic-gate 	if (tres == NULL) {
3667c478bd9Sstevel@tonic-gate 		(void) syslog(LOG_ERR, "svc_tli_create: No memory!");
3677c478bd9Sstevel@tonic-gate 		goto freedata;
3687c478bd9Sstevel@tonic-gate 	}
3697c478bd9Sstevel@tonic-gate 
3707c478bd9Sstevel@tonic-gate 	switch (state) {
3717c478bd9Sstevel@tonic-gate 	case T_UNBND:
37245916cd2Sjpk 		/* If this is a labeled system, then ask for an MLP */
37345916cd2Sjpk 		if (is_system_labeled() &&
37445916cd2Sjpk 		    (strcmp(nconf->nc_protofmly, NC_INET) == 0 ||
37545916cd2Sjpk 		    strcmp(nconf->nc_protofmly, NC_INET6) == 0)) {
37645916cd2Sjpk 			(void) __rpc_tli_set_options(fd, SOL_SOCKET,
37745916cd2Sjpk 			    SO_RECVUCRED, 1);
37845916cd2Sjpk 			if (mlp_flag)
37945916cd2Sjpk 				(void) __rpc_tli_set_options(fd, SOL_SOCKET,
38045916cd2Sjpk 				    SO_ANON_MLP, 1);
38145916cd2Sjpk 		}
38245916cd2Sjpk 
3837c478bd9Sstevel@tonic-gate 		if (bindaddr) {
384*dec98d2aSGordon Ross 			/*
385*dec98d2aSGordon Ross 			 * Services that specify a bind address typically
386*dec98d2aSGordon Ross 			 * use a fixed service (IP port) so we need to set
387*dec98d2aSGordon Ross 			 * SO_REUSEADDR to prevent bind errors on restart.
388*dec98d2aSGordon Ross 			 */
389*dec98d2aSGordon Ross 			if (bindaddr->addr.len != 0)
390*dec98d2aSGordon Ross 				(void) __rpc_tli_set_options(fd, SOL_SOCKET,
391*dec98d2aSGordon Ross 				    SO_REUSEADDR, 1);
392361f55a5SMarcel Telka 			if (t_bind(fd, (struct t_bind *)bindaddr, tres) == -1) {
3937c478bd9Sstevel@tonic-gate 				char errorstr[100];
3947c478bd9Sstevel@tonic-gate 
3957c478bd9Sstevel@tonic-gate 				__tli_sys_strerror(errorstr, sizeof (errorstr),
396361f55a5SMarcel Telka 				    t_errno, errno);
3977c478bd9Sstevel@tonic-gate 				(void) syslog(LOG_ERR,
398361f55a5SMarcel Telka 				    "svc_tli_create: could not bind: %s",
399361f55a5SMarcel Telka 				    errorstr);
4007c478bd9Sstevel@tonic-gate 				goto freedata;
4017c478bd9Sstevel@tonic-gate 			}
4027c478bd9Sstevel@tonic-gate 			/*
4037c478bd9Sstevel@tonic-gate 			 * Should compare the addresses only if addr.len
4047c478bd9Sstevel@tonic-gate 			 * was non-zero
4057c478bd9Sstevel@tonic-gate 			 */
4067c478bd9Sstevel@tonic-gate 			if (bindaddr->addr.len &&
407361f55a5SMarcel Telka 			    (memcmp(bindaddr->addr.buf, tres->addr.buf,
408361f55a5SMarcel Telka 			    (int)tres->addr.len) != 0)) {
409361f55a5SMarcel Telka 				(void) syslog(LOG_ERR, "svc_tli_create: could "
410361f55a5SMarcel Telka 				    "not bind to requested address: address "
411361f55a5SMarcel Telka 				    "mismatch");
4127c478bd9Sstevel@tonic-gate 				goto freedata;
4137c478bd9Sstevel@tonic-gate 			}
4147c478bd9Sstevel@tonic-gate 		} else {
415361f55a5SMarcel Telka 			if (rpc_control(__RPC_SVC_LSTNBKLOG_GET, &tres->qlen)
416361f55a5SMarcel Telka 			    == FALSE) {
417361f55a5SMarcel Telka 				syslog(LOG_ERR,
418361f55a5SMarcel Telka 				    "svc_tli_create: can't get listen backlog");
419361f55a5SMarcel Telka 				goto freedata;
420361f55a5SMarcel Telka 			}
4217c478bd9Sstevel@tonic-gate 			tres->addr.len = 0;
4227c478bd9Sstevel@tonic-gate 			if (t_bind(fd, tres, tres) == -1) {
4237c478bd9Sstevel@tonic-gate 				char errorstr[100];
4247c478bd9Sstevel@tonic-gate 
4257c478bd9Sstevel@tonic-gate 				__tli_sys_strerror(errorstr, sizeof (errorstr),
426361f55a5SMarcel Telka 				    t_errno, errno);
4277c478bd9Sstevel@tonic-gate 				(void) syslog(LOG_ERR,
428361f55a5SMarcel Telka 				    "svc_tli_create: could not bind: %s",
429361f55a5SMarcel Telka 				    errorstr);
4307c478bd9Sstevel@tonic-gate 				goto freedata;
4317c478bd9Sstevel@tonic-gate 			}
4327c478bd9Sstevel@tonic-gate 		}
4337c478bd9Sstevel@tonic-gate 
434*dec98d2aSGordon Ross 		/*
435*dec98d2aSGordon Ross 		 * If requested, set SO_EXCLBIND on each binding.
436*dec98d2aSGordon Ross 		 *
437*dec98d2aSGordon Ross 		 * SO_EXCLBIND has the following properties
438*dec98d2aSGordon Ross 		 *    - an fd bound to port P via IPv4 will prevent an IPv6
439*dec98d2aSGordon Ross 		 *    bind to port P (and vice versa)
440*dec98d2aSGordon Ross 		 *    - an fd bound to a wildcard IP address for port P will
441*dec98d2aSGordon Ross 		 *    prevent a more specific IP address bind to port P
442*dec98d2aSGordon Ross 		 *    (see {tcp,udp}.c for details)
443*dec98d2aSGordon Ross 		 *
444*dec98d2aSGordon Ross 		 * We use the latter property to prevent hijacking of RPC
445*dec98d2aSGordon Ross 		 * services that reside at non-privileged ports.
446*dec98d2aSGordon Ross 		 *
447*dec98d2aSGordon Ross 		 * When the bind address is not specified, each bind gets a
448*dec98d2aSGordon Ross 		 * new port number, and (for IP transports) we should set
449*dec98d2aSGordon Ross 		 * the exclusive flag after every IP bind.  That's the
450*dec98d2aSGordon Ross 		 * strcmp nc_proto part of the expression below.
451*dec98d2aSGordon Ross 		 *
452*dec98d2aSGordon Ross 		 * When the bind address IS specified, we need to set the
453*dec98d2aSGordon Ross 		 * exclusive flag only after we've bound both IPv6+IPv4,
454*dec98d2aSGordon Ross 		 * or the IPv4 bind will fail.  Setting the exclusive flag
455*dec98d2aSGordon Ross 		 * after the "tcp" or "udp" transport bind does that.
456*dec98d2aSGordon Ross 		 * That's the strcmp nc_netid part below.
457*dec98d2aSGordon Ross 		 */
458*dec98d2aSGordon Ross 		if (nconf != NULL && ((bindaddr == NULL &&
459*dec98d2aSGordon Ross 		    (strcmp(nconf->nc_proto, NC_TCP) == 0 ||
460*dec98d2aSGordon Ross 		    strcmp(nconf->nc_proto, NC_UDP) == 0)) ||
461*dec98d2aSGordon Ross 		    (strcmp(nconf->nc_netid, "tcp") == 0 ||
462*dec98d2aSGordon Ross 		    strcmp(nconf->nc_netid, "udp") == 0))) {
463*dec98d2aSGordon Ross 			bool_t exclbind = FALSE;
464*dec98d2aSGordon Ross 			(void) rpc_control(__RPC_SVC_EXCLBIND_GET, &exclbind);
465*dec98d2aSGordon Ross 			if (exclbind &&
466*dec98d2aSGordon Ross 			    __rpc_tli_set_options(fd, SOL_SOCKET,
467*dec98d2aSGordon Ross 			    SO_EXCLBIND, 1) < 0) {
468*dec98d2aSGordon Ross 				syslog(LOG_ERR,
469*dec98d2aSGordon Ross 			    "svc_tli_create: can't set EXCLBIND [netid='%s']",
470*dec98d2aSGordon Ross 				    nconf->nc_netid);
471*dec98d2aSGordon Ross 				goto freedata;
472*dec98d2aSGordon Ross 			}
473*dec98d2aSGordon Ross 		}
474*dec98d2aSGordon Ross 
4757c478bd9Sstevel@tonic-gate 		/* Enable options of returning the ip's for udp */
4767c478bd9Sstevel@tonic-gate 		if (nconf) {
477361f55a5SMarcel Telka 			int ret = 0;
478361f55a5SMarcel Telka 			if (strcmp(nconf->nc_netid, "udp6") == 0) {
479361f55a5SMarcel Telka 				ret = __rpc_tli_set_options(fd, IPPROTO_IPV6,
480361f55a5SMarcel Telka 				    IPV6_RECVPKTINFO, 1);
481361f55a5SMarcel Telka 				if (ret < 0) {
482361f55a5SMarcel Telka 					char errorstr[100];
483361f55a5SMarcel Telka 
484361f55a5SMarcel Telka 					__tli_sys_strerror(errorstr,
485361f55a5SMarcel Telka 					    sizeof (errorstr), t_errno, errno);
486361f55a5SMarcel Telka 					(void) syslog(LOG_ERR,
487361f55a5SMarcel Telka 					    "svc_tli_create: "
488361f55a5SMarcel Telka 					    "IPV6_RECVPKTINFO(2): %s",
489361f55a5SMarcel Telka 					    errorstr);
490361f55a5SMarcel Telka 					goto freedata;
491361f55a5SMarcel Telka 				}
492361f55a5SMarcel Telka 			} else if (strcmp(nconf->nc_netid, "udp") == 0) {
493361f55a5SMarcel Telka 				ret = __rpc_tli_set_options(fd, IPPROTO_IP,
494361f55a5SMarcel Telka 				    IP_RECVDSTADDR, 1);
495361f55a5SMarcel Telka 				if (ret < 0) {
496361f55a5SMarcel Telka 					char errorstr[100];
497361f55a5SMarcel Telka 
498361f55a5SMarcel Telka 					__tli_sys_strerror(errorstr,
499361f55a5SMarcel Telka 					    sizeof (errorstr), t_errno, errno);
500361f55a5SMarcel Telka 					(void) syslog(LOG_ERR,
501361f55a5SMarcel Telka 					    "svc_tli_create: "
502361f55a5SMarcel Telka 					    "IP_RECVDSTADDR(2): %s", errorstr);
503361f55a5SMarcel Telka 					goto freedata;
504361f55a5SMarcel Telka 				}
5057c478bd9Sstevel@tonic-gate 			}
5067c478bd9Sstevel@tonic-gate 		}
5077c478bd9Sstevel@tonic-gate 		break;
5087c478bd9Sstevel@tonic-gate 
5097c478bd9Sstevel@tonic-gate 	case T_IDLE:
5107c478bd9Sstevel@tonic-gate 		if (bindaddr) {
5117c478bd9Sstevel@tonic-gate 			/* Copy the entire stuff in tres */
5127c478bd9Sstevel@tonic-gate 			if (tres->addr.maxlen < bindaddr->addr.len) {
5137c478bd9Sstevel@tonic-gate 				(void) syslog(LOG_ERR,
5147c478bd9Sstevel@tonic-gate 				"svc_tli_create: illegal netbuf length");
5157c478bd9Sstevel@tonic-gate 				goto freedata;
5167c478bd9Sstevel@tonic-gate 			}
5177c478bd9Sstevel@tonic-gate 			tres->addr.len = bindaddr->addr.len;
5187c478bd9Sstevel@tonic-gate 			(void) memcpy(tres->addr.buf, bindaddr->addr.buf,
519361f55a5SMarcel Telka 			    (int)tres->addr.len);
5207c478bd9Sstevel@tonic-gate 		} else
5217c478bd9Sstevel@tonic-gate 			if (t_getname(fd, &(tres->addr), LOCALNAME) == -1)
5227c478bd9Sstevel@tonic-gate 				tres->addr.len = 0;
5237c478bd9Sstevel@tonic-gate 		break;
5247c478bd9Sstevel@tonic-gate 	case T_INREL:
5257c478bd9Sstevel@tonic-gate 		(void) t_rcvrel(fd);
5267c478bd9Sstevel@tonic-gate 		(void) t_sndrel(fd);
527361f55a5SMarcel Telka 		(void) syslog(LOG_ERR, "svc_tli_create: other side wants to "
528361f55a5SMarcel Telka 		    "release connection");
5297c478bd9Sstevel@tonic-gate 		goto freedata;
5307c478bd9Sstevel@tonic-gate 
5317c478bd9Sstevel@tonic-gate 	case T_INCON:
5327c478bd9Sstevel@tonic-gate 		/* Do nothing here. Assume this is handled in rendezvous */
5337c478bd9Sstevel@tonic-gate 		break;
5347c478bd9Sstevel@tonic-gate 	case T_DATAXFER:
5357c478bd9Sstevel@tonic-gate 		/*
5367c478bd9Sstevel@tonic-gate 		 * This takes care of the case where a fd
5377c478bd9Sstevel@tonic-gate 		 * is passed on which a connection has already
5387c478bd9Sstevel@tonic-gate 		 * been accepted.
5397c478bd9Sstevel@tonic-gate 		 */
5407c478bd9Sstevel@tonic-gate 		if (t_getname(fd, &(tres->addr), LOCALNAME) == -1)
5417c478bd9Sstevel@tonic-gate 			tres->addr.len = 0;
5427c478bd9Sstevel@tonic-gate 		break;
5437c478bd9Sstevel@tonic-gate 	default:
5447c478bd9Sstevel@tonic-gate 		(void) syslog(LOG_ERR,
5457c478bd9Sstevel@tonic-gate 		    "svc_tli_create: connection in a wierd state (%d)", state);
5467c478bd9Sstevel@tonic-gate 		goto freedata;
5477c478bd9Sstevel@tonic-gate 	}
5487c478bd9Sstevel@tonic-gate 
5497c478bd9Sstevel@tonic-gate 	/*
5507c478bd9Sstevel@tonic-gate 	 * call transport specific function.
5517c478bd9Sstevel@tonic-gate 	 */
5527c478bd9Sstevel@tonic-gate 	switch (tinfo.servtype) {
5537c478bd9Sstevel@tonic-gate 		case T_COTS_ORD:
5547c478bd9Sstevel@tonic-gate 		case T_COTS:
5557c478bd9Sstevel@tonic-gate 			if (state == T_DATAXFER)
5567c478bd9Sstevel@tonic-gate 				xprt = svc_fd_create_private(fd, sendsz,
557361f55a5SMarcel Telka 				    recvsz);
5587c478bd9Sstevel@tonic-gate 			else
5597c478bd9Sstevel@tonic-gate 				xprt = svc_vc_create_private(fd, sendsz,
560361f55a5SMarcel Telka 				    recvsz);
5617c478bd9Sstevel@tonic-gate 			if (!nconf || !xprt)
5627c478bd9Sstevel@tonic-gate 				break;
5637c478bd9Sstevel@tonic-gate 			if ((tinfo.servtype == T_COTS_ORD) &&
564361f55a5SMarcel Telka 			    (state != T_DATAXFER) &&
565361f55a5SMarcel Telka 			    (strcmp(nconf->nc_protofmly, "inet") == 0))
5667c478bd9Sstevel@tonic-gate 				(void) __svc_vc_setflag(xprt, TRUE);
5677c478bd9Sstevel@tonic-gate 			break;
5687c478bd9Sstevel@tonic-gate 		case T_CLTS:
5697c478bd9Sstevel@tonic-gate 			xprt = svc_dg_create_private(fd, sendsz, recvsz);
5707c478bd9Sstevel@tonic-gate 			break;
5717c478bd9Sstevel@tonic-gate 		default:
5727c478bd9Sstevel@tonic-gate 			(void) syslog(LOG_ERR,
5737c478bd9Sstevel@tonic-gate 			    "svc_tli_create: bad service type");
5747c478bd9Sstevel@tonic-gate 			goto freedata;
5757c478bd9Sstevel@tonic-gate 	}
57661961e0fSrobinson 	if (xprt == NULL)
5777c478bd9Sstevel@tonic-gate 		/*
5787c478bd9Sstevel@tonic-gate 		 * The error messages here are spitted out by the lower layers:
5797c478bd9Sstevel@tonic-gate 		 * svc_vc_create(), svc_fd_create() and svc_dg_create().
5807c478bd9Sstevel@tonic-gate 		 */
5817c478bd9Sstevel@tonic-gate 		goto freedata;
5827c478bd9Sstevel@tonic-gate 
5837c478bd9Sstevel@tonic-gate 	/* fill in the other xprt information */
5847c478bd9Sstevel@tonic-gate 
5857c478bd9Sstevel@tonic-gate 	/* Assign the local bind address */
5867c478bd9Sstevel@tonic-gate 	xprt->xp_ltaddr = tres->addr;
5877c478bd9Sstevel@tonic-gate 	/* Fill in type of service */
5887c478bd9Sstevel@tonic-gate 	xprt->xp_type = tinfo.servtype;
5897c478bd9Sstevel@tonic-gate 	tres->addr.buf = NULL;
5907c478bd9Sstevel@tonic-gate 	(void) t_free((char *)tres, T_BIND);
5917c478bd9Sstevel@tonic-gate 	tres = NULL;
5927c478bd9Sstevel@tonic-gate 
5937c478bd9Sstevel@tonic-gate 	xprt->xp_rtaddr.len = 0;
5947c478bd9Sstevel@tonic-gate 	xprt->xp_rtaddr.maxlen = __rpc_get_a_size(tinfo.addr);
5957c478bd9Sstevel@tonic-gate 
5967c478bd9Sstevel@tonic-gate 	/* Allocate space for the remote bind info */
59761961e0fSrobinson 	if ((xprt->xp_rtaddr.buf = malloc(xprt->xp_rtaddr.maxlen)) == NULL) {
5987c478bd9Sstevel@tonic-gate 		(void) syslog(LOG_ERR, "svc_tli_create: No memory!");
5997c478bd9Sstevel@tonic-gate 		goto freedata;
6007c478bd9Sstevel@tonic-gate 	}
6017c478bd9Sstevel@tonic-gate 
6027c478bd9Sstevel@tonic-gate 	if (nconf) {
6037c478bd9Sstevel@tonic-gate 		xprt->xp_netid = strdup(nconf->nc_netid);
6047c478bd9Sstevel@tonic-gate 		if (xprt->xp_netid == NULL) {
6057c478bd9Sstevel@tonic-gate 			if (xprt->xp_rtaddr.buf)
6067c478bd9Sstevel@tonic-gate 				free(xprt->xp_rtaddr.buf);
6077c478bd9Sstevel@tonic-gate 			syslog(LOG_ERR, "svc_tli_create: strdup failed!");
6087c478bd9Sstevel@tonic-gate 			goto freedata;
6097c478bd9Sstevel@tonic-gate 		}
6107c478bd9Sstevel@tonic-gate 		xprt->xp_tp = strdup(nconf->nc_device);
6117c478bd9Sstevel@tonic-gate 		if (xprt->xp_tp == NULL) {
6127c478bd9Sstevel@tonic-gate 			if (xprt->xp_rtaddr.buf)
6137c478bd9Sstevel@tonic-gate 				free(xprt->xp_rtaddr.buf);
6147c478bd9Sstevel@tonic-gate 			if (xprt->xp_netid)
6157c478bd9Sstevel@tonic-gate 				free(xprt->xp_netid);
6167c478bd9Sstevel@tonic-gate 			syslog(LOG_ERR, "svc_tli_create: strdup failed!");
6177c478bd9Sstevel@tonic-gate 			goto freedata;
6187c478bd9Sstevel@tonic-gate 		}
6197c478bd9Sstevel@tonic-gate 	}
6207c478bd9Sstevel@tonic-gate 
6217c478bd9Sstevel@tonic-gate /*
6227c478bd9Sstevel@tonic-gate  *	if (madefd && (tinfo.servtype == T_CLTS))
62361961e0fSrobinson  *		(void) ioctl(fd, I_POP, NULL);
6247c478bd9Sstevel@tonic-gate  */
6257c478bd9Sstevel@tonic-gate 	xprt_register(xprt);
6267c478bd9Sstevel@tonic-gate 	return (xprt);
6277c478bd9Sstevel@tonic-gate 
6287c478bd9Sstevel@tonic-gate freedata:
6297c478bd9Sstevel@tonic-gate 	if (madefd)
6307c478bd9Sstevel@tonic-gate 		(void) t_close(fd);
6317c478bd9Sstevel@tonic-gate 	if (tres)
6327c478bd9Sstevel@tonic-gate 		(void) t_free((char *)tres, T_BIND);
6337c478bd9Sstevel@tonic-gate 	if (xprt) {
6347c478bd9Sstevel@tonic-gate 		if (!madefd) /* so that svc_destroy doesnt close fd */
6357c478bd9Sstevel@tonic-gate 			xprt->xp_fd = RPC_ANYFD;
6367c478bd9Sstevel@tonic-gate 		SVC_DESTROY(xprt);
6377c478bd9Sstevel@tonic-gate 	}
63861961e0fSrobinson 	return (NULL);
6397c478bd9Sstevel@tonic-gate }
640