/src/modules/mcmd.c

https://code.google.com/ · C · 671 lines · 375 code · 91 blank · 205 comment · 68 complexity · 0574e7ac19a3033091aebcb4ffc40c54 MD5 · raw file

  1. /*****************************************************************************\
  2. * $Id$
  3. *****************************************************************************
  4. * Copyright (C) 2001-2006 The Regents of the University of California.
  5. * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
  6. * Written by Jim Garlick <garlick@llnl.gov>.
  7. * UCRL-CODE-2003-005.
  8. *
  9. * This file is part of Pdsh, a parallel remote shell program.
  10. * For details, see <http://www.llnl.gov/linux/pdsh/>.
  11. *
  12. * Pdsh is free software; you can redistribute it and/or modify it under
  13. * the terms of the GNU General Public License as published by the Free
  14. * Software Foundation; either version 2 of the License, or (at your option)
  15. * any later version.
  16. *
  17. * Pdsh is distributed in the hope that it will be useful, but WITHOUT ANY
  18. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  19. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  20. * details.
  21. *
  22. * You should have received a copy of the GNU General Public License along
  23. * with Pdsh; if not, write to the Free Software Foundation, Inc.,
  24. * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  25. \*****************************************************************************/
  26. /*
  27. * Started with BSD mcmd.c which is:
  28. *
  29. * Copyright (c) 1983, 1993, 1994, 2003
  30. * The Regents of the University of California. All rights reserved.
  31. *
  32. * Redistribution and use in source and binary forms, with or without
  33. * modification, are permitted provided that the following conditions
  34. * are met:
  35. * 1. Redistributions of source code must retain the above copyright
  36. * notice, this list of conditions and the following disclaimer.
  37. *
  38. * 2. Redistributions in binary form must reproduce the above copyright
  39. * notice, this list of conditions and the following disclaimer in the
  40. * documentation and/or other materials provided with the distribution.
  41. *
  42. * 3. All advertising materials mentioning features or use of this software
  43. * must display the following acknowledgement:
  44. * This product includes software developed by the University of
  45. * California, Berkeley and its contributors.
  46. *
  47. * 4. Neither the name of the University nor the names of its contributors
  48. * may be used to endorse or promote products derived from this software
  49. * without specific prior written permission.
  50. *
  51. * 5. This is free software; you can redistribute it and/or modify it
  52. * under the terms of the GNU General Public License as published
  53. * by the Free Software Foundation; either version 2 of the
  54. * License, or (at your option) any later version.
  55. *
  56. * 6. This is distributed in the hope that it will be useful, but
  57. * WITHOUT ANY WARRANTY; without even the implied warranty of
  58. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  59. * GNU General Public License for more details.
  60. *
  61. * 7. You should have received a copy of the GNU General Public License;
  62. * if not, write to the Free Software Foundation, Inc., 59 Temple
  63. * Place, Suite 330, Boston, MA 02111-1307 USA.
  64. *
  65. * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  66. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  67. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  68. * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  69. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  70. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  71. * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  72. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  73. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  74. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  75. * SUCH DAMAGE.
  76. */
  77. #if defined(LIBC_SCCS) && !defined(lint)
  78. static char sccsid[] = "@(#)mcmd.c Based from: 8.3 (Berkeley) 3/26/94";
  79. #endif /* LIBC_SCCS and not lint */
  80. #if HAVE_CONFIG_H
  81. #include "config.h"
  82. #endif
  83. #include <sys/param.h>
  84. #include <sys/types.h>
  85. #include <sys/time.h>
  86. #include <sys/socket.h>
  87. #include <sys/stat.h>
  88. #ifdef HAVE_PTHREAD
  89. #include <pthread.h>
  90. #endif
  91. #include <netinet/in.h>
  92. #include <arpa/inet.h>
  93. #include <signal.h>
  94. #if HAVE_FCNTL_H
  95. #include <fcntl.h>
  96. #endif
  97. #include <netdb.h>
  98. #if HAVE_UNISTD_H
  99. #include <unistd.h>
  100. #endif
  101. #include <pwd.h>
  102. #include <errno.h>
  103. #include <ctype.h>
  104. #include <string.h>
  105. #include <stdio.h>
  106. #include <string.h>
  107. #include <stdlib.h>
  108. #include <munge.h>
  109. #include "src/common/macros.h" /* LINEBUFSIZE && IP_ADDR_LEN */
  110. #include "src/common/err.h"
  111. #include "src/common/fd.h"
  112. #include "src/common/xpoll.h"
  113. #include "src/pdsh/mod.h"
  114. #define MRSH_PROTOCOL_VERSION "2.1"
  115. #define MRSH_PORT 21212
  116. #ifndef MAXHOSTNAMELEN
  117. #define MAXHOSTNAMELEN 64
  118. #endif
  119. #define MRSH_LOCALHOST_KEY "LHOST"
  120. #define MRSH_LOCALHOST_KEYLEN 5
  121. #ifdef HAVE_PTHREAD
  122. #define SET_PTHREAD() pthread_sigmask(SIG_BLOCK, &blockme, &oldset)
  123. #define RESTORE_PTHREAD() pthread_sigmask(SIG_SETMASK, &oldset, NULL)
  124. #define EXIT_PTHREAD() RESTORE_PTHREAD(); \
  125. return -1
  126. #else
  127. #define SET_PTHREAD()
  128. #define RESTORE_PTHREAD()
  129. #define EXIT_PTHREAD() return -1
  130. #endif
  131. #if STATIC_MODULES
  132. # define pdsh_module_info mcmd_module_info
  133. # define pdsh_module_priority mcmd_module_priority
  134. #endif
  135. int pdsh_module_priority = DEFAULT_MODULE_PRIORITY;
  136. static int mcmd_init(opt_t *);
  137. static int mcmd_signal(int, void *, int);
  138. static int mcmd(char *, char *, char *, char *, char *, int, int *, void **);
  139. /* random num for all jobs in this group */
  140. static unsigned int randy = -1;
  141. /*
  142. * Export pdsh module operations structure
  143. */
  144. struct pdsh_module_operations mcmd_module_ops = {
  145. (ModInitF) NULL,
  146. (ModExitF) NULL,
  147. (ModReadWcollF) NULL,
  148. (ModPostOpF) NULL,
  149. };
  150. /*
  151. * Export rcmd module operations
  152. */
  153. struct pdsh_rcmd_operations mcmd_rcmd_ops = {
  154. (RcmdInitF) mcmd_init,
  155. (RcmdSigF) mcmd_signal,
  156. (RcmdF) mcmd,
  157. };
  158. /*
  159. * Export module options
  160. */
  161. struct pdsh_module_option mcmd_module_options[] =
  162. {
  163. PDSH_OPT_TABLE_END
  164. };
  165. /*
  166. * Mcmd module info
  167. */
  168. struct pdsh_module pdsh_module_info = {
  169. "rcmd",
  170. "mrsh",
  171. "Al Chu <chu11@llnl.gov>",
  172. "mrsh rcmd connect method",
  173. DSH | PCP,
  174. &mcmd_module_ops,
  175. &mcmd_rcmd_ops,
  176. &mcmd_module_options[0],
  177. };
  178. static int
  179. mcmd_init(opt_t * opt)
  180. {
  181. int rv, rand_fd;
  182. /*
  183. * Drop privileges if running setuid root
  184. */
  185. if ((geteuid() == 0) && (getuid() != 0))
  186. setuid (getuid ());
  187. /*
  188. * Generate a random number to send in our package to the
  189. * server. We will see it again and compare it when the
  190. * server sets up the stderr socket and sends it to us.
  191. * We need to loop for the tiny possibility we read 0 :P
  192. */
  193. if ((rand_fd = open ("/dev/urandom", O_RDONLY | O_NONBLOCK)) < 0 ) {
  194. err("%p: mcmd: Open of /dev/urandom failed\n");
  195. return -1;
  196. }
  197. do {
  198. if ((rv = read (rand_fd, &randy, sizeof(uint32_t))) < 0) {
  199. close(rand_fd);
  200. err("%p: mcmd: Read of /dev/urandom failed\n");
  201. return -1;
  202. }
  203. if (rv < (int) (sizeof(uint32_t))) {
  204. close(rand_fd);
  205. err("%p: mcmd: Read returned too few bytes\n");
  206. return -1;
  207. }
  208. } while (randy == 0);
  209. close(rand_fd);
  210. return 0;
  211. }
  212. static int
  213. mcmd_signal(int fd, void *arg, int signum)
  214. {
  215. char c;
  216. if (fd >= 0) {
  217. /* set non-blocking mode for write - just take our best shot */
  218. if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
  219. err("%p: fcntl: %m\n");
  220. c = (char) signum;
  221. write(fd, &c, 1);
  222. }
  223. return 0;
  224. }
  225. /*
  226. * If `host' corresponds to a standard "locahost" target, then
  227. * encode mrsh localhost key and hostname into str, returning
  228. * number of bytes written into str. Otherwise do nothing and return 0.
  229. */
  230. static int
  231. encode_localhost_string (const char *host, char *str, int maxlen)
  232. {
  233. char hostname[MAXHOSTNAMELEN+1];
  234. if (strcmp (host, "localhost") && strcmp (host, "127.0.0.1"))
  235. return (0);
  236. if (maxlen < MRSH_LOCALHOST_KEYLEN)
  237. return (-1);
  238. memset (hostname, '\0', MAXHOSTNAMELEN + 1);
  239. if (gethostname (hostname, MAXHOSTNAMELEN) < 0)
  240. errx ("mcmd: gethostname: %m\n");
  241. strncpy (str, MRSH_LOCALHOST_KEY, MRSH_LOCALHOST_KEYLEN);
  242. strncat (str, hostname, maxlen - MRSH_LOCALHOST_KEYLEN - 1);
  243. return (strlen (str));
  244. }
  245. /*
  246. * Derived from the mcmd() libc call, with modified interface.
  247. * This version is MT-safe. Errors are displayed in pdsh-compat format.
  248. * Connection can time out.
  249. * ahost (IN) target hostname
  250. * addr (IN) 4 byte internet address
  251. * locuser (IN) local username
  252. * remuser (IN) remote username
  253. * cmd (IN) remote command to execute under shell
  254. * rank (IN) not used
  255. * fd2p (IN) if non NULL, return stderr file descriptor here
  256. * int (RETURN) -1 on error, socket for I/O on success
  257. *
  258. * Originally by Mike Haskell for mrsh, modified slightly to work with pdsh by:
  259. * - making mcmd always thread safe
  260. * - using "err" function output errors.
  261. * - passing in address as addr intead of calling gethostbyname
  262. * - using default mshell port instead of calling getservbyname
  263. *
  264. */
  265. static int
  266. mcmd(char *ahost, char *addr, char *locuser, char *remuser, char *cmd,
  267. int rank, int *fd2p, void **argp)
  268. {
  269. struct sockaddr m_socket;
  270. struct sockaddr_in *getp;
  271. struct sockaddr_in sin, from;
  272. struct sockaddr_storage ss;
  273. struct in_addr m_in;
  274. unsigned int rand, randl;
  275. unsigned char *hptr;
  276. int s, s2, rv, mcount, lport;
  277. char c;
  278. char num[6] = {0};
  279. char *mptr;
  280. char *mbuf;
  281. char *tmbuf;
  282. char *m;
  283. char *mpvers;
  284. char num_seq[12] = {0};
  285. socklen_t len;
  286. sigset_t blockme;
  287. sigset_t oldset;
  288. char haddrdot[MAXHOSTNAMELEN + MRSH_LOCALHOST_KEYLEN + 1] = {0};
  289. munge_ctx_t ctx;
  290. struct xpollfd xpfds[2];
  291. memset (xpfds, 0, sizeof (xpfds));
  292. memset (&sin, 0, sizeof (sin));
  293. sigemptyset(&blockme);
  294. sigaddset(&blockme, SIGURG);
  295. sigaddset(&blockme, SIGPIPE);
  296. SET_PTHREAD();
  297. /* Convert randy to decimal string, 0 if we dont' want stderr */
  298. if (fd2p != NULL)
  299. snprintf(num_seq, sizeof(num_seq),"%d",randy);
  300. else
  301. snprintf(num_seq, sizeof(num_seq),"%d",0);
  302. /*
  303. * Start setup of the stdin/stdout socket...
  304. */
  305. lport = 0;
  306. len = sizeof(struct sockaddr_in);
  307. if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  308. err("%p: %S: mcmd: socket call stdout failed: %m\n", ahost);
  309. EXIT_PTHREAD();
  310. }
  311. memset (&ss, '\0', sizeof(ss));
  312. ss.ss_family = AF_INET;
  313. if (bind(s, (struct sockaddr *)&ss, len) < 0) {
  314. err("%p: %S: mcmd: bind failed: %m\n", ahost);
  315. goto bad;
  316. }
  317. sin.sin_family = AF_INET;
  318. memcpy(&sin.sin_addr.s_addr, addr, IP_ADDR_LEN);
  319. sin.sin_port = htons(MRSH_PORT);
  320. if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
  321. err("%p: %S: mcmd: connect failed: %m\n", ahost);
  322. goto bad;
  323. }
  324. lport = 0;
  325. s2 = -1;
  326. if (fd2p != NULL) {
  327. /*
  328. * Start the socket setup for the stderr.
  329. */
  330. struct sockaddr_in sin2;
  331. if ((s2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  332. err("%p: %S: mcmd: socket call for stderr failed: %m\n", ahost);
  333. goto bad;
  334. }
  335. memset (&sin2, 0, sizeof(sin2));
  336. sin2.sin_family = AF_INET;
  337. sin2.sin_addr.s_addr = htonl(INADDR_ANY);
  338. sin2.sin_port = 0;
  339. if (bind(s2,(struct sockaddr *)&sin2, sizeof(sin2)) < 0) {
  340. err("%p: %S: mcmd: bind failed: %m\n", ahost);
  341. close(s2);
  342. goto bad;
  343. }
  344. len = sizeof(struct sockaddr);
  345. /*
  346. * Retrieve our port number so we can hand it to the server
  347. * for the return (stderr) connection...
  348. */
  349. /* getsockname is thread safe */
  350. if (getsockname(s2,&m_socket,&len) < 0) {
  351. err("%p: %S: mcmd: getsockname failed: %m\n", ahost);
  352. close(s2);
  353. goto bad;
  354. }
  355. getp = (struct sockaddr_in *)&m_socket;
  356. lport = ntohs(getp->sin_port);
  357. if (listen(s2, 5) < 0) {
  358. err("%p: %S: mcmd: listen() failed: %m\n", ahost);
  359. close(s2);
  360. goto bad;
  361. }
  362. }
  363. /* put port in buffer. will be 0 if user didn't want stderr */
  364. snprintf(num,sizeof(num),"%d",lport);
  365. /*
  366. * Use special keyed string if target is localhost, otherwise,
  367. * encode the IP addr string.
  368. */
  369. if (!encode_localhost_string (ahost, haddrdot, sizeof (haddrdot))) {
  370. /* inet_ntoa is not thread safe, so we use the following,
  371. * which is more or less ripped from glibc
  372. */
  373. memcpy(&m_in.s_addr, addr, IP_ADDR_LEN);
  374. hptr = (unsigned char *)&m_in;
  375. sprintf(haddrdot, "%u.%u.%u.%u", hptr[0], hptr[1], hptr[2], hptr[3]);
  376. }
  377. /*
  378. * We call munge_encode which will take what we write in and return a
  379. * pointer to an munged buffer. What we get back is a null terminated
  380. * string of encrypted characters.
  381. *
  382. * The format of the unmunged buffer is as follows (each a string terminated
  383. * with a '\0' (null):
  384. *
  385. * stderr_port_number & /dev/urandom_client_produce_number are 0
  386. * if user did not request stderr socket
  387. * SIZE EXAMPLE
  388. * ========== =============
  389. * remote_user_name variable "mhaskell"
  390. * '\0'
  391. * protocol version < 12 bytes "1.2"
  392. * '\0'
  393. * dotted_decimal_address_of_this_server 7-15 bytes "134.9.11.155"
  394. * '\0'
  395. * stderr_port_number 4-8 bytes "50111"
  396. * '\0'
  397. * /dev/urandom_client_produced_number 1-8 bytes "1f79ca0e"
  398. * '\0'
  399. * users_command variable "ls -al"
  400. * '\0' '\0'
  401. *
  402. * (The last extra null is accounted for in the following line's
  403. * last strlen() call.)
  404. *
  405. */
  406. mpvers = MRSH_PROTOCOL_VERSION;
  407. mcount = ((strlen(remuser)+1) + (strlen(mpvers)+1) +
  408. (strlen(haddrdot)+1) + (strlen(num)+1) +
  409. (strlen(num_seq)+1) + strlen(cmd)+2);
  410. tmbuf = mbuf = malloc(mcount);
  411. if (tmbuf == NULL) {
  412. err("%p: %S: mcmd: Error from malloc\n", ahost);
  413. close(s2);
  414. goto bad;
  415. }
  416. /*
  417. * The following memset() call takes the extra trailing null as
  418. * part of its count as well.
  419. */
  420. memset(mbuf,0,mcount);
  421. mptr = strcpy(mbuf, remuser);
  422. mptr += strlen(remuser)+1;
  423. mptr = strcpy(mptr, mpvers);
  424. mptr += strlen(mpvers)+1;
  425. mptr = strcpy(mptr, haddrdot);
  426. mptr += strlen(haddrdot)+1;
  427. mptr = strcpy(mptr, num);
  428. mptr += strlen(num)+1;
  429. mptr = strcpy(mptr, num_seq);
  430. mptr += strlen(num_seq)+1;
  431. mptr = strcpy(mptr, cmd);
  432. ctx = munge_ctx_create();
  433. if ((rv = munge_encode(&m,ctx,mbuf,mcount)) != EMUNGE_SUCCESS) {
  434. err("%p: %S: mcmd: munge_encode: %s\n", ahost, munge_ctx_strerror(ctx));
  435. munge_ctx_destroy(ctx);
  436. if (s2 >= 0)
  437. close(s2);
  438. free(tmbuf);
  439. goto bad;
  440. }
  441. munge_ctx_destroy(ctx);
  442. mcount = (strlen(m)+1);
  443. /*
  444. * Write stderr port in the clear in case we can't decode for
  445. * some reason (i.e. bad credentials). May be 0 if user
  446. * doesn't want stderr
  447. */
  448. if (fd2p != NULL) {
  449. rv = fd_write_n(s, num, strlen(num)+1);
  450. if (rv != (strlen(num) + 1)) {
  451. free(m);
  452. free(tmbuf);
  453. if (errno == EPIPE)
  454. err("%p: %S: mcmd: Lost connection (EPIPE): %m", ahost);
  455. else
  456. err("%p: %S: mcmd: Write of stderr port failed: %m\n", ahost);
  457. close(s2);
  458. goto bad;
  459. }
  460. } else {
  461. write(s, "", 1);
  462. lport = 0;
  463. }
  464. /*
  465. * Write the munge_encoded blob to the socket.
  466. */
  467. rv = fd_write_n(s, m, mcount);
  468. if (rv != mcount) {
  469. free(m);
  470. free(tmbuf);
  471. if (errno == EPIPE)
  472. err("%p: %S: mcmd: Lost connection: %m\n", ahost);
  473. else
  474. err("%p: %S: mcmd: Write to socket failed: %m\n", ahost);
  475. close(s2);
  476. goto bad;
  477. }
  478. free(m);
  479. free(tmbuf);
  480. if (fd2p != NULL) {
  481. /*
  482. * Wait for stderr connection from daemon.
  483. */
  484. int s3;
  485. errno = 0;
  486. xpfds[0].fd = s;
  487. xpfds[1].fd = s2;
  488. xpfds[0].events = xpfds[1].events = XPOLLREAD;
  489. if ( ((rv = xpoll(xpfds, 2, -1)) < 0)
  490. || rv != 1
  491. || (xpfds[0].revents > 0)) {
  492. if (errno != 0)
  493. err("%p: %S: mcmd: xpoll (setting up stderr): %m\n", ahost);
  494. else
  495. err("%p: %S: mcmd: xpoll: protocol failure in circuit setup\n",
  496. ahost);
  497. (void) close(s2);
  498. goto bad;
  499. }
  500. errno = 0;
  501. len = sizeof(from); /* arg to accept */
  502. if ((s3 = accept(s2, (struct sockaddr *)&from, &len)) < 0) {
  503. err("%p: %S: mcmd: accept (stderr) failed: %m\n", ahost);
  504. close(s2);
  505. goto bad;
  506. }
  507. if (from.sin_family != AF_INET) {
  508. err("%p: %S: mcmd: bad family type: %d\n", ahost, from.sin_family);
  509. goto bad2;
  510. }
  511. close(s2);
  512. /*
  513. * The following fixes a race condition between the daemon
  514. * and the client. The daemon is waiting for a null to
  515. * proceed. We do this to make sure that we have our
  516. * socket is up prior to the daemon running the command.
  517. */
  518. if (write(s,"",1) < 0) {
  519. err("%p: %S: mcmd: Could not communicate to daemon to proceed: %m\n", ahost);
  520. close(s3);
  521. goto bad;
  522. }
  523. /*
  524. * Read from our stderr. The server should have placed our
  525. * random number we generated onto this socket.
  526. */
  527. rv = fd_read_n(s3, &rand, sizeof(rand));
  528. if (rv != (ssize_t) (sizeof(rand))) {
  529. err("%p: %S: mcmd: Bad read of expected verification "
  530. "number off of stderr socket: %m\n", ahost);
  531. close(s3);
  532. goto bad;
  533. }
  534. randl = ntohl(rand);
  535. if (randl != randy) {
  536. char tmpbuf[LINEBUFSIZE] = {0};
  537. char *tptr = &tmpbuf[0];
  538. memcpy(tptr,(char *) &rand,sizeof(rand));
  539. tptr += sizeof(rand);
  540. if (fd_read_line (s3, tptr, LINEBUFSIZE) < 0)
  541. err("%p: %S: mcmd: Read error from remote host: %m\n", ahost);
  542. else
  543. err("%p: %S: mcmd: Error: %s\n", ahost, &tmpbuf[0]);
  544. close(s3);
  545. goto bad;
  546. }
  547. /*
  548. * Set the stderr file descriptor for the user...
  549. */
  550. *fd2p = s3;
  551. }
  552. if ((rv = read(s, &c, 1)) < 0) {
  553. err("%p: %S: mcmd: read: protocol failure: %m\n", ahost);
  554. goto bad2;
  555. }
  556. if (rv != 1) {
  557. err("%p: %S: mcmd: read: protocol failure: invalid response\n", ahost);
  558. goto bad2;
  559. }
  560. if (c != '\0') {
  561. /* retrieve error string from remote server */
  562. char tmpbuf[LINEBUFSIZE];
  563. if (fd_read_line (s, &tmpbuf[0], LINEBUFSIZE) < 0)
  564. err("%p: %S: mcmd: Error from remote host\n", ahost);
  565. else
  566. err("%p: %S: mcmd: Error: %s\n", ahost, tmpbuf);
  567. goto bad2;
  568. }
  569. RESTORE_PTHREAD();
  570. return (s);
  571. bad2:
  572. if (lport)
  573. close(*fd2p);
  574. bad:
  575. close(s);
  576. EXIT_PTHREAD();
  577. }
  578. /*
  579. * vi: tabstop=4 shiftwidth=4 expandtab
  580. */