/* Open.c */ #include "Sys.h" #ifdef SYSLOG # include #endif #include "Util.h" #include "Open.h" #include "GetPass.h" #include "Cmds.h" #include "RCmd.h" #include "Bookmark.h" #include "FTP.h" #include "Get.h" #include "Getopt.h" #include "Macro.h" #include "Hostwin.h" #include "Main.h" #include "Complete.h" /* This is a preference setting specifying whether we use anonymous logins * by default (as opposed to user/passwd logins). */ int gAnonOpen = (UOPEN == 0); /* A structure containing some extra information we've learned about * the remote host, so we don't need to find out the hard way each * time we do something. */ extern Bookmark gRmtInfo; /* Name of the host we're connected to. */ string gHost; /* We keep track if we've logged in to the host yet. It's possible to * be connected but not authorized (authenticated, whatever) to do anything. */ int gLoggedIn = 0; /* Need to know if we are trying to connect and login, so if we get * hangup during that, we will come back to the open without longjmp'ing * out. */ int gAttemptingConnection = 0; /* Need to know if they did a 'open -r' so we don't use the built-in pager * for long site-connect messages. Otherwise the program would stop to * wait for the user to hit each time we connected to that site * which would sort of defeat the purpose of redialing. */ int gRedialModeEnabled = 0; /* If we connect successfully, we'll want to save this port number * when we add a host file entry. */ int gPortNumberUsed; int gSavePasswords = 0; /* Open.c externs */ extern char *gOptArg; extern int gOptInd, gIsToTTY; extern unsigned int gFTPPort; extern long gEventNumber; extern char gRemoteCWD[256]; extern char gIPStr[32]; extern int gTransferType, gCurType, gRemoteServerType; extern int gRmtInfoIsNew, gWantRmtInfoSaved; extern int gConnected, gHasPASV, gVerbosity; extern int gDoneApplication, gWinInit, gMode; extern string gEmailAddress, gAnonPassword, gPager; extern UserInfo gUserInfo; extern FILE *gLogFile; extern LineList gRedir; extern void GetRemoteCWD(char *cdstub, ResponsePtr cwdrp); /* This is used primarily for non-anonymous logins. We'll have to ask * the user some questions, like their username, password, etc. */ int LoginQuestion( char *prompt, char *answer, size_t siz, char *defanswer, int noEcho) { string prompt2; /* Only do this if we have an empty string as the answer. */ if (*answer == '\0') { if (defanswer != NULL) sprintf(prompt2, "%s [%s]: ", prompt, defanswer); else sprintf(prompt2, "%s: ", prompt); GetAnswer(prompt2, answer, siz, noEcho); if ((*answer == '\0') && (defanswer != NULL)) Strncpy(answer, defanswer, siz); } return (*answer == '\0' ? (-1) : 0); } /* LoginQuestion */ int Login(char *u, char *p, char *a) { string u2, p2, a2; ResponsePtr rp; int result = -1; int isAnonLogin = 1; STRNCPY(u2, u); STRNCPY(p2, p); STRNCPY(a2, a); rp = InitResponse(); if (LoginQuestion("User", u2, sizeof(u2), "anonymous", 0) < 0) goto done; RCmd(rp, "USER %s", u2); for (;;) { /* Here's a mini finite-automaton for the login process. * * Originally, the FTP protocol was designed to be entirely * implementable from a FA. It could be done, but I don't think * it's something an interactive process could be the most * effective with. */ switch (rp->code) { case 230: /* 230 User logged in, proceed. */ case 202: /* Command not implemented, superfluous at this site. */ goto okay; case 421: /* 421 Service not available, closing control connection. */ goto done; case 331: /* 331 User name okay, need password. */ isAnonLogin = STREQ("anonymous", u2) || STREQ("ftp", u2); if (isAnonLogin) rp->printMode = kDontPrint; ReInitResponse(rp); (void) LoginQuestion( "Password", p2, sizeof(p2), isAnonLogin ? gAnonPassword : NULL, 1 ); RCmd(rp, "PASS %s", p2); break; case 332: /* 332 Need account for login. */ case 532: /* 532 Need account for storing files. */ ReInitResponse(rp); (void) LoginQuestion("Account", a2, sizeof(a2), NULL, 1); RCmd(rp, "ACCT %s", a2); break; case 501: /* Syntax error in parameters or arguments. */ case 503: /* Bad sequence of commands. */ case 530: /* Not logged in. */ case 550: /* Can't set guest privileges. */ goto done; default: if (rp->msg.first == NULL) { Error(kDontPerror, "Lost connection during login.\n"); } else { Error(kDontPerror, "Unexpected response: %s\n", rp->msg.first->line ); } goto done; } } okay: if (isAnonLogin) { gRmtInfo.user[0] = '\0'; gRmtInfo.pass[0] = '\0'; STRNCPY(gRmtInfo.acct, a2); } else { STRNCPY(gRmtInfo.user, u2); if (gSavePasswords) { STRNCPY(gRmtInfo.pass, p2); STRNCPY(gRmtInfo.acct, a2); } } result = 0; done: DoneWithResponse(rp); return result; } /* Login */ /* After closing a site, set or restore some things to their original * states. */ void PostCloseStuff(void) { #ifdef SYSLOG syslog (LOG_INFO, "%s disconnected from %s.", gUserInfo.userName, gHost); #endif if (gLoggedIn) { gRmtInfo.hasPASV = gHasPASV; gRmtInfo.port = gPortNumberUsed; gRmtInfo.nCalls++; time((time_t *) &gRmtInfo.lastCall); STRNCPY(gRmtInfo.lastIP, gIPStr); if (gEventNumber > 0L) { /* Only do these if we were not in batch mode (colon-mode). */ if (gRmtInfo.noSaveDir == 0) STRNCPY(gRmtInfo.dir, gRemoteCWD); (void) RunPrefixedMacro("close.", gRmtInfo.bookmarkName); (void) RunPrefixedMacro("close.", "any"); } else { /* Only do these if we are running colon mode. */ (void) RunPrefixedMacro("colon.close.", gRmtInfo.bookmarkName); (void) RunPrefixedMacro("colon.close.", "any"); } SaveCurHostBookmark(NULL); } gLoggedIn = 0; gRemoteCWD[0] = gHost[0] = '\0'; } /* PostCloseStuff */ /* Close the connection to the remote host and cleanup. */ void DoClose(int tryQUIT) { ResponsePtr rp; if (tryQUIT != 0) { rp = InitResponse(); rp->eofOkay = 1; /* We are expecting EOF after this cmd. */ RCmd(rp, "QUIT"); DoneWithResponse(rp); } CloseControlConnection(); CloseDataConnection(1); PostCloseStuff(); } /* DoClose */ int CloseCmd(void) { DoClose(gConnected); if (gWinInit == 0) SetScreenInfo(); /* Need for line mode. */ return kNoErr; } /* CloseCmd */ /* Given a pointer to an OpenOptions (structure containing all variables * that can be set from the command line), this routine makes sure all * the variables have valid values by setting them to their defaults. */ void InitOpenOptions(OpenOptions *openopt) { /* How do you want to open a site if neither -a or -u are given? * gAnonOpen is true (default to anonymous login), unless * Config.h was edited to set UOPEN to 0 instead. */ openopt->openmode = gAnonOpen ? kOpenImplicitAnon : kOpenImplicitUser; /* Normally you don't want to ignore the entry in your netrc. */ openopt->ignoreRC = 0; /* Set the default delay if the user specifies redial mode without * specifying the redial delay. */ openopt->redialDelay = kRedialDelay; /* Normally, you only want to try once. If you specify redial mode, * this is changed. */ openopt->maxDials = 1; /* You don't want to dump the file to stdout by default. */ openopt->ftpcat = kNoFTPCat; /* We'll set this later, but we'll use 0 to mean that the port * is explicitly not set by the user. */ openopt->port = kPortUnset; /* We are not in colon-mode (yet). */ openopt->colonModePath[0] = 0; /* Set the hostname to a null string, since there is no default host. */ openopt->hostname[0] = 0; /* Set the opening directory path to a null string. */ openopt->cdpath[0] = 0; openopt->interactiveColonMode = 0; /* Since we're opening with a possible colon-mode item, we have to * track options for the GetCmd too. */ InitGetOptions(&openopt->gopt); } /* InitOpenOptions */ /* This is responsible for parsing the command line and setting variables * in the OpenOptions structure according to the user's flags. */ int GetOpenOptions(int argc, char **argv, OpenOptions *openopt, int fromMain) { int opt, www; char *cp, *hostp, *cpath; /* First setup the openopt variables. */ InitOpenOptions(openopt); /* Tell Getopt() that we want to start over with a new command. */ GetoptReset(); while ((opt = Getopt(argc, argv, "aiup:rd:g:cmCfGRn:")) >= 0) { switch (opt) { case 'a': /* User wants to open anonymously. */ openopt->openmode = kOpenExplicitAnon; break; case 'u': /* User wants to open with a login and password. */ openopt->openmode = kOpenExplicitUser; break; case 'i': /* User wants to ignore the entry in the netrc. */ openopt->ignoreRC = 1; break; case 'p': /* User supplied a port number different from the default * ftp port. */ openopt->port = atoi(gOptArg); if (openopt->port <= 0) { /* Probably never happen, but just in case. */ (void) PrintF("%s: bad port number (%s).\n", argv[0], gOptArg); goto usage; } break; case 'd': /* User supplied a delay (in seconds) that differs from * the default. */ openopt->redialDelay = atoi(gOptArg); break; case 'g': /* User supplied an upper-bound on the number of redials * to try. */ openopt->maxDials = atoi(gOptArg); break; case 'r': openopt->maxDials = -1; break; case 'm': /* ftpcat mode is only available from your shell command-line, * not from the ncftp shell. Do that yourself with 'more zz'. */ if (gEventNumber == 0L) { /* If gEventNumber is zero, then we were called directly * from main(), and before the ftp shell has started. */ openopt->ftpcat = kFTPMore; /* ftpcat mode is really ftpmore mode. */ break; } else { PrintF( "You can only use this form of colon-mode (-m) from your shell command line.\n\ Try 'ncftp -m wuarchive.wustl.edu:/README'\n"); } goto usage; case 'c': /* ftpcat mode is only available from your shell command-line, * not from the ncftp shell. Do that yourself with 'get zz -'. */ if (gEventNumber == 0L) { /* If gEventNumber is zero, then we were called directly * from main(), and before the ftp shell has started. */ openopt->ftpcat = kFTPCat; break; } else { PrintF( "You can only use ftpcat/colon-mode from your shell command line.\n\ Try 'ncftp -c wuarchive.wustl.edu:/README > file.'\n"); } goto usage; /* These are options that can be passed to get. */ case 'C': case 'f': case 'G': case 'R': /* case 'z': Note that we can't handle the rename here. */ case 'n': if (fromMain) { (void) SetGetOption(&openopt->gopt, opt, gOptArg); break; } goto usage; default: if (fromMain) break; usage: return kUsageErr; } } if (argv[gOptInd] == NULL) { if (!fromMain) { if (openopt->hostname[0] == 0) goto usage; } } else { /* The user gave us a host to open. * * First, check to see if they gave us a colon-mode path * along with the hostname. We also understand a WWW path, * like "ftp://bang.nta.no/pub/fm2html.v.0.8.4.tar.Z". */ hostp = argv[gOptInd]; cpath = NULL; if ((cp = strchr(hostp, ':')) != NULL) { if (fromMain) { *cp++ = '\0'; cpath = cp; www = 0; /* Is 0 or 1, depending on the type of path. */ if ((*cp == '/') && (cp[1] == '/')) { /* First make sure the path was intended to be used * with ftp and not one of the other URLs. */ if (!ISTREQ(argv[gOptInd], "ftp")) { PrintF( "Bad URL '%s' -- WWW paths must be prefixed by 'ftp://'.\n", argv[gOptInd] ); goto usage; } cp += 2; hostp = cp; cpath = NULL; /* It could have been ftp://hostname only. */ if ((cp = strchr(hostp, '/')) != NULL) { *cp++ = '\0'; cpath = cp; } www = 1; } if (cpath != NULL) { (void) STRNCPY(openopt->colonModePath, www ? "/" : ""); (void) STRNCAT(openopt->colonModePath, cpath); cp = openopt->colonModePath + strlen(openopt->colonModePath) - 1; if (*cp == '/') { /* Colon-mode path ended in a slash, so you said it * was a directory. That means we should start from * this directory when we open this site. */ *cp = '\0'; openopt->interactiveColonMode = 1; } DebugMsg("Colon-Mode Path = '%s'\n", openopt->colonModePath); } } else { /* I may lift this restriction later. */ EPrintF("You can't use colon mode in the command shell.\n"); return kUsageErr; } } (void) STRNCPY(openopt->hostname, hostp); if ((openopt->colonModePath[0] == '\0') && (openopt->ftpcat != kNoFTPCat)) { /* User specified ftpcat mode, but didn't supply * the host:file. */ EPrintF("You didn't use colon mode correctly.\n\ If you use -c or -m, you need to do something like this:\n\ ncftp -c wuarchive.wustl.edu:/pub/README (to cat this file to stdout).\n"); return kUsageErr; } GetBookmark(openopt->hostname, sizeof(openopt->hostname)); if (openopt->port == kPortUnset) { /* No port specified, so use same port as last time. */ if (gRmtInfo.port == kPortUnset) openopt->port = gFTPPort; else openopt->port = gRmtInfo.port; } } return kNoErr; } /* GetOpenOptions */ void CheckRemoteSystemType(int force_binary) { string remoteSys; if (gRmtInfoIsNew) { /* If first time here, check somethings while we can. */ if (DoSystem(remoteSys, sizeof(remoteSys)) == 0) gRmtInfo.isUnix = !strncmp(remoteSys, "UNIX", (size_t) 4); /* Set to binary mode if any of the following are true: * (a) The user is using colon-mode (force_binary; below); * (b) The reply-string from SYST said it was UNIX with 8-bit chars. */ if (!strncmp(remoteSys, "UNIX Type: L8", (size_t) 13)) { gRmtInfo.xferType = 'I'; } /* Print a warning for that (extremely) rare Tenex machine. */ if (!strncmp(remoteSys, "TOPS20", (size_t) 6)) { gRmtInfo.xferType = 'T'; (void) PrintF("Using tenex mode to transfer files.\n"); } if (gRemoteServerType == kNcFTPd) { /* This server correctly implements * block transfer mode, so turn it * on by default. * * Unfortunately, most servers don't * support it, and those that do * (AIX ftpd) don't do it correctly. */ gRmtInfo.xferMode = 'B'; } } gTransferType = gRmtInfo.xferType; /* Use binary mode if this site was last set using ascii mode, * and we are using colon-mode. */ if ((force_binary) && (gTransferType == 'A')) gTransferType = 'I'; if (SetMode(gRmtInfo.xferMode) < 0) { gRmtInfo.xferMode = gMode; } } /* CheckRemoteSystemType */ /* This is called if the user opened the host with a file appended to * the host's name, like "wuarchive.wustl.edu:/pub/readme," or * "wuarchive.wustl.edu:/pub." In the former case, we open wuarchive, * and fetch "readme." In the latter case, we open wuarchive, then set * the current remote directory to "/pub." If we are fetching a file, * we can do some other tricks if "ftpcat mode" is enabled. This mode * must be selected from your shell's command line, and this allows you * to use the program as a one-liner to pipe a remote file into something, * like "ncftp -c wu:/pub/README | wc." If the user uses ftpcat mode, * the program immediately quits instead of going into its own command * shell. */ void ColonMode(OpenOptions *openopt) { int result; /* Check for FTP-cat mode, so we call the appropriate * fetching routine. */ if (openopt->ftpcat == kFTPCat) { InitGetOutputMode(&openopt->gopt, kDumpToStdout); openopt->gopt.rName = openopt->colonModePath; openopt->gopt.doReports = 0; result = DoGet(&openopt->gopt); } else if (openopt->ftpcat == kFTPMore) { result = DoPage(openopt->colonModePath); } else { /* Regular colon-mode, where we fetch the file, putting the * copy in the current local directory. */ InitGetOutputMode(&openopt->gopt, kSaveToDisk); openopt->gopt.doReports = 0; openopt->gopt.rName = openopt->colonModePath; result = DoGetWithGlobbingAndRecursion(&openopt->gopt); } DoQuit(result == 0 ? result : kExitColonModeFail); /*NOTREACHED*/ } /* ColonMode */ /* Now that we're logged in, do some other stuff prior to letting the * user type away at the shell. */ void PostLoginStuff(OpenOptions *openopt) { time_t now; int wasColonMode; gLoggedIn = 1; /* Clear out the old redir buffer, since we are on a new site. */ DisposeLineListContents(&gRedir); /* Since we're logged in okay, we know what we've collected so far * should be saved for next time. */ gWantRmtInfoSaved = 1; /* The FTP module keeps its own note of whether the site has PASV * or not, and has already initialized it to true. If the gRmtInfo * was fetched from our host file, we can tell FTP.c for sure if * PASV should even be attempted. If this was a new gRmtInfo, we * will just be a little redundant, since new entries also assume * PASV is supported. */ gHasPASV = gRmtInfo.hasPASV; /* Since we connected okay, save this port number for later. */ gPortNumberUsed = openopt->port; /* When a new site is opened, ASCII type is assumed (by protocol), * as well as stream transfer mode. */ gCurType = 'A'; gMode = 'S'; STRNCPY(gHost, openopt->hostname); #ifdef SYSLOG syslog (LOG_INFO, "%s logged into %s.", gUserInfo.userName, gHost); #endif if (gLogFile != NULL) { (void) time(&now); fprintf(gLogFile, "%s at %s", gHost, ctime(&now)); } wasColonMode = openopt->colonModePath[0] != (char)0; /* We need to check for unix and see if we should set binary * mode automatically. */ CheckRemoteSystemType(wasColonMode); if (wasColonMode) { if (openopt->interactiveColonMode) { /* Interactive colon mode simply means that the thing they * gave us was a directory, and we should just cd to that * directory when we start up. */ (void) DoChdir(openopt->colonModePath); } else { /* Regular colon-mode is fetching a file by specifying the * pathname of the file on the shell command line. */ (void) GetRemoteCWD(kDidNotChdir, NULL); ColonMode(openopt); /* Does not return... */ } } else if (gRmtInfo.dir[0]) { /* If we didn't have a colon-mode path, we try setting * the current remote directory to cdpath. The .dir field is * (usually) the last directory we were in the previous * time we called this site. */ (void) DoChdir(gRmtInfo.dir); } else { /* Freshen 'cwd' variable for the prompt. */ (void) GetRemoteCWD(kDidNotChdir, NULL); } if (wasColonMode) { /* Run separate sets of macros for colon-mode opens and regular, * interactive opens. */ (void) RunPrefixedMacro("colon.open.", "any"); (void) RunPrefixedMacro("colon.open.", gRmtInfo.bookmarkName); } else { (void) RunPrefixedMacro("open.", "any"); (void) RunPrefixedMacro("open.", gRmtInfo.bookmarkName); if (gWinInit == 0) SetScreenInfo(); /* Need for line mode. */ ClearDirCache(); } } /* PostLoginStuff */ /* Given a properly set up OpenOptions, we try connecting to the site, * redialing if necessary, and do some initialization steps so the user * can send commands. */ int Open(OpenOptions *openopt) { int hErr; int dials; char *user, *pass, *r_acct; int loginResult; int openMode; gRedialModeEnabled = (openopt->maxDials != 1); if (ISEXPLICITOPEN(openopt->openmode)) { /* User specified an open mode explictly, like open -u, * so do what the user said in spite of what we may have had * in the gRmtInfo. */ openMode = (ISANONOPEN(openopt->openmode)) ? 'a' : 'u'; } else { if (gRmtInfoIsNew == 1) { /* First time opening this site. Open it anonymously * unless you said not to with a -option. */ openMode = (ISANONOPEN(openopt->openmode)) ? 'a' : 'u'; } else { /* We've been here before, so use what we had last time. */ openMode = 'r'; } } if ((openMode == 'r') && (gRmtInfo.user[0] == '\0')) openMode = 'a'; if (openMode == 'a') { user = "anonymous"; pass = strchr(gRmtInfo.pass, '@') ? gRmtInfo.pass : gAnonPassword; r_acct = ""; } else if (openMode == 'u') { user = ""; pass = ""; r_acct = ""; } else { user = gRmtInfo.user; pass = gRmtInfo.pass; r_acct = gRmtInfo.acct; } if ((openopt->colonModePath[0]) && (!openopt->interactiveColonMode)) { /* If we're running from a shell script (or whatever) * don't dump any output. If the user is doing this from * the shell, we will at least print the error messages. * We also don't want to "pollute" ftpcat mode, but since * error messages are printed to stderr, and we weren't * going to print anything but error messages anyway, * we're okay by using kErrorsOnly. */ if (gIsToTTY != 0) (void) SetVerbose(kErrorsOnly); else (void) SetVerbose(kQuiet); } else { /* If we haven't already setup our interactive shell, which * would happen if you gave a host on the command line, then * we need to do that now because we want the remote host's * startup information to be displayed. */ Startup(); } PrintF("Trying to connect to %s...\n", openopt->hostname); for ( dials = 0; openopt->maxDials < 0 || dials < openopt->maxDials; dials++) { if (dials > 0) { /* If this is the second dial or higher, sleep a bit. */ PrintF("Sleeping %u seconds... ", (unsigned) openopt->redialDelay); FlushListWindow(); (void) sleep((unsigned) openopt->redialDelay); PrintF("Retry Number: %d\n", dials + 1); } FlushListWindow(); SetBar(NULL, "CONNECTING", NULL, 1, 1); gAttemptingConnection = 1; hErr = OpenControlConnection(openopt->hostname, openopt->port); if (hErr == kConnectErrFatal) { /* Irrecoverable error, so don't bother redialing. * The error message should have already been printed * from OpenControlConnection(). */ DebugMsg("Cannot recover from open error %d.\n", hErr); break; } else if (hErr == kConnectNoErr) { /* We were hooked up successfully. */ gRemoteCWD[0] = '\0'; /* This updates the status bar (for visual mode). */ SetBar(NULL, "LOGGING IN", NULL, 1, 1); SetPostHangupOnServerProc(PostCloseStuff); loginResult = Login(user, pass, r_acct); if (loginResult == 0) { PostLoginStuff(openopt); if (openopt->maxDials != 1) { /* If they selected redial mode, beep at the user * to get their attention. */ Beep(1); } gAttemptingConnection = 0; /* We are connected. */ gRedialModeEnabled = 0; /* Not dialing any more. */ return(kNoErr); /* Login okay, so done. */ } /* Otherwise, an error during login occurred, so try again. */ } else /* (hErr == kConnectErrReTryable), so redial. */ { /* Display error now. */ FlushListWindow(); } DoClose(gConnected); gAttemptingConnection = 0; } /* Display error now. */ FlushListWindow(); if ((openopt->colonModePath[0]) && (!openopt->interactiveColonMode)) { /* If we get here, we we're colon-moding and got a non-redialable * error or we ran out of attempts. */ DoQuit(kExitColonModeFail); } return (kCmdErr); } /* Open */ int OpenCmd(int argc, char **argv) { OpenOptions openopt; int result; /* If there is already a site open, close that one so we can * open a new one. */ DoClose(gConnected); if (argc < 2) { result = HostWindow(); } else { if ((result = GetOpenOptions(argc, argv, &openopt, 0)) == kNoErr) result = Open(&openopt); } return result; } /* OpenCmd */ /* Open.c */