/* FTP.c */ #include "Sys.h" #include #include #include #include #include #include #ifdef HAVE_NET_ERRNO_H # include #endif #include #include #include "Util.h" #include "FTP.h" #include "RCmd.h" #ifdef HAVE_LIBTERMNET # ifdef HAVE_TERMNET_H # include # else # ifdef HAVE_TERM_TERMNET_H # include # endif # endif #endif #ifdef HAVE_LIBSOCKS5 # define SOCKS 5 # include #else # ifdef HAVE_LIBSOCKS # define accept Raccept # define connect Rconnect # define getsockname Rgetsockname # define listen Rlisten # endif #endif /* The control stream we read responses to commands from. */ FILE *gControlIn = NULL; /* The control stream we write our request to. */ FILE *gControlOut = NULL; /* The actual socket for the data streams. */ int gDataSocket = kClosedFileDescriptor; int gDataSocketAccepted = kClosedFileDescriptor; /* Port number we're FTP'ing to. */ unsigned int gFTPPort = kPortUnset; /* Flag indicating whether we are connected to a remote host. */ int gConnected = 0; /* Method we would rather to specify where to send data to our local * host, either Passive (PASV) or SendPort (PORT). If you choose * Passive FTP, you can attempt to use SendPort if the PASV command * fails, since all FTP implementations must support PORT. */ #ifndef FTP_DATA_PORT_MODE int gPreferredDataPortMode = kSendPortMode; #else int gPreferredDataPortMode = FTP_DATA_PORT_MODE; #endif /* Method we actually ended up using on the current data transfer, * PORT or PASV. */ int gDataPortMode; /* We keep track of whether we can even attempt to use Passive FTP. * After we find out the remote server doesn't support it, we won't * keep asking every time we want to do a transfer. */ int gHasPASV; /* Need to special case if trying to read the startup message from the * server, so our command handler won't jump to the wrong spot if * the server hangs up at that point. */ int gReadingStartup = 0; int gRemoteServerType; /* Transfer mode. We support only stream mode and block mode. */ int gMode; HangupProc gHangupProc; /* Network addresses of the sockets we use. */ struct sockaddr_in gServerCtrlAddr; struct sockaddr_in gServerDataAddr; struct sockaddr_in gOurClientCtrlAddr; struct sockaddr_in gOurClientDataAddr; /* Real name (not alias) registered to the host we're connected to. */ string gActualHostName; /* Internet Protocol address of host we're connected to, as a string. */ char gIPStr[32]; extern int gDoneApplication; /* * Some declare these with int's, others (hpux...) with size_t. * extern int gethostname(char *, int), getdomainname(char *, int); * */ struct hostent *GetHostEntry(char *host, struct in_addr *ip_address); void InitDefaultFTPPort(void) { #ifdef FTP_PORT /* If FTP_PORT is defined, we use a different port number by default * than the one supplied in the servent structure. */ gFTPPort = FTP_PORT; #else struct servent *ftp; if ((ftp = getservbyname("ftp", "tcp")) == NULL) gFTPPort = (unsigned int) kDefaultFTPPort; else gFTPPort = (unsigned int) ntohs(ftp->s_port); #endif /* FTP_PORT */ #ifdef HAVE_LIBSOCKS SOCKSinit("ncftp"); #endif } /* InitDefaultFTPPort */ void MyInetAddr(char *dst, size_t siz, char **src, int i) { struct in_addr *ia; char *cp; Strncpy(dst, "???", siz); if (src != (char **) 0) { ia = (struct in_addr *) src[i]; cp = inet_ntoa(*ia); if ((cp != (char *) 0) && (cp != (char *) -1)) Strncpy(dst, cp, siz); } } /* MyInetAddr */ /* On entry, you should have 'host' be set to a symbolic name (like * cse.unl.edu), or set to a numeric address (like 129.93.3.1). * If the function fails, it will return NULL, but if the host was * a numeric style address, you'll have the ip_address to fall back on. */ struct hostent *GetHostEntry(char *host, struct in_addr *ip_address) { struct in_addr ip; struct hostent *hp; /* See if the host was given in the dotted IP format, like "36.44.0.2." * If it was, inet_addr will convert that to a 32-bit binary value; * it not, inet_addr will return (-1L). */ ip.s_addr = inet_addr(host); if (ip.s_addr != INADDR_NONE) { hp = gethostbyaddr((char *) &ip, (int) sizeof(ip), AF_INET); } else { /* No IP address, so it must be a hostname, like ftp.wustl.edu. */ hp = gethostbyname(host); if (hp != NULL) ip = * (struct in_addr *) hp->h_addr_list; } if (ip_address != NULL) *ip_address = ip; return (hp); } /* GetHostEntry */ /* Makes every effort to return a fully qualified domain name. */ int GetOurHostName(char *host, size_t siz) { #ifdef HOSTNAME /* You can hardcode in the name if this routine doesn't work * the way you want it to. */ Strncpy(host, HOSTNAME, siz); #else struct hostent *hp; int result; char **curAlias; char nodeName[64], domain[64], tmpdomain[64]; char *cp; size_t nodeNameLen; host[0] = '\0'; result = gethostname(host, (int) siz); if ((result < 0) || (host[0] == '\0')) { #if 0 (void) fprintf(stderr, "Could not determine the hostname. Re-compile with HOSTNAME defined\n\ to be the full name of your hostname, i.e. -DHOSTNAME=\\\"cse.unl.edu\\\".\n"); Exit(kExitBadHostName); #endif Strncpy(host, "hostname.unknown.com", siz); return(6); } if (strchr(host, '.') != NULL) { /* gethostname returned full name (like "cse.unl.edu"), instead * of just the node name (like "cse"). */ return (1); } hp = GetHostEntry(host, NULL); if (hp != NULL) { /* Maybe the host entry has the full name. */ Strncpy(host, (char *) hp->h_name, siz); if (strchr((char *) hp->h_name, '.') != NULL) { /* The 'name' field for the host entry had full name. */ return (2); } /* Now try the list of aliases, to see if any of those look real. */ STRNCPY(nodeName, host); nodeNameLen = strlen(nodeName); for (curAlias = hp->h_aliases; *curAlias != NULL; curAlias++) { /* See if this name is longer than the node name; we assume * longer == more detailed. */ if (strlen(*curAlias) > nodeNameLen) { /* We will use this one if it looks like this name is * a superset of the nodename; so if it contains the * nodename, assume that this will work. */ if (strstr(*curAlias, nodeName) != NULL) Strncpy(host, *curAlias, siz); } } /* See if the final thing we decided on in the host entry * looks complete. */ if (strchr(host, '.') != NULL) return (3); } /* Otherwise, we just have the node name. See if we can get the * domain name ourselves. * * It'd be so much simpler if you would just define DOMAINNAME * and get it over with! */ #ifdef DOMAINNAME (void) STRNCPY(domain, DOMAINNAME); #else domain[0] = '\0'; # ifdef HAVE_GETDOMAINNAME /* getdomainname() returns just the domain name, without a * preceding period. For example, on "cse.unl.edu", it would * return "unl.edu". * * SunOS note: getdomainname will return an empty string if * this machine isn't on NIS. */ if (getdomainname(domain, (int) sizeof(domain)) < 0) DebugMsg("getdomainname failed.\n"); if (domain[0] == '\0') DebugMsg("getdomainname did not return anything.\n"); else { /* * local domain names * * These can now be determined from the domainname system call. * The first component of the NIS domain name is stripped off unless * it begins with a dot or a plus sign. */ if (domain[0] == '+') domain[0] = '.'; # ifdef NIS_GETDOMAINNAME if (domain[0] != '.') { /* Strip off first component. */ cp = strchr(domain, '.'); if (cp != NULL) { STRNCPY(tmpdomain, cp + 1); STRNCPY(domain, tmpdomain); } } # else # ifdef DNS_GETDOMAINNAME /* Don't do anything, since it should have reutrned the * whole domain we want. */ # else { int dots; for (dots = 0, cp = domain; *cp; cp++) if (*cp == '.') ++dots; /* You didn't say if you were running NIS, so only assume a * NIS domain if there are three components or more (i.e. * getdomainname returned something like aaa.bbb.ccc), which * would have two dots or more. Otherwise, it would be an * error to strip off "unl." from "unl.edu" if there were * only two components returned. */ if ((dots > 1) && (domain[0] != '.')) { /* Strip off first component. */ cp = strchr(domain, '.'); if (cp != NULL) { STRNCPY(tmpdomain, cp + 1); STRNCPY(domain, tmpdomain); } } } # endif /* DNS_GETDOMAINNAME */ # endif /* NIS_GETDOMAINNAME */ } # endif /* HAVE_GETDOMAINNAME */ #endif /* DOMAINNAME */ if (domain[0] != '\0') { /* Supposedly, it's legal for getdomainname() to return one with * a period at the end. */ cp = domain + strlen(domain) - 1; if (*cp == '.') *cp = '\0'; if (domain[0] != '.') (void) Strncat(host, ".", siz); (void) Strncat(host, domain, siz); return(4); } else { Strncpy(host, "hostname.unknown.com", siz); #if 0 fprintf(stderr, "WARNING: could not determine full host name (have: '%s').\n\ The program should be re-compiled with DOMAINNAME defined to be the\n\ domain name, i.e. -DDOMAINNAME=\\\"unl.edu\\\"\n\n", host); #endif } return(5); #endif /* !HOSTNAME */ } /* GetOurHostName */ void CloseControlConnection(void) { /* This will close each file, if it was open. */ if (CloseFile(&gControlIn)) DebugMsg("Closed control connection.\n"); CloseFile(&gControlOut); gConnected = 0; } /* CloseControlConnection */ static int GetSocketAddress(int sockfd, struct sockaddr_in *saddr) { int len = (int) sizeof (struct sockaddr_in); int result = 0; if (getsockname(sockfd, (struct sockaddr *)saddr, &len) < 0) { Error(kDoPerror, "Could not get socket name.\n"); result = -1; } return (result); } /* GetSocketAddress */ void SetLinger(int sockfd) { #ifdef SO_LINGER struct linger li; li.l_onoff = 1; li.l_linger = 120; /* 2 minutes, but system ignores field. */ /* Have the system make an effort to deliver any unsent data, * even after we close the connection. */ if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, (char *) &li, (int) sizeof(li)) < 0) DebugMsg("Note: Linger mode could not be enabled.\n"); #endif /* SO_LINGER */ } /* SetLinger */ void SetTypeOfService(int sockfd, int tosType) { #ifdef IP_TOS /* Specify to the router what type of connection this is, so it * can prioritize packets. */ if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, (char *) &tosType, (int) sizeof(tosType)) < 0) DebugMsg("Note: Type-of-service could not be set.\n"); #endif /* IP_TOS */ } /* SetTypeOfService */ void SetInlineOutOfBandData(int sockfd) { #ifdef SO_OOBINLINE int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_OOBINLINE, (char *) &on, (int) sizeof(on)) < 0) DebugMsg("Note: May not be able to handle out-of-band data."); #endif /* SO_OOBINLINE */ } /* SetInlineOutOfBandData */ int OpenControlConnection(char *host, unsigned int port) { struct in_addr ip_address; int err = 0; int result; int sockfd = -1, sock2fd = -1; ResponsePtr rp; char **curaddr; struct hostent *hp; /* Since we're the client, we just have to get a socket() and * connect() it. */ ZERO(gServerCtrlAddr); /* Default transfer mode is stream mode. */ gMode = 'S'; /* Don't know what type of server it is yet. */ gRemoteServerType = kUnknownFTPd; /* Assume it's a fatal error, unless we say otherwise. */ result = kConnectErrFatal; /* Make sure we use network byte-order. */ port = (unsigned int) htons((unsigned short) port); gServerCtrlAddr.sin_port = port; hp = GetHostEntry(host, &ip_address); if (hp == NULL) { /* Okay, no host entry, but maybe we have a numeric address * in ip_address we can try. */ if (ip_address.s_addr == INADDR_NONE) { Error(kDontPerror, "%s: Unknown host.\n", host); return (result); } gServerCtrlAddr.sin_family = AF_INET; gServerCtrlAddr.sin_addr.s_addr = ip_address.s_addr; } else { gServerCtrlAddr.sin_family = hp->h_addrtype; /* We'll fill in the rest of the structure below. */ } if ((sockfd = socket(gServerCtrlAddr.sin_family, SOCK_STREAM, 0)) < 0) { Error(kDoPerror, "Could not get a socket.\n"); return (result); } /* Okay, we have a socket, now try to connect it to a remote * address. If we didn't get a host entry, we will only have * one thing to try (ip_address); if we do have one, we can try * every address in the list from the host entry. */ if (hp == NULL) { /* Since we're given a single raw address, and not a host entry, * we can only try this one address and not any other addresses * that could be present for a site with a host entry. */ err = connect(sockfd, (struct sockaddr *) &gServerCtrlAddr, (int) sizeof (gServerCtrlAddr)); } else { /* We can try each address in the list. We'll quit when we * run out of addresses to try or get a successful connection. */ for (curaddr = hp->h_addr_list; *curaddr != NULL; curaddr++) { /* This could overwrite the address field in the structure, * but this is okay because the structure has a junk field * just for this purpose. */ memcpy(&gServerCtrlAddr.sin_addr, *curaddr, (size_t) hp->h_length); err = connect(sockfd, (struct sockaddr *) &gServerCtrlAddr, (int) sizeof (gServerCtrlAddr)); if (err == 0) break; } } if (err < 0) { /* Could not connect. Close up shop and go home. */ /* If possible, tell the caller if they should bother * calling back later. */ switch (errno) { case ENETDOWN: case ENETUNREACH: case ECONNABORTED: case ETIMEDOUT: case ECONNREFUSED: case EHOSTDOWN: result = kConnectErrReTryable; /*FALLTHROUGH*/ default: Error(kDoPerror, "Could not connect to %s.\n", host); } goto fatal; } /* Get our end of the socket address for later use. */ if (GetSocketAddress(sockfd, &gOurClientCtrlAddr) < 0) goto fatal; /* We want Out-of-band data to appear in the regular stream, * since we can handle TELNET. */ SetInlineOutOfBandData(sockfd); #if defined(IP_TOS) && defined(IPTOS_LOWDELAY) /* Control connection is somewhat interactive, so quick response * is desired. */ SetTypeOfService(sockfd, IPTOS_LOWDELAY); #endif if ((sock2fd = dup(sockfd)) < 0) { Error(kDoPerror, "Could not duplicate a file descriptor.\n"); goto fatal; } /* Now setup the FILE pointers for use with the Std I/O library * routines. */ if ((gControlIn = fdopen(sockfd, "r")) == NULL) { Error(kDoPerror, "Could not fdopen.\n"); goto fatal; } if ((gControlOut = fdopen(sock2fd, "w")) == NULL) { Error(kDoPerror, "Could not fdopen.\n"); CloseFile(&gControlIn); sockfd = kClosedFileDescriptor; goto fatal; } /* We'll be reading and writing lines, so use line buffering. This * is necessary since the stdio library will use full buffering * for all streams not associated with the tty. */ #ifdef HAVE_SETLINEBUF setlinebuf(gControlIn); setlinebuf(gControlOut); #else (void) SETVBUF(gControlIn, NULL, _IOLBF, (size_t) BUFSIZ); (void) SETVBUF(gControlOut, NULL, _IOLBF, (size_t) BUFSIZ); #endif (void) STRNCPY(gIPStr, inet_ntoa(gServerCtrlAddr.sin_addr)); if ((hp == NULL) || (hp->h_name == NULL)) (void) STRNCPY(gActualHostName, host); else (void) STRNCPY(gActualHostName, (char *) hp->h_name); /* Read the startup message from the server. */ rp = InitResponse(); gReadingStartup = 1; if (GetResponse(rp) == 5) { /* They probably hung up on us right away. That's too bad, * but we can tell the caller that they can call back later * and try again. */ gReadingStartup = 0; result = kConnectErrReTryable; if (CloseFile(&gControlIn)) DebugMsg("Closed control connection.\n"); CloseFile(&gControlOut); sockfd = kClosedFileDescriptor; sock2fd = kClosedFileDescriptor; DoneWithResponse(rp); goto fatal; } if ((rp != NULL) && (rp->msg.first != NULL)) { if (strstr(rp->msg.first->line, "Version wu-") != NULL) { gRemoteServerType = kWuFTPd; DebugMsg("Remote server is running wu-ftpd.\n"); } else if (STRNEQ("NcFTPd", rp->msg.first->line, 6)) { gRemoteServerType = kNcFTPd; DebugMsg("Remote server is running NcFTPd!\n"); } } gReadingStartup = 0; DoneWithResponse(rp); gHasPASV = 1; /* Assume we have it, until proven otherwise. */ gConnected = 1; return (kConnectNoErr); fatal: if (sockfd > 0) close(sockfd); if (sock2fd > 0) close(sock2fd); return (result); } /* OpenControlConnection */ void CloseDataConnection(int mustClose) { /* Don't need to close if in block mode. */ if ((mustClose == 0) && (gMode == 'B')) { DebugMsg("Leaving data connection open.\n"); return; } gDataSocketAccepted = kClosedFileDescriptor; if (gDataSocket != kClosedFileDescriptor) { close(gDataSocket); gDataSocket = kClosedFileDescriptor; DebugMsg("Closed data connection.\n"); } else { DebugMsg("Data connection was already closed.\n"); } } /* CloseDataConnection */ int SetStartOffset(long restartPt) { if (restartPt != SZ(0)) { if (RCmd(kDefaultResponse, "REST %lu", (unsigned long) restartPt) == 3) { DebugMsg("Starting at %lu bytes.\n", (unsigned long) restartPt); } else { DebugMsg("Could not start at %lu bytes.\n", (unsigned long) restartPt); return (-1); } } return (0); } /* SetStartOffset */ static int SendPort(struct sockaddr_in *saddr) { char *a, *p; int result; ResponsePtr rp; rp = InitResponse(); /* These will point to data in network byte order. */ a = (char *) &saddr->sin_addr; p = (char *) &saddr->sin_port; #define UC(x) (int) (((int) x) & 0xff) /* Need to tell the other side which host (the address) and * which process (port) on that host to send data to. */ RCmd(rp, "PORT %d,%d,%d,%d,%d,%d", UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); /* A 500'ish response code means the PORT command failed. */ result = ((rp->codeType == 5) ? (-1) : 0); DoneWithResponse(rp); return (result); } /* SendPort */ static int Passive(struct sockaddr_in *saddr, int *weird) { ResponsePtr rp; int i[6], j; unsigned char n[6]; char *cp; int result; result = -1; rp = InitResponse(); RCmd(rp, "PASV"); if (rp->codeType != 2) { /* Didn't understand or didn't want passive port selection. */ goto done; } /* The other side returns a specification in the form of * an internet address as the first four integers (each * integer stands for 8-bits of the real 32-bit address), * and two more integers for the port (16-bit port). * * It should give us something like: * "Entering Passive Mode (129,93,33,1,10,187)", so look for * digits with sscanf() starting 24 characters down the string. */ for (cp = rp->msg.first->line; ; cp++) { if (*cp == '\0') { DebugMsg("Cannot parse PASV response: %s\n", rp->msg.first->line); goto done; } if (isdigit(*cp)) break; } if (sscanf(cp, "%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6) { DebugMsg("Cannot parse PASV response: %s\n", rp->msg.first->line); goto done; } for (j=0, *weird = 0; j<6; j++) { /* Some ftp servers return bogus port octets, such as * boombox.micro.umn.edu. Let the caller know if we got a * weird looking octet. */ if ((i[j] < 0) || (i[j] > 255)) *weird = *weird + 1; n[j] = (unsigned char) (i[j] & 0xff); } memcpy(&saddr->sin_addr, &n[0], (size_t) 4); memcpy(&saddr->sin_port, &n[4], (size_t) 2); result = 0; done: DoneWithResponse(rp); return (result); } /* Passive */ int OpenDataConnection(int mode) { int dataSocket; int weirdPort; /* Before we can transfer any data, and before we even ask the * remote server to start transferring via RETR/NLST/etc, we have * to setup the connection. */ if ((gMode == 'B') && (gDataSocket != kClosedFileDescriptor)) { /* Re-use existing connection. */ return (0); } tryPort2: dataSocket = socket(AF_INET, SOCK_STREAM, 0); if (dataSocket < 0) { Error(kDoPerror, "Could not get a data socket.\n"); return (-1); } if ((gHasPASV == 0) || (mode == kSendPortMode)) { tryPort: gOurClientDataAddr = gOurClientCtrlAddr; gOurClientDataAddr.sin_family = AF_INET; /* bind will assign us an unused port, typically between 1024-5000. */ gOurClientDataAddr.sin_port = 0; #ifdef HAVE_LIBSOCKS if (Rbind(dataSocket, (struct sockaddr *) &gOurClientDataAddr, (int) sizeof (gOurClientDataAddr), gServerCtrlAddr.sin_addr.s_addr) < 0) #else if (bind(dataSocket, (struct sockaddr *) &gOurClientDataAddr, (int) sizeof (gOurClientDataAddr)) < 0) #endif { Error(kDoPerror, "Could not bind the data socket.\n"); goto bad; } /* Need to do this so we can figure out which port the system * gave to us. */ if (GetSocketAddress(dataSocket, &gOurClientDataAddr) < 0) goto bad; if (listen(dataSocket, 1) < 0) { Error(kDoPerror, "listen failed.\n"); goto bad; } if (SendPort(&gOurClientDataAddr) < 0) goto bad; gDataPortMode = kSendPortMode; } else { /* Passive mode. Let the other side decide where to send. */ gOurClientDataAddr = gOurClientCtrlAddr; gOurClientDataAddr.sin_family = AF_INET; if ((!gHasPASV) || (Passive(&gOurClientDataAddr, &weirdPort) < 0)) { Error(kDontPerror, "Passive mode refused.\n"); gHasPASV = 0; /* We can try using regular PORT commands, which are required * by all FTP protocol compliant programs, if you said so. * * We don't do this automatically, because if your host * is running a firewall you (probably) do not want SendPort * FTP for security reasons. */ if (mode == kFallBackToSendPortMode) { gHasPASV = 0; goto tryPort; } goto bad; } if (connect(dataSocket, (struct sockaddr *) &gOurClientDataAddr, (int) sizeof(gOurClientDataAddr)) < 0 ) { if ((weirdPort > 0) && (errno == ECONNREFUSED)) { EPrintF("Server sent back a bogus port number.\nI will fall back to PORT instead of PASV mode.\n"); if (mode == kFallBackToSendPortMode) { close(dataSocket); gHasPASV = 0; goto tryPort2; } goto bad; } Error(kDoPerror, "connect failed.\n"); goto bad; } gDataPortMode = kPassiveMode; } SetLinger(dataSocket); #if defined(IP_TOS) && defined(IPTOS_THROUGHPUT) /* Data connection is a non-interactive data stream, so * high throughput is desired, at the expense of low * response time. */ SetTypeOfService(dataSocket, IPTOS_THROUGHPUT); #endif gDataSocket = dataSocket; return (0); bad: (void) close(dataSocket); return (-1); } /* OpenDataConnection */ int AcceptDataConnection(void) { int newSocket; int len; if ((gMode == 'B') && (gDataSocketAccepted != kClosedFileDescriptor)) { /* Re-use existing connection. */ return 0; } /* If we did a PORT, we have some things to finish up. * If we did a PASV, we're ready to go. */ if (gDataPortMode == kSendPortMode) { /* Accept will give us back the server's data address; at the * moment we don't do anything with it though. */ len = (int) sizeof(gServerDataAddr); newSocket = accept(gDataSocket, (struct sockaddr *) &gServerDataAddr, &len); (void) close(gDataSocket); if (newSocket < 0) { Error(kDoPerror, "Could not accept a data connection.\n"); gDataSocket = kClosedFileDescriptor; gDataSocketAccepted = kClosedFileDescriptor; return (-1); } gDataSocket = newSocket; gDataSocketAccepted = newSocket; } return (0); } /* AcceptDataConnection */ /* Kind of silly, but I wanted to keep this module as self-contained * as possible. */ void SetPostHangupOnServerProc(HangupProc proc) { gHangupProc = proc; } /* SetPostHangupOnServerProc */ void HangupOnServer(void) { int wasConn; wasConn = gConnected; /* Since we want to close both sides of the connection for each * socket, we can just have them closed with close() instead of * using shutdown(). */ if (wasConn != 0) { CloseControlConnection(); CloseDataConnection(1); if (gHangupProc != (HangupProc)0) (*gHangupProc)(); } } /* HangupOnServer */ void SendTelnetInterrupt(void) { unsigned char msg[4]; /* 1. User system inserts the Telnet "Interrupt Process" (IP) signal * in the Telnet stream. */ msg[0] = (unsigned char) IAC; msg[1] = (unsigned char) IP; /* 2. User system sends the Telnet "Sync" signal. */ msg[2] = (unsigned char) IAC; msg[3] = (unsigned char) DM; if (send(fileno(gControlOut), (char *) msg, 3, 0) != 3) Error(kDoPerror, "Could not send a telnet interrupt(a).\n"); if (send(fileno(gControlOut), (char *) msg + 3, 1, MSG_OOB) != 1) Error(kDoPerror, "Could not send a telnet interrupt(b).\n"); } /* SendTelnetInterrupt */ /* eof FTP.c */