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