1 /* glob.c
2  *
3  * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
4  * All rights reserved.
5  *
6  */
7 
8 #include "syshdrs.h"
9 
10 static const char *rwx[9] = { "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx", NULL };
11 
12 
13 
14 /* We need to use this because using NLST gives us more stuff than
15  * we want back sometimes.  For example, say we have:
16  *
17  * /a		(directory)
18  * /a/b		(directory)
19  * /a/b/b1
20  * /a/b/b2
21  * /a/b/b3
22  * /a/c		(directory)
23  * /a/c/c1
24  * /a/c/c2
25  * /a/c/c3
26  * /a/file
27  *
28  * If you did an "echo /a/<star>" in a normal unix shell, you would expect
29  * to get back /a/b /a/c /a/file.  But NLST gives back:
30  *
31  * /a/b/b1
32  * /a/b/b2
33  * /a/b/b3
34  * /a/c/c1
35  * /a/c/c2
36  * /a/c/c3
37  * /a/file
38  *
39  * So we use the following routine to convert into the format I expect.
40  */
41 
42 static void
43 RemoteGlobCollapse(const char *pattern, LineListPtr fileList)
44 {
45 	LinePtr lp, nextLine;
46 	string patPrefix;
47 	string cur, prev;
48 	char *endp, *cp, *dp;
49 	const char *pp;
50 	int wasGlobChar;
51 	size_t plen;
52 
53 	/* Copy all characters before the first glob-char. */
54 	dp = patPrefix;
55 	endp = dp + sizeof(patPrefix) - 1;
56 	wasGlobChar = 0;
57 	for (cp = (char *) pattern; dp < endp; ) {
58 		for (pp=kGlobChars; *pp != '\0'; pp++) {
59 			if (*pp == *cp) {
60 				wasGlobChar = 1;
61 				break;
62 			}
63 		}
64 		if (wasGlobChar)
65 			break;
66 		*dp++ = *cp++;
67 	}
68 	*dp = '\0';
69 	plen = (size_t) (dp - patPrefix);
70 
71 	*prev = '\0';
72 	for (lp=fileList->first; lp != NULL; lp = nextLine) {
73 		nextLine = lp->next;
74 		if (strncmp(lp->line, patPrefix, plen) == 0) {
75 			(void) STRNCPY(cur, lp->line + plen);
76 			cp = strchr(cur, '/');
77 			if (cp == NULL)
78 				cp = strchr(cur, '\\');
79 			if (cp != NULL)
80 				*cp = '\0';
81 			if ((*prev != '\0') && (STREQ(cur, prev))) {
82 				nextLine = RemoveLine(fileList, lp);
83 			} else {
84 				(void) STRNCPY(prev, cur);
85 				/* We are playing with a dynamically
86 				 * allocated string, but since the
87 				 * following expression is guaranteed
88 				 * to be the same or shorter, we won't
89 				 * overwrite the bounds.
90 				 */
91 				(void) sprintf(lp->line, "%s%s", patPrefix, cur);
92 			}
93 		}
94 	}
95 }	/* RemoteGlobCollapse */
96 
97 
98 
99 
100 #if 0
101 /* May need this later. */
102 static void
103 CheckForLS_d(FTPCIPtr cip)
104 {
105 	LineList lines;
106 	char *cp;
107 
108 	if (cip->hasNLST_d == kCommandAvailabilityUnknown) {
109 		if (FTPListToMemory2(cip, ".", &lines, "-d ", 0, (int *) 0) == kNoErr) {
110 			if ((lines.first != NULL) && (lines.first == lines.last)) {
111 				/* If we have only one item in the list, see if it really was
112 				 * an error message we would recognize.
113 				 */
114 				cp = strchr(lines.first->line, ':');
115 				if ((cp != NULL) && STREQ(cp, ": No such file or directory")) {
116 					cip->hasNLST_d = kCommandNotAvailable;
117 				} else {
118 					cip->hasNLST_d = kCommandAvailable;
119 				}
120 			} else {
121 				cip->hasNLST_d = kCommandNotAvailable;
122 			}
123 		} else {
124 			cip->hasNLST_d = kCommandNotAvailable;
125 		}
126 		DisposeLineListContents(&lines);
127 	}
128 }	/* CheckForLS_d */
129 #endif
130 
131 
132 
133 
134 static int
135 LsMonthNameToNum(char *cp)
136 {
137 	int mon;	/* 0..11 */
138 
139 	switch (*cp++) {
140 		case 'A':
141 			mon = (*cp == 'u') ? 7 : 3;
142 			break;
143 		case 'D':
144 			mon = 11;
145 			break;
146 		case 'F':
147 			mon = 1;
148 			break;
149 		default:
150 		case 'J':
151 			if (*cp++ == 'u')
152 				mon = (*cp == 'l') ? 6 : 5;
153 			else
154 				mon = 0;
155 			break;
156 		case 'M':
157 			mon = (*++cp == 'r') ? 2 : 4;
158 			break;
159 		case 'N':
160 			mon = 10;
161 			break;
162 		case 'O':
163 			mon = 9;
164 			break;
165 		case 'S':
166 			mon = 8;
167 	}
168 	return (mon);
169 }	/* LsMonthNameToNum */
170 
171 
172 
173 
174 static int
175 UnDosLine(	char *const line,
176 		const char *const curdir,
177 		size_t curdirlen,
178 		char *fname,
179 		size_t fnamesize,
180 		int *ftype,
181 		longest_int *fsize,
182 		time_t *ftime)
183 {
184 	char *cp;
185 	int hour, year;
186 	char *filestart;
187 	char *sizestart;
188 	struct tm ftm;
189 
190 	/*
191 	 *
192 0123456789012345678901234567890123456789012345678901234567890123456789
193 04-27-99  10:32PM               270158 Game booklet.pdf
194 03-11-99  10:03PM       <DIR>          Get A3d Banner
195 
196 We also try to parse the format from CMD.EXE, which is similar:
197 
198 03/22/2001  06:23p              62,325 cls.pdf
199 
200 	 *
201 	 */
202 	cp = line;
203 	if (
204 		isdigit((int) cp[0])
205 		&& isdigit((int) cp[1])
206 		&& ispunct((int) cp[2])
207 		&& isdigit((int) cp[3])
208 		&& isdigit((int) cp[4])
209 		&& ispunct((int) cp[5])
210 		&& isdigit((int) cp[6])
211 		&& isdigit((int) cp[7])
212 	) {
213 		(void) memset(&ftm, 0, sizeof(struct tm));
214 		ftm.tm_isdst = -1;
215 		cp[2] = '\0';
216 		ftm.tm_mon = atoi(cp + 0);
217 		if (ftm.tm_mon > 0)
218 			ftm.tm_mon -= 1;
219 		cp[5] = '\0';
220 		ftm.tm_mday = atoi(cp + 3);
221 		if ((isdigit((int) cp[8])) && (isdigit((int) cp[9]))) {
222 			/* Four-digit year */
223 			cp[10] = '\0';
224 			year = atoi(cp + 6);
225 			if (year > 1900)
226 				year -= 1900;
227 			ftm.tm_year = year;	/* years since 1900 */
228 			cp += 11;
229 		} else {
230 			/* Two-digit year */
231 			cp[8] = '\0';
232 			year = atoi(cp + 6);
233 			if (year < 98)
234 				year += 100;
235 			ftm.tm_year = year;	/* years since 1900 */
236 			cp += 9;
237 		}
238 
239 		for (;;) {
240 			if (*cp == '\0')
241 				return (-1);
242 			if (isdigit(*cp))
243 				break;
244 			cp++;
245 		}
246 
247 		cp[2] = '\0';
248 		hour = atoi(cp);
249 		if (((cp[5] == 'P') || (cp[5] == 'p')) && (hour < 12))
250 			hour += 12;
251 		else if (((cp[5] == 'A') || (cp[5] == 'a')) && (hour == 12))
252 			hour -= 12;
253 		ftm.tm_hour = hour;
254 		cp[5] = '\0';
255 		ftm.tm_min = atoi(cp + 3);
256 		*ftime = mktime(&ftm);
257 		if (*ftype == (time_t) -1)
258 			return (-1);
259 
260 		cp += 6;
261 		*ftype = '-';
262 		for (;;) {
263 			if (*cp == '\0')
264 				return (-1);
265 			if ((*cp == '<') && (cp[1] == 'D')) {
266 				/* found <DIR> */
267 				*ftype = 'd';
268 				cp += 5;
269 				break;	/* size field will end up being empty string */
270 			} else if ((*cp == '<') && (cp[1] == 'J')) {
271 				/* found <JUNCTION>
272 				 *
273 				 * Will we ever really see this?
274 				 * IIS from Win2000sp1 sends <DIR>
275 				 * for FTP, but CMD.EXE prints
276 				 * <JUNCTION>.
277 				 */
278 				*ftype = 'd';
279 				cp += 10;
280 				break;
281 			} else if (isdigit(*cp)) {
282 				break;
283 			} else {
284 				cp++;
285 			}
286 		}
287 
288 		sizestart = cp;
289 		for (;;) {
290 			if (*cp == '\0')
291 				return (-1);
292 #ifdef HAVE_MEMMOVE
293 			if (*cp == ',') {
294 				/* Yuck -- US Locale dependency */
295 				memmove(cp, cp + 1, strlen(cp + 1) + 1);
296 			}
297 #endif
298 			if (!isdigit(*cp)) {
299 				*cp++ = '\0';
300 				break;
301 			}
302 			cp++;
303 		}
304 
305 		if (fsize != NULL) {
306 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG)
307 			if (*ftype == 'd')
308 				*fsize = 0;
309 			else
310 				(void) sscanf(sizestart, SCANF_LONG_LONG, fsize);
311 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ)
312 			if (*ftype == 'd')
313 				*fsize = 0;
314 			else
315 				*fsize = (longest_int) strtoq(sizestart, NULL, 0);
316 #else
317 			*fsize = (longest_int) 0;
318 			if (*ftype != 'd') {
319 				long fsize2 = 0L;
320 
321 				(void) sscanf(sizestart, "%ld", &fsize2);
322 				*fsize = (longest_int) fsize2;
323 			}
324 #endif
325 		}
326 
327 		for (;;) {
328 			if (*cp == '\0')
329 				return (-1);
330 			if (!isspace(*cp)) {
331 				break;
332 			}
333 			cp++;
334 		}
335 
336 		filestart = cp;
337 		if (curdirlen == 0) {
338 			(void) Strncpy(fname, filestart, fnamesize);
339 		} else {
340 			(void) Strncpy(fname, curdir, fnamesize);
341 			(void) Strncat(fname, filestart, fnamesize);
342 		}
343 
344 		return (0);
345 	}
346 	return (-1);
347 }	/* UnDosLine */
348 
349 
350 
351 
352 static int
353 UnLslRLine(	char *const line,
354 		const char *const curdir,
355 		size_t curdirlen,
356 		char *fname,
357 		size_t fnamesize,
358 		char *linkto,
359 		size_t linktosize,
360 		int *ftype,
361 		longest_int *fsize,
362 		time_t *ftime,
363 		time_t now,
364 		int thisyear,
365 		int *plugend)
366 {
367 	char *cp;
368 	int mon = 0, dd = 0, hr = 0, min = 0, year = 0;
369 	char *monstart, *ddstart, *hrstart, *minstart, *yearstart;
370 	char *linktostart, *filestart = NULL;
371 	char *sizestart;
372 	char *pe;
373 	struct tm ftm;
374 
375 	/*
376 	 * Look for the digit just before the space
377 	 * before the month name.
378 	 *
379 -rw-rw----   1 gleason  sysdev      33404 Mar 24 01:29 RCmd.o
380 -rw-rw-r--   1 gleason  sysdevzz     1829 Jul  7  1996 README
381 -rw-rw-r--   1 gleason  sysdevzz     1829 Jul 7   1996 README
382 -rw-rw-r--   1 gleason  sysdevzz     1829 Jul  7 1996  README
383 -rw-rw-r--   1 gleason  sysdevzz     1829 Jul 7  1996  README
384          *
385 	 *------------------------------^
386 	 *                              0123456789012345
387 	 *------plugend--------^
388 	 *                     9876543210
389 	 *
390 	 */
391 	for (cp = line; *cp != '\0'; cp++) {
392 		if (	(isdigit((int) *cp))
393 			&& (isspace((int) cp[1]))
394 			&& (isupper((int) cp[2]))
395 			&& (islower((int) cp[3]))
396 		/*	&& (islower((int) cp[4])) */
397 			&& (isspace((int) cp[5]))
398 			&& (
399 ((isdigit((int) cp[6])) && (isdigit((int) cp[7])))
400 || ((isdigit((int) cp[6])) && (isspace((int) cp[7])))
401 || ((isspace((int) cp[6])) && (isdigit((int) cp[7])))
402 			)
403 			&& (isspace((int) cp[8]))
404 		) {
405 			monstart = cp + 2;
406 			ddstart = cp + 6;
407 			if (	((isspace((int) cp[9])) || (isdigit((int) cp[9])))
408 				&& (isdigit((int) cp[10]))
409 				&& (isdigit((int) cp[11]))
410 				&& (isdigit((int) cp[12]))
411 				&& ((isdigit((int) cp[13])) || (isspace((int) cp[13])))
412 			) {
413 				/* "Mon DD  YYYY" form */
414 				yearstart = cp + 9;
415 				if (isspace((int) *yearstart))
416 					yearstart++;
417 				hrstart = NULL;
418 				minstart = NULL;
419 				filestart = cp + 15;
420 				cp[1] = '\0';	/* end size */
421 				cp[5] = '\0';	/* end mon */
422 				cp[8] = '\0';	/* end dd */
423 				cp[14] = '\0';	/* end year */
424 				mon = LsMonthNameToNum(monstart);
425 				dd = atoi(ddstart);
426 				hr = 23;
427 				min = 59;
428 				year = atoi(yearstart);
429 
430 				pe = cp;
431 				while (isdigit((int) *pe))
432 					pe--;
433 				while (isspace((int) *pe))
434 					pe--;
435 				*plugend = (int) (pe - line) + 1;
436 				break;
437 			} else if (	/*
438 					 * Windows NT does not 0 pad.
439 					(isdigit((int) cp[9])) &&
440 					 */
441 				(isdigit((int) cp[10]))
442 				&& (cp[11] == ':')
443 				&& (isdigit((int) cp[12]))
444 				&& (isdigit((int) cp[13]))
445 			) {
446 				/* "Mon DD HH:MM" form */
447 				yearstart = NULL;
448 				hrstart = cp + 9;
449 				minstart = cp + 12;
450 				filestart = cp + 15;
451 				cp[1] = '\0';	/* end size */
452 				cp[5] = '\0';	/* end mon */
453 				cp[8] = '\0';	/* end dd */
454 				cp[11] = '\0';	/* end hr */
455 				cp[14] = '\0';	/* end min */
456 				mon = LsMonthNameToNum(monstart);
457 				dd = atoi(ddstart);
458 				hr = atoi(hrstart);
459 				min = atoi(minstart);
460 				year = 0;
461 
462 				pe = cp;
463 				while (isdigit((int) *pe))
464 					pe--;
465 				while (isspace((int) *pe))
466 					pe--;
467 				*plugend = (int) (pe - line) + 1;
468 				break;
469 			}
470 		}
471 	}
472 
473 	if (*cp == '\0')
474 		return (-1);
475 
476 	linktostart = strstr(filestart, " -> ");
477 	if (linktostart != NULL) {
478 		*linktostart = '\0';
479 		linktostart += 4;
480 		(void) Strncpy(linkto, linktostart, linktosize);
481 	} else {
482 		*linkto = '\0';
483 	}
484 
485 	if (curdirlen == 0) {
486 		(void) Strncpy(fname, filestart, fnamesize);
487 	} else {
488 		(void) Strncpy(fname, curdir, fnamesize);
489 		(void) Strncat(fname, filestart, fnamesize);
490 	}
491 
492 	if (ftime != NULL) {
493 		(void) memset(&ftm, 0, sizeof(struct tm));
494 		ftm.tm_mon = mon;
495 		ftm.tm_mday = dd;
496 		ftm.tm_hour = hr;
497 		ftm.tm_min = min;
498 		ftm.tm_isdst = -1;
499 		if (year == 0) {
500 			/* We guess the year, based on what the
501 			 * current year is.  We know the file
502 			 * on the remote server is either less
503 			 * than six months old or less than
504 			 * one hour into the future.
505 			 */
506 			ftm.tm_year = thisyear - 1900;
507 			*ftime = mktime(&ftm);
508 			if (*ftime == (time_t) -1) {
509 				/* panic */
510 			} else if (*ftime > (now + (15552000L + 86400L))) {
511 				--ftm.tm_year;
512 				*ftime = mktime(&ftm);
513 			} else if (*ftime < (now - (15552000L + 86400L))) {
514 				++ftm.tm_year;
515 				*ftime = mktime(&ftm);
516 			}
517 		} else {
518 			ftm.tm_year = year - 1900;
519 			*ftime = mktime(&ftm);
520 		}
521 	}
522 
523 	if (fsize != NULL) {
524 		while ((cp > line) && (isdigit((int) *cp)))
525 			--cp;
526 		sizestart = cp + 1;
527 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG)
528 		(void) sscanf(sizestart, SCANF_LONG_LONG, fsize);
529 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ)
530 		*fsize = (longest_int) strtoq(sizestart, NULL, 0);
531 #else
532 		{
533 			long fsize2 = 0L;
534 
535 			(void) sscanf(sizestart, "%ld", &fsize2);
536 			*fsize = (longest_int) fsize2;
537 		}
538 #endif
539 	}
540 
541 	switch (line[0]) {
542 		case 'd':
543 		case 'l':
544 			*ftype = (int) line[0];
545 			break;
546 		case 'b':
547 		case 'c':
548 		case 's':
549 			*ftype = (int) line[0];
550 			return (-1);
551 		default:
552 			*ftype = '-';
553 	}
554 
555 	return (0);
556 }	/* UnLslRLine */
557 
558 
559 
560 int
561 UnLslR(FileInfoListPtr filp, LineListPtr llp, int serverType)
562 {
563 	char curdir[256];
564 	char line[256];
565 	int hadblankline = 0;
566 	int len;
567 	size_t curdirlen = 0;
568 	char fname[256];
569 	char linkto[256];
570 	char *cp;
571 	longest_int fsize;
572 	int ftype;
573 	time_t ftime, now;
574 	int thisyear;
575 	struct tm *nowtm;
576 	int rc;
577 	LinePtr lp;
578 	FileInfo fi;
579 	int linesread = 0;
580 	int linesconverted = 0;
581 	size_t maxFileLen = 0;
582 	size_t maxPlugLen = 0;
583 	size_t fileLen;
584 	int plugend;
585 
586 	(void) time(&now);
587 	nowtm = localtime(&now);
588 	if (nowtm == NULL)
589 		thisyear = 1970;	/* should never happen */
590 	else
591 		thisyear = nowtm->tm_year + 1900;
592 
593 	curdir[0] = '\0';
594 
595 	InitFileInfoList(filp);
596 	for (lp = llp->first; lp != NULL; lp = lp->next) {
597 		len = (int) strlen(STRNCPY(line, lp->line));
598 		if ((line[0] == 't') && (strncmp(line, "total", 5) == 0)) {
599 			/* total XX line? */
600 			if (line[len - 1] != ':') {
601 				hadblankline = 0;
602 				continue;
603 			}
604 			/* else it was a subdir named total */
605 		} else {
606 			for (cp = line; ; cp++) {
607 				if ((*cp == '\0') || (!isspace((int) *cp)))
608 					break;
609 			}
610 			if (*cp == '\0') {
611 				/* Entire line was blank. */
612 				/* separator line between dirs */
613 				hadblankline = 1;
614 				continue;
615 			}
616 		}
617 
618 		if ((hadblankline != 0) && (line[len - 1] == ':')) {
619 			/* newdir */
620 			hadblankline = 0;
621 			if ((line[0] == '.') && (line[1] == '/')) {
622 				line[len - 1] = '/';
623 				(void) memcpy(curdir, line + 2, (size_t) len + 1 - 2);
624 				curdirlen = (size_t) (len - 2);
625 			} else if ((line[0] == '.') && (line[1] == '\\')) {
626 				line[len - 1] = '\\';
627 				(void) memcpy(curdir, line + 2, (size_t) len + 1 - 2);
628 				curdirlen = (size_t) (len - 2);
629 			} else {
630 				line[len - 1] = '/';
631 				(void) memcpy(curdir, line, (size_t) len + 1);
632 				curdirlen = (size_t) len;
633 			}
634 			continue;
635 		}
636 
637 		linesread++;
638 		rc = UnLslRLine(line, curdir, curdirlen, fname, sizeof(fname), linkto, sizeof(linkto), &ftype, &fsize, &ftime, now, thisyear, &plugend);
639 		if ((rc < 0) && (serverType == kServerTypeMicrosoftFTP)) {
640 			rc = UnDosLine(line, curdir, curdirlen, fname, sizeof(fname), &ftype, &fsize, &ftime);
641 			if (rc == 0) {
642 				*linkto = '\0';
643 				plugend = 0;
644 			}
645 		}
646 		if (rc == 0) {
647 			linesconverted++;
648 			fileLen = strlen(fname);
649 			if (fileLen > maxFileLen)
650 				maxFileLen = fileLen;
651 			fi.relnameLen = fileLen;
652 			fi.relname = StrDup(fname);
653 			fi.rname = NULL;
654 			fi.lname = NULL;
655 			fi.rlinkto = (linkto[0] == '\0') ? NULL : StrDup(linkto);
656 			fi.mdtm = ftime;
657 			fi.size = (longest_int) fsize;
658 			fi.type = ftype;
659 			if (plugend > 0) {
660 				fi.plug = (char *) malloc((size_t) plugend + 1);
661 				if (fi.plug != NULL) {
662 					(void) memcpy(fi.plug, line, (size_t) plugend);
663 					fi.plug[plugend] = '\0';
664 					if ((size_t) plugend > maxPlugLen)
665 						maxPlugLen = (size_t) plugend;
666 				}
667 			} else {
668 				fi.plug = (char *) malloc(32);
669 				if (fi.plug != NULL) {
670 					strcpy(fi.plug, "----------   1 ftpuser  ftpusers");
671 					fi.plug[0] = (char) ftype;
672 					if (30 > maxPlugLen)
673 						maxPlugLen = (size_t) 30;
674 				}
675 			}
676 			(void) AddFileInfo(filp, &fi);
677 		}
678 
679 		hadblankline = 0;
680 	}
681 
682 	filp->maxFileLen = maxFileLen;
683 	filp->maxPlugLen = maxPlugLen;
684 	if (linesread == 0)
685 		return (0);
686 	return ((linesconverted > 0) ? linesconverted : (-1));
687 }	/* UnLslR */
688 
689 
690 
691 
692 int
693 UnMlsT(const char *const line0, const MLstItemPtr mlip)
694 {
695 	char *cp, *val, *fact;
696 	int ec;
697 	size_t len;
698 	char line[1024];
699 
700 	memset(mlip, 0, sizeof(MLstItem));
701 	mlip->mode = -1;
702 	mlip->fsize = kSizeUnknown;
703 	mlip->ftype = '-';
704 	mlip->ftime = kModTimeUnknown;
705 
706 	len = strlen(line0);
707 	if (len > (sizeof(line) - 1))
708 		return (-1);	/* Line too long, sorry. */
709 	/* This should be re-coded so does not need to make a
710 	 * copy of the buffer; it could be done in place.
711 	 */
712 	memcpy(line, line0, len + 1);
713 
714 	/* Skip leading whitespace. */
715 	for (cp = line; *cp != '\0'; cp++) {
716 		if (! isspace(*cp))
717 			break;
718 	}
719 
720 	while (*cp != '\0') {
721 		for (fact = cp; ; cp++) {
722 			if ((*cp == '\0') || (*cp == ' ')) {
723 				/* protocol violation */
724 				return (-1);
725 			}
726 			if (*cp == '=') {
727 				/* End of fact name. */
728 				*cp++ = '\0';
729 				break;
730 			}
731 		}
732 		for (val = cp; ; cp++) {
733 			if (*cp == '\0') {
734 				/* protocol violation */
735 				return (-1);
736 			}
737 			if (*cp == ' ') {
738 				ec = ' ';
739 				*cp++ = '\0';
740 				break;
741 			} else if (*cp == ';') {
742 				if (cp[1] == ' ') {
743 					ec = ' ';
744 					*cp++ = '\0';
745 					*cp++ = '\0';
746 				} else {
747 					ec = ';';
748 					*cp++ = '\0';
749 				}
750 				break;
751 			}
752 		}
753 		if (ISTRNEQ(fact, "OS.", 3))
754 			fact += 3;
755 		if (ISTREQ(fact, "type")) {
756 			if (ISTREQ(val, "file")) {
757 				mlip->ftype = '-';
758 			} else if (ISTREQ(val, "dir")) {
759 				mlip->ftype = 'd';
760 			} else if (ISTREQ(val, "cdir")) {
761 				/* not supported: current directory */
762 				return (-2);
763 			} else if (ISTREQ(val, "pdir")) {
764 				/* not supported: parent directory */
765 				return (-2);
766 			} else {
767 				/* ? */
768 				return (-1);
769 			}
770 		} else if (ISTREQ(fact, "UNIX.mode")) {
771 			if (val[0] == '0')
772 				sscanf(val, "%o", &mlip->mode);
773 			else
774 				sscanf(val, "%i", &mlip->mode);
775 			if (mlip->mode != (-1))
776 				mlip->mode &= 00777;
777 		} else if (ISTREQ(fact, "perm")) {
778 			STRNCPY(mlip->perm, val);
779 		} else if (ISTREQ(fact, "size")) {
780 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG)
781 			(void) sscanf(val, SCANF_LONG_LONG, &mlip->fsize);
782 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ)
783 			mlip->fsize = (longest_int) strtoq(val, NULL, 0);
784 #else
785 			{
786 				long fsize2 = 0L;
787 
788 				(void) sscanf(val, "%ld", &fsize2);
789 				mlip->fsize = (longest_int) fsize2;
790 			}
791 #endif
792 		} else if (ISTREQ(fact, "modify")) {
793 			mlip->ftime = UnMDTMDate(val);
794 		} else if (ISTREQ(fact, "UNIX.owner")) {
795 			STRNCPY(mlip->owner, val);
796 		} else if (ISTREQ(fact, "UNIX.group")) {
797 			STRNCPY(mlip->group, val);
798 		} else if (ISTREQ(fact, "UNIX.uid")) {
799 			mlip->uid = atoi(val);
800 		} else if (ISTREQ(fact, "UNIX.gid")) {
801 			mlip->gid = atoi(val);
802 		} else if (ISTREQ(fact, "perm")) {
803 			STRNCPY(mlip->perm, val);
804 		}
805 
806 		/* End of facts? */
807 		if (ec == ' ')
808 			break;
809 	}
810 
811 	len = strlen(cp);
812 	if (len > (sizeof(mlip->fname) - 1)) {
813 		/* Filename too long */
814 		return (-1);
815 	}
816 	memcpy(mlip->fname, cp, len);
817 
818 	/* also set linkto here if used */
819 
820 	return (0);
821 }	/* UnMlsT */
822 
823 
824 
825 
826 int
827 UnMlsD(FileInfoListPtr filp, LineListPtr llp)
828 {
829 	MLstItem mli;
830 	char plug[64];
831 	char og[32];
832 	int rc;
833 	LinePtr lp;
834 	FileInfo fi;
835 	int linesread = 0;
836 	int linesconverted = 0;
837 	int linesignored = 0;
838 	size_t maxFileLen = 0;
839 	size_t maxPlugLen = 0;
840 	size_t fileLen, plugLen;
841 	int m1, m2, m3;
842 	const char *cm1, *cm2, *cm3;
843 
844 	InitFileInfoList(filp);
845 	for (lp = llp->first; lp != NULL; lp = lp->next) {
846 		linesread++;
847 		rc = UnMlsT(lp->line, &mli);
848 		if (rc == 0) {
849 			linesconverted++;
850 			fileLen = strlen(mli.fname);
851 			if (fileLen > maxFileLen)
852 				maxFileLen = fileLen;
853 			fi.relnameLen = fileLen;
854 			fi.relname = StrDup(mli.fname);
855 			fi.rname = NULL;
856 			fi.lname = NULL;
857 			fi.rlinkto = (mli.linkto[0] == '\0') ? NULL : StrDup(mli.linkto);
858 			fi.mdtm = mli.ftime;
859 			fi.size = (longest_int) mli.fsize;
860 			fi.type = mli.ftype;
861 			plug[0] = (char) mli.ftype;
862 			plug[1] = '\0';
863 			m1 = 0;
864 			m2 = 0;
865 			m3 = -1;
866 			if (mli.mode != (-1)) {
867 				m1 = (mli.mode & 00700) >> 6;
868 				m2 = (mli.mode & 00070) >> 3;
869 				m3 = (mli.mode & 00007);
870 			}
871 			if (mli.perm[0] != '\0') {
872 				m3 = 0;
873 				if (fi.type == 'd') {
874 					if (strchr(mli.perm, 'e') != NULL) {
875 						/* execute -> execute */
876 						m3 |= 00001;
877 					}
878 					if (strchr(mli.perm, 'c') != NULL) {
879 						/* create -> write */
880 						m3 |= 00002;
881 					}
882 					if (strchr(mli.perm, 'l') != NULL) {
883 						/* list -> read */
884 						m3 |= 00004;
885 					}
886 				} else {
887 					if (strchr(mli.perm, 'w') != NULL) {
888 						/* write -> write */
889 						m3 |= 00002;
890 					}
891 					if (strchr(mli.perm, 'r') != NULL) {
892 						/* read -> read */
893 						m3 |= 00004;
894 					}
895 				}
896 			}
897 			if (m3 != (-1)) {
898 				cm1 = rwx[m1];
899 				cm2 = rwx[m2];
900 				cm3 = rwx[m3];
901 				sprintf(plug + 1, "%s%s%s", cm1, cm2, cm3);
902 			}
903 			if (mli.owner[0] != '\0') {
904 				if (mli.group[0] != '\0') {
905 #ifdef HAVE_SNPRINTF
906 					snprintf(og, sizeof(og) - 1,
907 #else
908 					sprintf(og,
909 #endif	/* HAVE_SNPRINTF */
910 						"   %-8.8s %s",
911 						mli.owner, mli.group
912 					);
913 					STRNCAT(plug, og);
914 				} else {
915 					STRNCAT(plug, "   ");
916 					STRNCAT(plug, mli.owner);
917 				}
918 			}
919 			fi.plug = StrDup(plug);
920 			if (fi.plug != NULL) {
921 				plugLen = strlen(plug);
922 				if (plugLen > maxPlugLen)
923 					maxPlugLen = plugLen;
924 			}
925 			(void) AddFileInfo(filp, &fi);
926 		} else if (rc == (-2)) {
927 			linesignored++;
928 		}
929 	}
930 
931 	filp->maxFileLen = maxFileLen;
932 	filp->maxPlugLen = maxPlugLen;
933 	if (linesread == 0)
934 		return (0);
935 	linesconverted += linesignored;
936 	return ((linesconverted > 0) ? linesconverted : (-1));
937 }	/* UnMlsD */
938 
939 
940 
941 #if 0
942 static void
943 print1(FileInfoListPtr list)
944 {
945 	FileInfoPtr fip;
946 	int i;
947 
948 	for (i = 1, fip = list->first; fip != NULL; fip = fip->next, i++)
949 		printf("%d: %s\n", i, fip->relname == NULL ? "NULL" : fip->relname);
950 }
951 
952 
953 
954 static void
955 print2(FileInfoListPtr list)
956 {
957 	FileInfoPtr fip;
958 	int i, n;
959 
960 	n = list->nFileInfos;
961 	for (i=0; i<n; i++) {
962 		fip = list->vec[i];
963 		printf("%d: %s\n", i + 1, fip->relname == NULL ? "NULL" : fip->relname);
964 	}
965 }
966 
967 
968 
969 
970 static void
971 SortRecursiveFileList(FileInfoListPtr files)
972 {
973 	VectorizeFileInfoList(files);
974 	SortFileInfoList(files, 'b', '?');
975 	UnvectorizeFileInfoList(files);
976 }	/* SortRecursiveFileList */
977 #endif
978 
979 
980 
981 
982 int
983 FTPRemoteRecursiveFileList1(FTPCIPtr cip, char *const rdir, FileInfoListPtr files)
984 {
985 	LineList dirContents;
986 	FileInfoList fil;
987 	char cwd[512];
988 	int result;
989 
990 	if ((result = FTPGetCWD(cip, cwd, sizeof(cwd))) < 0)
991 		return (result);
992 
993 	InitFileInfoList(files);
994 
995 	if (rdir == NULL)
996 		return (-1);
997 
998 	if (FTPChdir(cip, rdir) < 0) {
999 		/* Probably not a directory.
1000 		 * Just add it as a plain file
1001 		 * to the list.
1002 		 */
1003 		(void) ConcatFileToFileInfoList(files, rdir);
1004 		return (kNoErr);
1005 	}
1006 
1007 	/* Paths collected must be relative. */
1008 	if ((result = FTPListToMemory2(cip, "", &dirContents, "-lRa", 1, (int *) 0)) < 0) {
1009 		if ((result = FTPChdir(cip, cwd)) < 0) {
1010 			return (result);
1011 		}
1012 	}
1013 
1014 	(void) UnLslR(&fil, &dirContents, cip->serverType);
1015 	DisposeLineListContents(&dirContents);
1016 	/* Could sort it to breadth-first here. */
1017 	/* (void) SortRecursiveFileList(&fil); */
1018 	(void) ComputeRNames(&fil, rdir, 1, 1);
1019 	(void) ConcatFileInfoList(files, &fil);
1020 	DisposeFileInfoListContents(&fil);
1021 
1022 	if ((result = FTPChdir(cip, cwd)) < 0) {
1023 		return (result);
1024 	}
1025 	return (kNoErr);
1026 }	/* FTPRemoteRecursiveFileList1 */
1027 
1028 
1029 
1030 
1031 int
1032 FTPRemoteRecursiveFileList(FTPCIPtr cip, LineListPtr fileList, FileInfoListPtr files)
1033 {
1034 	LinePtr filePtr, nextFilePtr;
1035 	LineList dirContents;
1036 	FileInfoList fil;
1037 	char cwd[512];
1038 	int result;
1039 	char *rdir;
1040 
1041 	if ((result = FTPGetCWD(cip, cwd, sizeof(cwd))) < 0)
1042 		return (result);
1043 
1044 	InitFileInfoList(files);
1045 
1046 	for (filePtr = fileList->first;
1047 		filePtr != NULL;
1048 		filePtr = nextFilePtr)
1049 	{
1050 		nextFilePtr = filePtr->next;
1051 
1052 		rdir = filePtr->line;
1053 		if (rdir == NULL)
1054 			continue;
1055 
1056 		if (FTPChdir(cip, rdir) < 0) {
1057 			/* Probably not a directory.
1058 			 * Just add it as a plain file
1059 			 * to the list.
1060 			 */
1061 			(void) ConcatFileToFileInfoList(files, rdir);
1062 			continue;
1063 		}
1064 
1065 		/* Paths collected must be relative. */
1066 		if ((result = FTPListToMemory2(cip, "", &dirContents, "-lRa", 1, (int *) 0)) < 0) {
1067 			goto goback;
1068 		}
1069 
1070 		(void) UnLslR(&fil, &dirContents, cip->serverType);
1071 		DisposeLineListContents(&dirContents);
1072 		(void) ComputeRNames(&fil, rdir, 1, 1);
1073 		(void) ConcatFileInfoList(files, &fil);
1074 		DisposeFileInfoListContents(&fil);
1075 
1076 goback:
1077 		if ((result = FTPChdir(cip, cwd)) < 0) {
1078 			return (result);
1079 		}
1080 	}
1081 	return (kNoErr);
1082 }	/* FTPRemoteRecursiveFileList */
1083 
1084 
1085 
1086 #if defined(WIN32) || defined(_WINDOWS)
1087 
1088 static void
1089 Traverse(FTPCIPtr cip, char *fullpath, struct Stat *st, char *relpath, FileInfoListPtr filp)
1090 {
1091 	WIN32_FIND_DATA ffd;
1092 	HANDLE searchHandle;
1093 	DWORD dwErr;
1094 	char *cp, *c2;
1095 	const char *file;
1096 	FileInfo fi;
1097 
1098 	/* Handle directory entry first. */
1099 	if (relpath[0] != '\0') {
1100 		fi.relname = StrDup(relpath);
1101 		fi.rname = NULL;
1102 		fi.lname = StrDup(fullpath);
1103 		fi.rlinkto = NULL;
1104 		fi.plug = NULL;
1105 		fi.mdtm = st->st_mtime;
1106 		fi.size = (longest_int) st->st_size;
1107 		fi.type = 'd';
1108 		(void) AddFileInfo(filp, &fi);
1109 	}
1110 
1111 	cp = fullpath + strlen(fullpath);
1112 	*cp++ = LOCAL_PATH_DELIM;
1113 	strcpy(cp, "*.*");
1114 
1115 	c2 = relpath + strlen(relpath);
1116 	*c2++ = LOCAL_PATH_DELIM;
1117 	*c2 = '\0';
1118 
1119 	memset(&ffd, 0, sizeof(ffd));
1120 
1121 	/* "Open" the directory. */
1122 	searchHandle = FindFirstFile(fullpath, &ffd);
1123 	if (searchHandle == INVALID_HANDLE_VALUE) {
1124 		return;
1125 	}
1126 
1127 	for (;;) {
1128 
1129 		file = ffd.cFileName;
1130 		if ((*file == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0')))) {
1131 			/* It was "." or "..", so skip it. */
1132 			goto next;
1133 		}
1134 
1135 		(void) strcpy(cp, file);	/* append name after slash */
1136 		(void) strcpy(c2, file);
1137 
1138 		if (Lstat(fullpath, st) < 0) {
1139 			Error(cip, kDoPerror, "could not stat %s.\n", fullpath);
1140 			goto next;
1141 		}
1142 
1143 		fi.relname = StrDup(relpath + (((relpath[0] == '/') || (relpath[0] == '\\')) ? 1 : 0));
1144 		fi.rname = NULL;
1145 		fi.lname = StrDup(fullpath);
1146 		fi.mdtm = st->st_mtime;
1147 		fi.size = (longest_int) st->st_size;
1148 		fi.rlinkto = NULL;
1149 		fi.plug = NULL;
1150 
1151 		if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
1152 			Traverse(cip, fullpath, st, relpath, filp);
1153 		} else {
1154 			/* file */
1155 			fi.type = '-';
1156 			(void) AddFileInfo(filp, &fi);
1157 		}
1158 
1159 next:
1160 #ifndef __REACTOS__
1161 #if _DEBUG
1162 		memset(&ffd, 0, sizeof(ffd));
1163 #endif
1164 #else //  __REACTOS__
1165 #ifdef _DEBUG
1166 		memset(&ffd, 0, sizeof(ffd));
1167 #endif
1168 #endif //  __REACTOS__
1169 		if (!FindNextFile(searchHandle, &ffd)) {
1170 			dwErr = GetLastError();
1171 			if (dwErr != ERROR_NO_MORE_FILES) {
1172 				FindClose(searchHandle);
1173 				return;
1174 			}
1175 			break;
1176 		}
1177 	}
1178 	FindClose(searchHandle);
1179 }	// Traverse
1180 
1181 #else
1182 
1183 static void
1184 Traverse(FTPCIPtr cip, char *fullpath, struct Stat *st, char *relpath, FileInfoListPtr filp)
1185 {
1186 	char *dname;
1187 	struct dirent *dirp;
1188 	mode_t m;
1189 	DIR *dp;
1190 	char *cp;
1191 	char *c2;
1192 	FileInfo fi;
1193 
1194 	if (relpath[0] != '\0') {
1195 		fi.relname = StrDup(relpath);
1196 		fi.rname = NULL;
1197 		fi.lname = StrDup(fullpath);
1198 		fi.rlinkto = NULL;
1199 		fi.plug = NULL;
1200 		fi.mdtm = st->st_mtime;
1201 		fi.size = (longest_int) st->st_size;
1202 		fi.type = 'd';
1203 		(void) AddFileInfo(filp, &fi);
1204 	}
1205 
1206 	/* Handle directory entry first. */
1207 	cp = fullpath + strlen(fullpath);
1208 	*cp++ = '/';
1209 	*cp = '\0';
1210 
1211 	c2 = relpath + strlen(relpath);
1212 	*c2++ = '/';
1213 	*c2 = '\0';
1214 
1215 	if ((dp = opendir(fullpath)) == NULL) {
1216 		cp[-1] = '\0';
1217 		c2[-1] = '\0';
1218 		Error(cip, kDoPerror, "could not opendir %s.\n", fullpath);
1219 		return;
1220 	}
1221 
1222 	while ((dirp = readdir(dp)) != NULL) {
1223 		dname = dirp->d_name;
1224 		if ((dname[0] == '.') && ((dname[1] == '\0') || ((dname[1] == '.') && (dname[2] == '\0'))))
1225 			continue;	/* skip "." and ".." directories. */
1226 
1227 		(void) strcpy(cp, dirp->d_name);	/* append name after slash */
1228 		(void) strcpy(c2, dirp->d_name);
1229 		if (Lstat(fullpath, st) < 0) {
1230 			Error(cip, kDoPerror, "could not stat %s.\n", fullpath);
1231 			continue;
1232 		}
1233 
1234 		fi.relname = StrDup(relpath + (((relpath[0] == '/') || (relpath[0] == '\\')) ? 1 : 0));
1235 		fi.rname = NULL;
1236 		fi.lname = StrDup(fullpath);
1237 		fi.mdtm = st->st_mtime;
1238 		fi.size = (longest_int) st->st_size;
1239 		fi.rlinkto = NULL;
1240 		fi.plug = NULL;
1241 
1242 		m = st->st_mode;
1243 		if (S_ISREG(m) != 0) {
1244 			/* file */
1245 			fi.type = '-';
1246 			(void) AddFileInfo(filp, &fi);
1247 		} else if (S_ISDIR(m)) {
1248 			Traverse(cip, fullpath, st, relpath, filp);
1249 #ifdef S_ISLNK
1250 		} else if (S_ISLNK(m)) {
1251 			fi.type = 'l';
1252 			fi.rlinkto = calloc(128, 1);
1253 			if (fi.rlinkto != NULL) {
1254 				if (readlink(fullpath, fi.rlinkto, 127) < 0) {
1255 					free(fi.rlinkto);
1256 				} else {
1257 					(void) AddFileInfo(filp, &fi);
1258 				}
1259 			}
1260 #endif	/* S_ISLNK */
1261 		}
1262 	}
1263 	cp[-1] = '\0';
1264 	c2[-1] = '\0';
1265 
1266 	(void) closedir(dp);
1267 }	/* Traverse */
1268 
1269 #endif
1270 
1271 
1272 
1273 
1274 
1275 int
1276 FTPLocalRecursiveFileList2(FTPCIPtr cip, LineListPtr fileList, FileInfoListPtr files, int erelative)
1277 {
1278 	LinePtr filePtr, nextFilePtr;
1279 #if defined(WIN32) || defined(_WINDOWS)
1280 	char fullpath[_MAX_PATH + 1];
1281 	char relpath[_MAX_PATH + 1];
1282 #else
1283 	char fullpath[512];
1284 	char relpath[512];
1285 #endif
1286 	struct Stat st;
1287 	FileInfo fi;
1288 	char *cp;
1289 
1290 	InitFileInfoList(files);
1291 
1292 	for (filePtr = fileList->first;
1293 		filePtr != NULL;
1294 		filePtr = nextFilePtr)
1295 	{
1296 		nextFilePtr = filePtr->next;
1297 
1298 		(void) STRNCPY(fullpath, filePtr->line);	/* initialize fullpath */
1299 		if ((erelative != 0) || (strcmp(filePtr->line, ".") == 0) || (filePtr->line[0] == '\0'))
1300 			(void) STRNCPY(relpath, "");
1301 		else if ((cp = StrRFindLocalPathDelim(filePtr->line)) == NULL)
1302 			(void) STRNCPY(relpath, filePtr->line);
1303 		else
1304 			(void) STRNCPY(relpath, cp + 1);
1305 		if (Lstat(fullpath, &st) < 0) {
1306 			Error(cip, kDoPerror, "could not stat %s.\n", fullpath);
1307 			continue;
1308 		}
1309 
1310 		if (S_ISDIR(st.st_mode) == 0) {
1311 			fi.relname = StrDup(relpath);
1312 			fi.rname = NULL;
1313 			fi.lname = StrDup(fullpath);
1314 			fi.mdtm = st.st_mtime;
1315 			fi.size = (longest_int) st.st_size;
1316 			fi.rlinkto = NULL;
1317 			fi.plug = NULL;
1318 			fi.type = '-';
1319 			(void) AddFileInfo(files, &fi);
1320 			continue;			/* wasn't a directory */
1321 		}
1322 
1323 		/* Paths collected must be relative. */
1324 		Traverse(cip, fullpath, &st, relpath, files);
1325 	}
1326 	return (kNoErr);
1327 }	/* FTPLocalRecursiveFileList */
1328 
1329 
1330 
1331 
1332 int
1333 FTPLocalRecursiveFileList(FTPCIPtr cip, LineListPtr fileList, FileInfoListPtr files)
1334 {
1335 	return (FTPLocalRecursiveFileList2(cip, fileList, files, 0));
1336 }	/* FTPLocalRecursiveFileList */
1337 
1338 
1339 
1340 int
1341 FTPRemoteGlob(FTPCIPtr cip, LineListPtr fileList, const char *pattern, int doGlob)
1342 {
1343 	char *cp;
1344 	const char *lsflags;
1345 	LinePtr lp;
1346 	int result;
1347 
1348 	if (cip == NULL)
1349 		return (kErrBadParameter);
1350 	if (strcmp(cip->magic, kLibraryMagic))
1351 		return (kErrBadMagic);
1352 
1353 	if (fileList == NULL)
1354 		return (kErrBadParameter);
1355 	InitLineList(fileList);
1356 
1357 	if ((pattern == NULL) || (pattern[0] == '\0'))
1358 		return (kErrBadParameter);
1359 
1360 	/* Note that we do attempt to use glob characters even if the remote
1361 	 * host isn't UNIX.  Most non-UNIX remote FTP servers look for UNIX
1362 	 * style wildcards.
1363 	 */
1364 	if ((doGlob == 1) && (GLOBCHARSINSTR(pattern))) {
1365 		/* Use NLST, which lists files one per line. */
1366 		lsflags = "";
1367 
1368 		/* Optimize for "NLST *" case which is same as "NLST". */
1369 		if (strcmp(pattern, "*") == 0) {
1370 			pattern = "";
1371 		} else if (strcmp(pattern, "**") == 0) {
1372 			/* Hack; Lets you try "NLST -a" if you're daring. */
1373 			pattern = "";
1374 			lsflags = "-a";
1375 		}
1376 
1377 		if ((result = FTPListToMemory2(cip, pattern, fileList, lsflags, 0, (int *) 0)) < 0) {
1378 			if (*lsflags == '\0')
1379 				return (result);
1380 			/* Try again, without "-a" */
1381 			lsflags = "";
1382 			if ((result = FTPListToMemory2(cip, pattern, fileList, lsflags, 0, (int *) 0)) < 0) {
1383 				return (result);
1384 			}
1385 		}
1386 		if (fileList->first == NULL) {
1387 			cip->errNo = kErrGlobNoMatch;
1388 			return (kErrGlobNoMatch);
1389 		}
1390 		if (fileList->first == fileList->last) {
1391 #define glberr(a) (ISTRNEQ(cp, a, strlen(a)))
1392 			/* If we have only one item in the list, see if it really was
1393 			 * an error message we would recognize.
1394 			 */
1395 			cp = strchr(fileList->first->line, ':');
1396 			if (cp != NULL) {
1397 				if (glberr(": No such file or directory")) {
1398 					(void) RemoveLine(fileList, fileList->first);
1399 					cip->errNo = kErrGlobFailed;
1400 					return (kErrGlobFailed);
1401 				} else if (glberr(": No match")) {
1402 					cip->errNo = kErrGlobNoMatch;
1403 					return (kErrGlobNoMatch);
1404 				}
1405 			}
1406 		}
1407 		RemoteGlobCollapse(pattern, fileList);
1408 		for (lp=fileList->first; lp != NULL; lp = lp->next)
1409 			PrintF(cip, "  Rglob [%s]\n", lp->line);
1410 	} else {
1411 		/* Or, if there were no globbing characters in 'pattern', then the
1412 		 * pattern is really just a filename.  So for this case the
1413 		 * file list is really just a single file.
1414 		 */
1415 		fileList->first = fileList->last = NULL;
1416 		(void) AddLine(fileList, pattern);
1417 	}
1418 	return (kNoErr);
1419 }	/* FTPRemoteGlob */
1420 
1421 
1422 
1423 
1424 /* This does "tilde-expansion."  Examples:
1425  * ~/pub         -->  /usr/gleason/pub
1426  * ~pdietz/junk  -->  /usr/pdietz/junk
1427  */
1428 static void
1429 ExpandTilde(char *pattern, size_t siz)
1430 {
1431 	string pat;
1432 	char *cp, *rest, *firstent;
1433 #if defined(WIN32) || defined(_WINDOWS)
1434 #else
1435 	struct passwd *pw;
1436 #endif
1437 	string hdir;
1438 
1439 	if ((pattern[0] == '~') &&
1440 	(isalnum((int) pattern[1]) || IsLocalPathDelim(pattern[1]) || (pattern[1] == '\0'))) {
1441 		(void) STRNCPY(pat, pattern);
1442 		if ((cp = StrFindLocalPathDelim(pat)) != NULL) {
1443 			*cp = 0;
1444 			rest = cp + 1;	/* Remember stuff after the ~/ part. */
1445 		} else {
1446 			rest = NULL;	/* Was just a ~ or ~username.  */
1447 		}
1448 		if (pat[1] == '\0') {
1449 			/* Was just a ~ or ~/rest type.  */
1450 			GetHomeDir(hdir, sizeof(hdir));
1451 			firstent = hdir;
1452 		} else {
1453 #if defined(WIN32) || defined(_WINDOWS)
1454 			return;
1455 #else
1456 			/* Was just a ~username or ~username/rest type.  */
1457 			pw = getpwnam(pat + 1);
1458 			if (pw != NULL)
1459 				firstent = pw->pw_dir;
1460 			else
1461 				return;		/* Bad user -- leave it alone. */
1462 #endif
1463 		}
1464 
1465 		(void) Strncpy(pattern, firstent, siz);
1466 		if (rest != NULL) {
1467 			(void) Strncat(pattern, LOCAL_PATH_DELIM_STR, siz);
1468 			(void) Strncat(pattern, rest, siz);
1469 		}
1470 	}
1471 }	/* ExpandTilde */
1472 
1473 
1474 
1475 
1476 
1477 #if defined(WIN32) || defined(_WINDOWS)
1478 
1479 static int
1480 WinLocalGlob(FTPCIPtr cip, LineListPtr fileList, const char *const srcpat)
1481 {
1482 	char pattern[_MAX_PATH];
1483 	WIN32_FIND_DATA ffd;
1484 	HANDLE searchHandle;
1485 	DWORD dwErr;
1486 	char *cp;
1487 	const char *file;
1488 	int result;
1489 
1490 	STRNCPY(pattern, srcpat);
1491 
1492 	/* Get rid of trailing slashes. */
1493 	cp = pattern + strlen(pattern) - 1;
1494 	while ((cp >= pattern) && IsLocalPathDelim(*cp))
1495 		*cp-- = '\0';
1496 
1497 	memset(&ffd, 0, sizeof(ffd));
1498 
1499 	/* "Open" the directory. */
1500 	searchHandle = FindFirstFile(pattern, &ffd);
1501 	if (searchHandle == INVALID_HANDLE_VALUE) {
1502 		dwErr = GetLastError();
1503 		return ((dwErr == 0) ? 0 : -1);
1504 	}
1505 
1506 	/* Get rid of basename. */
1507 	cp = StrRFindLocalPathDelim(pattern);
1508 	if (cp == NULL)
1509 		cp = pattern;
1510 	else
1511 		cp++;
1512 	*cp = '\0';
1513 
1514 	for (result = 0;;) {
1515 		file = ffd.cFileName;
1516 		if ((file[0] == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0')))) {
1517 			/* skip */
1518 		} else {
1519 			Strncpy(cp, ffd.cFileName, sizeof(pattern) - (cp - pattern));
1520 			PrintF(cip, "  Lglob [%s]\n", pattern);
1521 			(void) AddLine(fileList, pattern);
1522 		}
1523 
1524 		if (!FindNextFile(searchHandle, &ffd)) {
1525 			dwErr = GetLastError();
1526 			if (dwErr != ERROR_NO_MORE_FILES) {
1527 				result = ((dwErr == 0) ? 0 : -1);
1528 			}
1529 			break;
1530 		}
1531 	}
1532 
1533 	return (result);
1534 }	// WinLocalGlob
1535 
1536 #else
1537 
1538 static int
1539 LazyUnixLocalGlob(FTPCIPtr cip, LineListPtr fileList, const char *const pattern)
1540 {
1541 	string cmd;
1542 	longstring gfile;
1543 	FILE *fp;
1544 	FTPSigProc sp;
1545 
1546 	/* Do it the easy way and have the shell do the dirty
1547 	 * work for us.
1548 	 */
1549 #ifdef HAVE_SNPRINTF
1550 	(void) snprintf(cmd, sizeof(cmd) - 1, "%s -c \"%s %s %s\"", "/bin/sh", "/bin/ls",
1551 		"-d", pattern);
1552 	cmd[sizeof(cmd) - 1] = '\0';
1553 #else
1554 	(void) sprintf(cmd, "%s -c \"%s %s %s\"", "/bin/sh", "/bin/ls",
1555 		"-d", pattern);
1556 #endif
1557 
1558 	fp = (FILE *) popen(cmd, "r");
1559 	if (fp == NULL) {
1560 		Error(cip, kDoPerror, "Could not Lglob: [%s]\n", cmd);
1561 		cip->errNo = kErrGlobFailed;
1562 		return (kErrGlobFailed);
1563 	}
1564 	sp = NcSignal(SIGPIPE, (FTPSigProc) SIG_IGN);
1565 	while (FGets(gfile, sizeof(gfile), (FILE *) fp) != NULL) {
1566 		PrintF(cip, "  Lglob [%s]\n", gfile);
1567 		(void) AddLine(fileList, gfile);
1568 	}
1569 	(void) pclose(fp);
1570 	(void) NcSignal(SIGPIPE, sp);
1571 	return (kNoErr);
1572 }	/* LazyUnixLocalGlob */
1573 
1574 #endif
1575 
1576 
1577 
1578 
1579 int
1580 FTPLocalGlob(FTPCIPtr cip, LineListPtr fileList, const char *pattern, int doGlob)
1581 {
1582 	string pattern2;
1583 	int result;
1584 
1585 	if (cip == NULL)
1586 		return (kErrBadParameter);
1587 	if (strcmp(cip->magic, kLibraryMagic))
1588 		return (kErrBadMagic);
1589 
1590 	if (fileList == NULL)
1591 		return (kErrBadParameter);
1592 	InitLineList(fileList);
1593 
1594 	if ((pattern == NULL) || (pattern[0] == '\0'))
1595 		return (kErrBadParameter);
1596 
1597 	(void) STRNCPY(pattern2, pattern);	/* Don't nuke the original. */
1598 
1599 	/* Pre-process for ~'s. */
1600 	ExpandTilde(pattern2, sizeof(pattern2));
1601 	InitLineList(fileList);
1602 	result = kNoErr;
1603 
1604 	if ((doGlob == 1) && (GLOBCHARSINSTR(pattern2))) {
1605 #if defined(WIN32) || defined(_WINDOWS)
1606 		result = WinLocalGlob(cip, fileList, pattern2);
1607 #else
1608 		result = LazyUnixLocalGlob(cip, fileList, pattern2);
1609 #endif
1610 	} else {
1611 		/* Or, if there were no globbing characters in 'pattern', then
1612 		 * the pattern is really just a single pathname.
1613 		 */
1614 		(void) AddLine(fileList, pattern2);
1615 	}
1616 
1617 	return (result);
1618 }	/* FTPLocalGlob */
1619 
1620 
1621 
1622 
1623 static int
1624 FTPFtwL2(const FTPCIPtr cip, char *dir, char *end, size_t dirsize, FTPFtwProc proc, int maxdepth)
1625 {
1626 	LineList fileList;
1627 	LinePtr filePtr;
1628 	char *file, *cp;
1629 	int result;
1630 
1631 	if (maxdepth <= 0) {
1632 		result = cip->errNo = kErrRecursionLimitReached;
1633 		return (result);
1634 	}
1635 
1636 	result = FTPRemoteGlob(cip, &fileList, "**", kGlobYes);
1637 	if (result != kNoErr) {
1638 		if (result == kErrGlobNoMatch)
1639 			result = kNoErr;	/* empty directory is okay. */
1640 		return (result);
1641 	}
1642 
1643 	for (filePtr = fileList.first;
1644 		filePtr != NULL;
1645 		filePtr = filePtr->next)
1646 	{
1647 		file = filePtr->line;
1648 		if (file == NULL) {
1649 			cip->errNo = kErrBadLineList;
1650 			break;
1651 		}
1652 
1653 		if ((file[0] == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0'))))
1654 			continue;	/* Skip . and .. */
1655 
1656 		result = FTPIsDir(cip, file);
1657 		if (result < 0) {
1658 			/* error */
1659 			/* could be just a stat error, so continue */
1660 			continue;
1661 		} else if (result == 1) {
1662 			/* directory */
1663 			cp = Strnpcat(dir, file, dirsize);
1664 			result = (*proc)(cip, dir, kFtwDir);
1665 			if (result != kNoErr)
1666 				break;
1667 
1668 			if ((strchr(dir, '/') == NULL) && (strrchr(dir, '\\') != NULL))
1669 				*cp++ = '\\';
1670 			else
1671 				*cp++ = '/';
1672 			*cp = '\0';
1673 
1674 			result = FTPChdir(cip, file);
1675 			if (result == kNoErr) {
1676 				result = FTPFtwL2(cip, dir, cp, dirsize, proc, maxdepth - 1);
1677 				if (result != kNoErr)
1678 					break;
1679 				if (FTPChdir(cip, "..") < 0) {
1680 					result = kErrCannotGoToPrevDir;
1681 					cip->errNo = kErrCannotGoToPrevDir;
1682 					break;
1683 				}
1684 			}
1685 
1686 			*end = '\0';
1687 			if (result != 0)
1688 				break;
1689 		} else {
1690 			/* file */
1691 			cp = Strnpcat(dir, file, dirsize);
1692 			result = (*proc)(cip, dir, kFtwFile);
1693 			*end = '\0';
1694 			if (result != 0)
1695 				break;
1696 		}
1697 	}
1698 	DisposeLineListContents(&fileList);
1699 
1700 	return (result);
1701 } 	/* FTPFtwL2 */
1702 
1703 
1704 
1705 int
1706 FTPFtw(const FTPCIPtr cip, const char *const dir, FTPFtwProc proc, int maxdepth)
1707 {
1708 	int result, result2;
1709 	char *cp;
1710 	char savedcwd[1024];
1711 	char curcwd[2048];
1712 
1713 	result = FTPIsDir(cip, dir);
1714 	if (result < 0) {
1715 		/* error */
1716 		return result;
1717 	} else if (result == 0) {
1718 		result = cip->errNo = kErrNotADirectory;
1719 		return (result);
1720 	}
1721 
1722 	/* Preserve old working directory. */
1723 	(void) FTPGetCWD(cip, savedcwd, sizeof(savedcwd));
1724 
1725 	result = FTPChdir(cip, dir);
1726 	if (result != kNoErr) {
1727 		return (result);
1728 	}
1729 
1730 	/* Get full working directory we just changed to. */
1731 	result = FTPGetCWD(cip, curcwd, sizeof(curcwd) - 3);
1732 	if (result != kNoErr) {
1733 		if (FTPChdir(cip, savedcwd) != kNoErr) {
1734 			result = kErrCannotGoToPrevDir;
1735 			cip->errNo = kErrCannotGoToPrevDir;
1736 		}
1737 		return (result);
1738 	}
1739 
1740 	result2 = (*proc)(cip, curcwd, kFtwDir);
1741 	if (result2 == kNoErr) {
1742 		cp = curcwd + strlen(curcwd);
1743 
1744 		if ((strchr(curcwd, '/') == NULL) && (strrchr(curcwd, '\\') != NULL))
1745 			*cp++ = '\\';
1746 		else
1747 			*cp++ = '/';
1748 		*cp = '\0';
1749 		result = FTPFtwL2(cip, curcwd, cp, sizeof(curcwd), proc, maxdepth - 1);
1750 	}
1751 
1752 
1753 	if (FTPChdir(cip, savedcwd) != kNoErr) {
1754 		/* Could not cd back to the original user directory -- bad. */
1755 		result = kErrCannotGoToPrevDir;
1756 		cip->errNo = kErrCannotGoToPrevDir;
1757 		return (result);
1758 	}
1759 
1760 	if ((result2 != kNoErr) && (result == kNoErr))
1761 		result = result2;
1762 
1763 	return (result);
1764 }	/* FTPFtw */
1765 
1766 /* eof */
1767