|
1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License, Version 1.0 only |
|
6 * (the "License"). You may not use this file except in compliance |
|
7 * with the License. |
|
8 * |
|
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
10 * or http://www.opensolaris.org/os/licensing. |
|
11 * See the License for the specific language governing permissions |
|
12 * and limitations under the License. |
|
13 * |
|
14 * When distributing Covered Code, include this CDDL HEADER in each |
|
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
16 * If applicable, add the following below this CDDL HEADER, with the |
|
17 * fields enclosed by brackets "[]" replaced with your own identifying |
|
18 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
19 * |
|
20 * CDDL HEADER END |
|
21 */ |
|
22 /* |
|
23 * Copyright 1986-2003 Sun Microsystems, Inc. All rights reserved. |
|
24 * Use is subject to license terms. |
|
25 */ |
|
26 |
|
27 /* Copyright (c) 1988 AT&T */ |
|
28 /* All Rights Reserved */ |
|
29 |
|
30 #pragma ident "%Z%%M% %I% %E% SMI" |
|
31 |
|
32 #if !defined(lint) && defined(SCCSIDS) |
|
33 static char sccsid[] = "@(#)svc_generic.c 1.21 89/02/28 Copyr 1988 Sun Micro"; |
|
34 #endif |
|
35 |
|
36 /* |
|
37 * svc_generic.c, Server side for RPC. |
|
38 * |
|
39 */ |
|
40 |
|
41 #include "mt.h" |
|
42 #include <sys/socket.h> |
|
43 #include <netinet/in.h> |
|
44 #include <netinet/tcp.h> |
|
45 #include <netinet/udp.h> |
|
46 #include <inttypes.h> |
|
47 #include "rpc_mt.h" |
|
48 #include <stdio.h> |
|
49 #include <rpc/rpc.h> |
|
50 #include <sys/types.h> |
|
51 #include <rpc/trace.h> |
|
52 #include <errno.h> |
|
53 #include <syslog.h> |
|
54 #include <rpc/nettype.h> |
|
55 #include <malloc.h> |
|
56 #include <string.h> |
|
57 #include <stropts.h> |
|
58 |
|
59 |
|
60 extern int __svc_vc_setflag(); |
|
61 |
|
62 extern SVCXPRT *svc_dg_create_private(); |
|
63 extern SVCXPRT *svc_vc_create_private(); |
|
64 extern SVCXPRT *svc_fd_create_private(); |
|
65 |
|
66 extern bool_t __svc_add_to_xlist(); |
|
67 extern void __svc_free_xlist(); |
|
68 |
|
69 /* |
|
70 * The highest level interface for server creation. |
|
71 * It tries for all the nettokens in that particular class of token |
|
72 * and returns the number of handles it can create and/or find. |
|
73 * |
|
74 * It creates a link list of all the handles it could create. |
|
75 * If svc_create() is called multiple times, it uses the handle |
|
76 * created earlier instead of creating a new handle every time. |
|
77 */ |
|
78 |
|
79 /* VARIABLES PROTECTED BY xprtlist_lock: xprtlist */ |
|
80 |
|
81 SVCXPRT_LIST *_svc_xprtlist = NULL; |
|
82 extern mutex_t xprtlist_lock; |
|
83 |
|
84 void |
|
85 __svc_free_xprtlist() |
|
86 { |
|
87 __svc_free_xlist(&_svc_xprtlist, &xprtlist_lock); |
|
88 } |
|
89 |
|
90 int |
|
91 svc_create(dispatch, prognum, versnum, nettype) |
|
92 void (*dispatch)(); /* Dispatch function */ |
|
93 rpcprog_t prognum; /* Program number */ |
|
94 rpcvers_t versnum; /* Version number */ |
|
95 const char *nettype; /* Networktype token */ |
|
96 { |
|
97 SVCXPRT_LIST *l; |
|
98 int num = 0; |
|
99 SVCXPRT *xprt; |
|
100 struct netconfig *nconf; |
|
101 void *handle; |
|
102 bool_t try_others; |
|
103 int __rpc_try_doors(); |
|
104 |
|
105 trace3(TR_svc_create, 0, prognum, versnum); |
|
106 /* |
|
107 * Check if service should register over doors transport. |
|
108 */ |
|
109 if (__rpc_try_doors(nettype, &try_others)) { |
|
110 if (svc_door_create(dispatch, prognum, versnum, 0) == NULL) |
|
111 (void) syslog(LOG_ERR, |
|
112 "svc_create: could not register over doors"); |
|
113 else |
|
114 num++; |
|
115 } |
|
116 if (!try_others) |
|
117 return (num); |
|
118 if ((handle = __rpc_setconf((char *)nettype)) == NULL) { |
|
119 (void) syslog(LOG_ERR, "svc_create: unknown protocol"); |
|
120 trace3(TR_svc_create, 1, prognum, versnum); |
|
121 return (0); |
|
122 } |
|
123 while (nconf = __rpc_getconf(handle)) { |
|
124 mutex_lock(&xprtlist_lock); |
|
125 for (l = _svc_xprtlist; l; l = l->next) { |
|
126 if (strcmp(l->xprt->xp_netid, nconf->nc_netid) == 0) { |
|
127 /* Found an old one, use it */ |
|
128 (void) rpcb_unset(prognum, versnum, nconf); |
|
129 if (svc_reg(l->xprt, prognum, versnum, |
|
130 dispatch, nconf) == FALSE) |
|
131 (void) syslog(LOG_ERR, |
|
132 "svc_create: could not register prog %d vers %d on %s", |
|
133 prognum, versnum, nconf->nc_netid); |
|
134 else |
|
135 num++; |
|
136 break; |
|
137 } |
|
138 } |
|
139 mutex_unlock(&xprtlist_lock); |
|
140 if (l == NULL) { |
|
141 /* It was not found. Now create a new one */ |
|
142 xprt = svc_tp_create(dispatch, prognum, versnum, nconf); |
|
143 if (xprt) { |
|
144 if (!__svc_add_to_xlist(&_svc_xprtlist, xprt, |
|
145 &xprtlist_lock)) { |
|
146 (void) syslog(LOG_ERR, |
|
147 "svc_create: no memory"); |
|
148 trace3(TR_svc_create, 1, prognum, |
|
149 versnum); |
|
150 return (0); |
|
151 } |
|
152 num++; |
|
153 } |
|
154 } |
|
155 } |
|
156 __rpc_endconf(handle); |
|
157 /* |
|
158 * In case of num == 0; the error messages are generated by the |
|
159 * underlying layers; and hence not needed here. |
|
160 */ |
|
161 trace3(TR_svc_create, 1, prognum, versnum); |
|
162 return (num); |
|
163 } |
|
164 |
|
165 /* |
|
166 * The high level interface to svc_tli_create(). |
|
167 * It tries to create a server for "nconf" and registers the service |
|
168 * with the rpcbind. It calls svc_tli_create(); |
|
169 */ |
|
170 SVCXPRT * |
|
171 svc_tp_create(dispatch, prognum, versnum, nconf) |
|
172 void (*dispatch)(); /* Dispatch function */ |
|
173 rpcprog_t prognum; /* Program number */ |
|
174 rpcvers_t versnum; /* Version number */ |
|
175 const struct netconfig *nconf; /* Netconfig structure for the network */ |
|
176 { |
|
177 SVCXPRT *xprt; |
|
178 |
|
179 trace3(TR_svc_tp_create, 0, prognum, versnum); |
|
180 if (nconf == (struct netconfig *)NULL) { |
|
181 (void) syslog(LOG_ERR, |
|
182 "svc_tp_create: invalid netconfig structure for prog %d vers %d", |
|
183 prognum, versnum); |
|
184 trace3(TR_svc_tp_create, 1, prognum, versnum); |
|
185 return ((SVCXPRT *)NULL); |
|
186 } |
|
187 xprt = svc_tli_create(RPC_ANYFD, nconf, (struct t_bind *)NULL, 0, 0); |
|
188 if (xprt == (SVCXPRT *)NULL) { |
|
189 trace3(TR_svc_tp_create, 1, prognum, versnum); |
|
190 return ((SVCXPRT *)NULL); |
|
191 } |
|
192 (void) rpcb_unset(prognum, versnum, (struct netconfig *)nconf); |
|
193 if (svc_reg(xprt, prognum, versnum, dispatch, nconf) == FALSE) { |
|
194 (void) syslog(LOG_ERR, |
|
195 "svc_tp_create: Could not register prog %d vers %d on %s", |
|
196 prognum, versnum, nconf->nc_netid); |
|
197 SVC_DESTROY(xprt); |
|
198 trace3(TR_svc_tp_create, 1, prognum, versnum); |
|
199 return ((SVCXPRT *)NULL); |
|
200 } |
|
201 trace3(TR_svc_tp_create, 1, prognum, versnum); |
|
202 return (xprt); |
|
203 } |
|
204 |
|
205 /* |
|
206 * If fd is RPC_ANYFD, then it opens a fd for the given transport |
|
207 * provider (nconf cannot be NULL then). If the t_state is T_UNBND and |
|
208 * bindaddr is NON-NULL, it performs a t_bind using the bindaddr. For |
|
209 * NULL bindadr and Connection oriented transports, the value of qlen |
|
210 * is set arbitrarily. |
|
211 * |
|
212 * If sendsz or recvsz are zero, their default values are chosen. |
|
213 */ |
|
214 SVCXPRT * |
|
215 svc_tli_create(fd, nconf, bindaddr, sendsz, recvsz) |
|
216 int fd; /* Connection end point */ |
|
217 const struct netconfig *nconf; /* Netconfig struct for nettoken */ |
|
218 const struct t_bind *bindaddr; /* Local bind address */ |
|
219 uint_t sendsz; /* Max sendsize */ |
|
220 uint_t recvsz; /* Max recvsize */ |
|
221 { |
|
222 SVCXPRT *xprt = NULL; /* service handle */ |
|
223 struct t_info tinfo; /* transport info */ |
|
224 struct t_bind *tres = NULL; /* bind info */ |
|
225 bool_t madefd = FALSE; /* whether fd opened here */ |
|
226 int state; /* state of the transport provider */ |
|
227 |
|
228 trace4(TR_svc_tli_create, 0, fd, sendsz, recvsz); |
|
229 if (fd == RPC_ANYFD) { |
|
230 if (nconf == (struct netconfig *)NULL) { |
|
231 (void) syslog(LOG_ERR, |
|
232 "svc_tli_create: invalid netconfig"); |
|
233 trace2(TR_svc_tli_create, 1, fd); |
|
234 return ((SVCXPRT *)NULL); |
|
235 } |
|
236 fd = t_open(nconf->nc_device, O_RDWR, &tinfo); |
|
237 if (fd == -1) { |
|
238 char errorstr[100]; |
|
239 |
|
240 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
241 t_errno, errno); |
|
242 (void) syslog(LOG_ERR, |
|
243 "svc_tli_create: could not open connection for %s: %s", |
|
244 nconf->nc_netid, errorstr); |
|
245 trace2(TR_svc_tli_create, 1, fd); |
|
246 return ((SVCXPRT *)NULL); |
|
247 } |
|
248 madefd = TRUE; |
|
249 state = T_UNBND; |
|
250 } else { |
|
251 /* |
|
252 * It is an open descriptor. Sync it & get the transport info. |
|
253 */ |
|
254 if ((state = t_sync(fd)) == -1) { |
|
255 char errorstr[100]; |
|
256 |
|
257 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
258 t_errno, errno); |
|
259 (void) syslog(LOG_ERR, |
|
260 "svc_tli_create: could not do t_sync: %s", |
|
261 errorstr); |
|
262 trace2(TR_svc_tli_create, 1, fd); |
|
263 return ((SVCXPRT *)NULL); |
|
264 } |
|
265 if (t_getinfo(fd, &tinfo) == -1) { |
|
266 char errorstr[100]; |
|
267 |
|
268 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
269 t_errno, errno); |
|
270 (void) syslog(LOG_ERR, |
|
271 "svc_tli_create: could not get transport information: %s", |
|
272 errorstr); |
|
273 trace2(TR_svc_tli_create, 1, fd); |
|
274 return ((SVCXPRT *)NULL); |
|
275 } |
|
276 /* Enable options of returning the ip's for udp */ |
|
277 if (nconf) { |
|
278 int ret = 0; |
|
279 if (strcmp(nconf->nc_netid, "udp6") == 0) { |
|
280 ret = __rpc_tli_set_options(fd, IPPROTO_IPV6, |
|
281 IPV6_RECVPKTINFO, 1); |
|
282 if (ret < 0) { |
|
283 char errorstr[100]; |
|
284 |
|
285 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
286 t_errno, errno); |
|
287 (void) syslog(LOG_ERR, |
|
288 "svc_tli_create: IPV6_RECVPKTINFO(1): %s", |
|
289 errorstr); |
|
290 trace2(TR_svc_tli_create, 1, fd); |
|
291 return ((SVCXPRT *)NULL); |
|
292 } |
|
293 } else if (strcmp(nconf->nc_netid, "udp") == 0) { |
|
294 ret = __rpc_tli_set_options(fd, IPPROTO_IP, |
|
295 IP_RECVDSTADDR, 1); |
|
296 if (ret < 0) { |
|
297 char errorstr[100]; |
|
298 |
|
299 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
300 t_errno, errno); |
|
301 (void) syslog(LOG_ERR, |
|
302 "svc_tli_create: IP_RECVDSTADDR(1): %s", |
|
303 errorstr); |
|
304 trace2(TR_svc_tli_create, 1, fd); |
|
305 return ((SVCXPRT *)NULL); |
|
306 } |
|
307 } |
|
308 } |
|
309 } |
|
310 |
|
311 /* |
|
312 * If the fd is unbound, try to bind it. |
|
313 * In any case, try to get its bound info in tres |
|
314 */ |
|
315 /* LINTED pointer alignment */ |
|
316 tres = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR); |
|
317 if (tres == NULL) { |
|
318 (void) syslog(LOG_ERR, "svc_tli_create: No memory!"); |
|
319 goto freedata; |
|
320 } |
|
321 |
|
322 switch (state) { |
|
323 bool_t tcp, exclbind; |
|
324 case T_UNBND: |
|
325 /* |
|
326 * {TCP,UDP}_EXCLBIND has the following properties |
|
327 * - an fd bound to port P via IPv4 will prevent an IPv6 |
|
328 * bind to port P (and vice versa) |
|
329 * - an fd bound to a wildcard IP address for port P will |
|
330 * prevent a more specific IP address bind to port P |
|
331 * (see {tcp,udp}.c for details) |
|
332 * |
|
333 * We use the latter property to prevent hijacking of RPC |
|
334 * services that reside at non-privileged ports. |
|
335 */ |
|
336 tcp = nconf ? (strcmp(nconf->nc_proto, NC_TCP) == 0) : 0; |
|
337 if (nconf && |
|
338 (tcp || (strcmp(nconf->nc_proto, NC_UDP) == 0)) && |
|
339 rpc_control(__RPC_SVC_EXCLBIND_GET, &exclbind)) { |
|
340 if (exclbind) { |
|
341 if (__rpc_tli_set_options(fd, |
|
342 tcp ? IPPROTO_TCP : IPPROTO_UDP, |
|
343 tcp ? TCP_EXCLBIND : UDP_EXCLBIND, |
|
344 1) < 0) { |
|
345 syslog(LOG_ERR, |
|
346 "svc_tli_create: can't set EXCLBIND [netid='%s']", |
|
347 nconf->nc_netid); |
|
348 goto freedata; |
|
349 } |
|
350 } |
|
351 } |
|
352 if (bindaddr) { |
|
353 if (t_bind(fd, (struct t_bind *)bindaddr, |
|
354 tres) == -1) { |
|
355 char errorstr[100]; |
|
356 |
|
357 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
358 t_errno, errno); |
|
359 (void) syslog(LOG_ERR, |
|
360 "svc_tli_create: could not bind: %s", |
|
361 errorstr); |
|
362 goto freedata; |
|
363 } |
|
364 /* |
|
365 * Should compare the addresses only if addr.len |
|
366 * was non-zero |
|
367 */ |
|
368 if (bindaddr->addr.len && |
|
369 (memcmp(bindaddr->addr.buf, tres->addr.buf, |
|
370 (int)tres->addr.len) != 0)) { |
|
371 (void) syslog(LOG_ERR, |
|
372 "svc_tli_create: could not bind to requested address: %s", |
|
373 "address mismatch"); |
|
374 goto freedata; |
|
375 } |
|
376 } else { |
|
377 tres->qlen = 64; /* Chosen Arbitrarily */ |
|
378 tres->addr.len = 0; |
|
379 if (t_bind(fd, tres, tres) == -1) { |
|
380 char errorstr[100]; |
|
381 |
|
382 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
383 t_errno, errno); |
|
384 (void) syslog(LOG_ERR, |
|
385 "svc_tli_create: could not bind: %s", |
|
386 errorstr); |
|
387 goto freedata; |
|
388 } |
|
389 } |
|
390 |
|
391 /* Enable options of returning the ip's for udp */ |
|
392 if (nconf) { |
|
393 int ret = 0; |
|
394 if (strcmp(nconf->nc_netid, "udp6") == 0) { |
|
395 ret = __rpc_tli_set_options(fd, IPPROTO_IPV6, |
|
396 IPV6_RECVPKTINFO, 1); |
|
397 if (ret < 0) { |
|
398 char errorstr[100]; |
|
399 |
|
400 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
401 t_errno, errno); |
|
402 (void) syslog(LOG_ERR, |
|
403 "svc_tli_create: IPV6_RECVPKTINFO(2): %s", |
|
404 errorstr); |
|
405 goto freedata; |
|
406 } |
|
407 } else if (strcmp(nconf->nc_netid, "udp") == 0) { |
|
408 ret = __rpc_tli_set_options(fd, IPPROTO_IP, |
|
409 IP_RECVDSTADDR, 1); |
|
410 if (ret < 0) { |
|
411 char errorstr[100]; |
|
412 |
|
413 __tli_sys_strerror(errorstr, sizeof (errorstr), |
|
414 t_errno, errno); |
|
415 (void) syslog(LOG_ERR, |
|
416 "svc_tli_create: IP_RECVDSTADDR(2): %s", |
|
417 errorstr); |
|
418 goto freedata; |
|
419 } |
|
420 } |
|
421 } |
|
422 break; |
|
423 |
|
424 case T_IDLE: |
|
425 if (bindaddr) { |
|
426 /* Copy the entire stuff in tres */ |
|
427 if (tres->addr.maxlen < bindaddr->addr.len) { |
|
428 (void) syslog(LOG_ERR, |
|
429 "svc_tli_create: illegal netbuf length"); |
|
430 goto freedata; |
|
431 } |
|
432 tres->addr.len = bindaddr->addr.len; |
|
433 (void) memcpy(tres->addr.buf, bindaddr->addr.buf, |
|
434 (int)tres->addr.len); |
|
435 } else |
|
436 if (t_getname(fd, &(tres->addr), LOCALNAME) == -1) |
|
437 tres->addr.len = 0; |
|
438 break; |
|
439 case T_INREL: |
|
440 (void) t_rcvrel(fd); |
|
441 (void) t_sndrel(fd); |
|
442 (void) syslog(LOG_ERR, |
|
443 "svc_tli_create: other side wants to\ |
|
444 release connection"); |
|
445 goto freedata; |
|
446 |
|
447 case T_INCON: |
|
448 /* Do nothing here. Assume this is handled in rendezvous */ |
|
449 break; |
|
450 case T_DATAXFER: |
|
451 /* |
|
452 * This takes care of the case where a fd |
|
453 * is passed on which a connection has already |
|
454 * been accepted. |
|
455 */ |
|
456 if (t_getname(fd, &(tres->addr), LOCALNAME) == -1) |
|
457 tres->addr.len = 0; |
|
458 break; |
|
459 default: |
|
460 (void) syslog(LOG_ERR, |
|
461 "svc_tli_create: connection in a wierd state (%d)", state); |
|
462 goto freedata; |
|
463 } |
|
464 |
|
465 /* |
|
466 * call transport specific function. |
|
467 */ |
|
468 switch (tinfo.servtype) { |
|
469 case T_COTS_ORD: |
|
470 case T_COTS: |
|
471 if (state == T_DATAXFER) |
|
472 xprt = svc_fd_create_private(fd, sendsz, |
|
473 recvsz); |
|
474 else |
|
475 xprt = svc_vc_create_private(fd, sendsz, |
|
476 recvsz); |
|
477 if (!nconf || !xprt) |
|
478 break; |
|
479 if ((tinfo.servtype == T_COTS_ORD) && |
|
480 (state != T_DATAXFER) && |
|
481 (strcmp(nconf->nc_protofmly, "inet") == 0)) |
|
482 (void) __svc_vc_setflag(xprt, TRUE); |
|
483 break; |
|
484 case T_CLTS: |
|
485 xprt = svc_dg_create_private(fd, sendsz, recvsz); |
|
486 break; |
|
487 default: |
|
488 (void) syslog(LOG_ERR, |
|
489 "svc_tli_create: bad service type"); |
|
490 goto freedata; |
|
491 } |
|
492 if (xprt == (SVCXPRT *)NULL) |
|
493 /* |
|
494 * The error messages here are spitted out by the lower layers: |
|
495 * svc_vc_create(), svc_fd_create() and svc_dg_create(). |
|
496 */ |
|
497 goto freedata; |
|
498 |
|
499 /* fill in the other xprt information */ |
|
500 |
|
501 /* Assign the local bind address */ |
|
502 xprt->xp_ltaddr = tres->addr; |
|
503 /* Fill in type of service */ |
|
504 xprt->xp_type = tinfo.servtype; |
|
505 tres->addr.buf = NULL; |
|
506 (void) t_free((char *)tres, T_BIND); |
|
507 tres = NULL; |
|
508 |
|
509 xprt->xp_rtaddr.len = 0; |
|
510 xprt->xp_rtaddr.maxlen = __rpc_get_a_size(tinfo.addr); |
|
511 |
|
512 /* Allocate space for the remote bind info */ |
|
513 if ((xprt->xp_rtaddr.buf = mem_alloc(xprt->xp_rtaddr.maxlen)) == NULL) { |
|
514 (void) syslog(LOG_ERR, "svc_tli_create: No memory!"); |
|
515 goto freedata; |
|
516 } |
|
517 |
|
518 if (nconf) { |
|
519 xprt->xp_netid = strdup(nconf->nc_netid); |
|
520 if (xprt->xp_netid == NULL) { |
|
521 if (xprt->xp_rtaddr.buf) |
|
522 free(xprt->xp_rtaddr.buf); |
|
523 syslog(LOG_ERR, "svc_tli_create: strdup failed!"); |
|
524 goto freedata; |
|
525 } |
|
526 xprt->xp_tp = strdup(nconf->nc_device); |
|
527 if (xprt->xp_tp == NULL) { |
|
528 if (xprt->xp_rtaddr.buf) |
|
529 free(xprt->xp_rtaddr.buf); |
|
530 if (xprt->xp_netid) |
|
531 free(xprt->xp_netid); |
|
532 syslog(LOG_ERR, "svc_tli_create: strdup failed!"); |
|
533 goto freedata; |
|
534 } |
|
535 } |
|
536 |
|
537 /* |
|
538 * if (madefd && (tinfo.servtype == T_CLTS)) |
|
539 * (void) ioctl(fd, I_POP, (char *)NULL); |
|
540 */ |
|
541 xprt_register(xprt); |
|
542 trace2(TR_svc_tli_create, 1, fd); |
|
543 return (xprt); |
|
544 |
|
545 freedata: |
|
546 if (madefd) |
|
547 (void) t_close(fd); |
|
548 if (tres) |
|
549 (void) t_free((char *)tres, T_BIND); |
|
550 if (xprt) { |
|
551 if (!madefd) /* so that svc_destroy doesnt close fd */ |
|
552 xprt->xp_fd = RPC_ANYFD; |
|
553 SVC_DESTROY(xprt); |
|
554 } |
|
555 trace2(TR_svc_tli_create, 1, fd); |
|
556 return ((SVCXPRT *)NULL); |
|
557 } |