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