1 /* rcmd.c
2  *
3  * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
4  * All rights reserved.
5  *
6  */
7 
8 #include "syshdrs.h"
9 
10 #if !defined(NO_SIGNALS) && (USE_SIO || !defined(SIGALRM) || !defined(SIGPIPE) || !defined(SIGINT))
11 #	define NO_SIGNALS 1
12 #endif
13 
14 #ifndef NO_SIGNALS
15 
16 #ifdef HAVE_SIGSETJMP
17 static sigjmp_buf gBrokenCtrlJmp;
18 #else
19 static jmp_buf gBrokenCtrlJmp;
20 #endif	/* HAVE_SIGSETJMP */
21 
22 static void
BrokenCtrl(int UNUSED (signumIgnored))23 BrokenCtrl(int UNUSED(signumIgnored))
24 {
25 	LIBNCFTP_USE_VAR(signumIgnored); 		/* Shut up gcc */
26 #ifdef HAVE_SIGSETJMP
27 	siglongjmp(gBrokenCtrlJmp, 1);
28 #else
29 	longjmp(gBrokenCtrlJmp, 1);
30 #endif	/* HAVE_SIGSETJMP */
31 }	/* BrokenCtrl */
32 
33 #endif	/* NO_SIGNALS */
34 
35 
36 /* A 'Response' parameter block is simply zeroed to be considered init'ed. */
37 ResponsePtr
InitResponse(void)38 InitResponse(void)
39 {
40 	ResponsePtr rp;
41 
42 	rp = (ResponsePtr) calloc(SZ(1), sizeof(Response));
43 	if (rp != NULL)
44 		InitLineList(&rp->msg);
45 	return (rp);
46 }	/* InitResponse */
47 
48 
49 
50 
51 /* If we don't print it to the screen, we may want to save it to our
52  * trace log.
53  */
54 void
TraceResponse(const FTPCIPtr cip,ResponsePtr rp)55 TraceResponse(const FTPCIPtr cip, ResponsePtr rp)
56 {
57 	LinePtr lp;
58 
59 	if (rp != NULL)	{
60 		lp = rp->msg.first;
61 		if (lp != NULL) {
62 			PrintF(cip, "%3d: %s\n", rp->code, lp->line);
63 			for (lp = lp->next; lp != NULL; lp = lp->next)
64 				PrintF(cip, "     %s\n", lp->line);
65 		}
66 	}
67 }	/* TraceResponse */
68 
69 
70 
71 
72 
73 void
PrintResponse(const FTPCIPtr cip,LineListPtr llp)74 PrintResponse(const FTPCIPtr cip, LineListPtr llp)
75 {
76 	LinePtr lp;
77 
78 	if (llp != NULL) {
79 		for (lp = llp->first; lp != NULL; lp = lp->next)
80 			PrintF(cip, "%s\n", lp->line);
81 	}
82 }	/* PrintResponse */
83 
84 
85 
86 
87 
88 static void
SaveLastResponse(const FTPCIPtr cip,ResponsePtr rp)89 SaveLastResponse(const FTPCIPtr cip, ResponsePtr rp)
90 {
91 	if (rp == NULL) {
92 		cip->lastFTPCmdResultStr[0] = '\0';
93 		cip->lastFTPCmdResultNum = -1;
94 		DisposeLineListContents(&cip->lastFTPCmdResultLL);
95 	} else if ((rp->msg.first == NULL) || (rp->msg.first->line == NULL)) {
96 		cip->lastFTPCmdResultStr[0] = '\0';
97 		cip->lastFTPCmdResultNum = rp->code;
98 		DisposeLineListContents(&cip->lastFTPCmdResultLL);
99 	} else {
100 		(void) STRNCPY(cip->lastFTPCmdResultStr, rp->msg.first->line);
101 		cip->lastFTPCmdResultNum = rp->code;
102 
103 		/* Dispose previous command's line list. */
104 		DisposeLineListContents(&cip->lastFTPCmdResultLL);
105 
106 		/* Save this command's line list. */
107 		cip->lastFTPCmdResultLL = rp->msg;
108 	}
109 }	/* SaveLastResponse */
110 
111 
112 
113 void
DoneWithResponse(const FTPCIPtr cip,ResponsePtr rp)114 DoneWithResponse(const FTPCIPtr cip, ResponsePtr rp)
115 {
116 	/* Dispose space taken up by the Response, and clear it out
117 	 * again.  For some reason, I like to return memory to zeroed
118 	 * when not in use.
119 	 */
120 	if (rp != NULL) {
121 		TraceResponse(cip, rp);
122 		if (cip->printResponseProc != 0) {
123 			if ((rp->printMode & kResponseNoProc) == 0)
124 				(*cip->printResponseProc)(cip, rp);
125 		}
126 		if ((rp->printMode & kResponseNoSave) == 0)
127 			SaveLastResponse(cip, rp);
128 		else
129 			DisposeLineListContents(&rp->msg);
130 		(void) memset(rp, 0, sizeof(Response));
131 		free(rp);
132 	}
133 }	/* DoneWithResponse */
134 
135 
136 
137 
138 /* This takes an existing Response and recycles it, by clearing out
139  * the current contents.
140  */
141 void
ReInitResponse(const FTPCIPtr cip,ResponsePtr rp)142 ReInitResponse(const FTPCIPtr cip, ResponsePtr rp)
143 {
144 	if (rp != NULL) {
145 		TraceResponse(cip, rp);
146 		if (cip->printResponseProc != 0) {
147 			if ((rp->printMode & kResponseNoProc) == 0)
148 				(*cip->printResponseProc)(cip, rp);
149 		}
150 		if ((rp->printMode & kResponseNoSave) == 0)
151 			SaveLastResponse(cip, rp);
152 		else
153 			DisposeLineListContents(&rp->msg);
154 		(void) memset(rp, 0, sizeof(Response));
155 	}
156 }	/* ReInitResponse */
157 
158 
159 
160 
161 #ifndef NO_SIGNALS
162 
163 /* Since the control stream is defined by the Telnet protocol (RFC 854),
164  * we follow Telnet rules when reading the control stream.  We use this
165  * routine when we want to read a response from the host.
166  */
167 int
GetTelnetString(const FTPCIPtr cip,char * str,size_t siz,FILE * cin,FILE * cout)168 GetTelnetString(const FTPCIPtr cip, char *str, size_t siz, FILE *cin, FILE *cout)
169 {
170 	int c;
171 	size_t n;
172 	int eofError;
173 	char *cp;
174 
175 	cp = str;
176 	--siz;		/* We'll need room for the \0. */
177 
178 	if ((cin == NULL) || (cout == NULL)) {
179 		eofError = -1;
180 		goto done;
181 	}
182 
183 	for (n = (size_t)0, eofError = 0; ; ) {
184 		c = fgetc(cin);
185 checkChar:
186 		if (c == EOF) {
187 eof:
188 			eofError = -1;
189 			break;
190 		} else if (c == '\r') {
191 			/* A telnet string can have a CR by itself.  But to denote that,
192 			 * the protocol uses \r\0;  an end of line is denoted \r\n.
193 			 */
194 			c = fgetc(cin);
195 			if (c == '\n') {
196 				/* Had \r\n, so done. */
197 				goto done;
198 			} else if (c == EOF) {
199 				goto eof;
200 			} else if (c == '\0') {
201 				c = '\r';
202 				goto addChar;
203 			} else {
204 				/* Telnet protocol violation! */
205 				goto checkChar;
206 			}
207 		} else if (c == '\n') {
208 			/* Really shouldn't get here.  If we do, the other side
209 			 * violated the TELNET protocol, since eoln's are CR/LF,
210 			 * and not just LF.
211 			 */
212 			PrintF(cip, "TELNET protocol violation:  raw LF.\n");
213 			goto done;
214 		} else if (c == IAC) {
215 			/* Since the control connection uses the TELNET protocol,
216 			 * we have to handle some of its commands ourselves.
217 			 * IAC is the protocol's escape character, meaning that
218 			 * the next character after the IAC (Interpret as Command)
219 			 * character is a telnet command.  But, if there just
220 			 * happened to be a character in the text stream with the
221 			 * same numerical value of IAC, 255, the sender denotes
222 			 * that by having an IAC followed by another IAC.
223 			 */
224 
225 			/* Get the telnet command. */
226 			c = fgetc(cin);
227 
228 			switch (c) {
229 				case WILL:
230 				case WONT:
231 					/* Get the option code. */
232 					c = fgetc(cin);
233 
234 					/* Tell the other side that we don't want
235 					 * to do what they're offering to do.
236 					 */
237 					(void) fprintf(cout, "%c%c%c",IAC,DONT,c);
238 					(void) fflush(cout);
239 					break;
240 				case DO:
241 				case DONT:
242 					/* Get the option code. */
243 					c = fgetc(cin);
244 
245 					/* The other side said they are DOing (or not)
246 					 * something, which would happen if our side
247 					 * asked them to.  Since we didn't do that,
248 					 * ask them to not do this option.
249 					 */
250 					(void) fprintf(cout, "%c%c%c",IAC,WONT,c);
251 					(void) fflush(cout);
252 					break;
253 
254 				case EOF:
255 					goto eof;
256 
257 				default:
258 					/* Just add this character, since it was most likely
259 					 * just an escaped IAC character.
260 					 */
261 					goto addChar;
262 			}
263 		} else {
264 addChar:
265 			/* If the buffer supplied has room, add this character to it. */
266 			if (n < siz) {
267 				*cp++ = c;
268 				++n;
269 			}
270 		}
271 	}
272 
273 done:
274 	*cp = '\0';
275 	return (eofError);
276 }	/* GetTelnetString */
277 
278 #endif	/* NO_SIGNALS */
279 
280 
281 
282 /* Returns 0 if a response was read, or (-1) if an error occurs.
283  * This reads the entire response text into a LineList, which is kept
284  * in the 'Response' structure.
285  */
286 int
GetResponse(const FTPCIPtr cip,ResponsePtr rp)287 GetResponse(const FTPCIPtr cip, ResponsePtr rp)
288 {
289 	longstring str;
290 	int eofError;
291 	str16 code;
292 	char *cp;
293 	int continuation;
294 	volatile FTPCIPtr vcip;
295 #ifdef NO_SIGNALS
296 	int result;
297 #else	/* NO_SIGNALS */
298 	volatile FTPSigProc osigpipe;
299 	int sj;
300 #endif	/* NO_SIGNALS */
301 
302 	/* RFC 959 states that a reply may span multiple lines.  A single
303 	 * line message would have the 3-digit code <space> then the msg.
304 	 * A multi-line message would have the code <dash> and the first
305 	 * line of the msg, then additional lines, until the last line,
306 	 * which has the code <space> and last line of the msg.
307 	 *
308 	 * For example:
309 	 *	123-First line
310 	 *	Second line
311 	 *	234 A line beginning with numbers
312 	 *	123 The last line
313 	 */
314 
315 #ifdef NO_SIGNALS
316 	vcip = cip;
317 #else	/* NO_SIGNALS */
318 	osigpipe = (volatile FTPSigProc) signal(SIGPIPE, BrokenCtrl);
319 	vcip = cip;
320 
321 #ifdef HAVE_SIGSETJMP
322 	sj = sigsetjmp(gBrokenCtrlJmp, 1);
323 #else
324 	sj = setjmp(gBrokenCtrlJmp);
325 #endif	/* HAVE_SIGSETJMP */
326 
327 	if (sj != 0) {
328 		(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
329 		FTPShutdownHost(vcip);
330 		vcip->errNo = kErrRemoteHostClosedConnection;
331 		return(vcip->errNo);
332 	}
333 #endif	/* NO_SIGNALS */
334 
335 #ifdef NO_SIGNALS
336 	cp = str;
337 	eofError = 0;
338 	if (cip->dataTimedOut > 0) {
339 		/* Give up immediately unless the server had already
340 		 * sent a message. Odds are since the data is timed
341 		 * out, so is the control.
342 		 */
343 		if (SWaitUntilReadyForReading(cip->ctrlSocketR, 0) == 0) {
344 			/* timeout */
345 			Error(cip, kDontPerror, "Could not read reply from control connection -- timed out.\n");
346 			FTPShutdownHost(vcip);
347 			cip->errNo = kErrControlTimedOut;
348 			return (cip->errNo);
349 		}
350 	}
351 	result = SReadline(&cip->ctrlSrl, str, sizeof(str) - 1);
352 	if (result == kTimeoutErr) {
353 		/* timeout */
354 		Error(cip, kDontPerror, "Could not read reply from control connection -- timed out.\n");
355 		FTPShutdownHost(vcip);
356 		cip->errNo = kErrControlTimedOut;
357 		return (cip->errNo);
358 	} else if (result == 0) {
359 		/* eof */
360 		eofError = 1;
361 		rp->hadEof = 1;
362 		if (rp->eofOkay == 0)
363 			Error(cip, kDontPerror, "Remote host has closed the connection.\n");
364 		FTPShutdownHost(vcip);
365 		cip->errNo = kErrRemoteHostClosedConnection;
366 		return (cip->errNo);
367 	} else if (result < 0) {
368 		/* error */
369 		Error(cip, kDoPerror, "Could not read reply from control connection");
370 		FTPShutdownHost(vcip);
371 		cip->errNo = kErrInvalidReplyFromServer;
372 		return (cip->errNo);
373 	}
374 
375 	if (str[result - 1] == '\n')
376 		str[result - 1] = '\0';
377 
378 #else	/* NO_SIGNALS */
379 	/* Get the first line of the response. */
380 	eofError = GetTelnetString(cip, str, sizeof(str), cip->cin, cip->cout);
381 
382 	cp = str;
383 	if (*cp == '\0') {
384 		if (eofError < 0) {
385 			/* No bytes read for reply, and EOF detected. */
386 			rp->hadEof = 1;
387 			if (rp->eofOkay == 0)
388 				Error(cip, kDontPerror, "Remote host has closed the connection.\n");
389 			FTPShutdownHost(vcip);
390 			cip->errNo = kErrRemoteHostClosedConnection;
391 			(void) signal(SIGPIPE, osigpipe);
392 			return(cip->errNo);
393 		}
394 	}
395 #endif	/* NO_SIGNALS */
396 
397 	if (!isdigit((int) *cp)) {
398 		Error(cip, kDontPerror, "Invalid reply: \"%s\"\n", cp);
399 		cip->errNo = kErrInvalidReplyFromServer;
400 #ifndef NO_SIGNALS
401 		(void) signal(SIGPIPE, osigpipe);
402 #endif
403 		return (cip->errNo);
404 	}
405 
406 	rp->codeType = *cp - '0';
407 	cp += 3;
408 	continuation = (*cp == '-');
409 	*cp++ = '\0';
410 	(void) STRNCPY(code, str);
411 	rp->code = atoi(code);
412 	(void) AddLine(&rp->msg, cp);
413 	if (eofError < 0) {
414 		/* Read reply, but EOF was there also. */
415 		rp->hadEof = 1;
416 	}
417 
418 	while (continuation) {
419 
420 #ifdef NO_SIGNALS
421 		result = SReadline(&cip->ctrlSrl, str, sizeof(str) - 1);
422 		if (result == kTimeoutErr) {
423 			/* timeout */
424 			Error(cip, kDontPerror, "Could not read reply from control connection -- timed out.\n");
425 			FTPShutdownHost(vcip);
426 			cip->errNo = kErrControlTimedOut;
427 			return (cip->errNo);
428 		} else if (result == 0) {
429 			/* eof */
430 			eofError = 1;
431 			rp->hadEof = 1;
432 			if (rp->eofOkay == 0)
433 				Error(cip, kDontPerror, "Remote host has closed the connection.\n");
434 			FTPShutdownHost(vcip);
435 			cip->errNo = kErrRemoteHostClosedConnection;
436 			return (cip->errNo);
437 		} else if (result < 0) {
438 			/* error */
439 			Error(cip, kDoPerror, "Could not read reply from control connection");
440 			FTPShutdownHost(vcip);
441 			cip->errNo = kErrInvalidReplyFromServer;
442 			return (cip->errNo);
443 		}
444 
445 		if (str[result - 1] == '\n')
446 			str[result - 1] = '\0';
447 #else	/* NO_SIGNALS */
448 		eofError = GetTelnetString(cip, str, sizeof(str), cip->cin, cip->cout);
449 		if (eofError < 0) {
450 			/* Read reply, but EOF was there also. */
451 			rp->hadEof = 1;
452 			continuation = 0;
453 		}
454 #endif	/* NO_SIGNALS */
455 		cp = str;
456 		if (strncmp(code, cp, SZ(3)) == 0) {
457 			cp += 3;
458 			if (*cp == ' ')
459 				continuation = 0;
460 			++cp;
461 		}
462 		(void) AddLine(&rp->msg, cp);
463 	}
464 
465 	if (rp->code == 421) {
466 		/*
467 		 *   421 Service not available, closing control connection.
468 		 *       This may be a reply to any command if the service knows it
469 		 *       must shut down.
470 		 */
471 		if (rp->eofOkay == 0)
472 			Error(cip, kDontPerror, "Remote host has closed the connection.\n");
473 		FTPShutdownHost(vcip);
474 		cip->errNo = kErrRemoteHostClosedConnection;
475 #ifndef NO_SIGNALS
476 		(void) signal(SIGPIPE, osigpipe);
477 #endif
478 		return(cip->errNo);
479 	}
480 
481 #ifndef NO_SIGNALS
482 	(void) signal(SIGPIPE, osigpipe);
483 #endif
484 	return (kNoErr);
485 }	/* GetResponse */
486 
487 
488 
489 
490 /* This creates the complete command text to send, and writes it
491  * on the stream.
492  */
493 #ifdef NO_SIGNALS
494 
495 static int
SendCommand(const FTPCIPtr cip,const char * cmdspec,va_list ap)496 SendCommand(const FTPCIPtr cip, const char *cmdspec, va_list ap)
497 {
498 	longstring command;
499 	int result;
500 
501 	if (cip->ctrlSocketW != kClosedFileDescriptor) {
502 #ifdef HAVE_VSNPRINTF
503 		(void) vsnprintf(command, sizeof(command) - 1, cmdspec, ap);
504 		command[sizeof(command) - 1] = '\0';
505 #else
506 		(void) vsprintf(command, cmdspec, ap);
507 #endif
508 		if ((strncmp(command, "PASS", SZ(4)) != 0) || ((strcmp(cip->user, "anonymous") == 0) && (cip->firewallType == kFirewallNotInUse)))
509 			PrintF(cip, "Cmd: %s\n", command);
510 		else
511 			PrintF(cip, "Cmd: %s\n", "PASS xxxxxxxx");
512 		(void) STRNCAT(command, "\r\n");	/* Use TELNET end-of-line. */
513 		cip->lastFTPCmdResultStr[0] = '\0';
514 		cip->lastFTPCmdResultNum = -1;
515 
516 		result = SWrite(cip->ctrlSocketW, command, strlen(command), (int) cip->ctrlTimeout, 0);
517 
518 		if (result < 0) {
519 			cip->errNo = kErrSocketWriteFailed;
520 			Error(cip, kDoPerror, "Could not write to control stream.\n");
521 			return (cip->errNo);
522 		}
523 		return (kNoErr);
524 	}
525 	return (kErrNotConnected);
526 }	/* SendCommand */
527 
528 #else	/* NO_SIGNALS */
529 
530 static int
SendCommand(const FTPCIPtr cip,const char * cmdspec,va_list ap)531 SendCommand(const FTPCIPtr cip, const char *cmdspec, va_list ap)
532 {
533 	longstring command;
534 	int result;
535 	volatile FTPCIPtr vcip;
536 	volatile FTPSigProc osigpipe;
537 	int sj;
538 
539 	if (cip->cout != NULL) {
540 #ifdef HAVE_VSNPRINTF
541 		(void) vsnprintf(command, sizeof(command) - 1, cmdspec, ap);
542 		command[sizeof(command) - 1] = '\0';
543 #else
544 		(void) vsprintf(command, cmdspec, ap);
545 #endif
546 		if ((strncmp(command, "PASS", SZ(4)) != 0) || ((strcmp(cip->user, "anonymous") == 0) && (cip->firewallType == kFirewallNotInUse)))
547 			PrintF(cip, "Cmd: %s\n", command);
548 		else
549 			PrintF(cip, "Cmd: %s\n", "PASS xxxxxxxx");
550 		(void) STRNCAT(command, "\r\n");	/* Use TELNET end-of-line. */
551 		cip->lastFTPCmdResultStr[0] = '\0';
552 		cip->lastFTPCmdResultNum = -1;
553 
554 		osigpipe = (volatile FTPSigProc) signal(SIGPIPE, BrokenCtrl);
555 		vcip = cip;
556 
557 #ifdef HAVE_SIGSETJMP
558 		sj = sigsetjmp(gBrokenCtrlJmp, 1);
559 #else
560 		sj = setjmp(gBrokenCtrlJmp);
561 #endif	/* HAVE_SIGSETJMP */
562 
563 		if (sj != 0) {
564 			(void) signal(SIGPIPE, (FTPSigProc) osigpipe);
565 			FTPShutdownHost(vcip);
566 			if (vcip->eofOkay == 0) {
567 				Error(cip, kDontPerror, "Remote host has closed the connection.\n");
568 				vcip->errNo = kErrRemoteHostClosedConnection;
569 				return(vcip->errNo);
570 			}
571 			return (kNoErr);
572 		}
573 
574 		result = fputs(command, cip->cout);
575 		if (result < 0) {
576 			(void) signal(SIGPIPE, osigpipe);
577 			cip->errNo = kErrSocketWriteFailed;
578 			Error(cip, kDoPerror, "Could not write to control stream.\n");
579 			return (cip->errNo);
580 		}
581 		result = fflush(cip->cout);
582 		if (result < 0) {
583 			(void) signal(SIGPIPE, osigpipe);
584 			cip->errNo = kErrSocketWriteFailed;
585 			Error(cip, kDoPerror, "Could not write to control stream.\n");
586 			return (cip->errNo);
587 		}
588 		(void) signal(SIGPIPE, osigpipe);
589 		return (kNoErr);
590 	}
591 	return (kErrNotConnected);
592 }	/* SendCommand */
593 #endif	/* NO_SIGNALS */
594 
595 
596 
597 /* For "simple" (i.e. not data transfer) commands, this routine is used
598  * to send the command and receive one response.  It returns the codeType
599  * field of the 'Response' as the result, or a negative number upon error.
600  */
601 /*VARARGS*/
602 int
FTPCmd(const FTPCIPtr cip,const char * const cmdspec,...)603 FTPCmd(const FTPCIPtr cip, const char *const cmdspec, ...)
604 {
605 	va_list ap;
606 	int result;
607 	ResponsePtr rp;
608 
609 	if (cip == NULL)
610 		return (kErrBadParameter);
611 	if (strcmp(cip->magic, kLibraryMagic))
612 		return (kErrBadMagic);
613 
614 	rp = InitResponse();
615 	if (rp == NULL) {
616 		result = kErrMallocFailed;
617 		cip->errNo = kErrMallocFailed;
618 		Error(cip, kDontPerror, "Malloc failed.\n");
619 		return (cip->errNo);
620 	}
621 
622 	va_start(ap, cmdspec);
623 #ifndef NO_SIGNALS
624 	if (cip->ctrlTimeout > 0)
625 		(void) alarm(cip->ctrlTimeout);
626 #endif	/* NO_SIGNALS */
627 	result = SendCommand(cip, cmdspec, ap);
628 	va_end(ap);
629 	if (result < 0) {
630 #ifndef NO_SIGNALS
631 		if (cip->ctrlTimeout > 0)
632 			(void) alarm(0);
633 #endif	/* NO_SIGNALS */
634 		return (result);
635 	}
636 
637 	/* Get the response to the command we sent. */
638 	result = GetResponse(cip, rp);
639 #ifndef NO_SIGNALS
640 	if (cip->ctrlTimeout > 0)
641 		(void) alarm(0);
642 #endif	/* NO_SIGNALS */
643 
644 	if (result == kNoErr)
645 		result = rp->codeType;
646 	DoneWithResponse(cip, rp);
647 	return (result);
648 }	/* FTPCmd */
649 
650 
651 
652 
653 /* This is for debugging the library -- don't use. */
654 /*VARARGS*/
655 int
FTPCmdNoResponse(const FTPCIPtr cip,const char * const cmdspec,...)656 FTPCmdNoResponse(const FTPCIPtr cip, const char *const cmdspec, ...)
657 {
658 	va_list ap;
659 
660 	if (cip == NULL)
661 		return (kErrBadParameter);
662 	if (strcmp(cip->magic, kLibraryMagic))
663 		return (kErrBadMagic);
664 
665 	va_start(ap, cmdspec);
666 #ifndef NO_SIGNALS
667 	if (cip->ctrlTimeout > 0)
668 		(void) alarm(cip->ctrlTimeout);
669 #endif	/* NO_SIGNALS */
670 	(void) SendCommand(cip, cmdspec, ap);
671 #ifndef NO_SIGNALS
672 	if (cip->ctrlTimeout > 0)
673 		(void) alarm(0);
674 #endif	/* NO_SIGNALS */
675 	va_end(ap);
676 
677 	return (kNoErr);
678 }	/* FTPCmdNoResponse */
679 
680 
681 
682 
683 int
WaitResponse(const FTPCIPtr cip,unsigned int sec)684 WaitResponse(const FTPCIPtr cip, unsigned int sec)
685 {
686 	int result;
687 	fd_set ss;
688 	struct timeval tv;
689 	int fd;
690 
691 #ifdef NO_SIGNALS
692 	fd = cip->ctrlSocketR;
693 #else	/* NO_SIGNALS */
694 	if (cip->cin == NULL)
695 		return (-1);
696 	fd = fileno(cip->cin);
697 #endif	/* NO_SIGNALS */
698 	if (fd < 0)
699 		return (-1);
700 	FD_ZERO(&ss);
701 	FD_SET(fd, &ss);
702 	tv.tv_sec = (unsigned long) sec;
703 	tv.tv_usec = 0;
704 	result = select(fd + 1, SELECT_TYPE_ARG234 &ss, NULL, NULL, &tv);
705 	return (result);
706 }	/* WaitResponse */
707 
708 
709 
710 
711 /* For "simple" (i.e. not data transfer) commands, this routine is used
712  * to send the command and receive one response.  It returns the codeType
713  * field of the 'Response' as the result, or a negative number upon error.
714  */
715 
716 /*VARARGS*/
717 int
RCmd(const FTPCIPtr cip,ResponsePtr rp,const char * cmdspec,...)718 RCmd(const FTPCIPtr cip, ResponsePtr rp, const char *cmdspec, ...)
719 {
720 	va_list ap;
721 	int result;
722 
723 	if (cip == NULL)
724 		return (kErrBadParameter);
725 	if (strcmp(cip->magic, kLibraryMagic))
726 		return (kErrBadMagic);
727 
728 	va_start(ap, cmdspec);
729 #ifndef NO_SIGNALS
730 	if (cip->ctrlTimeout > 0)
731 		(void) alarm(cip->ctrlTimeout);
732 #endif	/* NO_SIGNALS */
733 	result = SendCommand(cip, cmdspec, ap);
734 	va_end(ap);
735 	if (result < 0) {
736 #ifndef NO_SIGNALS
737 		if (cip->ctrlTimeout > 0)
738 			(void) alarm(0);
739 #endif	/* NO_SIGNALS */
740 		return (result);
741 	}
742 
743 	/* Get the response to the command we sent. */
744 	result = GetResponse(cip, rp);
745 #ifndef NO_SIGNALS
746 	if (cip->ctrlTimeout > 0)
747 		(void) alarm(0);
748 #endif	/* NO_SIGNALS */
749 
750 	if (result == kNoErr)
751 		result = rp->codeType;
752 	return (result);
753 }	/* RCmd */
754 
755 
756 
757 /* Returns -1 if an error occurred, or 0 if not.
758  * This differs from RCmd, which returns the code class of a response.
759  */
760 
761 /*VARARGS*/
762 int
FTPStartDataCmd(const FTPCIPtr cip,int netMode,int type,longest_int startPoint,const char * cmdspec,...)763 FTPStartDataCmd(const FTPCIPtr cip, int netMode, int type, longest_int startPoint, const char *cmdspec, ...)
764 {
765 	va_list ap;
766 	int result;
767 	int respCode;
768 	ResponsePtr rp;
769 
770 	if (cip == NULL)
771 		return (kErrBadParameter);
772 	if (strcmp(cip->magic, kLibraryMagic))
773 		return (kErrBadMagic);
774 
775 	result = FTPSetTransferType(cip, type);
776 	if (result < 0)
777 		return (result);
778 
779 	/* Re-set the cancellation flag. */
780 	cip->cancelXfer = 0;
781 
782 	/* To transfer data, we do these things in order as specifed by
783 	 * the RFC.
784 	 *
785 	 * First, we tell the other side to set up a data line.  This
786 	 * is done below by calling OpenDataConnection(), which sets up
787 	 * the socket.  When we do that, the other side detects a connection
788 	 * attempt, so it knows we're there.  Then tell the other side
789 	 * (by using listen()) that we're willing to receive a connection
790 	 * going to our side.
791 	 */
792 
793 	if ((result = OpenDataConnection(cip, cip->dataPortMode)) < 0)
794 		goto done;
795 
796 	/* If asked, attempt to start at a later position in the remote file. */
797 	if (startPoint != (longest_int) 0) {
798 		if ((startPoint == kSizeUnknown) || ((result = SetStartOffset(cip, startPoint)) != 0))
799 			startPoint = (longest_int) 0;
800 	}
801 	cip->startPoint = startPoint;
802 
803 	/* Now we tell the server what we want to do.  This sends the
804 	 * the type of transfer we want (RETR, STOR, LIST, etc) and the
805 	 * parameters for that (files to send, directories to list, etc).
806 	 */
807 	va_start(ap, cmdspec);
808 #ifndef NO_SIGNALS
809 	if (cip->ctrlTimeout > 0)
810 		(void) alarm(cip->ctrlTimeout);
811 #endif	/* NO_SIGNALS */
812 	result = SendCommand(cip, cmdspec, ap);
813 	va_end(ap);
814 	if (result < 0) {
815 #ifndef NO_SIGNALS
816 		if (cip->ctrlTimeout > 0)
817 			(void) alarm(0);
818 #endif	/* NO_SIGNALS */
819 		goto done;
820 	}
821 
822 	/* Get the response to the transfer command we sent, to see if
823 	 * they can accomodate the request.  If everything went okay,
824 	 * we will get a preliminary response saying that the transfer
825 	 * initiation was successful and that the data is there for
826 	 * reading (for retrieves;  for sends, they will be waiting for
827 	 * us to send them something).
828 	 */
829 	rp = InitResponse();
830 	if (rp == NULL) {
831 		Error(cip, kDontPerror, "Malloc failed.\n");
832 		cip->errNo = kErrMallocFailed;
833 		result = cip->errNo;
834 		goto done;
835 	}
836 	result = GetResponse(cip, rp);
837 #ifndef NO_SIGNALS
838 	if (cip->ctrlTimeout > 0)
839 		(void) alarm(0);
840 #endif	/* NO_SIGNALS */
841 
842 	if (result < 0)
843 		goto done;
844 	respCode = rp->codeType;
845 	DoneWithResponse(cip, rp);
846 
847 	if (respCode > 2) {
848 		cip->errNo = kErrCouldNotStartDataTransfer;
849 		result = cip->errNo;
850 		goto done;
851 	}
852 
853 	/* Now we accept the data connection that the other side is offering
854 	 * to us.  Then we can do the actual I/O on the data we want.
855 	 */
856 	cip->netMode = netMode;
857 	if ((result = AcceptDataConnection(cip)) < 0)
858 		goto done;
859 	return (kNoErr);
860 
861 done:
862 	(void) FTPEndDataCmd(cip, 0);
863 	return (result);
864 }	/* FTPStartDataCmd */
865 
866 
867 
868 
869 void
FTPAbortDataTransfer(const FTPCIPtr cip)870 FTPAbortDataTransfer(const FTPCIPtr cip)
871 {
872 	ResponsePtr rp;
873 	int result;
874 
875 	if (cip->dataSocket != kClosedFileDescriptor) {
876 		PrintF(cip, "Starting abort sequence.\n");
877 		SendTelnetInterrupt(cip);		/* Probably could get by w/o doing this. */
878 
879 		result = FTPCmdNoResponse(cip, "ABOR");
880 		if (result != kNoErr) {
881 			/* Linger could cause close to block, so unset it. */
882 			(void) SetLinger(cip, cip->dataSocket, 0);
883 			CloseDataConnection(cip);
884 			PrintF(cip, "Could not send abort command.\n");
885 			return;
886 		}
887 
888 		if (cip->abortTimeout > 0) {
889 			result = WaitResponse(cip, (unsigned int) cip->abortTimeout);
890 			if (result <= 0) {
891 				/* Error or no response received to ABOR in time. */
892 				(void) SetLinger(cip, cip->dataSocket, 0);
893 				CloseDataConnection(cip);
894 				PrintF(cip, "No response received to abort request.\n");
895 				return;
896 			}
897 		}
898 
899 		rp = InitResponse();
900 		if (rp == NULL) {
901 			Error(cip, kDontPerror, "Malloc failed.\n");
902 			cip->errNo = kErrMallocFailed;
903 			result = cip->errNo;
904 			return;
905 		}
906 
907 		result = GetResponse(cip, rp);
908 		if (result < 0) {
909 			/* Shouldn't happen, and doesn't matter if it does. */
910 			(void) SetLinger(cip, cip->dataSocket, 0);
911 			CloseDataConnection(cip);
912 			PrintF(cip, "Invalid response to abort request.\n");
913 			DoneWithResponse(cip, rp);
914 			return;
915 		}
916 		DoneWithResponse(cip, rp);
917 
918 		/* A response to the abort request has been received.
919 		 * Now the only thing left to do is close the data
920 		 * connection, making sure to turn off linger mode
921 		 * since we don't care about straggling data bits.
922 		 */
923 		(void) SetLinger(cip, cip->dataSocket, 0);
924 		CloseDataConnection(cip);		/* Must close (by protocol). */
925 		PrintF(cip, "End abort.\n");
926 	}
927 }	/* FTPAbortDataTransfer */
928 
929 
930 
931 
932 int
FTPEndDataCmd(const FTPCIPtr cip,int didXfer)933 FTPEndDataCmd(const FTPCIPtr cip, int didXfer)
934 {
935 	int result;
936 	int respCode;
937 	ResponsePtr rp;
938 
939 	if (cip == NULL)
940 		return (kErrBadParameter);
941 	if (strcmp(cip->magic, kLibraryMagic))
942 		return (kErrBadMagic);
943 
944 	CloseDataConnection(cip);
945 	result = kNoErr;
946 	if (didXfer) {
947 		/* Get the response to the data transferred.  Most likely a message
948 		 * saying that the transfer completed succesfully.  However, if
949 		 * we tried to abort the transfer using ABOR, we will have a response
950 		 * to that command instead.
951 		 */
952 		rp = InitResponse();
953 		if (rp == NULL) {
954 			Error(cip, kDontPerror, "Malloc failed.\n");
955 			cip->errNo = kErrMallocFailed;
956 			result = cip->errNo;
957 			return (result);
958 		}
959 		result = GetResponse(cip, rp);
960 		if (result < 0)
961 			return (result);
962 		respCode = rp->codeType;
963 		DoneWithResponse(cip, rp);
964 		if (respCode != 2) {
965 			cip->errNo = kErrDataTransferFailed;
966 			result = cip->errNo;
967 		} else {
968 			result = kNoErr;
969 		}
970 	}
971 	return (result);
972 }	/* FTPEndDataCmd */
973 
974 
975 
976 
977 int
BufferGets(char * buf,size_t bufsize,int inStream,char * secondaryBuf,char ** secBufPtr,char ** secBufLimit,size_t secBufSize)978 BufferGets(char *buf, size_t bufsize, int inStream, char *secondaryBuf, char **secBufPtr, char **secBufLimit, size_t secBufSize)
979 {
980 	int err;
981 	char *src;
982 	char *dst;
983 	char *dstlim;
984 	int len;
985 	int nr;
986 	int haveEof = 0;
987 
988 	err = 0;
989 	dst = buf;
990 	dstlim = dst + bufsize - 1;		/* Leave room for NUL. */
991 	src = (*secBufPtr);
992 	for ( ; dst < dstlim; ) {
993 		if (src >= (*secBufLimit)) {
994 			/* Fill the buffer. */
995 
996 /* Don't need to poll it here.  The routines that use BufferGets don't
997  * need any special processing during timeouts (i.e. progress reports),
998  * so go ahead and just let it block until there is data to read.
999  */
1000 			nr = (int) read(inStream, secondaryBuf, secBufSize);
1001 			if (nr == 0) {
1002 				/* EOF. */
1003 				haveEof = 1;
1004 				goto done;
1005 			} else if (nr < 0) {
1006 				/* Error. */
1007 				err = -1;
1008 				goto done;
1009 			}
1010 			(*secBufPtr) = secondaryBuf;
1011 			(*secBufLimit) = secondaryBuf + nr;
1012 			src = (*secBufPtr);
1013 			if (nr < (int) secBufSize)
1014 				src[nr] = '\0';
1015 		}
1016 		if (*src == '\r') {
1017 			++src;
1018 		} else {
1019 			if (*src == '\n') {
1020 				/* *dst++ = *src++; */	++src;
1021 				goto done;
1022 			}
1023 			*dst++ = *src++;
1024 		}
1025 	}
1026 
1027 done:
1028 	(*secBufPtr) = src;
1029 	*dst = '\0';
1030 	len = (int) (dst - buf);
1031 	if (err < 0)
1032 		return (err);
1033 	if ((len == 0) && (haveEof == 1))
1034 		return (-1);
1035 	return (len);	/* May be zero, if a blank line. */
1036 }	/* BufferGets */
1037 
1038 /* eof */
1039