1 /* cmds.c
2  *
3  * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
4  * All rights reserved.
5  *
6  */
7 
8 #include "syshdrs.h"
9 
10 int
11 FTPChdir(const FTPCIPtr cip, const char *const cdCwd)
12 {
13 	int result;
14 
15 	if (cip == NULL)
16 		return (kErrBadParameter);
17 	if (strcmp(cip->magic, kLibraryMagic))
18 		return (kErrBadMagic);
19 
20 	if (cdCwd == NULL) {
21 		result = kErrInvalidDirParam;
22 		cip->errNo = kErrInvalidDirParam;
23 	} else {
24 		if (cdCwd[0] == '\0')	/* But allow FTPChdir(cip, ".") to go through. */
25 			result = 2;
26 		else if (strcmp(cdCwd, "..") == 0)
27 			result = FTPCmd(cip, "CDUP");
28 		else
29 			result = FTPCmd(cip, "CWD %s", cdCwd);
30 		if (result >= 0) {
31 			if (result == 2) {
32 				result = kNoErr;
33 			} else {
34 				result = kErrCWDFailed;
35 				cip->errNo = kErrCWDFailed;
36 			}
37 		}
38 	}
39 	return (result);
40 }	/* FTPChdir */
41 
42 
43 
44 
45 int
46 FTPChmod(const FTPCIPtr cip, const char *const pattern, const char *const mode, const int doGlob)
47 {
48 	LineList fileList;
49 	LinePtr filePtr;
50 	char *file;
51 	int onceResult, batchResult;
52 
53 	if (cip == NULL)
54 		return (kErrBadParameter);
55 	if (strcmp(cip->magic, kLibraryMagic))
56 		return (kErrBadMagic);
57 
58 	batchResult = FTPRemoteGlob(cip, &fileList, pattern, doGlob);
59 	if (batchResult != kNoErr)
60 		return (batchResult);
61 
62 	for (batchResult = kNoErr, filePtr = fileList.first;
63 		filePtr != NULL;
64 		filePtr = filePtr->next)
65 	{
66 		file = filePtr->line;
67 		if (file == NULL) {
68 			batchResult = kErrBadLineList;
69 			cip->errNo = kErrBadLineList;
70 			break;
71 		}
72 		onceResult = FTPCmd(cip, "SITE CHMOD %s %s", mode, file);
73 		if (onceResult < 0) {
74 			batchResult = onceResult;
75 			break;
76 		}
77 		if (onceResult != 2) {
78 			batchResult = kErrChmodFailed;
79 			cip->errNo = kErrChmodFailed;
80 		}
81 	}
82 	DisposeLineListContents(&fileList);
83 	return (batchResult);
84 }	/* FTPChmod */
85 
86 
87 
88 
89 static int
90 FTPRmdirRecursiveL2(const FTPCIPtr cip)
91 {
92 	LineList fileList;
93 	LinePtr filePtr;
94 	char *file;
95 	int result;
96 
97 	result = FTPRemoteGlob(cip, &fileList, "**", kGlobYes);
98 	if (result != kNoErr) {
99 		return (result);
100 	}
101 
102 	for (filePtr = fileList.first;
103 		filePtr != NULL;
104 		filePtr = filePtr->next)
105 	{
106 		file = filePtr->line;
107 		if (file == NULL) {
108 			cip->errNo = kErrBadLineList;
109 			break;
110 		}
111 
112 		if ((file[0] == '.') && ((file[1] == '\0') || ((file[1] == '.') && (file[2] == '\0'))))
113 			continue;	/* Skip . and .. */
114 
115 		if (FTPChdir(cip, file) == kNoErr) {
116 			/* It was a directory.
117 			 * Go in and wax it.
118 			 */
119 			result = FTPRmdirRecursiveL2(cip);
120 
121 			if (FTPChdir(cip, "..") != kNoErr) {
122 				/* Panic -- we can no longer
123 				 * cd back to the directory
124 				 * we were in before.
125 				 */
126 				result = kErrCannotGoToPrevDir;
127 				cip->errNo = kErrCannotGoToPrevDir;
128 				return (result);
129 			}
130 
131 			if ((result < 0) && (result != kErrGlobNoMatch))
132 				return (result);
133 
134 			result = FTPRmdir(cip, file, kRecursiveNo, kGlobNo);
135 			if (result != kNoErr) {
136 				/* Well, we couldn't remove the empty
137 				 * directory.  Perhaps we screwed up
138 				 * and the directory wasn't empty.
139 				 */
140 				return (result);
141 			}
142 		} else {
143 			/* Assume it was a file -- remove it. */
144 			result = FTPDelete(cip, file, kRecursiveNo, kGlobNo);
145 			/* Try continuing to remove the rest,
146 			 * even if this failed.
147 			 */
148 		}
149 	}
150 	DisposeLineListContents(&fileList);
151 
152 	return (result);
153 } 	/* FTPRmdirRecursiveL2 */
154 
155 
156 
157 static int
158 FTPRmdirRecursive(const FTPCIPtr cip, const char *const dir)
159 {
160 	int result, result2;
161 
162 	/* Preserve old working directory. */
163 	(void) FTPGetCWD(cip, cip->buf, cip->bufSize);
164 
165 	result = FTPChdir(cip, dir);
166 	if (result != kNoErr) {
167 		return (result);
168 	}
169 
170 	result = FTPRmdirRecursiveL2(cip);
171 
172 	if (FTPChdir(cip, cip->buf) != kNoErr) {
173 		/* Could not cd back to the original user directory -- bad. */
174 		if (result != kNoErr) {
175 			result = kErrCannotGoToPrevDir;
176 			cip->errNo = kErrCannotGoToPrevDir;
177 		}
178 		return (result);
179 	}
180 
181 	/* Now rmdir the last node, the root of the tree
182 	 * we just went through.
183 	 */
184 	result2 = FTPRmdir(cip, dir, kRecursiveNo, kGlobNo);
185 	if ((result2 != kNoErr) && (result == kNoErr))
186 		result = result2;
187 
188 	return (result);
189 }	/* FTPRmdirRecursive */
190 
191 
192 
193 
194 int
195 FTPDelete(const FTPCIPtr cip, const char *const pattern, const int recurse, const int doGlob)
196 {
197 	LineList fileList;
198 	LinePtr filePtr;
199 	char *file;
200 	int onceResult, batchResult;
201 
202 	if (cip == NULL)
203 		return (kErrBadParameter);
204 	if (strcmp(cip->magic, kLibraryMagic))
205 		return (kErrBadMagic);
206 
207 	batchResult = FTPRemoteGlob(cip, &fileList, pattern, doGlob);
208 	if (batchResult != kNoErr)
209 		return (batchResult);
210 
211 	for (batchResult = kNoErr, filePtr = fileList.first;
212 		filePtr != NULL;
213 		filePtr = filePtr->next)
214 	{
215 		file = filePtr->line;
216 		if (file == NULL) {
217 			batchResult = kErrBadLineList;
218 			cip->errNo = kErrBadLineList;
219 			break;
220 		}
221 		onceResult = FTPCmd(cip, "DELE %s", file);
222 		if (onceResult < 0) {
223 			batchResult = onceResult;
224 			break;
225 		}
226 		if (onceResult != 2) {
227 			if (recurse != kRecursiveYes) {
228 				batchResult = kErrDELEFailed;
229 				cip->errNo = kErrDELEFailed;
230 			} else {
231 				onceResult = FTPCmd(cip, "RMD %s", file);
232 				if (onceResult < 0) {
233 					batchResult = onceResult;
234 					break;
235 				}
236 				if (onceResult != 2) {
237 					onceResult = FTPRmdirRecursive(cip, file);
238 					if (onceResult < 0) {
239 						batchResult = kErrRMDFailed;
240 						cip->errNo = kErrRMDFailed;
241 					}
242 				}
243 			}
244 		}
245 	}
246 	DisposeLineListContents(&fileList);
247 	return (batchResult);
248 }	/* FTPDelete */
249 
250 
251 
252 
253 int
254 FTPGetCWD(const FTPCIPtr cip, char *const newCwd, const size_t newCwdSize)
255 {
256 	ResponsePtr rp;
257 	char *l, *r;
258 	int result;
259 
260 	if (cip == NULL)
261 		return (kErrBadParameter);
262 	if (strcmp(cip->magic, kLibraryMagic))
263 		return (kErrBadMagic);
264 
265 	if ((newCwd == NULL) || (newCwdSize == 0)) {
266 		result = kErrInvalidDirParam;
267 		cip->errNo = kErrInvalidDirParam;
268 	} else {
269 		rp = InitResponse();
270 		if (rp == NULL) {
271 			result = kErrMallocFailed;
272 			cip->errNo = kErrMallocFailed;
273 			Error(cip, kDontPerror, "Malloc failed.\n");
274 		} else {
275 			result = RCmd(cip, rp, "PWD");
276 			if (result == 2) {
277 				if ((r = strrchr(rp->msg.first->line, '"')) != NULL) {
278 					/* "xxxx" is current directory.
279 					 * Strip out just the xxxx to copy into the remote cwd.
280 					 */
281 					l = strchr(rp->msg.first->line, '"');
282 					if ((l != NULL) && (l != r)) {
283 						*r = '\0';
284 						++l;
285 						(void) Strncpy(newCwd, l, newCwdSize);
286 						*r = '"';	/* Restore, so response prints correctly. */
287 					}
288 				} else {
289 					/* xxxx is current directory.
290 					 * Mostly for VMS.
291 					 */
292 					if ((r = strchr(rp->msg.first->line, ' ')) != NULL) {
293 						*r = '\0';
294 						(void) Strncpy(newCwd, (rp->msg.first->line), newCwdSize);
295 						*r = ' ';	/* Restore, so response prints correctly. */
296 					}
297 				}
298 				result = kNoErr;
299 			} else if (result > 0) {
300 				result = kErrPWDFailed;
301 				cip->errNo = kErrPWDFailed;
302 			}
303 			DoneWithResponse(cip, rp);
304 		}
305 	}
306 	return (result);
307 }	/* FTPGetCWD */
308 
309 
310 
311 
312 int
313 FTPChdirAndGetCWD(const FTPCIPtr cip, const char *const cdCwd, char *const newCwd, const size_t newCwdSize)
314 {
315 	ResponsePtr rp;
316 	char *l, *r;
317 	int result;
318 
319 	if (cip == NULL)
320 		return (kErrBadParameter);
321 	if (strcmp(cip->magic, kLibraryMagic))
322 		return (kErrBadMagic);
323 
324 	if ((newCwd == NULL) || (cdCwd == NULL)) {
325 		result = kErrInvalidDirParam;
326 		cip->errNo = kErrInvalidDirParam;
327 	} else {
328 		if (cdCwd[0] == '\0') {	/* But allow FTPChdir(cip, ".") to go through. */
329 			result = FTPGetCWD(cip, newCwd, newCwdSize);
330 			return (result);
331 		}
332 		rp = InitResponse();
333 		if (rp == NULL) {
334 			result = kErrMallocFailed;
335 			cip->errNo = kErrMallocFailed;
336 			Error(cip, kDontPerror, "Malloc failed.\n");
337 		} else {
338 			if (strcmp(cdCwd, "..") == 0)
339 				result = RCmd(cip, rp, "CDUP");
340 			else
341 				result = RCmd(cip, rp, "CWD %s", cdCwd);
342 			if (result == 2) {
343 				l = strchr(rp->msg.first->line, '"');
344 				if ((l == rp->msg.first->line) && ((r = strrchr(rp->msg.first->line, '"')) != NULL) && (l != r)) {
345 					/* "xxxx" is current directory.
346 					 * Strip out just the xxxx to copy into the remote cwd.
347 					 *
348 					 * This is nice because we didn't have to do a PWD.
349 					 */
350 					*r = '\0';
351 					++l;
352 					(void) Strncpy(newCwd, l, newCwdSize);
353 					*r = '"';	/* Restore, so response prints correctly. */
354 					DoneWithResponse(cip, rp);
355 					result = kNoErr;
356 				} else {
357 					DoneWithResponse(cip, rp);
358 					result = FTPGetCWD(cip, newCwd, newCwdSize);
359 				}
360 			} else if (result > 0) {
361 				result = kErrCWDFailed;
362 				cip->errNo = kErrCWDFailed;
363 				DoneWithResponse(cip, rp);
364 			} else {
365 				DoneWithResponse(cip, rp);
366 			}
367 		}
368 	}
369 	return (result);
370 }	/* FTPChdirAndGetCWD */
371 
372 
373 
374 
375 int
376 FTPChdir3(FTPCIPtr cip, const char *const cdCwd, char *const newCwd, const size_t newCwdSize, int flags)
377 {
378 	char *cp, *startcp;
379 	int result;
380 	int lastSubDir;
381 	int mkd, pwd;
382 
383 	if (cip == NULL)
384 		return (kErrBadParameter);
385 	if (strcmp(cip->magic, kLibraryMagic))
386 		return (kErrBadMagic);
387 
388 	if (cdCwd == NULL) {
389 		result = kErrInvalidDirParam;
390 		cip->errNo = kErrInvalidDirParam;
391 		return result;
392 	}
393 
394 	if (flags == kChdirOnly)
395 		return (FTPChdir(cip, cdCwd));
396 	if (flags == kChdirAndGetCWD) {
397 		return (FTPChdirAndGetCWD(cip, cdCwd, newCwd, newCwdSize));
398 	} else if (flags == kChdirAndMkdir) {
399 		result = FTPMkdir(cip, cdCwd, kRecursiveYes);
400 		if (result == kNoErr)
401 			result = FTPChdir(cip, cdCwd);
402 		return result;
403 	} else if (flags == (kChdirAndMkdir|kChdirAndGetCWD)) {
404 		result = FTPMkdir(cip, cdCwd, kRecursiveYes);
405 		if (result == kNoErr)
406 			result = FTPChdirAndGetCWD(cip, cdCwd, newCwd, newCwdSize);
407 		return result;
408 	}
409 
410 	/* else: (flags | kChdirOneSubdirAtATime) == true */
411 
412 	cp = cip->buf;
413 	cp[cip->bufSize - 1] = '\0';
414 	(void) Strncpy(cip->buf, cdCwd, cip->bufSize);
415 	if (cp[cip->bufSize - 1] != '\0')
416 		return (kErrBadParameter);
417 
418 	mkd = (flags & kChdirAndMkdir);
419 	pwd = (flags & kChdirAndGetCWD);
420 
421 	if ((cdCwd[0] == '\0') || (strcmp(cdCwd, ".") == 0)) {
422 		result = 0;
423 		if (flags == kChdirAndGetCWD)
424 			result = FTPGetCWD(cip, newCwd, newCwdSize);
425 		return (result);
426 	}
427 
428 	lastSubDir = 0;
429 	do {
430 		startcp = cp;
431 		cp = StrFindLocalPathDelim(cp);
432 		if (cp != NULL) {
433 			/* If this is the first slash in an absolute
434 			 * path, then startcp will be empty.  We will
435 			 * use this below to treat this as the root
436 			 * directory.
437 			 */
438 			*cp++ = '\0';
439 		} else {
440 			lastSubDir = 1;
441 		}
442 		if (strcmp(startcp, ".") == 0) {
443 			result = 0;
444 			if ((lastSubDir != 0) && (pwd != 0))
445 				result = FTPGetCWD(cip, newCwd, newCwdSize);
446 		} else if ((lastSubDir != 0) && (pwd != 0)) {
447 			result = FTPChdirAndGetCWD(cip, (*startcp != '\0') ? startcp : "/", newCwd, newCwdSize);
448 		} else {
449 			result = FTPChdir(cip, (*startcp != '\0') ? startcp : "/");
450 		}
451 		if (result < 0) {
452 			if ((mkd != 0) && (*startcp != '\0')) {
453 				if (FTPCmd(cip, "MKD %s", startcp) == 2) {
454 					result = FTPChdir(cip, startcp);
455 				} else {
456 					/* couldn't change nor create */
457 					cip->errNo = result;
458 				}
459 			} else {
460 				cip->errNo = result;
461 			}
462 		}
463 	} while ((!lastSubDir) && (result == 0));
464 
465 	return (result);
466 }	/* FTPChdir3 */
467 
468 
469 
470 
471 int
472 FTPMkdir2(const FTPCIPtr cip, const char *const newDir, const int recurse, const char *const curDir)
473 {
474 	int result, result2;
475 	char *cp, *newTreeStart, *cp2;
476 	char dir[512];
477 	char dir2[512];
478 	char c;
479 
480 	if (cip == NULL)
481 		return (kErrBadParameter);
482 	if (strcmp(cip->magic, kLibraryMagic))
483 		return (kErrBadMagic);
484 
485 	if ((newDir == NULL) || (newDir[0] == '\0')) {
486 		cip->errNo = kErrInvalidDirParam;
487 		return (kErrInvalidDirParam);
488 	}
489 
490 	/* Preserve old working directory. */
491 	if ((curDir == NULL) || (curDir[0] == '\0')) {
492 		/* This hack is nice so you can eliminate an
493 		 * unnecessary "PWD" command on the server,
494 		 * since if you already knew what directory
495 		 * you're in.  We want to minimize the number
496 		 * of client-server exchanges when feasible.
497 		 */
498 		(void) FTPGetCWD(cip, cip->buf, cip->bufSize);
499 	}
500 
501 	result = FTPChdir(cip, newDir);
502 	if (result == kNoErr) {
503 		/* Directory already exists -- but we
504 		 * must now change back to where we were.
505 		 */
506 		result2 = FTPChdir(cip, ((curDir == NULL) || (curDir[0] == '\0')) ? cip->buf : curDir);
507 		if (result2 < 0) {
508 			result = kErrCannotGoToPrevDir;
509 			cip->errNo = kErrCannotGoToPrevDir;
510 			return (result);
511 		}
512 
513 		/* Don't need to create it. */
514 		return (kNoErr);
515 	}
516 
517 	if (recurse == kRecursiveNo) {
518 		result = FTPCmd(cip, "MKD %s", newDir);
519 		if (result > 0) {
520 			if (result != 2) {
521 				Error(cip, kDontPerror, "MKD %s failed; [%s]\n", newDir, cip->lastFTPCmdResultStr);
522 				result = kErrMKDFailed;
523 				cip->errNo = kErrMKDFailed;
524 				return (result);
525 			} else {
526 				result = kNoErr;
527 			}
528 		}
529 	} else {
530 		(void) STRNCPY(dir, newDir);
531 
532 		/* Strip trailing slashes. */
533 		cp = dir + strlen(dir) - 1;
534 		for (;;) {
535 			if (cp <= dir) {
536 				if ((newDir == NULL) || (newDir[0] == '\0')) {
537 					cip->errNo = kErrInvalidDirParam;
538 					result = kErrInvalidDirParam;
539 					return (result);
540 				}
541 			}
542 			if ((*cp != '/') && (*cp != '\\')) {
543 				cp[1] = '\0';
544 				break;
545 			}
546 			--cp;
547 		}
548 		(void) STRNCPY(dir2, dir);
549 
550 		if ((strrchr(dir, '/') == dir) || (strrchr(dir, '\\') == dir)) {
551 			/* Special case "mkdir /subdir" */
552 			result = FTPCmd(cip, "MKD %s", dir);
553 			if (result < 0) {
554 				return (result);
555 			}
556 			if (result != 2) {
557 				Error(cip, kDontPerror, "MKD %s failed; [%s]\n", dir, cip->lastFTPCmdResultStr);
558 				result = kErrMKDFailed;
559 				cip->errNo = kErrMKDFailed;
560 				return (result);
561 			}
562 			/* Haven't chdir'ed, don't need to goto goback. */
563 			return (kNoErr);
564 		}
565 
566 		for (;;) {
567 			cp = strrchr(dir, '/');
568 			if (cp == NULL)
569 				cp = strrchr(dir, '\\');
570 			if (cp == NULL) {
571 				cp = dir + strlen(dir) - 1;
572 				if (dir[0] == '\0') {
573 					result = kErrMKDFailed;
574 					cip->errNo = kErrMKDFailed;
575 					return (result);
576 				}
577 				/* Note: below we will refer to cp + 1
578 				 * which is why we set cp to point to
579 				 * the byte before the array begins!
580 				 */
581 				cp = dir - 1;
582 				break;
583 			}
584 			if (cp == dir) {
585 				result = kErrMKDFailed;
586 				cip->errNo = kErrMKDFailed;
587 				return (result);
588 			}
589 			*cp = '\0';
590 			result = FTPChdir(cip, dir);
591 			if (result == 0) {
592 				break;	/* Found a valid parent dir. */
593 				/* from this point, we need to preserve old dir. */
594 			}
595 		}
596 
597 		newTreeStart = dir2 + ((cp + 1) - dir);
598 		for (cp = newTreeStart; ; ) {
599 			cp2 = cp;
600 			cp = strchr(cp2, '/');
601 			c = '/';
602 			if (cp == NULL)
603 				cp = strchr(cp2, '\\');
604 			if (cp != NULL) {
605 				c = *cp;
606 				*cp = '\0';
607 				if (cp[1] == '\0') {
608 					/* Done, if they did "mkdir /tmp/dir/" */
609 					break;
610 				}
611 			}
612 			result = FTPCmd(cip, "MKD %s", newTreeStart);
613 			if (result < 0) {
614 				return (result);
615 			}
616 			if (result != 2) {
617 				Error(cip, kDontPerror, "Cwd=%s; MKD %s failed; [%s]\n", cip->buf, newTreeStart, cip->lastFTPCmdResultStr);
618 				result = kErrMKDFailed;
619 				cip->errNo = kErrMKDFailed;
620 				goto goback;
621 			}
622 			if (cp == NULL)
623 				break;	/* No more to make, done. */
624 			*cp++ = c;
625 		}
626 		result = kNoErr;
627 
628 goback:
629 		result2 = FTPChdir(cip, ((curDir == NULL) || (curDir[0] == '\0')) ? cip->buf : curDir);
630 		if ((result == 0) && (result2 < 0)) {
631 			result = kErrCannotGoToPrevDir;
632 			cip->errNo = kErrCannotGoToPrevDir;
633 		}
634 	}
635 	return (result);
636 }	/* FTPMkdir2 */
637 
638 
639 
640 int
641 FTPMkdir(const FTPCIPtr cip, const char *const newDir, const int recurse)
642 {
643 	return (FTPMkdir2(cip, newDir, recurse, NULL));
644 }	/* FTPMkdir */
645 
646 
647 
648 int
649 FTPFileModificationTime(const FTPCIPtr cip, const char *const file, time_t *const mdtm)
650 {
651 	int result;
652 	ResponsePtr rp;
653 
654 	if (cip == NULL)
655 		return (kErrBadParameter);
656 	if (strcmp(cip->magic, kLibraryMagic))
657 		return (kErrBadMagic);
658 
659 	if ((mdtm == NULL) || (file == NULL))
660 		return (kErrBadParameter);
661 	*mdtm = kModTimeUnknown;
662 
663 	if (cip->hasMDTM == kCommandNotAvailable) {
664 		cip->errNo = kErrMDTMNotAvailable;
665 		result = kErrMDTMNotAvailable;
666 	} else {
667 		rp = InitResponse();
668 		if (rp == NULL) {
669 			result = kErrMallocFailed;
670 			cip->errNo = kErrMallocFailed;
671 			Error(cip, kDontPerror, "Malloc failed.\n");
672 		} else {
673 			result = RCmd(cip, rp, "MDTM %s", file);
674 			if (result < 0) {
675 				DoneWithResponse(cip, rp);
676 				return (result);
677 			} else if (strncmp(rp->msg.first->line, "19100", 5) == 0) {
678 				Error(cip, kDontPerror, "Warning: Server has Y2K Bug in \"MDTM\" command.\n");
679 				cip->errNo = kErrMDTMFailed;
680 				result = kErrMDTMFailed;
681 			} else if (result == 2) {
682 				*mdtm = UnMDTMDate(rp->msg.first->line);
683 				cip->hasMDTM = kCommandAvailable;
684 				result = kNoErr;
685 			} else if (UNIMPLEMENTED_CMD(rp->code)) {
686 				cip->hasMDTM = kCommandNotAvailable;
687 				cip->errNo = kErrMDTMNotAvailable;
688 				result = kErrMDTMNotAvailable;
689 			} else {
690 				cip->errNo = kErrMDTMFailed;
691 				result = kErrMDTMFailed;
692 			}
693 			DoneWithResponse(cip, rp);
694 		}
695 	}
696 	return (result);
697 }	/* FTPFileModificationTime */
698 
699 
700 
701 
702 int
703 FTPRename(const FTPCIPtr cip, const char *const oldname, const char *const newname)
704 {
705 	int result;
706 
707 	if (cip == NULL)
708 		return (kErrBadParameter);
709 	if (strcmp(cip->magic, kLibraryMagic))
710 		return (kErrBadMagic);
711 	if ((oldname == NULL) || (oldname[0] == '\0'))
712 		return (kErrBadParameter);
713 	if ((newname == NULL) || (oldname[0] == '\0'))
714 		return (kErrBadParameter);
715 
716 
717 	result = FTPCmd(cip, "RNFR %s", oldname);
718 	if (result < 0)
719 		return (result);
720 	if (result != 3) {
721 		cip->errNo = kErrRenameFailed;
722 		return (cip->errNo);
723 	}
724 
725 	result = FTPCmd(cip, "RNTO %s", newname);
726 	if (result < 0)
727 		return (result);
728 	if (result != 2) {
729 		cip->errNo = kErrRenameFailed;
730 		return (cip->errNo);
731 	}
732 	return (kNoErr);
733 }	/* FTPRename */
734 
735 
736 
737 
738 int
739 FTPRemoteHelp(const FTPCIPtr cip, const char *const pattern, const LineListPtr llp)
740 {
741 	int result;
742 	ResponsePtr rp;
743 
744 	if ((cip == NULL) || (llp == NULL))
745 		return (kErrBadParameter);
746 	if (strcmp(cip->magic, kLibraryMagic))
747 		return (kErrBadMagic);
748 
749 	InitLineList(llp);
750 	rp = InitResponse();
751 	if (rp == NULL) {
752 		result = kErrMallocFailed;
753 		cip->errNo = kErrMallocFailed;
754 		Error(cip, kDontPerror, "Malloc failed.\n");
755 	} else {
756 		if ((pattern == NULL) || (*pattern == '\0'))
757 			result = RCmd(cip, rp, "HELP");
758 		else
759 			result = RCmd(cip, rp, "HELP %s", pattern);
760 		if (result < 0) {
761 			DoneWithResponse(cip, rp);
762 			return (result);
763 		} else if (result == 2) {
764 			if (CopyLineList(llp, &rp->msg) < 0) {
765 				result = kErrMallocFailed;
766 				cip->errNo = kErrMallocFailed;
767 				Error(cip, kDontPerror, "Malloc failed.\n");
768 			} else {
769 				result = kNoErr;
770 			}
771 		} else {
772 			cip->errNo = kErrHELPFailed;
773 			result = kErrHELPFailed;
774 		}
775 		DoneWithResponse(cip, rp);
776 	}
777 	return (result);
778 }	/* FTPRemoteHelp */
779 
780 
781 
782 
783 int
784 FTPRmdir(const FTPCIPtr cip, const char *const pattern, const int recurse, const int doGlob)
785 {
786 	LineList fileList;
787 	LinePtr filePtr;
788 	char *file;
789 	int onceResult, batchResult;
790 
791 	if (cip == NULL)
792 		return (kErrBadParameter);
793 	if (strcmp(cip->magic, kLibraryMagic))
794 		return (kErrBadMagic);
795 
796 	batchResult = FTPRemoteGlob(cip, &fileList, pattern, doGlob);
797 	if (batchResult != kNoErr)
798 		return (batchResult);
799 
800 	for (batchResult = kNoErr, filePtr = fileList.first;
801 		filePtr != NULL;
802 		filePtr = filePtr->next)
803 	{
804 		file = filePtr->line;
805 		if (file == NULL) {
806 			batchResult = kErrBadLineList;
807 			cip->errNo = kErrBadLineList;
808 			break;
809 		}
810 		onceResult = FTPCmd(cip, "RMD %s", file);
811 		if (onceResult < 0) {
812 			batchResult = onceResult;
813 			break;
814 		}
815 		if (onceResult != 2) {
816 			if (recurse == kRecursiveYes) {
817 				onceResult = FTPRmdirRecursive(cip, file);
818 				if (onceResult < 0) {
819 					batchResult = kErrRMDFailed;
820 					cip->errNo = kErrRMDFailed;
821 				}
822 			} else {
823 				batchResult = kErrRMDFailed;
824 				cip->errNo = kErrRMDFailed;
825 			}
826 		}
827 	}
828 	DisposeLineListContents(&fileList);
829 	return (batchResult);
830 }	/* FTPRmdir */
831 
832 
833 
834 
835 int
836 FTPSetTransferType(const FTPCIPtr cip, int type)
837 {
838 	int result;
839 
840 	if (cip == NULL)
841 		return (kErrBadParameter);
842 	if (strcmp(cip->magic, kLibraryMagic))
843 		return (kErrBadMagic);
844 
845 	if (cip->curTransferType != type) {
846 		switch (type) {
847 			case kTypeAscii:
848 			case kTypeBinary:
849 			case kTypeEbcdic:
850 				break;
851 			case 'i':
852 			case 'b':
853 			case 'B':
854 				type = kTypeBinary;
855 				break;
856 			case 'e':
857 				type = kTypeEbcdic;
858 				break;
859 			case 'a':
860 				type = kTypeAscii;
861 				break;
862 			default:
863 				/* Yeah, we don't support Tenex.  Who cares? */
864 				Error(cip, kDontPerror, "Bad transfer type [%c].\n", type);
865 				cip->errNo = kErrBadTransferType;
866 				return (kErrBadTransferType);
867 		}
868 		result = FTPCmd(cip, "TYPE %c", type);
869 		if (result != 2) {
870 			result = kErrTYPEFailed;
871 			cip->errNo = kErrTYPEFailed;
872 			return (result);
873 		}
874 		cip->curTransferType = type;
875 	}
876 	return (kNoErr);
877 }	/* FTPSetTransferType */
878 
879 
880 
881 
882 /* If the remote host supports the SIZE command, we can find out the exact
883  * size of a remote file, depending on the transfer type in use.  SIZE
884  * returns different values for ascii and binary modes!
885  */
886 int
887 FTPFileSize(const FTPCIPtr cip, const char *const file, longest_int *const size, const int type)
888 {
889 	int result;
890 	ResponsePtr rp;
891 
892 	if (cip == NULL)
893 		return (kErrBadParameter);
894 	if (strcmp(cip->magic, kLibraryMagic))
895 		return (kErrBadMagic);
896 
897 	if ((size == NULL) || (file == NULL))
898 		return (kErrBadParameter);
899 	*size = kSizeUnknown;
900 
901 	result = FTPSetTransferType(cip, type);
902 	if (result < 0)
903 		return (result);
904 
905 	if (cip->hasSIZE == kCommandNotAvailable) {
906 		cip->errNo = kErrSIZENotAvailable;
907 		result = kErrSIZENotAvailable;
908 	} else {
909 		rp = InitResponse();
910 		if (rp == NULL) {
911 			result = kErrMallocFailed;
912 			cip->errNo = kErrMallocFailed;
913 			Error(cip, kDontPerror, "Malloc failed.\n");
914 		} else {
915 			result = RCmd(cip, rp, "SIZE %s", file);
916 			if (result < 0) {
917 				DoneWithResponse(cip, rp);
918 				return (result);
919 			} else if (result == 2) {
920 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG)
921 				(void) sscanf(rp->msg.first->line, SCANF_LONG_LONG, size);
922 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ)
923 				*size = (longest_int) strtoq(rp->msg.first->line, NULL, 0);
924 #else
925 				(void) sscanf(rp->msg.first->line, "%ld", size);
926 #endif
927 				cip->hasSIZE = kCommandAvailable;
928 				result = kNoErr;
929 			} else if (UNIMPLEMENTED_CMD(rp->code)) {
930 				cip->hasSIZE = kCommandNotAvailable;
931 				cip->errNo = kErrSIZENotAvailable;
932 				result = kErrSIZENotAvailable;
933 			} else {
934 				cip->errNo = kErrSIZEFailed;
935 				result = kErrSIZEFailed;
936 			}
937 			DoneWithResponse(cip, rp);
938 		}
939 	}
940 	return (result);
941 }	/* FTPFileSize */
942 
943 
944 
945 
946 int
947 FTPMListOneFile(const FTPCIPtr cip, const char *const file, const MLstItemPtr mlip)
948 {
949 	int result;
950 	ResponsePtr rp;
951 
952 	/* We do a special check for older versions of NcFTPd which
953 	 * are based off of an incompatible previous version of IETF
954 	 * extensions.
955 	 *
956 	 * Roxen also seems to be way outdated, where MLST was on the
957 	 * data connection among other things.
958 	 *
959 	 */
960 	if (
961 		(cip->hasMLST == kCommandNotAvailable) ||
962 		((cip->serverType == kServerTypeNcFTPd) && (cip->ietfCompatLevel < 19981201)) ||
963 		(cip->serverType == kServerTypeRoxen)
964 	) {
965 		cip->errNo = kErrMLSTNotAvailable;
966 		return (cip->errNo);
967 	}
968 
969 	rp = InitResponse();
970 	if (rp == NULL) {
971 		result = cip->errNo = kErrMallocFailed;
972 		Error(cip, kDontPerror, "Malloc failed.\n");
973 	} else {
974 		result = RCmd(cip, rp, "MLST %s", file);
975 		if (
976 			(result == 2) &&
977 			(rp->msg.first->line != NULL) &&
978 			(rp->msg.first->next != NULL) &&
979 			(rp->msg.first->next->line != NULL)
980 		) {
981 			result = UnMlsT(rp->msg.first->next->line, mlip);
982 			if (result < 0) {
983 				cip->errNo = result = kErrInvalidMLSTResponse;
984 			}
985 		} else if (UNIMPLEMENTED_CMD(rp->code)) {
986 			cip->hasMLST = kCommandNotAvailable;
987 			cip->errNo = kErrMLSTNotAvailable;
988 			result = kErrMLSTNotAvailable;
989 		} else {
990 			cip->errNo = kErrMLSTFailed;
991 			result = kErrMLSTFailed;
992 		}
993 		DoneWithResponse(cip, rp);
994 	}
995 
996 	return (result);
997 }	/* FTPMListOneFile */
998 
999 
1000 
1001 
1002 /* We only use STAT to see if files or directories exist.
1003  * But since it is so rarely used in the wild, we need to
1004  * make sure the server supports the use where we pass
1005  * a pathname as a parameter.
1006  */
1007 int
1008 FTPFileExistsStat(const FTPCIPtr cip, const char *const file)
1009 {
1010 	int result;
1011 	ResponsePtr rp;
1012 	LineList fileList;
1013 	char savedCwd[512];
1014 
1015 	if (cip == NULL)
1016 		return (kErrBadParameter);
1017 	if (strcmp(cip->magic, kLibraryMagic))
1018 		return (kErrBadMagic);
1019 
1020 	if (file == NULL)
1021 		return (kErrBadParameter);
1022 
1023 	if (cip->STATfileParamWorks == kCommandNotAvailable) {
1024 		cip->errNo = result = kErrSTATwithFileNotAvailable;
1025 		return (result);
1026 	}
1027 
1028 	if (cip->STATfileParamWorks == kCommandAvailabilityUnknown) {
1029 		rp = InitResponse();
1030 		if (rp == NULL) {
1031 			result = kErrMallocFailed;
1032 			cip->errNo = kErrMallocFailed;
1033 			Error(cip, kDontPerror, "Malloc failed.\n");
1034 			return (result);
1035 
1036 		}
1037 
1038 		/* First, make sure that when we STAT a pathname
1039 		 * that does not exist, that we get an error back.
1040 		 *
1041 		 * We also assume that a valid STAT response has
1042 		 * at least 3 lines of response text, typically
1043 		 * a "start" line, intermediate data, and then
1044 		 * a trailing line.
1045 		 *
1046 		 * We also can see a one-line case.
1047 		 */
1048 		result = RCmd(cip, rp, "STAT %s", "NoSuchFile");
1049 		if ((result == 2) && ((rp->msg.nLines >= 3) || (rp->msg.nLines == 1))) {
1050 			/* Hmmm.... it gave back a positive
1051 			 * response.  So STAT <file> does not
1052 			 * work correctly.
1053 			 */
1054 			if (
1055 				(rp->msg.first->next != NULL) &&
1056 				(rp->msg.first->next->line != NULL) &&
1057 				(
1058 					(strstr(rp->msg.first->next->line, "o such file") != NULL) ||
1059 					(strstr(rp->msg.first->next->line, "ot found") != NULL)
1060 				)
1061 			) {
1062 				/* OK, while we didn't want a 200
1063 				 * level response, some servers,
1064 				 * like wu-ftpd print an error
1065 				 * message "No such file or
1066 				 * directory" which we can special
1067 				 * case.
1068 				 */
1069 				result = kNoErr;
1070 			} else {
1071 				cip->STATfileParamWorks = kCommandNotAvailable;
1072 				cip->errNo = result = kErrSTATwithFileNotAvailable;
1073 				DoneWithResponse(cip, rp);
1074 				return (result);
1075 			}
1076 		}
1077 		DoneWithResponse(cip, rp);
1078 
1079 		/* We can't assume that we can simply say STAT rootdir/firstfile,
1080 		 * since the remote host may not be using / as a directory
1081 		 * delimiter.  So we have to change to the root directory
1082 		 * and then do the STAT on that file.
1083 		 */
1084 		if (
1085 			(FTPGetCWD(cip, savedCwd, sizeof(savedCwd)) != kNoErr) ||
1086 			(FTPChdir(cip, cip->startingWorkingDirectory) != kNoErr)
1087 		) {
1088 			return (cip->errNo);
1089 		}
1090 
1091 		/* OK, we get an error when we stat
1092 		 * a non-existant file, but now we need to
1093 		 * see if we get a positive reply when
1094 		 * we stat a file that does exist.
1095 		 *
1096 		 * To do this, we list the root directory,
1097 		 * which we assume has one or more items.
1098 		 * If it doesn't, the user can't do anything
1099 		 * anyway.  Then we stat the first item
1100 		 * we found to see if STAT says it exists.
1101 		 */
1102 		if (
1103 			((result = FTPListToMemory2(cip, "", &fileList, "", 0, (int *) 0)) < 0) ||
1104 			(fileList.last == NULL) ||
1105 			(fileList.last->line == NULL)
1106 		) {
1107 			/* Hmmm... well, in any case we can't use STAT. */
1108 			cip->STATfileParamWorks = kCommandNotAvailable;
1109 			cip->errNo = result = kErrSTATwithFileNotAvailable;
1110 			DisposeLineListContents(&fileList);
1111 			(void) FTPChdir(cip, savedCwd);
1112 			return (result);
1113 		}
1114 
1115 		rp = InitResponse();
1116 		if (rp == NULL) {
1117 			result = kErrMallocFailed;
1118 			cip->errNo = kErrMallocFailed;
1119 			Error(cip, kDontPerror, "Malloc failed.\n");
1120 			DisposeLineListContents(&fileList);
1121 			(void) FTPChdir(cip, savedCwd);
1122 			return (result);
1123 
1124 		}
1125 
1126 		result = RCmd(cip, rp, "STAT %s", fileList.last->line);
1127 		DisposeLineListContents(&fileList);
1128 
1129 		if ((result != 2) || (rp->msg.nLines == 2)) {
1130 			/* Hmmm.... it gave back a negative
1131 			 * response.  So STAT <file> does not
1132 			 * work correctly.
1133 			 */
1134 			cip->STATfileParamWorks = kCommandNotAvailable;
1135 			cip->errNo = result = kErrSTATwithFileNotAvailable;
1136 			DoneWithResponse(cip, rp);
1137 			(void) FTPChdir(cip, savedCwd);
1138 			return (result);
1139 		} else if (
1140 				(rp->msg.first->next != NULL) &&
1141 				(rp->msg.first->next->line != NULL) &&
1142 				(
1143 					(strstr(rp->msg.first->next->line, "o such file") != NULL) ||
1144 					(strstr(rp->msg.first->next->line, "ot found") != NULL)
1145 				)
1146 		) {
1147 			/* Same special-case of the second line of STAT response. */
1148 			cip->STATfileParamWorks = kCommandNotAvailable;
1149 			cip->errNo = result = kErrSTATwithFileNotAvailable;
1150 			DoneWithResponse(cip, rp);
1151 			(void) FTPChdir(cip, savedCwd);
1152 			return (result);
1153 		}
1154 		DoneWithResponse(cip, rp);
1155 		cip->STATfileParamWorks = kCommandAvailable;
1156 
1157 		/* Don't forget to change back to the original directory. */
1158 		(void) FTPChdir(cip, savedCwd);
1159 	}
1160 
1161 	rp = InitResponse();
1162 	if (rp == NULL) {
1163 		result = kErrMallocFailed;
1164 		cip->errNo = kErrMallocFailed;
1165 		Error(cip, kDontPerror, "Malloc failed.\n");
1166 		return (result);
1167 	}
1168 
1169 	result = RCmd(cip, rp, "STAT %s", file);
1170 	if (result == 2) {
1171 		result = kNoErr;
1172 		if (((rp->msg.nLines >= 3) || (rp->msg.nLines == 1))) {
1173 			if (
1174 				(rp->msg.first->next != NULL) &&
1175 				(rp->msg.first->next->line != NULL) &&
1176 				(
1177 					(strstr(rp->msg.first->next->line, "o such file") != NULL) ||
1178 					(strstr(rp->msg.first->next->line, "ot found") != NULL)
1179 				)
1180 			) {
1181 				cip->errNo = kErrSTATFailed;
1182 				result = kErrSTATFailed;
1183 			} else {
1184 				result = kNoErr;
1185 			}
1186 		} else if (rp->msg.nLines == 2) {
1187 			cip->errNo = kErrSTATFailed;
1188 			result = kErrSTATFailed;
1189 		} else {
1190 			result = kNoErr;
1191 		}
1192 	} else {
1193 		cip->errNo = kErrSTATFailed;
1194 		result = kErrSTATFailed;
1195 	}
1196 	DoneWithResponse(cip, rp);
1197 	return (result);
1198 }	/* FTPFileExistsStat */
1199 
1200 
1201 
1202 
1203 /* We only use STAT to see if files or directories exist.
1204  * But since it is so rarely used in the wild, we need to
1205  * make sure the server supports the use where we pass
1206  * a pathname as a parameter.
1207  */
1208 int
1209 FTPFileExistsNlst(const FTPCIPtr cip, const char *const file)
1210 {
1211 	int result;
1212 	LineList fileList, rootFileList;
1213 	char savedCwd[512];
1214 
1215 	if (cip == NULL)
1216 		return (kErrBadParameter);
1217 	if (strcmp(cip->magic, kLibraryMagic))
1218 		return (kErrBadMagic);
1219 
1220 	if (file == NULL)
1221 		return (kErrBadParameter);
1222 
1223 	if (cip->NLSTfileParamWorks == kCommandNotAvailable) {
1224 		cip->errNo = result = kErrNLSTwithFileNotAvailable;
1225 		return (result);
1226 	}
1227 
1228 	if (cip->NLSTfileParamWorks == kCommandAvailabilityUnknown) {
1229 		/* First, make sure that when we NLST a pathname
1230 		 * that does not exist, that we get an error back.
1231 		 *
1232 		 * We also assume that a valid NLST response has
1233 		 * at least 3 lines of response text, typically
1234 		 * a "start" line, intermediate data, and then
1235 		 * a trailing line.
1236 		 *
1237 		 * We also can see a one-line case.
1238 		 */
1239 		if (
1240 			((FTPListToMemory2(cip, "NoSuchFile", &fileList, "", 0, (int *) 0)) == kNoErr) &&
1241 			(fileList.nLines >= 1) &&
1242 			(strstr(fileList.last->line, "o such file") == NULL) &&
1243 			(strstr(fileList.last->line, "ot found") == NULL) &&
1244 			(strstr(fileList.last->line, "o Such File") == NULL) &&
1245 			(strstr(fileList.last->line, "ot Found") == NULL)
1246 
1247 		) {
1248 			cip->NLSTfileParamWorks = kCommandNotAvailable;
1249 			cip->errNo = result = kErrNLSTwithFileNotAvailable;
1250 			DisposeLineListContents(&fileList);
1251 			return (result);
1252 		}
1253 		DisposeLineListContents(&fileList);
1254 
1255 		/* We can't assume that we can simply say NLST rootdir/firstfile,
1256 		 * since the remote host may not be using / as a directory
1257 		 * delimiter.  So we have to change to the root directory
1258 		 * and then do the NLST on that file.
1259 		 */
1260 		if (
1261 			(FTPGetCWD(cip, savedCwd, sizeof(savedCwd)) != kNoErr) ||
1262 			(FTPChdir(cip, cip->startingWorkingDirectory) != kNoErr)
1263 		) {
1264 			return (cip->errNo);
1265 		}
1266 
1267 		/* OK, we get an error when we list
1268 		 * a non-existant file, but now we need to
1269 		 * see if we get a positive reply when
1270 		 * we stat a file that does exist.
1271 		 *
1272 		 * To do this, we list the root directory,
1273 		 * which we assume has one or more items.
1274 		 * If it doesn't, the user can't do anything
1275 		 * anyway.  Then we do the first item
1276 		 * we found to see if NLST says it exists.
1277 		 */
1278 		if (
1279 			((result = FTPListToMemory2(cip, "", &rootFileList, "", 0, (int *) 0)) < 0) ||
1280 			(rootFileList.last == NULL) ||
1281 			(rootFileList.last->line == NULL)
1282 		) {
1283 			/* Hmmm... well, in any case we can't use NLST. */
1284 			cip->NLSTfileParamWorks = kCommandNotAvailable;
1285 			cip->errNo = result = kErrNLSTwithFileNotAvailable;
1286 			DisposeLineListContents(&rootFileList);
1287 			(void) FTPChdir(cip, savedCwd);
1288 			return (result);
1289 		}
1290 
1291 		if (
1292 			((FTPListToMemory2(cip, rootFileList.last->line, &fileList, "", 0, (int *) 0)) == kNoErr) &&
1293 			(fileList.nLines >= 1) &&
1294 			(strstr(fileList.last->line, "o such file") == NULL) &&
1295 			(strstr(fileList.last->line, "ot found") == NULL) &&
1296 			(strstr(fileList.last->line, "o Such File") == NULL) &&
1297 			(strstr(fileList.last->line, "ot Found") == NULL)
1298 
1299 		) {
1300 			/* Good.  We listed the item. */
1301 			DisposeLineListContents(&fileList);
1302 			DisposeLineListContents(&rootFileList);
1303 			cip->NLSTfileParamWorks = kCommandAvailable;
1304 
1305 			/* Don't forget to change back to the original directory. */
1306 			(void) FTPChdir(cip, savedCwd);
1307 		} else {
1308 			cip->NLSTfileParamWorks = kCommandNotAvailable;
1309 			cip->errNo = result = kErrNLSTwithFileNotAvailable;
1310 			DisposeLineListContents(&fileList);
1311 			DisposeLineListContents(&rootFileList);
1312 			(void) FTPChdir(cip, savedCwd);
1313 			return (result);
1314 		}
1315 	}
1316 
1317 	/* Check the requested item. */
1318 	InitLineList(&fileList);
1319 	if (
1320 		((FTPListToMemory2(cip, file, &fileList, "", 0, (int *) 0)) == kNoErr) &&
1321 		(fileList.nLines >= 1) &&
1322 		(strstr(fileList.last->line, "o such file") == NULL) &&
1323 		(strstr(fileList.last->line, "ot found") == NULL) &&
1324 		(strstr(fileList.last->line, "o Such File") == NULL) &&
1325 		(strstr(fileList.last->line, "ot Found") == NULL)
1326 
1327 	) {
1328 		/* The item existed. */
1329 		result = kNoErr;
1330 	} else {
1331 		cip->errNo = kErrNLSTFailed;
1332 		result = kErrNLSTFailed;
1333 	}
1334 
1335 	DisposeLineListContents(&fileList);
1336 	return (result);
1337 }	/* FTPFileExistsNlst*/
1338 
1339 
1340 
1341 
1342 /* This functions goes to a great deal of trouble to try and determine if the
1343  * remote file specified exists.  Newer servers support commands that make
1344  * it relatively inexpensive to find the answer, but older servers do not
1345  * provide a standard way.  This means we may try a whole bunch of things,
1346  * but the good news is that the library saves information about which things
1347  * worked so if you do this again it uses the methods that work.
1348  */
1349 int
1350 FTPFileExists2(const FTPCIPtr cip, const char *const file, const int tryMDTM, const int trySIZE, const int tryMLST, const int trySTAT, const int tryNLST)
1351 {
1352 	int result;
1353 	time_t mdtm;
1354 	longest_int size;
1355 	MLstItem mlsInfo;
1356 
1357 	if (tryMDTM != 0) {
1358 		result = FTPFileModificationTime(cip, file, &mdtm);
1359 		if (result == kNoErr)
1360 			return (kNoErr);
1361 		if (result == kErrMDTMFailed) {
1362 			cip->errNo = kErrNoSuchFileOrDirectory;
1363 			return (kErrNoSuchFileOrDirectory);
1364 		}
1365 		/* else keep going */
1366 	}
1367 
1368 	if (trySIZE != 0) {
1369 		result = FTPFileSize(cip, file, &size, kTypeBinary);
1370 		if (result == kNoErr)
1371 			return (kNoErr);
1372 		/* SIZE could fail if the server does
1373 		 * not support it for directories.
1374 		 *
1375 		 * if (result == kErrSIZEFailed)
1376 		 *	return (kErrNoSuchFileOrDirectory);
1377 		 */
1378 		/* else keep going */
1379 	}
1380 
1381 
1382 	if (tryMLST != 0) {
1383 		result = FTPMListOneFile(cip, file, &mlsInfo);
1384 		if (result == kNoErr)
1385 			return (kNoErr);
1386 		if (result == kErrMLSTFailed) {
1387 			cip->errNo = kErrNoSuchFileOrDirectory;
1388 			return (kErrNoSuchFileOrDirectory);
1389 		}
1390 		/* else keep going */
1391 	}
1392 
1393 	if (trySTAT != 0) {
1394 		result = FTPFileExistsStat(cip, file);
1395 		if (result == kNoErr)
1396 			return (kNoErr);
1397 		if (result == kErrSTATFailed) {
1398 			cip->errNo = kErrNoSuchFileOrDirectory;
1399 			return (kErrNoSuchFileOrDirectory);
1400 		}
1401 		/* else keep going */
1402 	}
1403 
1404 	if (tryNLST != 0) {
1405 		result = FTPFileExistsNlst(cip, file);
1406 		if (result == kNoErr)
1407 			return (kNoErr);
1408 		if (result == kErrNLSTFailed) {
1409 			cip->errNo = kErrNoSuchFileOrDirectory;
1410 			return (kErrNoSuchFileOrDirectory);
1411 		}
1412 		/* else keep going */
1413 	}
1414 
1415 	cip->errNo = kErrCantTellIfFileExists;
1416 	return (kErrCantTellIfFileExists);
1417 }	/* FTPFileExists2 */
1418 
1419 
1420 
1421 
1422 int
1423 FTPFileExists(const FTPCIPtr cip, const char *const file)
1424 {
1425 	return (FTPFileExists2(cip, file, 1, 1, 1, 1, 1));
1426 }	/* FTPFileExists */
1427 
1428 
1429 
1430 
1431 
1432 int
1433 FTPFileSizeAndModificationTime(const FTPCIPtr cip, const char *const file, longest_int *const size, const int type, time_t *const mdtm)
1434 {
1435 	MLstItem mlsInfo;
1436 	int result;
1437 
1438 	if (cip == NULL)
1439 		return (kErrBadParameter);
1440 	if (strcmp(cip->magic, kLibraryMagic))
1441 		return (kErrBadMagic);
1442 
1443 	if ((mdtm == NULL) || (size == NULL) || (file == NULL))
1444 		return (kErrBadParameter);
1445 
1446 	*mdtm = kModTimeUnknown;
1447 	*size = kSizeUnknown;
1448 
1449 	result = FTPSetTransferType(cip, type);
1450 	if (result < 0)
1451 		return (result);
1452 
1453 	result = FTPMListOneFile(cip, file, &mlsInfo);
1454 	if (result < 0) {
1455 		/* Do it the regular way, where
1456 		 * we do a SIZE and then a MDTM.
1457 		 */
1458 		result = FTPFileSize(cip, file, size, type);
1459 		if (result < 0)
1460 			return (result);
1461 		result = FTPFileModificationTime(cip, file, mdtm);
1462 		return (result);
1463 	} else {
1464 		*mdtm = mlsInfo.ftime;
1465 		*size = mlsInfo.fsize;
1466 	}
1467 
1468 	return (result);
1469 }	/* FTPFileSizeAndModificationTime */
1470 
1471 
1472 
1473 
1474 int
1475 FTPFileType(const FTPCIPtr cip, const char *const file, int *const ftype)
1476 {
1477 	int result;
1478 	MLstItem mlsInfo;
1479 
1480 	if (cip == NULL)
1481 		return (kErrBadParameter);
1482 	if (strcmp(cip->magic, kLibraryMagic))
1483 		return (kErrBadMagic);
1484 
1485 	if ((file == NULL) || (file[0] == '\0')) {
1486 		cip->errNo = kErrBadParameter;
1487 		return (kErrBadParameter);
1488 	}
1489 
1490 	if (ftype == NULL) {
1491 		cip->errNo = kErrBadParameter;
1492 		return (kErrBadParameter);
1493 	}
1494 
1495 	*ftype = 0;
1496 	result = FTPMListOneFile(cip, file, &mlsInfo);
1497 	if (result == kNoErr) {
1498 		*ftype = mlsInfo.ftype;
1499 		return (kNoErr);
1500 	}
1501 
1502 	/* Preserve old working directory. */
1503 	(void) FTPGetCWD(cip, cip->buf, cip->bufSize);
1504 
1505 	result = FTPChdir(cip, file);
1506 	if (result == kNoErr) {
1507 		*ftype = 'd';
1508 		/* Yes it was a directory, now go back to
1509 		 * where we were.
1510 		 */
1511 		(void) FTPChdir(cip, cip->buf);
1512 
1513 		/* Note:  This improperly assumes that we
1514 		 * will be able to chdir back, which is
1515 		 * not guaranteed.
1516 		 */
1517 		return (kNoErr);
1518 	}
1519 
1520 	result = FTPFileExists2(cip, file, 1, 1, 0, 1, 1);
1521 	if (result != kErrNoSuchFileOrDirectory)
1522 		result = kErrFileExistsButCannotDetermineType;
1523 
1524 	return (result);
1525 }	/* FTPFileType */
1526 
1527 
1528 
1529 
1530 int
1531 FTPIsDir(const FTPCIPtr cip, const char *const dir)
1532 {
1533 	int result, ftype;
1534 
1535 	if (cip == NULL)
1536 		return (kErrBadParameter);
1537 	if (strcmp(cip->magic, kLibraryMagic))
1538 		return (kErrBadMagic);
1539 
1540 	if ((dir == NULL) || (dir[0] == '\0')) {
1541 		cip->errNo = kErrInvalidDirParam;
1542 		return (kErrInvalidDirParam);
1543 	}
1544 
1545 	result = FTPFileType(cip, dir, &ftype);
1546 	if ((result == kNoErr) || (result == kErrFileExistsButCannotDetermineType)) {
1547 		result = 0;
1548 		if (ftype == 'd')
1549 			result = 1;
1550 	}
1551 	return (result);
1552 }	/* FTPIsDir */
1553 
1554 
1555 
1556 
1557 int
1558 FTPIsRegularFile(const FTPCIPtr cip, const char *const file)
1559 {
1560 	int result, ftype;
1561 
1562 	if (cip == NULL)
1563 		return (kErrBadParameter);
1564 	if (strcmp(cip->magic, kLibraryMagic))
1565 		return (kErrBadMagic);
1566 
1567 	if ((file == NULL) || (file[0] == '\0')) {
1568 		cip->errNo = kErrBadParameter;
1569 		return (kErrBadParameter);
1570 	}
1571 
1572 	result = FTPFileType(cip, file, &ftype);
1573 	if ((result == kNoErr) || (result == kErrFileExistsButCannotDetermineType)) {
1574 		result = 1;
1575 		if (ftype == 'd')
1576 			result = 0;
1577 	}
1578 	return (result);
1579 }	/* FTPIsRegularFile */
1580 
1581 
1582 
1583 
1584 int
1585 FTPSymlink(const FTPCIPtr cip, const char *const lfrom, const char *const lto)
1586 {
1587 	if (strcmp(cip->magic, kLibraryMagic))
1588 		return (kErrBadMagic);
1589 	if ((cip == NULL) || (lfrom == NULL) || (lto == NULL))
1590 		return (kErrBadParameter);
1591 	if ((lfrom[0] == '\0') || (lto[0] == '\0'))
1592 		return (kErrBadParameter);
1593 	if (FTPCmd(cip, "SITE SYMLINK %s %s", lfrom, lto) == 2)
1594 		return (kNoErr);
1595 	return (kErrSYMLINKFailed);
1596 }	/* FTPSymlink */
1597 
1598 
1599 
1600 
1601 int
1602 FTPUmask(const FTPCIPtr cip, const char *const umsk)
1603 {
1604 	if (cip == NULL)
1605 		return (kErrBadParameter);
1606 	if (strcmp(cip->magic, kLibraryMagic))
1607 		return (kErrBadMagic);
1608 	if ((umsk == NULL) || (umsk[0] == '\0'))
1609 		return (kErrBadParameter);
1610 	if (FTPCmd(cip, "SITE UMASK %s", umsk) == 2)
1611 		return (kNoErr);
1612 	return (kErrUmaskFailed);
1613 }	/* FTPUmask */
1614 
1615 
1616 
1617 
1618 static void
1619 GmTimeStr(char *const dst, const size_t dstsize, time_t t)
1620 {
1621 	char buf[64];
1622 	struct tm *gtp;
1623 
1624 	gtp = gmtime(&t);
1625 	if (gtp == NULL) {
1626 		dst[0] = '\0';
1627 	} else {
1628 #ifdef HAVE_SNPRINTF
1629 		buf[sizeof(buf) - 1] = '\0';
1630 		(void) snprintf(buf, sizeof(buf) - 1, "%04d%02d%02d%02d%02d%02d",
1631 #else
1632 		(void) sprintf(buf, "%04d%02d%02d%02d%02d%02d",
1633 #endif
1634 			gtp->tm_year + 1900,
1635 			gtp->tm_mon + 1,
1636 			gtp->tm_mday,
1637 			gtp->tm_hour,
1638 			gtp->tm_min,
1639 			gtp->tm_sec
1640 		);
1641 		(void) Strncpy(dst, buf, dstsize);
1642 	}
1643 }	/* GmTimeStr */
1644 
1645 
1646 
1647 
1648 int
1649 FTPUtime(const FTPCIPtr cip, const char *const file, time_t actime, time_t modtime, time_t crtime)
1650 {
1651 	char mstr[64], astr[64], cstr[64];
1652 	int result;
1653 	ResponsePtr rp;
1654 
1655 	if (cip == NULL)
1656 		return (kErrBadParameter);
1657 	if (strcmp(cip->magic, kLibraryMagic))
1658 		return (kErrBadMagic);
1659 
1660 	if (cip->hasUTIME == kCommandNotAvailable) {
1661 		cip->errNo = kErrUTIMENotAvailable;
1662 		result = kErrUTIMENotAvailable;
1663 	} else {
1664 		if ((actime == (time_t) 0) || (actime == (time_t) -1))
1665 			(void) time(&actime);
1666 		if ((modtime == (time_t) 0) || (modtime == (time_t) -1))
1667 			(void) time(&modtime);
1668 		if ((crtime == (time_t) 0) || (crtime == (time_t) -1))
1669 			crtime = modtime;
1670 
1671 		(void) GmTimeStr(astr, sizeof(astr), actime);
1672 		(void) GmTimeStr(mstr, sizeof(mstr), modtime);
1673 		(void) GmTimeStr(cstr, sizeof(cstr), crtime);
1674 
1675 		rp = InitResponse();
1676 		if (rp == NULL) {
1677 			result = kErrMallocFailed;
1678 			cip->errNo = kErrMallocFailed;
1679 			Error(cip, kDontPerror, "Malloc failed.\n");
1680 		} else {
1681 			result = RCmd(cip, rp, "SITE UTIME %s %s %s %s UTC", file, astr, mstr, cstr);
1682 			if (result < 0) {
1683 				DoneWithResponse(cip, rp);
1684 				return (result);
1685 			} else if (result == 2) {
1686 				cip->hasUTIME = kCommandAvailable;
1687 				result = kNoErr;
1688 			} else if (UNIMPLEMENTED_CMD(rp->code)) {
1689 				cip->hasUTIME = kCommandNotAvailable;
1690 				cip->errNo = kErrUTIMENotAvailable;
1691 				result = kErrUTIMENotAvailable;
1692 			} else {
1693 				cip->errNo = kErrUTIMEFailed;
1694 				result = kErrUTIMEFailed;
1695 			}
1696 			DoneWithResponse(cip, rp);
1697 		}
1698 	}
1699 	return (result);
1700 }	/* FTPUtime */
1701