xref: /openbsd-src/usr.bin/rdist/common.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
1 /*	$OpenBSD: common.c,v 1.37 2015/12/22 08:48:39 mmcc Exp $	*/
2 
3 /*
4  * Copyright (c) 1983 Regents of the University of California.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/stat.h>
33 #include <sys/time.h>
34 #include <sys/wait.h>
35 
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <grp.h>
39 #include <limits.h>
40 #include <paths.h>
41 #include <stdarg.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #include "defs.h"
48 
49 /*
50  * Things common to both the client and server.
51  */
52 
53 /*
54  * Variables common to both client and server
55  */
56 char			host[HOST_NAME_MAX+1];	/* Name of this host */
57 uid_t			userid = (uid_t)-1;	/* User's UID */
58 gid_t			groupid = (gid_t)-1;	/* User's GID */
59 char		       *homedir = NULL;		/* User's $HOME */
60 char		       *locuser = NULL;		/* Local User's name */
61 int			isserver = FALSE;	/* We're the server */
62 int     		amchild = 0;		/* This PID is a child */
63 int			do_fork = 1;		/* Fork child process */
64 char		       *currenthost = NULL;	/* Current client hostname */
65 char		       *progname = NULL;	/* Name of this program */
66 int			rem_r = -1;		/* Client file descriptor */
67 int			rem_w = -1;		/* Client file descriptor */
68 struct passwd	       *pw = NULL;		/* Local user's pwd entry */
69 volatile sig_atomic_t 	contimedout = FALSE;	/* Connection timed out */
70 int			rtimeout = RTIMEOUT;	/* Response time out */
71 jmp_buf			finish_jmpbuf;		/* Finish() jmp buffer */
72 int			setjmp_ok = FALSE;	/* setjmp()/longjmp() status */
73 char		      **realargv;		/* Real main() argv */
74 int			realargc;		/* Real main() argc */
75 opt_t			options = 0;		/* Global install options */
76 char			defowner[64] = "bin";	/* Default owner */
77 char			defgroup[64] = "bin";	/* Default group */
78 
79 static int sendcmdmsg(int, char *, size_t);
80 static ssize_t remread(int, u_char *, size_t);
81 static int remmore(void);
82 
83 /*
84  * Front end to write() that handles partial write() requests.
85  */
86 ssize_t
87 xwrite(int fd, void *buf, size_t len)
88 {
89     	size_t nleft = len;
90 	ssize_t nwritten;
91 	char *ptr = buf;
92 
93 	while (nleft > 0) {
94 	    	if ((nwritten = write(fd, ptr, nleft)) <= 0) {
95 			return nwritten;
96 	    	}
97 	    	nleft -= nwritten;
98 	    	ptr += nwritten;
99 	}
100 
101 	return len;
102 }
103 
104 /*
105  * Do run-time initialization
106  */
107 int
108 init(int argc, char **argv, char **envp)
109 {
110 	int i;
111 
112 	/*
113 	 * Save a copy of our argc and argv before setargs() overwrites them
114 	 */
115 	realargc = argc;
116 	realargv = xmalloc(sizeof(char *) * (argc+1));
117 	for (i = 0; i < argc; i++)
118 		realargv[i] = xstrdup(argv[i]);
119 
120 	pw = getpwuid(userid = getuid());
121 	if (pw == NULL) {
122 		error("Your user id (%u) is not known to this system.",
123 		      getuid());
124 		return(-1);
125 	}
126 
127 	debugmsg(DM_MISC, "UserID = %u pwname = '%s' home = '%s'\n",
128 		 userid, pw->pw_name, pw->pw_dir);
129 	homedir = xstrdup(pw->pw_dir);
130 	locuser = xstrdup(pw->pw_name);
131 	groupid = pw->pw_gid;
132 	gethostname(host, sizeof(host));
133 #if 0
134 	if ((cp = strchr(host, '.')) != NULL)
135 	    	*cp = CNULL;
136 #endif
137 
138 	/*
139 	 * If we're not root, disable paranoid ownership checks
140 	 * since normal users cannot chown() files.
141 	 */
142 	if (!isserver && userid != 0) {
143 		FLAG_ON(options, DO_NOCHKOWNER);
144 		FLAG_ON(options, DO_NOCHKGROUP);
145 	}
146 
147 	return(0);
148 }
149 
150 /*
151  * Finish things up before ending.
152  */
153 void
154 finish(void)
155 {
156 	debugmsg(DM_CALL,
157 		 "finish() called: do_fork = %d amchild = %d isserver = %d",
158 		 do_fork, amchild, isserver);
159 	cleanup(0);
160 
161 	/*
162 	 * There's no valid finish_jmpbuf for the rdist master parent.
163 	 */
164 	if (!do_fork || amchild || isserver) {
165 
166 		if (!setjmp_ok) {
167 #ifdef DEBUG_SETJMP
168 			error("attemping longjmp() without target");
169 			abort();
170 #else
171 			exit(1);
172 #endif
173 		}
174 
175 		longjmp(finish_jmpbuf, 1);
176 		/*NOTREACHED*/
177 		error("Unexpected failure of longjmp() in finish()");
178 		exit(2);
179 	} else
180 		exit(1);
181 }
182 
183 /*
184  * Handle lost connections
185  */
186 void
187 lostconn(void)
188 {
189 	/* Prevent looping */
190 	(void) signal(SIGPIPE, SIG_IGN);
191 
192 	rem_r = rem_w = -1;	/* Ensure we don't try to send to server */
193 	checkhostname();
194 	error("Lost connection to %s",
195 	      (currenthost) ? currenthost : "(unknown)");
196 
197 	finish();
198 }
199 
200 /*
201  * General signal handler
202  */
203 void
204 sighandler(int sig)
205 {
206 	int save_errno = errno;
207 
208 	/* XXX signal race */
209 	debugmsg(DM_CALL, "sighandler() received signal %d\n", sig);
210 
211 	switch (sig) {
212 	case SIGALRM:
213 		contimedout = TRUE;
214 		/* XXX signal race */
215 		checkhostname();
216 		error("Response time out");
217 		finish();
218 		break;
219 
220 	case SIGPIPE:
221 		/* XXX signal race */
222 		lostconn();
223 		break;
224 
225 	case SIGFPE:
226 		debug = !debug;
227 		break;
228 
229 	case SIGHUP:
230 	case SIGINT:
231 	case SIGQUIT:
232 	case SIGTERM:
233 		/* XXX signal race */
234 		finish();
235 		break;
236 
237 	default:
238 		/* XXX signal race */
239 		fatalerr("No signal handler defined for signal %d.", sig);
240 	}
241 	errno = save_errno;
242 }
243 
244 /*
245  * Function to actually send the command char and message to the
246  * remote host.
247  */
248 static int
249 sendcmdmsg(int cmd, char *msg, size_t msgsize)
250 {
251 	int len;
252 
253 	if (rem_w < 0)
254 		return(-1);
255 
256 	/*
257 	 * All commands except C_NONE should have a newline
258 	 */
259 	if (cmd != C_NONE && !strchr(msg + 1, '\n'))
260 		(void) strlcat(msg + 1, "\n", msgsize - 1);
261 
262 	if (cmd == C_NONE)
263 		len = strlen(msg);
264 	else {
265 		len = strlen(msg + 1) + 1;
266 		msg[0] = cmd;
267 	}
268 
269 	debugmsg(DM_PROTO, ">>> Cmd = %c (\\%3.3o) Msg = \"%.*s\"",
270 		 cmd, cmd,
271 		 (cmd == C_NONE) ? len-1 : len-2,
272 		 (cmd == C_NONE) ? msg : msg + 1);
273 
274 	return(!(xwrite(rem_w, msg, len) == len));
275 }
276 
277 /*
278  * Send a command message to the remote host.
279  * Called as sendcmd(char cmdchar, char *fmt, arg1, arg2, ...)
280  * The fmt may be NULL, in which case there are no args.
281  */
282 int
283 sendcmd(char cmd, const char *fmt, ...)
284 {
285 	static char buf[BUFSIZ];
286 	va_list args;
287 
288 	va_start(args, fmt);
289 	if (fmt)
290 		(void) vsnprintf(buf + (cmd != C_NONE),
291 				 sizeof(buf) - (cmd != C_NONE), fmt, args);
292 	else
293 		buf[1] = CNULL;
294 	va_end(args);
295 
296 	return(sendcmdmsg(cmd, buf, sizeof(buf)));
297 }
298 
299 /*
300  * Internal variables and routines for reading lines from the remote.
301  */
302 static u_char rembuf[BUFSIZ];
303 static u_char *remptr;
304 static ssize_t remleft;
305 
306 #define remc() (--remleft < 0 ? remmore() : *remptr++)
307 
308 /*
309  * Back end to remote read()
310  */
311 static ssize_t
312 remread(int fd, u_char *buf, size_t bufsiz)
313 {
314 	return(read(fd, (char *)buf, bufsiz));
315 }
316 
317 static int
318 remmore(void)
319 {
320 	(void) signal(SIGALRM, sighandler);
321 	(void) alarm(rtimeout);
322 
323 	remleft = remread(rem_r, rembuf, sizeof(rembuf));
324 
325 	(void) alarm(0);
326 
327 	if (remleft < 0)
328 		return (-2);	/* error */
329 	if (remleft == 0)
330 		return (-1);	/* EOF */
331 	remptr = rembuf;
332 	remleft--;
333 	return (*remptr++);
334 }
335 
336 /*
337  * Read an input line from the remote.  Return the number of bytes
338  * stored (equivalent to strlen(p)).  If `cleanup' is set, EOF at
339  * the beginning of a line is returned as EOF (-1); other EOFs, or
340  * errors, call cleanup() or lostconn().  In other words, unless
341  * the third argument is nonzero, this routine never returns failure.
342  */
343 int
344 remline(u_char *buffer, int space, int doclean)
345 {
346 	int c, left = space;
347 	u_char *p = buffer;
348 
349 	if (rem_r < 0) {
350 		error("Cannot read remote input: Remote descriptor not open.");
351 		return(-1);
352 	}
353 
354 	while (left > 0) {
355 		if ((c = remc()) < -1) {	/* error */
356 			if (doclean) {
357 				finish();
358 				/*NOTREACHED*/
359 			}
360 			lostconn();
361 			/*NOTREACHED*/
362 		}
363 		if (c == -1) {			/* got EOF */
364 			if (doclean) {
365 				if (left == space)
366 					return (-1);/* signal proper EOF */
367 				finish();	/* improper EOF */
368 				/*NOTREACHED*/
369 			}
370 			lostconn();
371 			/*NOTREACHED*/
372 		}
373 		if (c == '\n') {
374 			*p = CNULL;
375 
376 			if (debug) {
377 				static char mbuf[BUFSIZ];
378 
379 				(void) snprintf(mbuf, sizeof(mbuf),
380 					"<<< Cmd = %c (\\%3.3o) Msg = \"%s\"",
381 					       buffer[0], buffer[0],
382 					       buffer + 1);
383 
384 				debugmsg(DM_PROTO, "%s", mbuf);
385 			}
386 
387 			return (space - left);
388 		}
389 		*p++ = c;
390 		left--;
391 	}
392 
393 	/* this will probably blow the entire session */
394 	error("remote input line too long");
395 	p[-1] = CNULL;		/* truncate */
396 	return (space);
397 }
398 
399 /*
400  * Non-line-oriented remote read.
401  */
402 ssize_t
403 readrem(char *p, ssize_t space)
404 {
405 	if (remleft <= 0) {
406 		/*
407 		 * Set remote time out alarm.
408 		 */
409 		(void) signal(SIGALRM, sighandler);
410 		(void) alarm(rtimeout);
411 
412 		remleft = remread(rem_r, rembuf, sizeof(rembuf));
413 
414 		(void) alarm(0);
415 		remptr = rembuf;
416 	}
417 
418 	if (remleft <= 0)
419 		return (remleft);
420 	if (remleft < space)
421 		space = remleft;
422 
423 	memcpy(p, remptr, space);
424 
425 	remptr += space;
426 	remleft -= space;
427 
428 	return (space);
429 }
430 
431 /*
432  * Get the user name for the uid.
433  */
434 char *
435 getusername(uid_t uid, char *file, opt_t opts)
436 {
437 	static char buf[100];
438 	static uid_t lastuid = (uid_t)-1;
439 	struct passwd *pwd = NULL;
440 
441 	/*
442 	 * The value of opts may have changed so we always
443 	 * do the opts check.
444 	 */
445   	if (IS_ON(opts, DO_NUMCHKOWNER)) {
446 		(void) snprintf(buf, sizeof(buf), ":%u", uid);
447 		return(buf);
448   	}
449 
450 	/*
451 	 * Try to avoid getpwuid() call.
452 	 */
453 	if (lastuid == uid && buf[0] != '\0' && buf[0] != ':')
454 		return(buf);
455 
456 	lastuid = uid;
457 
458 	if ((pwd = getpwuid(uid)) == NULL) {
459 		if (IS_ON(opts, DO_DEFOWNER) && !isserver)
460 			(void) strlcpy(buf, defowner, sizeof(buf));
461 		else {
462 			message(MT_WARNING,
463 				"%s: No password entry for uid %u", file, uid);
464 			(void) snprintf(buf, sizeof(buf), ":%u", uid);
465 		}
466 	} else {
467 		(void) strlcpy(buf, pwd->pw_name, sizeof(buf));
468 	}
469 
470 	return(buf);
471 }
472 
473 /*
474  * Get the group name for the gid.
475  */
476 char *
477 getgroupname(gid_t gid, char *file, opt_t opts)
478 {
479 	static char buf[100];
480 	static gid_t lastgid = (gid_t)-1;
481 	struct group *grp = NULL;
482 
483 	/*
484 	 * The value of opts may have changed so we always
485 	 * do the opts check.
486 	 */
487   	if (IS_ON(opts, DO_NUMCHKGROUP)) {
488 		(void) snprintf(buf, sizeof(buf), ":%u", gid);
489 		return(buf);
490   	}
491 
492 	/*
493 	 * Try to avoid getgrgid() call.
494 	 */
495 	if (lastgid == gid && buf[0] != '\0' && buf[0] != ':')
496 		return(buf);
497 
498 	lastgid = gid;
499 
500 	if ((grp = (struct group *)getgrgid(gid)) == NULL) {
501 		if (IS_ON(opts, DO_DEFGROUP) && !isserver)
502 			(void) strlcpy(buf, defgroup, sizeof(buf));
503 		else {
504 			message(MT_WARNING, "%s: No name for group %u",
505 				file, gid);
506 			(void) snprintf(buf, sizeof(buf), ":%u", gid);
507 		}
508 	} else
509 		(void) strlcpy(buf, grp->gr_name, sizeof(buf));
510 
511 	return(buf);
512 }
513 
514 /*
515  * Read a response from the remote host.
516  */
517 int
518 response(void)
519 {
520 	static u_char resp[BUFSIZ];
521 	u_char *s;
522 	int n;
523 
524 	debugmsg(DM_CALL, "response() start\n");
525 
526 	n = remline(s = resp, sizeof(resp), 0);
527 
528 	n--;
529 	switch (*s++) {
530         case C_ACK:
531 		debugmsg(DM_PROTO, "received ACK\n");
532 		return(0);
533 	case C_LOGMSG:
534 		if (n > 0) {
535 			message(MT_CHANGE, "%s", s);
536 			return(1);
537 		}
538 		debugmsg(DM_PROTO, "received EMPTY logmsg\n");
539 		return(0);
540 	case C_NOTEMSG:
541 		if (s)
542 			message(MT_NOTICE, "%s", s);
543 		return(response());
544 
545 	default:
546 		s--;
547 		n++;
548 		/* fall into... */
549 
550 	case C_ERRMSG:	/* Normal error message */
551 		if (s)
552 			message(MT_NERROR, "%s", s);
553 		return(-1);
554 
555 	case C_FERRMSG:	/* Fatal error message */
556 		if (s)
557 			message(MT_FERROR, "%s", s);
558 		finish();
559 		return(-1);
560 	}
561 	/*NOTREACHED*/
562 }
563 
564 /*
565  * This should be in expand.c but the other routines call other modules
566  * that we don't want to load in.
567  *
568  * Expand file names beginning with `~' into the
569  * user's home directory path name. Return a pointer in buf to the
570  * part corresponding to `file'.
571  */
572 char *
573 exptilde(char *ebuf, char *file, size_t ebufsize)
574 {
575 	char *pw_dir, *rest;
576 	size_t len;
577 
578 	if (*file != '~') {
579 notilde:
580 		(void) strlcpy(ebuf, file, ebufsize);
581 		return(ebuf);
582 	}
583 	if (*++file == CNULL) {
584 		pw_dir = homedir;
585 		rest = NULL;
586 	} else if (*file == '/') {
587 		pw_dir = homedir;
588 		rest = file;
589 	} else {
590 		rest = file;
591 		while (*rest && *rest != '/')
592 			rest++;
593 		if (*rest == '/')
594 			*rest = CNULL;
595 		else
596 			rest = NULL;
597 		if (pw == NULL || strcmp(pw->pw_name, file) != 0) {
598 			if ((pw = getpwnam(file)) == NULL) {
599 				error("%s: unknown user name", file);
600 				if (rest != NULL)
601 					*rest = '/';
602 				return(NULL);
603 			}
604 		}
605 		if (rest != NULL)
606 			*rest = '/';
607 		pw_dir = pw->pw_dir;
608 	}
609 	if ((len = strlcpy(ebuf, pw_dir, ebufsize)) >= ebufsize)
610 		goto notilde;
611 	pw_dir = ebuf + len;
612 	if (rest != NULL) {
613 		pw_dir++;
614 		if ((len = strlcat(ebuf, rest, ebufsize)) >= ebufsize)
615 			goto notilde;
616 	}
617 	return(pw_dir);
618 }
619 
620 
621 
622 /*
623  * Set access and modify times of a given file
624  */
625 int
626 setfiletime(char *file, time_t atime, time_t mtime)
627 {
628 	struct timeval tv[2];
629 
630 	if (atime != 0 && mtime != 0) {
631 		tv[0].tv_sec = atime;
632 		tv[1].tv_sec = mtime;
633 		tv[0].tv_usec = tv[1].tv_usec = 0;
634 		return (utimes(file, tv));
635 	} else	/* Set to current time */
636 		return (utimes(file, NULL));
637 }
638 
639 /*
640  * Get version info
641  */
642 char *
643 getversion(void)
644 {
645 	static char buff[BUFSIZ];
646 
647 	(void) snprintf(buff, sizeof(buff),
648 	"Version %s.%d (%s) - Protocol Version %d, Release %s, Patch level %d",
649 		       DISTVERSION, PATCHLEVEL, DISTSTATUS,
650 		       VERSION, DISTVERSION, PATCHLEVEL);
651 
652 	return(buff);
653 }
654 
655 /*
656  * Execute a shell command to handle special cases.
657  * This is now common to both server and client
658  */
659 void
660 runcommand(char *cmd)
661 {
662 	ssize_t nread;
663 	pid_t pid, wpid;
664 	char *cp, *s;
665 	char sbuf[BUFSIZ], buf[BUFSIZ];
666 	int fd[2], status;
667 
668 	if (pipe(fd) < 0) {
669 		error("pipe of %s failed: %s", cmd, SYSERR);
670 		return;
671 	}
672 
673 	if ((pid = fork()) == 0) {
674 		/*
675 		 * Return everything the shell commands print.
676 		 */
677 		(void) close(0);
678 		(void) close(1);
679 		(void) close(2);
680 		(void) open(_PATH_DEVNULL, O_RDONLY);
681 		(void) dup(fd[PIPE_WRITE]);
682 		(void) dup(fd[PIPE_WRITE]);
683 		(void) close(fd[PIPE_READ]);
684 		(void) close(fd[PIPE_WRITE]);
685 		(void) execl(_PATH_BSHELL, "sh", "-c", cmd, (char *)NULL);
686 		_exit(127);
687 	}
688 	(void) close(fd[PIPE_WRITE]);
689 	s = sbuf;
690 	*s++ = C_LOGMSG;
691 	while ((nread = read(fd[PIPE_READ], buf, sizeof(buf))) > 0) {
692 		cp = buf;
693 		do {
694 			*s++ = *cp++;
695 			if (cp[-1] != '\n') {
696 				if (s < (char *) &sbuf[sizeof(sbuf)-1])
697 					continue;
698 				*s++ = '\n';
699 			}
700 			/*
701 			 * Throw away blank lines.
702 			 */
703 			if (s == &sbuf[2]) {
704 				s--;
705 				continue;
706 			}
707 			if (isserver)
708 				(void) xwrite(rem_w, sbuf, s - sbuf);
709 			else {
710 				*s = CNULL;
711 				message(MT_INFO, "%s", sbuf+1);
712 			}
713 			s = &sbuf[1];
714 		} while (--nread);
715 	}
716 	if (s > (char *) &sbuf[1]) {
717 		*s++ = '\n';
718 		if (isserver)
719 			(void) xwrite(rem_w, sbuf, s - sbuf);
720 		else {
721 			*s = CNULL;
722 			message(MT_INFO, "%s", sbuf+1);
723 		}
724 	}
725 	while ((wpid = wait(&status)) != pid && wpid != -1)
726 		;
727 	if (wpid == -1)
728 		status = -1;
729 	(void) close(fd[PIPE_READ]);
730 	if (status)
731 		error("shell returned %d", status);
732 	else if (isserver)
733 		ack();
734 }
735 
736 /*
737  * Malloc with error checking
738  */
739 void *
740 xmalloc(size_t amt)
741 {
742 	void *ptr;
743 
744 	if ((ptr = malloc(amt)) == NULL)
745 		fatalerr("Cannot malloc %zu bytes of memory.", amt);
746 
747 	return (ptr);
748 }
749 
750 /*
751  * realloc with error checking
752  */
753 void *
754 xrealloc(void *baseptr, size_t amt)
755 {
756 	void *new;
757 
758 	if ((new = realloc(baseptr, amt)) == NULL)
759 		fatalerr("Cannot realloc %zu bytes of memory.", amt);
760 
761 	return (new);
762 }
763 
764 /*
765  * calloc with error checking
766  */
767 void *
768 xcalloc(size_t num, size_t esize)
769 {
770 	void *ptr;
771 
772 	if ((ptr = calloc(num, esize)) == NULL)
773 		fatalerr("Cannot calloc %zu * %zu = %zu bytes of memory.",
774 		      num, esize, num * esize);
775 
776 	return (ptr);
777 }
778 
779 /*
780  * Strdup with error checking
781  */
782 char *
783 xstrdup(const char *str)
784 {
785 	size_t len = strlen(str) + 1;
786 	char *nstr = xmalloc(len);
787 
788 	return (memcpy(nstr, str, len));
789 }
790 
791 /*
792  * Private version of basename()
793  */
794 char *
795 xbasename(char *path)
796 {
797 	char *cp;
798 
799 	if ((cp = strrchr(path, '/')) != NULL)
800 		return(cp+1);
801 	else
802 		return(path);
803 }
804 
805 /*
806  * Take a colon (':') separated path to a file and
807  * search until a component of that path is found and
808  * return the found file name.
809  */
810 char *
811 searchpath(char *path)
812 {
813 	char *file;
814 	char *space;
815 	int found;
816 	struct stat statbuf;
817 
818 	for (found = 0; !found && (file = strsep(&path, ":")) != NULL; ) {
819 		if ((space = strchr(file, ' ')) != NULL)
820 			*space = CNULL;
821 		found = stat(file, &statbuf) == 0;
822 		if (space)
823 			*space = ' ';		/* Put back what we zapped */
824 	}
825 	return (file);
826 }
827