1 /* File Transfer Protocol (FTP) Client
2 ** for a WorldWideWeb browser
3 ** ===================================
5 ** A cache of control connections is kept.
7 ** Note: Port allocation
9 ** It is essential that the port is allocated by the system, rather
10 ** than chosen in rotation by us (POLL_PORTS), or the following
13 ** It seems that an attempt by the server to connect to a port which has
14 ** been used recently by a listen on the same socket, or by another
15 ** socket this or another process causes a hangup of (almost exactly)
16 ** one minute. Therefore, we have to use a rotating port number.
17 ** The problem remains that if the application is run twice in quick
18 ** succession, it will hang for what remains of a minute.
21 ** 2 May 91 Written TBL, as a part of the WorldWideWeb project.
22 ** 15 Jan 92 Bug fix: close() was used for NETCLOSE for control soc
23 ** 10 Feb 92 Retry if cached connection times out or breaks
26 ** LISTEN We listen, the other guy connects for data.
27 ** Otherwise, other way round, but problem finding our
31 #define LISTEN /* @@@@ Test */
34 BUGS: @@@ Limit connection cache size!
35 Error reporting to user.
36 400 & 500 errors are acked by user with windows.
37 Use configuration file for user names
38 Prompt user for password
40 ** Note for portablility this version does not use select() and
41 ** so does not watch the control and data channels at the
45 #define REPEAT_PORT /* Give the port number for each file */
46 #define REPEAT_LISTEN /* Close each listen socket and open a new one */
48 /* define POLL_PORTS If allocation does not work, poll ourselves.*/
49 #define LISTEN_BACKLOG 2 /* Number of pending connect requests (TCP)*/
51 #define FIRST_TCP_PORT 1024 /* Region to try for a listening port */
52 #define LAST_TCP_PORT 5999
54 #define LINE_LENGTH 256
55 #define COMMAND_LENGTH 256
71 extern HTStyleSheet * styleSheet;
72 PRIVATE HTStyle *h1Style; /* For heading level 1 */
73 PRIVATE HTStyle *h2Style; /* For heading level 2 */
74 PRIVATE HTStyle *textStyle; /* Text style */
75 PRIVATE HTStyle *dirStyle; /* Compact List style */
78 extern char *malloc();
80 extern char *strncpy();
83 typedef struct _connection {
84 struct _connection * next; /* Link on list */
85 u_long addr; /* IP address */
86 int socket; /* Socket number for communication */
94 /* Module-Wide Variables
95 ** ---------------------
97 PRIVATE connection * connections =0; /* Linked list of connections */
98 PRIVATE char response_text[LINE_LENGTH+1];/* Last response from NewsHost */
99 PRIVATE connection * control; /* Current connection */
100 PRIVATE int data_soc = -1; /* Socket for data transfer =invalid */
103 PRIVATE unsigned short port_number = FIRST_TCP_PORT;
107 PRIVATE int master_socket = -1; /* Listening socket = invalid */
108 PRIVATE char port_command[255]; /* Command for setting the port */
109 PRIVATE fd_set open_sockets; /* Mask of active channels */
110 PRIVATE int num_sockets; /* Number of sockets to scan */
112 PRIVATE unsigned short passive_port; /* Port server specified for data */
116 #define NEXT_CHAR HTGetChararcter() /* Use function in HTFormat.c */
118 #define DATA_BUFFER_SIZE 2048
119 PRIVATE char data_buffer[DATA_BUFFER_SIZE]; /* Input data buffer */
120 PRIVATE char * data_read_pointer;
121 PRIVATE char * data_write_pointer;
122 #define NEXT_DATA_CHAR next_data_char()
125 /* Procedure: Read a character from the data connection
126 ** ----------------------------------------------------
128 PRIVATE char next_data_char
132 if (data_read_pointer >= data_write_pointer) {
133 status = NETREAD(data_soc, data_buffer, DATA_BUFFER_SIZE);
134 /* Get some more data */
135 if (status <= 0) return (char)-1;
136 data_write_pointer = data_buffer + status;
137 data_read_pointer = data_buffer;
141 char c = *data_read_pointer++;
145 return *data_read_pointer++;
150 /* Close an individual connection
154 PRIVATE int close_connection(connection * con)
156 PRIVATE int close_connection(con)
161 int status = NETCLOSE(con->socket);
162 if (TRACE) printf("FTP: Closing control socket %d\n", con->socket);
163 if (connections==con) {
164 connections = con->next;
167 for(scan=connections; scan; scan=scan->next) {
168 if (scan->next == con) {
169 scan->next = con->next; /* Unlink */
170 if (control==con) control = (connection*)0;
174 return -1; /* very strange -- was not on list. */
178 /* Execute Command and get Response
179 ** --------------------------------
181 ** See the state machine illustrated in RFC959, p57. This implements
182 ** one command/reply sequence. It also interprets lines which are to
183 ** be continued, which are marked with a "-" immediately after the
187 ** con points to the connection which is established.
188 ** cmd points to a command, or is NIL to just get the response.
190 ** The command is terminated with the CRLF pair.
193 ** returns: The first digit of the reply type,
194 ** or negative for communication failure.
197 PRIVATE int response(char * cmd)
199 PRIVATE int response(cmd)
203 int result; /* Three-digit decimal code */
208 if(TRACE) printf("FTP: No control connection set up!!\n");
214 if (TRACE) printf(" Tx: %s", cmd);
219 for(p=cmd; *p; p++) {
224 status = NETWRITE(control->socket, cmd, (int)strlen(cmd));
227 "FTP: Error %d sending command: closing socket %d\n",
228 status, control->socket);
229 close_connection(control);
235 char *p = response_text;
237 if (((*p++=NEXT_CHAR) == '\n')
238 || (p == &response_text[LINE_LENGTH])) {
239 *p++=0; /* Terminate the string */
240 if (TRACE) printf(" Rx: %s", response_text);
241 sscanf(response_text, "%d%c", &result, &continuation);
243 } /* if end of line */
246 if(TRACE) fprintf(stderr, "Error on rx: closing socket %d\n",
248 strcpy(response_text, "000 *** TCP read error on response\n");
249 close_connection(control);
250 return -1; /* End of file on response */
252 } /* Loop over characters */
254 } while (continuation == '-');
257 if(TRACE) fprintf(stderr, "FTP: They close so we close socket %d\n",
259 close_connection(control);
266 /* Get a valid connection to the host
267 ** ----------------------------------
270 ** arg points to the name of the host in a hypertext address
272 ** returns <0 if error
273 ** socket number if success
275 ** This routine takes care of managing timed-out connections, and
276 ** limiting the number of connections in use at any one time.
278 ** It ensures that all connections are logged in if they exist.
279 ** It ensures they have the port number transferred.
281 PRIVATE int get_connection ARGS1 (CONST char *,arg)
283 struct hostent * phost; /* Pointer to host -- See netdb.h */
284 struct sockaddr_in soc_address; /* Binary network address */
285 struct sockaddr_in* sin = &soc_address;
290 if (!arg) return -1; /* Bad if no name sepcified */
291 if (!*arg) return -1; /* Bad if name had zero length */
295 sin->sin_family = AF_INET; /* Family, host order */
296 sin->sin_port = htons(IPPORT_FTP); /* Well Known Number */
298 if (TRACE) printf("FTP: Looking for %s\n", arg);
303 char *p1 = HTParse(arg, "", PARSE_HOST);
304 char *p2 = strchr(p1, '@'); /* user? */
308 *p2=0; /* terminate */
309 p1 = p2+1; /* point to host */
310 pw = strchr(username, ':');
316 HTParseInet(sin, p1);
317 if (!username) free(p1);
321 /* Now we check whether we already have a connection to that port.
326 for (scan=connections; scan; scan=scan->next) {
327 if (sin->sin_addr.s_addr == scan->addr) {
329 "FTP: Already have connection for %d.%d.%d.%d.\n",
330 (int)*((unsigned char *)(&scan->addr)+0),
331 (int)*((unsigned char *)(&scan->addr)+1),
332 (int)*((unsigned char *)(&scan->addr)+2),
333 (int)*((unsigned char *)(&scan->addr)+3));
334 if (username) free(username);
335 return scan->socket; /* Good return */
338 "FTP: Existing connection is %d.%d.%d.%d\n",
339 (int)*((unsigned char *)(&scan->addr)+0),
340 (int)*((unsigned char *)(&scan->addr)+1),
341 (int)*((unsigned char *)(&scan->addr)+2),
342 (int)*((unsigned char *)(&scan->addr)+3));
348 /* Now, let's get a socket set up from the server:
352 connection * con = (connection *)malloc(sizeof(*con));
353 if (con == NULL) outofmem(__FILE__, "get_connection");
354 con->addr = sin->sin_addr.s_addr; /* save it */
355 status = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
357 (void) HTInetStatus("socket");
359 if (username) free(username);
362 con->socket = status;
364 status = connect(con->socket, (struct sockaddr*)&soc_address,
365 sizeof(soc_address));
367 (void) HTInetStatus("connect");
369 "FTP: Unable to connect to remote host for `%s'.\n",
371 NETCLOSE(con->socket);
373 if (username) free(username);
374 return status; /* Bad return */
377 if (TRACE) printf("FTP connected, socket %d\n", con->socket);
378 control = con; /* Current control connection */
379 con->next = connections; /* Link onto list of good ones */
381 HTInitInput(con->socket);/* Initialise buffering for contron connection */
384 /* Now we log in Look up username, prompt for pw.
387 int status = response(NIL); /* Get greeting */
389 if (status == 2) { /* Send username */
392 command = (char*)malloc(10+strlen(username)+2+1);
393 if (command == NULL) outofmem(__FILE__, "get_connection");
394 sprintf(command, "USER %s\r\n", username);
396 command = (char*)malloc(25);
397 if (command == NULL) outofmem(__FILE__, "get_connection");
398 sprintf(command, "USER anonymous\r\n");
400 status = response(command);
403 if (status == 3) { /* Send password */
406 command = (char*)malloc(10+strlen(password)+2+1);
407 if (command == NULL) outofmem(__FILE__, "get_connection");
408 sprintf(command, "PASS %s\r\n", password);
410 command = (char*)malloc(10+strlen(HTHostName())+2+1);
411 if (command == NULL) outofmem(__FILE__, "get_connection");
413 "PASS user@%s\r\n", HTHostName()); /*@@*/
415 status = response(command);
418 if (username) free(username);
420 if (status == 3) status = response("ACCT noaccount\r\n");
423 if (TRACE) printf("FTP: Login fail: %s", response_text);
424 if (control) close_connection(control);
425 return -1; /* Bad return */
427 if (TRACE) printf("FTP: Logged in.\n");
430 /* Now we inform the server of the port number we will listen on
434 int status = response(port_command);
436 if (control) close_connection(control);
437 return -status; /* Bad return */
439 if (TRACE) printf("FTP: Port defined.\n");
442 return con->socket; /* Good return */
449 /* Close Master (listening) socket
450 ** -------------------------------
455 PRIVATE int close_master_socket(void)
457 PRIVATE int close_master_socket()
461 FD_CLR(master_socket, &open_sockets);
462 status = NETCLOSE(master_socket);
463 if (TRACE) printf("FTP: Closed master socket %d\n", master_socket);
465 if (status<0) return HTInetStatus("close master socket");
470 /* Open a master socket for listening on
471 ** -------------------------------------
473 ** When data is transferred, we open a port, and wait for the server to
474 ** connect with the data.
477 ** master_socket Must be negative if not set up already.
479 ** Returns socket number if good
480 ** less than zero if error.
481 ** master_socket is socket number if good, else negative.
482 ** port_number is valid if good.
485 PRIVATE int get_listen_socket(void)
487 PRIVATE int get_listen_socket()
490 struct sockaddr_in soc_address; /* Binary network address */
491 struct sockaddr_in* sin = &soc_address;
492 int new_socket; /* Will be master_socket */
495 FD_ZERO(&open_sockets); /* Clear our record of open sockets */
498 #ifndef REPEAT_LISTEN
499 if (master_socket>=0) return master_socket; /* Done already */
502 /* Create internet socket
504 new_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
507 return HTInetStatus("socket for master socket");
509 if (TRACE) printf("FTP: Opened master socket number %d\n", new_socket);
511 /* Search for a free port.
513 sin->sin_family = AF_INET; /* Family = internet, host order */
514 sin->sin_addr.s_addr = INADDR_ANY; /* Any peer address */
517 unsigned short old_port_number = port_number;
518 for(port_number=old_port_number+1;;port_number++){
520 if (port_number > LAST_TCP_PORT)
521 port_number = FIRST_TCP_PORT;
522 if (port_number == old_port_number) {
523 return HTInetStatus("bind");
525 soc_address.sin_port = htons(port_number);
526 if ((status=bind(new_socket,
527 (struct sockaddr*)&soc_address,
528 /* Cast to generic sockaddr */
529 sizeof(soc_address))) == 0)
532 "TCP bind attempt to port %d yields %d, errno=%d\n",
533 port_number, status, errno);
539 int address_length = sizeof(soc_address);
540 status = getsockname(control->socket,
541 (struct sockaddr *)&soc_address,
543 if (status<0) return HTInetStatus("getsockname");
544 CTRACE(tfp, "FTP: This host is %s\n",
547 soc_address.sin_port = 0; /* Unspecified: please allocate */
548 status=bind(new_socket,
549 (struct sockaddr*)&soc_address,
550 /* Cast to generic sockaddr */
551 sizeof(soc_address));
552 if (status<0) return HTInetStatus("bind");
554 address_length = sizeof(soc_address);
555 status = getsockname(new_socket,
556 (struct sockaddr*)&soc_address,
558 if (status<0) return HTInetStatus("getsockname");
562 CTRACE(tfp, "FTP: bound to port %d on %s\n",
563 (unsigned int)ntohs(sin->sin_port),
567 if (master_socket>=0)
568 (void) close_master_socket();
571 master_socket = new_socket;
573 /* Now we must find out who we are to tell the other guy
575 (void)HTHostName(); /* Make address valid - doesn't work*/
576 sprintf(port_command, "PORT %d,%d,%d,%d,%d,%d\r\n",
577 (int)*((unsigned char *)(&sin->sin_addr)+0),
578 (int)*((unsigned char *)(&sin->sin_addr)+1),
579 (int)*((unsigned char *)(&sin->sin_addr)+2),
580 (int)*((unsigned char *)(&sin->sin_addr)+3),
581 (int)*((unsigned char *)(&sin->sin_port)+0),
582 (int)*((unsigned char *)(&sin->sin_port)+1));
585 /* Inform TCP that we will accept connections
587 if (listen(master_socket, 1)<0) {
589 return HTInetStatus("listen");
591 CTRACE(tfp, "TCP: Master socket(), bind() and listen() all OK\n");
592 FD_SET(master_socket, &open_sockets);
593 if ((master_socket+1) > num_sockets) num_sockets=master_socket+1;
595 return master_socket; /* Good */
597 } /* get_listen_socket */
601 /* Get Styles from stylesheet
602 ** --------------------------
604 PRIVATE void get_styles NOARGS
606 if (!h1Style) h1Style = HTStyleNamed(styleSheet, "Heading1");
607 if (!h2Style) h2Style = HTStyleNamed(styleSheet, "Heading2");
608 if (!textStyle) textStyle = HTStyleNamed(styleSheet, "Example");
609 if (!dirStyle) dirStyle = HTStyleNamed(styleSheet, "Dir");
613 /* Read a directory into an hypertext object from the data socket
614 ** --------------------------------------------------------------
617 ** anchor Parent anchor to link the HText to
618 ** address Address of the directory
620 ** returns HT_LOADED if OK
623 PRIVATE int read_directory
625 HTParentAnchor *, parent,
626 CONST char *, address
629 HText * HT = HText_new (parent);
630 char *filename = HTParse(address, "", PARSE_PATH + PARSE_PUNCTUATION);
631 HTChildAnchor * child; /* corresponds to an entry of the directory */
633 #define LASTPATH_LENGTH 150 /* @@@@ Horrible limit on the entry size */
634 char lastpath[LASTPATH_LENGTH + 1];
637 HText_beginAppend (HT);
640 HTAnchor_setTitle (parent, "FTP Directory of ");
641 HTAnchor_appendTitle (parent, address + 5); /* +5 gets rid of "file:" */
642 HText_setStyle (HT, h1Style);
643 HText_appendText (HT, filename);
645 HText_setStyle (HT, dirStyle);
646 data_read_pointer = data_write_pointer = data_buffer;
648 if (*filename == 0) /* Empty filename : use root */
649 strcpy (lastpath, "/");
651 p = filename + strlen (filename) - 2;
652 while (p >= filename && *p != '/')
654 strcpy (lastpath, p + 1); /* relative path */
657 entry = lastpath + strlen (lastpath);
658 if (*(entry-1) != '/')
659 *entry++ = '/'; /* ready to append entries */
661 if (strlen (lastpath) > 1) { /* Current file is not the FTP root */
662 strcpy (entry, "..");
663 child = HTAnchor_findChildAndLink (parent, 0, lastpath, 0);
664 HText_beginAnchor (HT, child);
665 HText_appendText (HT, "Parent Directory");
666 HText_endAnchor (HT);
667 HText_appendCharacter (HT, '\t');
671 while (p - lastpath < LASTPATH_LENGTH) {
677 printf ("Warning: No newline but %d after carriage return.\n", c);
681 if (c == '\n' || c == (char) EOF)
685 if (c == (char) EOF && p == entry)
688 child = HTAnchor_findChildAndLink (parent, 0, lastpath, 0);
689 HText_beginAnchor (HT, child);
690 HText_appendText (HT, entry);
691 HText_endAnchor (HT);
692 HText_appendCharacter (HT, '\t');
695 HText_appendParagraph (HT);
696 HText_endAppend (HT);
697 return response(NIL) == 2 ? HT_LOADED : -1;
702 /* Retrieve File from Server
703 ** -------------------------
706 ** name WWW address of a file: document, including hostname
708 ** returns Socket number for file if good.
711 PUBLIC int HTFTP_open_file_read
714 HTParentAnchor *, anchor
717 BOOL isDirectory = NO;
719 int retry; /* How many times tried? */
721 for (retry=0; retry<2; retry++) { /* For timed out/broken connections */
723 status = get_connection(name);
724 if (status<0) return status;
727 status = get_listen_socket();
728 if (status<0) return status;
731 /* Inform the server of the port number we will listen on
734 status = response(port_command);
735 if (status !=2) { /* Could have timed out */
736 if (status<0) continue; /* try again - net error*/
737 return -status; /* bad reply */
739 if (TRACE) printf("FTP: Port defined.\n");
743 /* Tell the server to be passive
747 int reply, h0, h1, h2, h3, p0, p1; /* Parts of reply */
748 status = response("PASV\r\n");
750 if (status<0) continue; /* retry or Bad return */
751 return -status; /* bad reply */
753 for(p=response_text; *p; p++)
754 if ((*p<'0')||(*p>'9')) *p = ' '; /* Keep only digits */
755 status = sscanf(response_text, "%d%d%d%d%d%d%d",
756 &reply, &h0, &h1, &h2, &h3, &p0, &p1);
758 if (TRACE) printf("FTP: PASV reply has no inet address!\n");
761 passive_port = (p0<<8) + p1;
762 if (TRACE) printf("FTP: Server is listening on port %d\n",
766 /* Open connection for data:
769 struct sockaddr_in soc_address;
770 int status = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
772 (void) HTInetStatus("socket for data socket");
777 soc_address.sin_addr.s_addr = control->addr;
778 soc_address.sin_port = htons(passive_port);
779 soc_address.sin_family = AF_INET; /* Family, host order */
781 "FTP: Data remote address is port %d, inet %d.%d.%d.%d\n",
782 (unsigned int)ntohs(soc_address.sin_port),
783 (int)*((unsigned char *)(&soc_address.sin_addr)+0),
784 (int)*((unsigned char *)(&soc_address.sin_addr)+1),
785 (int)*((unsigned char *)(&soc_address.sin_addr)+2),
786 (int)*((unsigned char *)(&soc_address.sin_addr)+3));
788 status = connect(data_soc, (struct sockaddr*)&soc_address,
789 sizeof(soc_address));
791 (void) HTInetStatus("connect for data");
793 return status; /* Bad return */
796 if (TRACE) printf("FTP data connected, socket %d\n", data_soc);
798 #endif /* use PASV */
800 break; /* No more retries */
803 if (status<0) return status; /* Failed with this code */
808 char *filename = HTParse(name, "", PARSE_PATH + PARSE_PUNCTUATION);
809 char command[LINE_LENGTH+1];
810 if (!*filename) StrAllocCopy(filename, "/");
811 sprintf(command, "RETR %s\r\n", filename);
812 status = response(command);
813 if (status != 1) { /* Failed : try to CWD to it */
814 sprintf(command, "CWD %s\r\n", filename);
815 status = response(command);
816 if (status == 2) { /* Successed : let's NAME LIST it */
818 sprintf(command, "NLST\r\n");
819 status = response (command);
823 if (status != 1) return -status; /* Action not started */
827 /* Wait for the connection
830 struct sockaddr_in soc_address;
831 int soc_addrlen=sizeof(soc_address);
832 status = accept(master_socket,
833 (struct sockaddr *)&soc_address,
836 return HTInetStatus("accept");
837 CTRACE(tfp, "TCP: Accepted new socket %d\n", status);
844 return read_directory (anchor, name);
845 /* returns HT_LOADED or error */
849 } /* open_file_read */
852 /* Close socket opened for reading a file, and get final message
853 ** -------------------------------------------------------------
856 PUBLIC int HTFTP_close_file
862 if (TRACE) printf("HTFTP: close socket %d: (not FTP data socket).\n",
864 return NETCLOSE(soc);
866 status = NETCLOSE(data_soc);
867 if (TRACE) printf("FTP: Closing data socket %d\n", data_soc);
868 if (status<0) (void) HTInetStatus("close"); /* Comment only */
869 data_soc = -1; /* invalidate it */
871 status = response(NIL);
872 if (status!=2) return -status;
874 return status; /* Good */
878 /*___________________________________________________________________________
883 ** Compiling this with -DTEST produces a test program. Unix only
886 ** test <file or option> ...
888 ** options: -v Verbose: Turn trace on at this point
892 PUBLIC int WWW_TraceFlag;
895 int main(int argc, char*argv[])
903 int arg; /* Argument number */
906 WWW_TraceFlag = (0==strcmp(argv[1], "-v")); /* diagnostics ? */
909 status = get_listen_socket();
910 if (TRACE) printf("get_listen_socket returns %d\n\n", status);
911 if (status<0) exit(status);
913 status = get_connection(argv[1]); /* test double use */
914 if (TRACE) printf("get_connection(`%s') returned %d\n\n", argv[1], status);
915 if (status<0) exit(status);
918 for(arg=1; arg<argc; arg++) { /* For each argument */
919 if (0==strcmp(argv[arg], "-v")) {
920 WWW_TraceFlag = 1; /* -v => Trace on */
922 } else { /* Filename: */
924 status = HTTP_open_file_read(argv[arg]);
925 if (TRACE) printf("open_file_read returned %d\n\n", status);
926 if (status<0) exit(status);
928 /* Copy the file to std out:
931 char buffer[INPUT_BUFFER_SIZE];
933 int status = NETREAD(data_soc, buffer, INPUT_BUFFER_SIZE);
935 if (status<0) (void) HTInetStatus("read");
938 status = write(1, buffer, status);
940 printf("Write failure!\n");
946 status = HTTP_close_file(data_soc);
947 if (TRACE) printf("Close_file returned %d\n\n", status);
951 status = response("QUIT\r\n"); /* Be good */
952 if (TRACE) printf("Quit returned %d\n", status);
954 while(connections) close_connection(connections);
956 close_master_socket();