home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Geek Gadgets 1
/
ADE-1.bin
/
ade-dist
/
ncftp-2.3.0-base.tgz
/
ncftp-2.3.0-base.tar
/
contrib
/
ncftp
/
Open.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-11-26
|
23KB
|
872 lines
/* Open.c */
#include "Sys.h"
#ifdef SYSLOG
# include <syslog.h>
#endif
#include "Open.h"
#include "Util.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 <space> 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;
extern int gRmtInfoIsNew, gWantRmtInfoSaved;
extern int gConnected, gHasPASV, gVerbosity;
extern int gDoneApplication, gWinInit;
extern string gEmailAddress, gAnonPassword, gPager;
extern UserInfo gUserInfo;
extern FILE *gLogFile;
extern LineList gRedir;
/* 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();
PostCloseStuff();
} /* DoClose */
int CloseCmd(int argc, char **argv)
{
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");
}
}
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';
} /* 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 mode is assumed (by protocol). */
gCurType = 'A';
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);
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);
}
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;
/* This should be reset for each site, because a slow site may have
* bumped the value rather high.
*/
ResetBlockTimeout();
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 */