1 /* open.c
2  *
3  * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
4  * All rights reserved.
5  *
6  */
7 
8 #include "syshdrs.h"
9 
10 static void
FTPDeallocateHost(const FTPCIPtr cip)11 FTPDeallocateHost(const FTPCIPtr cip)
12 {
13 	/* Requires the cip->bufSize field set,
14 	 * and the cip->buf set if the
15 	 * buffer is allocated.
16 	 */
17 	if (cip->buf != NULL) {
18 		(void) memset(cip->buf, 0, cip->bufSize);
19 		free(cip->buf);
20 		cip->buf = NULL;
21 	}
22 
23 	if (cip->startingWorkingDirectory != NULL) {
24 		free(cip->startingWorkingDirectory);
25 		cip->startingWorkingDirectory = NULL;
26 	}
27 
28 #if USE_SIO
29 	DisposeSReadlineInfo(&cip->ctrlSrl);
30 #endif
31 	DisposeLineListContents(&cip->lastFTPCmdResultLL);
32 }	/* FTPDeallocateHost */
33 
34 
35 
36 
37 
38 static int
FTPAllocateHost(const FTPCIPtr cip)39 FTPAllocateHost(const FTPCIPtr cip)
40 {
41 	char *buf;
42 
43 	/* Requires the cip->bufSize field set,
44 	 * and the cip->buf cleared if the
45 	 * buffer is not allocated.
46 	 */
47 	if (cip->buf == NULL) {
48 		buf = (char *) calloc((size_t) 1, cip->bufSize);
49 		if (buf == NULL) {
50 			Error(cip, kDontPerror, "Malloc failed.\n");
51 			cip->errNo = kErrMallocFailed;
52 			return (kErrMallocFailed);
53 		}
54 		cip->buf = buf;
55 	}
56 	return (kNoErr);
57 }	/* FTPAllocateHost */
58 
59 
60 
61 
62 void
FTPInitializeOurHostName(const FTPLIPtr lip)63 FTPInitializeOurHostName(const FTPLIPtr lip)
64 {
65 	if (lip == NULL)
66 		return;
67 	if (strcmp(lip->magic, kLibraryMagic))
68 		return;
69 
70 	if (lip->htried == 0) {
71 		(void) memset(lip->ourHostName, 0, sizeof(lip->ourHostName));
72 		lip->hresult = GetOurHostName(lip->ourHostName, sizeof(lip->ourHostName));
73 	}
74 	lip->htried++;
75 }	/* FTPInitializeOurHostName */
76 
77 
78 
79 
80 void
FTPInitializeAnonPassword(const FTPLIPtr lip)81 FTPInitializeAnonPassword(const FTPLIPtr lip)
82 {
83 	if (lip == NULL)
84 		return;
85 	if (strcmp(lip->magic, kLibraryMagic))
86 		return;
87 
88 	FTPInitializeOurHostName(lip);
89 
90 	if (lip->defaultAnonPassword[0] == '\0') {
91 #ifdef SPAM_PROBLEM_HAS_BEEN_SOLVED_FOREVER
92 		GetUsrName(lip->defaultAnonPassword, sizeof(lip->defaultAnonPassword));
93 		(void) STRNCAT(lip->defaultAnonPassword, "@");
94 
95 		/* Default to the "user@" notation
96 		 * supported by NcFTPd and wu-ftpd.
97 		 */
98 		if (lip->htried > 0)
99 			(void) STRNCAT(lip->defaultAnonPassword, lip->ourHostName);
100 #else
101 		(void) STRNCPY(lip->defaultAnonPassword, "NcFTP@");
102 #endif
103 	}
104 }	/* FTPInitializeAnonPassword */
105 
106 
107 
108 
109 int
FTPLoginHost(const FTPCIPtr cip)110 FTPLoginHost(const FTPCIPtr cip)
111 {
112 	ResponsePtr rp;
113 	int result = kErrLoginFailed;
114 	int anonLogin;
115 	int sentpass = 0;
116 	int fwloggedin;
117 	int firstTime;
118 	char cwd[512];
119 
120 	if (cip == NULL)
121 		return (kErrBadParameter);
122 	if ((cip->firewallType < kFirewallNotInUse) || (cip->firewallType > kFirewallLastType))
123 		return (kErrBadParameter);
124 
125 	if (strcmp(cip->magic, kLibraryMagic))
126 		return (kErrBadMagic);
127 
128 	anonLogin = 0;
129 	if (cip->user[0] == '\0')
130 		(void) STRNCPY(cip->user, "anonymous");
131 	if ((strcmp(cip->user, "anonymous") == 0) || (strcmp(cip->user, "ftp") == 0)) {
132 		anonLogin = 1;
133 		/* Try to get the email address if you didn't specify
134 		 * a password when the user is anonymous.
135 		 */
136 		if (cip->pass[0] == '\0') {
137 			FTPInitializeAnonPassword(cip->lip);
138 			(void) STRNCPY(cip->pass, cip->lip->defaultAnonPassword);
139 		}
140 	}
141 
142 	rp = InitResponse();
143 	if (rp == NULL) {
144 		result = kErrMallocFailed;
145 		cip->errNo = kErrMallocFailed;
146 		goto done2;
147 	}
148 
149 	for (firstTime = 1, fwloggedin = 0; ; ) {
150 		/* Here's a mini finite-automaton for the login process.
151 		 *
152 		 * Originally, the FTP protocol was designed to be entirely
153 		 * implementable from a FA.  It could be done, but I don't think
154 		 * it's something an interactive process could be the most
155 		 * effective with.
156 		 */
157 
158 		if (firstTime != 0) {
159 			rp->code = 220;
160 			firstTime = 0;
161 		} else if (result < 0) {
162 			goto done;
163 		}
164 
165 		switch (rp->code) {
166 			case 220:	/* Welcome, ready for new user. */
167 				if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) {
168 					ReInitResponse(cip, rp);
169 					result = RCmd(cip, rp, "USER %s", cip->user);
170 				} else if (cip->firewallType == kFirewallUserAtSite) {
171 					ReInitResponse(cip, rp);
172 					result = RCmd(cip, rp, "USER %s@%s", cip->user, cip->host);
173 				} else if (cip->firewallType == kFirewallUserAtUserPassAtPass) {
174 					ReInitResponse(cip, rp);
175 					result = RCmd(cip, rp, "USER %s@%s@%s", cip->user, cip->firewallUser, cip->host);
176 				} else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) {
177 					ReInitResponse(cip, rp);
178 					result = RCmd(cip, rp, "USER %s@%s %s", cip->user, cip->host, cip->firewallUser);
179 				} else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) {
180 					/* only reached when !fwloggedin */
181 					ReInitResponse(cip, rp);
182 					result = RCmd(cip, rp, "USER %s@%s", cip->firewallUser, cip->host);
183 				} else if (cip->firewallType > kFirewallNotInUse) {
184 					ReInitResponse(cip, rp);
185 					result = RCmd(cip, rp, "USER %s", cip->firewallUser);
186 				} else {
187 					goto unknown;
188 				}
189 				break;
190 
191 			case 230:	/* 230 User logged in, proceed. */
192 			case 231:	/* User name accepted. */
193 			case 202:	/* Command not implemented, superfluous at this site. */
194 				if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0))
195 					goto okay;
196 
197 				/* Now logged in to the firewall. */
198 				fwloggedin++;
199 
200 				if (cip->firewallType == kFirewallLoginThenUserAtSite) {
201 					ReInitResponse(cip, rp);
202 					result = RCmd(cip, rp, "USER %s@%s", cip->user, cip->host);
203 				} else if (cip->firewallType == kFirewallUserAtUserPassAtPass) {
204 					goto okay;
205 				} else if (cip->firewallType == kFirewallOpenSite) {
206 					ReInitResponse(cip, rp);
207 					result = RCmd(cip, rp, "OPEN %s", cip->host);
208 				} else if (cip->firewallType == kFirewallSiteSite) {
209 					ReInitResponse(cip, rp);
210 					result = RCmd(cip, rp, "SITE %s", cip->host);
211 				} else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) {
212 					/* only reached when !fwloggedin */
213 					ReInitResponse(cip, rp);
214 					result = RCmd(cip, rp, "USER %s", cip->user);
215 				} else /* kFirewallUserAtSite */ {
216 					goto okay;
217 				}
218 				break;
219 
220 			case 421:	/* 421 Service not available, closing control connection. */
221 				result = kErrHostDisconnectedDuringLogin;
222 				goto done;
223 
224 			case 331:	/* 331 User name okay, need password. */
225 				if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) {
226 					if ((cip->pass[0] == '\0') && (cip->passphraseProc != NoGetPassphraseProc))
227 						(*cip->passphraseProc)(cip, &rp->msg, cip->pass, sizeof(cip->pass));
228 					ReInitResponse(cip, rp);
229 					result = RCmd(cip, rp, "PASS %s", cip->pass);
230 				} else if (cip->firewallType == kFirewallUserAtSite) {
231 					ReInitResponse(cip, rp);
232 					result = RCmd(cip, rp, "PASS %s", cip->pass);
233 				} else if (cip->firewallType == kFirewallUserAtUserPassAtPass) {
234 					ReInitResponse(cip, rp);
235 					result = RCmd(cip, rp, "PASS %s@%s", cip->pass, cip->firewallPass);
236 				} else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) {
237 					ReInitResponse(cip, rp);
238 					result = RCmd(cip, rp, "PASS %s", cip->pass);
239 				} else if (cip->firewallType == kFirewallFwuAtSiteFwpUserPass) {
240 					/* only reached when !fwloggedin */
241 					ReInitResponse(cip, rp);
242 					result = RCmd(cip, rp, "PASS %s", cip->firewallPass);
243 				} else if (cip->firewallType > kFirewallNotInUse) {
244 					ReInitResponse(cip, rp);
245 					result = RCmd(cip, rp, "PASS %s", cip->firewallPass);
246 				} else {
247 					goto unknown;
248 				}
249 				sentpass++;
250 				break;
251 
252 			case 332:	/* 332 Need account for login. */
253 			case 532: 	/* 532 Need account for storing files. */
254 				if ((cip->firewallType == kFirewallNotInUse) || (fwloggedin != 0)) {
255 					ReInitResponse(cip, rp);
256 					result = RCmd(cip, rp, "ACCT %s", cip->acct);
257 				} else if (cip->firewallType == kFirewallUserAtSiteFwuPassFwp) {
258 					ReInitResponse(cip, rp);
259 					result = RCmd(cip, rp, "ACCT %s", cip->firewallPass);
260 				} else {
261 					/* ACCT not supported on firewall. */
262 					goto unknown;
263 				}
264 				break;
265 
266 			case 530:	/* Not logged in. */
267 				result = (sentpass != 0) ? kErrBadRemoteUserOrPassword : kErrBadRemoteUser;
268 				goto done;
269 
270 			case 501:	/* Syntax error in parameters or arguments. */
271 			case 503:	/* Bad sequence of commands. */
272 			case 550:	/* Can't set guest privileges. */
273 				goto done;
274 
275 			default:
276 			unknown:
277 				if (rp->msg.first == NULL) {
278 					Error(cip, kDontPerror, "Lost connection during login.\n");
279 				} else {
280 					Error(cip, kDontPerror, "Unexpected response: %s\n",
281 						rp->msg.first->line
282 					);
283 				}
284 				goto done;
285 		}
286 	}
287 
288 okay:
289 	/* Do the application's connect message callback, if present. */
290 	if (cip->onLoginMsgProc != 0)
291 		(*cip->onLoginMsgProc)(cip, rp);
292 	DoneWithResponse(cip, rp);
293 	result = 0;
294 	cip->loggedIn = 1;
295 
296 	/* Make a note of what our root directory is.
297 	 * This is often different from "/" when not
298 	 * logged in anonymously.
299 	 */
300 	if (cip->startingWorkingDirectory != NULL) {
301 		free(cip->startingWorkingDirectory);
302 		cip->startingWorkingDirectory = NULL;
303 	}
304 	if ((cip->doNotGetStartingWorkingDirectory == 0) &&
305 		(FTPGetCWD(cip, cwd, sizeof(cwd)) == kNoErr))
306 	{
307 		cip->startingWorkingDirectory = StrDup(cwd);
308 	}
309 
310 	/* When a new site is opened, ASCII mode is assumed (by protocol). */
311 	cip->curTransferType = 'A';
312 	PrintF(cip, "Logged in to %s as %s.\n", cip->host, cip->user);
313 
314 	/* Don't leave cleartext password in memory. */
315 	if ((anonLogin == 0) && (cip->leavePass == 0))
316 		(void) memset(cip->pass, '*', strlen(cip->pass));
317 
318 	if (result < 0)
319 		cip->errNo = result;
320 	return result;
321 
322 done:
323 	DoneWithResponse(cip, rp);
324 
325 done2:
326 	/* Don't leave cleartext password in memory. */
327 	if ((anonLogin == 0) && (cip->leavePass == 0))
328 		(void) memset(cip->pass, '*', strlen(cip->pass));
329 	if (result < 0)
330 		cip->errNo = result;
331 	return result;
332 }	/* FTPLoginHost */
333 
334 
335 
336 
337 static void
FTPExamineMlstFeatures(const FTPCIPtr cip,const char * features)338 FTPExamineMlstFeatures(const FTPCIPtr cip, const char *features)
339 {
340 	char buf[256], *feat;
341 	int flags;
342 
343 	flags = 0;
344 	STRNCPY(buf, features);
345 	feat = strtok(buf, ";*");
346 	while (feat != NULL) {
347 		if (ISTRNEQ(feat, "OS.", 3))
348 			feat += 3;
349 		if (ISTREQ(feat, "type")) {
350 			flags |= kMlsOptType;
351 		} else if (ISTREQ(feat, "size")) {
352 			flags |= kMlsOptSize;
353 		} else if (ISTREQ(feat, "modify")) {
354 			flags |= kMlsOptModify;
355 		} else if (ISTREQ(feat, "UNIX.mode")) {
356 			flags |= kMlsOptUNIXmode;
357 		} else if (ISTREQ(feat, "UNIX.owner")) {
358 			flags |= kMlsOptUNIXowner;
359 		} else if (ISTREQ(feat, "UNIX.group")) {
360 			flags |= kMlsOptUNIXgroup;
361 		} else if (ISTREQ(feat, "perm")) {
362 			flags |= kMlsOptPerm;
363 		} else if (ISTREQ(feat, "UNIX.uid")) {
364 			flags |= kMlsOptUNIXuid;
365 		} else if (ISTREQ(feat, "UNIX.gid")) {
366 			flags |= kMlsOptUNIXgid;
367 		} else if (ISTREQ(feat, "UNIX.gid")) {
368 			flags |= kMlsOptUnique;
369 		}
370 		feat = strtok(NULL, ";*");
371 	}
372 
373 	cip->mlsFeatures = flags;
374 }	/* FTPExamineMlstFeatures */
375 
376 
377 
378 
379 int
FTPQueryFeatures(const FTPCIPtr cip)380 FTPQueryFeatures(const FTPCIPtr cip)
381 {
382 	ResponsePtr rp;
383 	int result;
384 	LinePtr lp;
385 	char *cp, *p;
386 
387 	if (cip == NULL)
388 		return (kErrBadParameter);
389 	if (strcmp(cip->magic, kLibraryMagic))
390 		return (kErrBadMagic);
391 
392 	if (cip->serverType == kServerTypeNetWareFTP) {
393 		/* NetWare 5.00 server freaks out when
394 		 * you give it a command it doesn't
395 		 * recognize, so cheat here and return.
396 		 */
397 		cip->hasPASV = kCommandAvailable;
398 		cip->hasSIZE = kCommandNotAvailable;
399 		cip->hasMDTM = kCommandNotAvailable;
400 		cip->hasREST = kCommandNotAvailable;
401 		cip->NLSTfileParamWorks = kCommandAvailable;
402 		cip->hasUTIME = kCommandNotAvailable;
403 		cip->hasCLNT = kCommandNotAvailable;
404 		cip->hasMLST = kCommandNotAvailable;
405 		cip->hasMLSD = kCommandNotAvailable;
406 		return (kNoErr);
407 	}
408 
409 	rp = InitResponse();
410 	if (rp == NULL) {
411 		cip->errNo = kErrMallocFailed;
412 		result = cip->errNo;
413 	} else {
414 		rp->printMode = (kResponseNoPrint|kResponseNoSave);
415 		result = RCmd(cip, rp, "FEAT");
416 		if (result < kNoErr) {
417 			DoneWithResponse(cip, rp);
418 			return (result);
419 		} else if (result != 2) {
420 			/* We cheat here and pre-populate some
421 			 * fields when the server is wu-ftpd.
422 			 * This server is very common and we
423 			 * know it has always had these.
424 			 */
425 			 if (cip->serverType == kServerTypeWuFTPd) {
426 				cip->hasPASV = kCommandAvailable;
427 				cip->hasSIZE = kCommandAvailable;
428 				cip->hasMDTM = kCommandAvailable;
429 				cip->hasREST = kCommandAvailable;
430 				cip->NLSTfileParamWorks = kCommandAvailable;
431 			} else if (cip->serverType == kServerTypeNcFTPd) {
432 				cip->hasPASV = kCommandAvailable;
433 				cip->hasSIZE = kCommandAvailable;
434 				cip->hasMDTM = kCommandAvailable;
435 				cip->hasREST = kCommandAvailable;
436 				cip->NLSTfileParamWorks = kCommandAvailable;
437 			}
438 
439 			/* Newer commands are only shown in FEAT,
440 			 * so we don't have to do the "try it,
441 			 * then save that it didn't work" thing.
442 			 */
443 			cip->hasMLST = kCommandNotAvailable;
444 			cip->hasMLSD = kCommandNotAvailable;
445 		} else {
446 			cip->hasFEAT = kCommandAvailable;
447 
448 			for (lp = rp->msg.first; lp != NULL; lp = lp->next) {
449 				/* If first character was not a space it is
450 				 * either:
451 				 *
452 				 * (a) The header line in the response;
453 				 * (b) The trailer line in the response;
454 				 * (c) A protocol violation.
455 				 */
456 				cp = lp->line;
457 				if (*cp++ != ' ')
458 					continue;
459 				if (ISTRNCMP(cp, "PASV", 4) == 0) {
460 					cip->hasPASV = kCommandAvailable;
461 				} else if (ISTRNCMP(cp, "SIZE", 4) == 0) {
462 					cip->hasSIZE = kCommandAvailable;
463 				} else if (ISTRNCMP(cp, "MDTM", 4) == 0) {
464 					cip->hasMDTM = kCommandAvailable;
465 				} else if (ISTRNCMP(cp, "REST", 4) == 0) {
466 					cip->hasREST = kCommandAvailable;
467 				} else if (ISTRNCMP(cp, "UTIME", 5) == 0) {
468 					cip->hasUTIME = kCommandAvailable;
469 				} else if (ISTRNCMP(cp, "MLST", 4) == 0) {
470 					cip->hasMLST = kCommandAvailable;
471 					cip->hasMLSD = kCommandAvailable;
472 					FTPExamineMlstFeatures(cip, cp + 5);
473 				} else if (ISTRNCMP(cp, "CLNT", 4) == 0) {
474 					cip->hasCLNT = kCommandAvailable;
475 				} else if (ISTRNCMP(cp, "Compliance Level: ", 18) == 0) {
476 					/* Probably only NcFTPd will ever implement this.
477 					 * But we use it internally to differentiate
478 					 * between different NcFTPd implementations of
479 					 * IETF extensions.
480 					 */
481 					cip->ietfCompatLevel = atoi(cp + 18);
482 				}
483 			}
484 		}
485 
486 		ReInitResponse(cip, rp);
487 		result = RCmd(cip, rp, "HELP SITE");
488 		if (result == 2) {
489 			for (lp = rp->msg.first; lp != NULL; lp = lp->next) {
490 				cp = lp->line;
491 				if (strstr(cp, "RETRBUFSIZE") != NULL)
492 					cip->hasRETRBUFSIZE = kCommandAvailable;
493 				if (strstr(cp, "RBUFSZ") != NULL)
494 					cip->hasRBUFSZ = kCommandAvailable;
495 				/* See if RBUFSIZ matches (but not STORBUFSIZE) */
496 				if (
497 					((p = strstr(cp, "RBUFSIZ")) != NULL) &&
498 					(
499 					 	(p == cp) ||
500 						((p > cp) && (!isupper(p[-1])))
501 					)
502 				)
503 					cip->hasRBUFSIZ = kCommandAvailable;
504 				if (strstr(cp, "STORBUFSIZE") != NULL)
505 					cip->hasSTORBUFSIZE = kCommandAvailable;
506 				if (strstr(cp, "SBUFSIZ") != NULL)
507 					cip->hasSBUFSIZ = kCommandAvailable;
508 				if (strstr(cp, "SBUFSZ") != NULL)
509 					cip->hasSBUFSZ = kCommandAvailable;
510 				if (strstr(cp, "BUFSIZE") != NULL)
511 					cip->hasBUFSIZE = kCommandAvailable;
512 			}
513 		}
514 		DoneWithResponse(cip, rp);
515 	}
516 
517 	return (kNoErr);
518 }	/* FTPQueryFeatures */
519 
520 
521 
522 int
FTPCloseHost(const FTPCIPtr cip)523 FTPCloseHost(const FTPCIPtr cip)
524 {
525 	ResponsePtr rp;
526 	int result;
527 
528 	if (cip == NULL)
529 		return (kErrBadParameter);
530 	if (strcmp(cip->magic, kLibraryMagic))
531 		return (kErrBadMagic);
532 
533 	/* Data connection shouldn't be open normally. */
534 	if (cip->dataSocket != kClosedFileDescriptor)
535 		FTPAbortDataTransfer(cip);
536 
537 	result = kNoErr;
538 	if (cip->connected != 0) {
539 		rp = InitResponse();
540 		if (rp == NULL) {
541 			cip->errNo = kErrMallocFailed;
542 			result = cip->errNo;
543 		} else {
544 			rp->eofOkay = 1;	/* We are expecting EOF after this cmd. */
545 			cip->eofOkay = 1;
546 			(void) RCmd(cip, rp, "QUIT");
547 			DoneWithResponse(cip, rp);
548 		}
549 	}
550 
551 	CloseControlConnection(cip);
552 
553 	/* Dispose dynamic data structures, so you won't leak
554 	 * if you OpenHost with this again.
555 	 */
556 	FTPDeallocateHost(cip);
557 	return (result);
558 }	/* FTPCloseHost */
559 
560 
561 
562 
563 void
FTPShutdownHost(const FTPCIPtr cip)564 FTPShutdownHost(const FTPCIPtr cip)
565 {
566 #ifdef SIGPIPE
567 	FTPSigProc osigpipe;
568 #endif
569 
570 	if (cip == NULL)
571 		return;
572 	if (strcmp(cip->magic, kLibraryMagic))
573 		return;
574 
575 #ifdef SIGPIPE
576 	osigpipe = signal(SIGPIPE, (FTPSigProc) SIG_IGN);
577 #endif
578 
579 	/* Linger could cause close to block, so unset it. */
580 	if (cip->dataSocket != kClosedFileDescriptor)
581 		(void) SetLinger(cip, cip->dataSocket, 0);
582 	CloseDataConnection(cip);	/* Shouldn't be open normally. */
583 
584 	/* Linger should already be turned off for this. */
585 	CloseControlConnection(cip);
586 
587 	FTPDeallocateHost(cip);
588 
589 #ifdef SIGPIPE
590 	(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
591 #endif
592 }	/* FTPShutdownHost */
593 
594 
595 
596 
597 void
URLCopyToken(char * dst,size_t dsize,const char * src,size_t howmuch)598 URLCopyToken(char *dst, size_t dsize, const char *src, size_t howmuch)
599 {
600 	char *dlim;
601 	const char *slim;
602 	unsigned int hc;
603 	int c;
604 	char h[4];
605 
606 	dlim = dst + dsize - 1;		/* leave room for \0 */
607 	slim = src + howmuch;
608 	while (src < slim) {
609 		c = *src++;
610 		if (c == '\0')
611 			break;
612 		if (c == '%') {
613 			/* hex representation */
614 			if (src < slim + 2) {
615 				h[0] = *src++;
616 				h[1] = *src++;
617 				h[2] = '\0';
618 				hc = 0xeeff;
619 				if ((sscanf(h, "%x", &hc) >= 0) && (hc != 0xeeff)) {
620 					if (dst < dlim) {
621 						*(unsigned char *)dst = (unsigned char) hc;
622 						dst++;
623 					}
624 				}
625 			} else {
626 				break;
627 			}
628 		} else {
629 			*dst++ = (char) c;
630 		}
631 	}
632 	*dst = '\0';
633 }	/* URLCopyToken */
634 
635 
636 
637 
638 int
FTPDecodeURL(const FTPCIPtr cip,char * const url,LineListPtr cdlist,char * const fn,const size_t fnsize,int * const xtype,int * const wantnlst)639 FTPDecodeURL(
640 	const FTPCIPtr cip,	/* area pointed to may be modified */
641 	char *const url,	/* always modified */
642 	LineListPtr cdlist,	/* always modified */
643 	char *const fn,		/* always modified */
644 	const size_t fnsize,
645 	int *const xtype,	/* optional; may be modified */
646 	int *const wantnlst	/* optional; always modified */
647 )
648 {
649 	char *cp;
650 	char *hstart, *hend;
651 	char *h2start;
652 	char *at1;
653 	char portstr[32];
654 	int port;
655 	int sc;
656 	char *lastslash;
657 	char *parsestr;
658 	char *tok;
659 	char subdir[128];
660 	char *semi;
661 
662 	InitLineList(cdlist);
663 	*fn = '\0';
664 	if (wantnlst != NULL)
665 		*wantnlst = 0;
666 	if (xtype != NULL)
667 		*xtype = kTypeBinary;
668 
669 	cp = NULL;	/* shut up warnings */
670 #ifdef HAVE_STRCASECMP
671 	if (strncasecmp(url, "<URL:ftp://", 11) == 0) {
672 		cp = url + strlen(url) - 1;
673 		if (*cp != '>')
674 			return (kMalformedURL);	/* missing closing > */
675 		*cp = '\0';
676 		cp = url + 11;
677 	} else if (strncasecmp(url, "ftp://", 6) == 0) {
678 		cp = url + 6;
679 	} else {
680 		return (-1);		/* not a RFC 1738 URL */
681 	}
682 #else	/* HAVE_STRCASECMP */
683 	if (strncmp(url, "<URL:ftp://", 11) == 0) {
684 		cp = url + strlen(url) - 1;
685 		if (*cp != '>')
686 			return (kMalformedURL);	/* missing closing > */
687 		*cp = '\0';
688 		cp = url + 11;
689 	} else if (strncmp(url, "ftp://", 6) == 0) {
690 		cp = url + 6;
691 	} else {
692 		return (-1);		/* not a RFC 1738 URL */
693 	}
694 #endif	/* HAVE_STRCASECMP */
695 
696 	/* //<user>:<password>@<host>:<port>/<url-path> */
697 
698 	at1 = NULL;
699 	for (hstart = cp; ; cp++) {
700 		if (*cp == '@') {
701 			if (at1 == NULL)
702 				at1 = cp;
703 			else
704 				return (kMalformedURL);
705 		} else if ((*cp == '\0') || (*cp == '/')) {
706 			hend = cp;
707 			break;
708 		}
709 	}
710 
711 	sc = *hend;
712 	*hend = '\0';
713 	if (at1 == NULL) {
714 		/* no user or password */
715 		h2start = hstart;
716 	} else {
717 		*at1 = '\0';
718 		cp = strchr(hstart, ':');
719 		if (cp == NULL) {
720 			/* whole thing is the user name then */
721 			URLCopyToken(cip->user, sizeof(cip->user), hstart, (size_t) (at1 - hstart));
722 		} else if (strchr(cp + 1, ':') != NULL) {
723 			/* Too many colons */
724 			return (kMalformedURL);
725 		} else {
726 			URLCopyToken(cip->user, sizeof(cip->user), hstart, (size_t) (cp - hstart));
727 			URLCopyToken(cip->pass, sizeof(cip->pass), cp + 1, (size_t) (at1 - (cp + 1)));
728 		}
729 		*at1 = '@';
730 		h2start = at1 + 1;
731 	}
732 
733 	cp = strchr(h2start, ':');
734 	if (cp == NULL) {
735 		/* whole thing is the host then */
736 		URLCopyToken(cip->host, sizeof(cip->host), h2start, (size_t) (hend - h2start));
737 	} else if (strchr(cp + 1, ':') != NULL) {
738 		/* Too many colons */
739 		return (kMalformedURL);
740 	} else {
741 		URLCopyToken(cip->host, sizeof(cip->host), h2start, (size_t) (cp - h2start));
742 		URLCopyToken(portstr, sizeof(portstr), cp + 1, (size_t) (hend - (cp + 1)));
743 		port = atoi(portstr);
744 		if (port > 0)
745 			cip->port = port;
746 	}
747 
748 	*hend = (char) sc;
749 	if ((*hend == '\0') || ((*hend == '/') && (hend[1] == '\0'))) {
750 		/* no path, okay */
751 		return (0);
752 	}
753 
754 	lastslash = strrchr(hend, '/');
755 	if (lastslash == NULL) {
756 		/* no path, okay */
757 		return (0);
758 	}
759 	*lastslash = '\0';
760 
761 	if ((semi = strchr(lastslash + 1, ';')) != NULL) {
762 		*semi++ = '\0';
763 #ifdef HAVE_STRCASECMP
764 		if (strcasecmp(semi, "type=i") == 0) {
765 			if (xtype != NULL)
766 				*xtype = kTypeBinary;
767 		} else if (strcasecmp(semi, "type=a") == 0) {
768 			if (xtype != NULL)
769 				*xtype = kTypeAscii;
770 		} else if (strcasecmp(semi, "type=b") == 0) {
771 			if (xtype != NULL)
772 				*xtype = kTypeBinary;
773 		} else if (strcasecmp(semi, "type=d") == 0) {
774 			if (wantnlst != NULL) {
775 				*wantnlst = 1;
776 				if (xtype != NULL)
777 					*xtype = kTypeAscii;
778 			} else {
779 				/* You didn't want these. */
780 				return (kMalformedURL);
781 			}
782 		}
783 #else	/* HAVE_STRCASECMP */
784 		if (strcmp(semi, "type=i") == 0) {
785 			if (xtype != NULL)
786 				*xtype = kTypeBinary;
787 		} else if (strcmp(semi, "type=a") == 0) {
788 			if (xtype != NULL)
789 				*xtype = kTypeAscii;
790 		} else if (strcmp(semi, "type=b") == 0) {
791 			if (xtype != NULL)
792 				*xtype = kTypeBinary;
793 		} else if (strcmp(semi, "type=d") == 0) {
794 			if (wantnlst != NULL) {
795 				*wantnlst = 1;
796 				if (xtype != NULL)
797 					*xtype = kTypeAscii;
798 			} else {
799 				/* You didn't want these. */
800 				return (kMalformedURL);
801 			}
802 		}
803 #endif	/* HAVE_STRCASECMP */
804 	}
805 	URLCopyToken(fn, fnsize, lastslash + 1, strlen(lastslash + 1));
806 	for (parsestr = hend; (tok = strtok(parsestr, "/")) != NULL; parsestr = NULL) {
807 		URLCopyToken(subdir, sizeof(subdir), tok, strlen(tok));
808 		(void) AddLine(cdlist, subdir);
809 	}
810 	*lastslash = '/';
811 	return (kNoErr);
812 }	/* FTPDecodeURL */
813 
814 
815 
816 
817 int
FTPOpenHost(const FTPCIPtr cip)818 FTPOpenHost(const FTPCIPtr cip)
819 {
820 	int result;
821 	time_t t0, t1;
822 	int elapsed;
823 	int dials;
824 
825 	if (cip == NULL)
826 		return (kErrBadParameter);
827 	if (strcmp(cip->magic, kLibraryMagic))
828 		return (kErrBadMagic);
829 
830 	if (cip->host[0] == '\0') {
831 		cip->errNo = kErrBadParameter;
832 		return (kErrBadParameter);
833 	}
834 
835 	for (	result = kErrConnectMiscErr, dials = 0;
836 		cip->maxDials < 0 || dials < cip->maxDials;
837 		dials++)
838 	{
839 		/* Allocate (or if the host was closed, reallocate)
840 		 * the transfer data buffer.
841 		 */
842 		result = FTPAllocateHost(cip);
843 		if (result < 0)
844 			return (result);
845 
846 		if (dials > 0)
847 			PrintF(cip, "Retry Number: %d\n", dials);
848 		if (cip->redialStatusProc != 0)
849 			(*cip->redialStatusProc)(cip, kRedialStatusDialing, dials);
850 		(void) time(&t0);
851 		result = OpenControlConnection(cip, cip->host, cip->port);
852 		(void) time(&t1);
853 		if (result == kNoErr) {
854 			/* We were hooked up successfully. */
855 			PrintF(cip, "Connected to %s.\n", cip->host);
856 
857 			result = FTPLoginHost(cip);
858 			if (result == kNoErr) {
859 				(void) FTPQueryFeatures(cip);
860 				break;
861 			}
862 
863 			/* Close and try again. */
864 			(void) FTPCloseHost(cip);
865 
866 			/* Originally we also stopped retyring if
867 			 * we got kErrBadRemoteUser and non-anonymous,
868 			 * but some FTP servers apparently do their
869 			 * max user check after the username is sent.
870 			 */
871 			if (result == kErrBadRemoteUserOrPassword /* || (result == kErrBadRemoteUser) */) {
872 				if (strcmp(cip->user, "anonymous") != 0) {
873 					/* Non-anonymous login was denied, and
874 					 * retrying is not going to help.
875 					 */
876 					break;
877 				}
878 			}
879 		} else if ((result != kErrConnectRetryableErr) && (result != kErrConnectRefused) && (result != kErrRemoteHostClosedConnection)) {
880 			/* Irrecoverable error, so don't bother redialing.
881 			 * The error message should have already been printed
882 			 * from OpenControlConnection().
883 			 */
884 			PrintF(cip, "Cannot recover from miscellaneous open error %d.\n", result);
885 			return result;
886 		}
887 
888 		/* Retryable error, wait and then redial. */
889 		if (cip->redialDelay > 0) {
890 			/* But don't sleep if this is the last loop. */
891 			if ((cip->maxDials < 0) || (dials < (cip->maxDials - 1))) {
892 				elapsed = (int) (t1 - t0);
893 				if (elapsed < cip->redialDelay) {
894 					PrintF(cip, "Sleeping %u seconds.\n",
895 						(unsigned) cip->redialDelay - elapsed);
896 					if (cip->redialStatusProc != 0)
897 						(*cip->redialStatusProc)(cip, kRedialStatusSleeping, cip->redialDelay - elapsed);
898 					(void) sleep((unsigned) cip->redialDelay - elapsed);
899 				}
900 			}
901 		}
902 	}
903 	return (result);
904 }	/* FTPOpenHost */
905 
906 
907 
908 
909 int
FTPOpenHostNoLogin(const FTPCIPtr cip)910 FTPOpenHostNoLogin(const FTPCIPtr cip)
911 {
912 	int result;
913 	time_t t0, t1;
914 	int elapsed;
915 	int dials;
916 
917 	if (cip == NULL)
918 		return (kErrBadParameter);
919 	if (strcmp(cip->magic, kLibraryMagic))
920 		return (kErrBadMagic);
921 
922 	if (cip->host[0] == '\0') {
923 		cip->errNo = kErrBadParameter;
924 		return (kErrBadParameter);
925 	}
926 
927 	for (	result = kErrConnectMiscErr, dials = 0;
928 		cip->maxDials < 0 || dials < cip->maxDials;
929 		dials++)
930 	{
931 
932 		/* Allocate (or if the host was closed, reallocate)
933 		 * the transfer data buffer.
934 		 */
935 		result = FTPAllocateHost(cip);
936 		if (result < 0)
937 			return (result);
938 
939 		if (dials > 0)
940 			PrintF(cip, "Retry Number: %d\n", dials);
941 		if (cip->redialStatusProc != 0)
942 			(*cip->redialStatusProc)(cip, kRedialStatusDialing, dials);
943 		(void) time(&t0);
944 		result = OpenControlConnection(cip, cip->host, cip->port);
945 		(void) time(&t1);
946 		if (result == kNoErr) {
947 			/* We were hooked up successfully. */
948 			PrintF(cip, "Connected to %s.\n", cip->host);
949 
950 			/* Not logging in... */
951 			if (result == kNoErr)
952 				break;
953 		} else if ((result != kErrConnectRetryableErr) && (result != kErrConnectRefused) && (result != kErrRemoteHostClosedConnection)) {
954 			/* Irrecoverable error, so don't bother redialing.
955 			 * The error message should have already been printed
956 			 * from OpenControlConnection().
957 			 */
958 			PrintF(cip, "Cannot recover from miscellaneous open error %d.\n", result);
959 			return result;
960 		}
961 
962 		/* Retryable error, wait and then redial. */
963 		if (cip->redialDelay > 0) {
964 			/* But don't sleep if this is the last loop. */
965 			if ((cip->maxDials < 0) || (dials < (cip->maxDials - 1))) {
966 				elapsed = (int) (t1 - t0);
967 				if (elapsed < cip->redialDelay) {
968 					PrintF(cip, "Sleeping %u seconds.\n",
969 						(unsigned) cip->redialDelay - elapsed);
970 					if (cip->redialStatusProc != 0)
971 						(*cip->redialStatusProc)(cip, kRedialStatusSleeping, cip->redialDelay - elapsed);
972 					(void) sleep((unsigned) cip->redialDelay - elapsed);
973 				}
974 			}
975 		}
976 	}
977 	return (result);
978 }	/* FTPOpenHostNoLogin */
979 
980 
981 
982 
983 int
FTPInitConnectionInfo(const FTPLIPtr lip,const FTPCIPtr cip,size_t bufSize)984 FTPInitConnectionInfo(const FTPLIPtr lip, const FTPCIPtr cip, size_t bufSize)
985 {
986 	size_t siz;
987 
988 	if ((lip == NULL) || (cip == NULL) || (bufSize == 0))
989 		return (kErrBadParameter);
990 
991 	siz = sizeof(FTPConnectionInfo);
992 	(void) memset(cip, 0, siz);
993 
994 	if (strcmp(lip->magic, kLibraryMagic))
995 		return (kErrBadMagic);
996 
997 	cip->buf = NULL;	/* denote that it needs to be allocated. */
998 	cip->bufSize = bufSize;
999 	cip->port = lip->defaultPort;
1000 	cip->firewallPort = lip->defaultPort;
1001 	cip->maxDials = kDefaultMaxDials;
1002 	cip->redialDelay = kDefaultRedialDelay;
1003 	cip->xferTimeout = kDefaultXferTimeout;
1004 	cip->connTimeout = kDefaultConnTimeout;
1005 	cip->ctrlTimeout = kDefaultCtrlTimeout;
1006 	cip->abortTimeout = kDefaultAbortTimeout;
1007 	cip->ctrlSocketR = kClosedFileDescriptor;
1008 	cip->ctrlSocketW = kClosedFileDescriptor;
1009 	cip->dataPortMode = kSendPortMode;
1010 	cip->dataSocket = kClosedFileDescriptor;
1011 	cip->lip = lip;
1012 	cip->hasPASV = kCommandAvailabilityUnknown;
1013 	cip->hasSIZE = kCommandAvailabilityUnknown;
1014 	cip->hasMDTM = kCommandAvailabilityUnknown;
1015 	cip->hasREST = kCommandAvailabilityUnknown;
1016 	cip->hasNLST_d = kCommandAvailabilityUnknown;
1017 	cip->hasUTIME = kCommandAvailabilityUnknown;
1018 	cip->hasFEAT = kCommandAvailabilityUnknown;
1019 	cip->hasMLSD = kCommandAvailabilityUnknown;
1020 	cip->hasMLST = kCommandAvailabilityUnknown;
1021 	cip->hasCLNT = kCommandAvailabilityUnknown;
1022 	cip->hasRETRBUFSIZE = kCommandAvailabilityUnknown;
1023 	cip->hasRBUFSIZ = kCommandAvailabilityUnknown;
1024 	cip->hasRBUFSZ = kCommandAvailabilityUnknown;
1025 	cip->hasSTORBUFSIZE = kCommandAvailabilityUnknown;
1026 	cip->hasSBUFSIZ = kCommandAvailabilityUnknown;
1027 	cip->hasSBUFSZ = kCommandAvailabilityUnknown;
1028 	cip->STATfileParamWorks = kCommandAvailabilityUnknown;
1029 	cip->NLSTfileParamWorks = kCommandAvailabilityUnknown;
1030 	cip->firewallType = kFirewallNotInUse;
1031 	cip->startingWorkingDirectory = NULL;
1032 	(void) STRNCPY(cip->magic, kLibraryMagic);
1033 	(void) STRNCPY(cip->user, "anonymous");
1034 	return (kNoErr);
1035 }	/* FTPInitConnectionInfo */
1036 
1037 
1038 
1039 
1040 int
FTPRebuildConnectionInfo(const FTPLIPtr lip,const FTPCIPtr cip)1041 FTPRebuildConnectionInfo(const FTPLIPtr lip, const FTPCIPtr cip)
1042 {
1043 	char *buf;
1044 
1045 	cip->lip = lip;
1046 	cip->debugLog = NULL;
1047 	cip->errLog = NULL;
1048 	cip->debugLogProc = NULL;
1049 	cip->errLogProc = NULL;
1050 	cip->buf = NULL;
1051 	cip->cin = NULL;
1052 	cip->cout = NULL;
1053 	cip->errNo = 0;
1054 	cip->progress = NULL;
1055 	cip->rname = NULL;
1056 	cip->lname = NULL;
1057 	cip->onConnectMsgProc = NULL;
1058 	cip->redialStatusProc = NULL;
1059 	cip->printResponseProc = NULL;
1060 	cip->onLoginMsgProc = NULL;
1061 	cip->passphraseProc = NULL;
1062 	cip->startingWorkingDirectory = NULL;
1063 	cip->asciiFilenameExtensions = NULL;
1064 
1065 	(void) memset(&cip->lastFTPCmdResultLL, 0, sizeof(LineList));
1066 
1067 	/* Allocate a new buffer. */
1068 	buf = (char *) calloc((size_t) 1, cip->bufSize);
1069 	if (buf == NULL) {
1070 		cip->errNo = kErrMallocFailed;
1071 		return (kErrMallocFailed);
1072 	}
1073 	cip->buf = buf;
1074 
1075 	/* Reattach the FILE pointers for use with the Std I/O library
1076 	 * routines.
1077 	 */
1078 	if ((cip->cin = _fdopen(cip->ctrlSocketR, "r")) == NULL) {
1079 		cip->errNo = kErrFdopenR;
1080 		cip->ctrlSocketR = kClosedFileDescriptor;
1081 		cip->ctrlSocketW = kClosedFileDescriptor;
1082 		return (kErrFdopenR);
1083 	}
1084 
1085 	if ((cip->cout = _fdopen(cip->ctrlSocketW, "w")) == NULL) {
1086 		CloseFile(&cip->cin);
1087 		cip->errNo = kErrFdopenW;
1088 		cip->ctrlSocketR = kClosedFileDescriptor;
1089 		cip->ctrlSocketW = kClosedFileDescriptor;
1090 		return (kErrFdopenW);
1091 	}
1092 
1093 #if USE_SIO
1094 	if (InitSReadlineInfo(&cip->ctrlSrl, cip->ctrlSocketR, cip->srlBuf, sizeof(cip->srlBuf), (int) cip->ctrlTimeout, 1) < 0) {
1095 		cip->errNo = kErrFdopenW;
1096 		CloseFile(&cip->cin);
1097 		cip->errNo = kErrFdopenW;
1098 		cip->ctrlSocketR = kClosedFileDescriptor;
1099 		cip->ctrlSocketW = kClosedFileDescriptor;
1100 		return (kErrFdopenW);
1101 	}
1102 #endif
1103 	return (kNoErr);
1104 }	/* FTPRebuildConnectionInfo */
1105 
1106 
1107 
1108 
1109 int
FTPInitLibrary(const FTPLIPtr lip)1110 FTPInitLibrary(const FTPLIPtr lip)
1111 {
1112 	struct servent *ftp;
1113 
1114 	if (lip == NULL)
1115 		return (kErrBadParameter);
1116 
1117 	(void) memset(lip, 0, sizeof(FTPLibraryInfo));
1118 	if ((ftp = getservbyname("ftp", "tcp")) == NULL)
1119 		lip->defaultPort = (unsigned int) kDefaultFTPPort;
1120 	else
1121 		lip->defaultPort = (unsigned int) ntohs(ftp->s_port);
1122 
1123 	lip->init = 1;
1124 	(void) STRNCPY(lip->magic, kLibraryMagic);
1125 
1126 	/* We'll initialize the defaultAnonPassword field
1127 	 * later when we try the first anon ftp connection.
1128 	 */
1129 
1130 #ifdef HAVE_LIBSOCKS
1131 	SOCKSinit("libncftp");
1132 	lip->socksInit = 1;
1133 #endif
1134 	return (kNoErr);
1135 }	/* FTPInitLibrary */
1136 
1137 /* Open.c */
1138