X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/00db133f300dd6aa08111d2b42c749a940ecede9..7d2c1789c5a8ccf8a767fb11082bff34c1b7c5aa:/psftp.c diff --git a/psftp.c b/psftp.c index 2923e732..ae509f37 100644 --- a/psftp.c +++ b/psftp.c @@ -25,7 +25,7 @@ */ static int psftp_connect(char *userhost, char *user, int portnumber); -static void do_sftp_init(void); +static int do_sftp_init(void); /* ---------------------------------------------------------------------- * sftp client state. @@ -188,15 +188,15 @@ int sftp_cmd_quit(struct sftp_command *cmd) */ static int sftp_ls_compare(const void *av, const void *bv) { - const struct fxp_name *a = (const struct fxp_name *) av; - const struct fxp_name *b = (const struct fxp_name *) bv; - return strcmp(a->filename, b->filename); + const struct fxp_name *const *a = (const struct fxp_name *const *) av; + const struct fxp_name *const *b = (const struct fxp_name *const *) bv; + return strcmp((*a)->filename, (*b)->filename); } int sftp_cmd_ls(struct sftp_command *cmd) { struct fxp_handle *dirh; struct fxp_names *names; - struct fxp_name *ournames; + struct fxp_name **ournames; int nnames, namesize; char *dir, *cdir; int i; @@ -247,9 +247,8 @@ int sftp_cmd_ls(struct sftp_command *cmd) } for (i = 0; i < names->nnames; i++) - ournames[nnames++] = names->names[i]; + ournames[nnames++] = fxp_dup_name(&names->names[i]); - names->nnames = 0; /* prevent free_names */ fxp_free_names(names); } fxp_close(dirh); @@ -263,8 +262,11 @@ int sftp_cmd_ls(struct sftp_command *cmd) /* * And print them. */ - for (i = 0; i < nnames; i++) - printf("%s\n", ournames[i].longname); + for (i = 0; i < nnames; i++) { + printf("%s\n", ournames[i]->longname); + fxp_free_name(ournames[i]); + } + sfree(ournames); } sfree(cdir); @@ -904,6 +906,66 @@ static int sftp_cmd_open(struct sftp_command *cmd) return 1; } +static int sftp_cmd_lcd(struct sftp_command *cmd) +{ + char *currdir; + int len; + + if (cmd->nwords < 2) { + printf("lcd: expects a local directory name\n"); + return 0; + } + + if (!SetCurrentDirectory(cmd->words[1])) { + LPVOID message; + int i; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&message, 0, NULL); + i = strcspn((char *)message, "\n"); + printf("lcd: unable to change directory: %.*s\n", i, (LPCTSTR)message); + LocalFree(message); + return 0; + } + + currdir = smalloc(256); + len = GetCurrentDirectory(256, currdir); + if (len > 256) + currdir = srealloc(currdir, len); + GetCurrentDirectory(len, currdir); + printf("New local directory is %s\n", currdir); + sfree(currdir); + + return 1; +} + +static int sftp_cmd_lpwd(struct sftp_command *cmd) +{ + char *currdir; + int len; + + currdir = smalloc(256); + len = GetCurrentDirectory(256, currdir); + if (len > 256) + currdir = srealloc(currdir, len); + GetCurrentDirectory(len, currdir); + printf("Current local directory is %s\n", currdir); + sfree(currdir); + + return 1; +} + +static int sftp_cmd_pling(struct sftp_command *cmd) +{ + int exitcode; + + exitcode = system(cmd->words[1]); + return (exitcode == 0); +} + static int sftp_cmd_help(struct sftp_command *cmd); static struct sftp_cmd_lookup { @@ -920,6 +982,7 @@ static struct sftp_cmd_lookup { * `shorthelp' is the name of a primary command, which * contains the help that should double up for this command. */ + int listed; /* do we list this in primary help? */ char *shorthelp; char *longhelp; int (*obey) (struct sftp_command *); @@ -929,13 +992,19 @@ static struct sftp_cmd_lookup { * in ASCII order. */ { - "bye", "finish your SFTP session", + "!", TRUE, "run a local Windows command", + "\n" + " Runs a local Windows command. For example, \"!del myfile\".\n", + sftp_cmd_pling + }, + { + "bye", TRUE, "finish your SFTP session", "\n" " Terminates your SFTP session and quits the PSFTP program.\n", sftp_cmd_quit }, { - "cd", "change your remote working directory", + "cd", TRUE, "change your remote working directory", " [ ]\n" " Change the remote working directory for your SFTP session.\n" " If a new working directory is not supplied, you will be\n" @@ -943,7 +1012,7 @@ static struct sftp_cmd_lookup { sftp_cmd_cd }, { - "chmod", "change file permissions and modes", + "chmod", TRUE, "change file permissions and modes", " ( | ) \n" " Change the file permissions on a file or directory.\n" " can be any octal Unix permission specifier.\n" @@ -970,19 +1039,16 @@ static struct sftp_cmd_lookup { sftp_cmd_chmod }, { - "del", "delete a file", + "del", TRUE, "delete a file", " \n" " Delete a file.\n", sftp_cmd_rm }, { - "delete", "delete a file", - "\n" - " Delete a file.\n", - sftp_cmd_rm + "delete", FALSE, "del", NULL, sftp_cmd_rm }, { - "dir", "list contents of a remote directory", + "dir", TRUE, "list contents of a remote directory", " [ ]\n" " List the contents of a specified directory on the server.\n" " If is not given, the current working directory\n" @@ -990,10 +1056,10 @@ static struct sftp_cmd_lookup { sftp_cmd_ls }, { - "exit", "bye", NULL, sftp_cmd_quit + "exit", TRUE, "bye", NULL, sftp_cmd_quit }, { - "get", "download a file from the server to your local machine", + "get", TRUE, "download a file from the server to your local machine", " [ ]\n" " Downloads a file on the server and stores it locally under\n" " the same name, or under a different one if you supply the\n" @@ -1001,7 +1067,7 @@ static struct sftp_cmd_lookup { sftp_cmd_get }, { - "help", "give help", + "help", TRUE, "give help", " [ [ ... ] ]\n" " Give general help if no commands are specified.\n" " If one or more commands are specified, give specific help on\n" @@ -1009,32 +1075,38 @@ static struct sftp_cmd_lookup { sftp_cmd_help }, { - "ls", "dir", NULL, + "lcd", TRUE, "change local working directory", + " \n" + " Change the local working directory of the PSFTP program (the\n" + " default location where the \"get\" command will save files).\n", + sftp_cmd_lcd + }, + { + "lpwd", TRUE, "print local working directory", + "\n" + " Print the local working directory of the PSFTP program (the\n" + " default location where the \"get\" command will save files).\n", + sftp_cmd_lpwd + }, + { + "ls", TRUE, "dir", NULL, sftp_cmd_ls }, { - "mkdir", "create a directory on the remote server", + "mkdir", TRUE, "create a directory on the remote server", " \n" " Creates a directory with the given name on the server.\n", sftp_cmd_mkdir }, { - "mv", "move or rename a file on the remote server", + "mv", TRUE, "move or rename a file on the remote server", " \n" " Moves or renames the file on the server,\n" " so that it is accessible under the name .\n", sftp_cmd_mv }, { - "put", "upload a file from your local machine to the server", - " [ ]\n" - " Uploads a file to the server and stores it there under\n" - " the same name, or under a different one if you supply the\n" - " argument .\n", - sftp_cmd_put - }, - { - "open", "connect to a host", + "open", TRUE, "connect to a host", " [@]\n" " Establishes an SFTP connection to a given host. Only usable\n" " when you did not already specify a host name on the command\n" @@ -1042,17 +1114,25 @@ static struct sftp_cmd_lookup { sftp_cmd_open }, { - "pwd", "print your remote working directory", + "put", TRUE, "upload a file from your local machine to the server", + " [ ]\n" + " Uploads a file to the server and stores it there under\n" + " the same name, or under a different one if you supply the\n" + " argument .\n", + sftp_cmd_put + }, + { + "pwd", TRUE, "print your remote working directory", "\n" " Print the current remote working directory for your SFTP session.\n", sftp_cmd_pwd }, { - "quit", "bye", NULL, + "quit", TRUE, "bye", NULL, sftp_cmd_quit }, { - "reget", "continue downloading a file", + "reget", TRUE, "continue downloading a file", " [ ]\n" " Works exactly like the \"get\" command, but the local file\n" " must already exist. The download will begin at the end of the\n" @@ -1060,15 +1140,15 @@ static struct sftp_cmd_lookup { sftp_cmd_reget }, { - "ren", "mv", NULL, + "ren", TRUE, "mv", NULL, sftp_cmd_mv }, { - "rename", "mv", NULL, + "rename", FALSE, "mv", NULL, sftp_cmd_mv }, { - "reput", "continue uploading a file", + "reput", TRUE, "continue uploading a file", " [ ]\n" " Works exactly like the \"put\" command, but the remote file\n" " must already exist. The upload will begin at the end of the\n" @@ -1076,11 +1156,11 @@ static struct sftp_cmd_lookup { sftp_cmd_reput }, { - "rm", "del", NULL, + "rm", TRUE, "del", NULL, sftp_cmd_rm }, { - "rmdir", "remove a directory on the remote server", + "rmdir", TRUE, "remove a directory on the remote server", " \n" " Removes the directory with the given name on the server.\n" " The directory will not be removed unless it is empty.\n", @@ -1118,12 +1198,17 @@ static int sftp_cmd_help(struct sftp_command *cmd) int maxlen; maxlen = 0; for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) { - int len = strlen(sftp_lookup[i].name); + int len; + if (!sftp_lookup[i].listed) + continue; + len = strlen(sftp_lookup[i].name); if (maxlen < len) maxlen = len; } for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) { const struct sftp_cmd_lookup *lookup; + if (!sftp_lookup[i].listed) + continue; lookup = &sftp_lookup[i]; printf("%-*s", maxlen+2, lookup->name); if (lookup->longhelp == NULL) @@ -1199,49 +1284,65 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) printf("%s\n", line); } - /* - * Parse the command line into words. The syntax is: - * - double quotes are removed, but cause spaces within to be - * treated as non-separating. - * - a double-doublequote pair is a literal double quote, inside - * _or_ outside quotes. Like this: - * - * firstword "second word" "this has ""quotes"" in" sodoes""this"" - * - * becomes - * - * >firstword< - * >second word< - * >this has "quotes" in< - * >sodoes"this"< - */ p = line; - while (*p) { - /* skip whitespace */ - while (*p && (*p == ' ' || *p == '\t')) - p++; - /* mark start of word */ - q = r = p; /* q sits at start, r writes word */ - quoting = 0; + while (*p && (*p == ' ' || *p == '\t')) + p++; + + if (*p == '!') { + /* + * Special case: the ! command. This is always parsed as + * exactly two words: one containing the !, and the second + * containing everything else on the line. + */ + cmd->nwords = cmd->wordssize = 2; + cmd->words = srealloc(cmd->words, cmd->wordssize * sizeof(char *)); + cmd->words[0] = "!"; + cmd->words[1] = p+1; + } else { + + /* + * Parse the command line into words. The syntax is: + * - double quotes are removed, but cause spaces within to be + * treated as non-separating. + * - a double-doublequote pair is a literal double quote, inside + * _or_ outside quotes. Like this: + * + * firstword "second word" "this has ""quotes"" in" and""this"" + * + * becomes + * + * >firstword< + * >second word< + * >this has "quotes" in< + * >and"this"< + */ while (*p) { - if (!quoting && (*p == ' ' || *p == '\t')) - break; /* reached end of word */ - else if (*p == '"' && p[1] == '"') - p += 2, *r++ = '"'; /* a literal quote */ - else if (*p == '"') - p++, quoting = !quoting; - else - *r++ = *p++; - } - if (*p) - p++; /* skip over the whitespace */ - *r = '\0'; - if (cmd->nwords >= cmd->wordssize) { - cmd->wordssize = cmd->nwords + 16; - cmd->words = - srealloc(cmd->words, cmd->wordssize * sizeof(char *)); + /* skip whitespace */ + while (*p && (*p == ' ' || *p == '\t')) + p++; + /* mark start of word */ + q = r = p; /* q sits at start, r writes word */ + quoting = 0; + while (*p) { + if (!quoting && (*p == ' ' || *p == '\t')) + break; /* reached end of word */ + else if (*p == '"' && p[1] == '"') + p += 2, *r++ = '"'; /* a literal quote */ + else if (*p == '"') + p++, quoting = !quoting; + else + *r++ = *p++; + } + if (*p) + p++; /* skip over the whitespace */ + *r = '\0'; + if (cmd->nwords >= cmd->wordssize) { + cmd->wordssize = cmd->nwords + 16; + cmd->words = + srealloc(cmd->words, cmd->wordssize * sizeof(char *)); + } + cmd->words[cmd->nwords++] = q; } - cmd->words[cmd->nwords++] = q; } /* @@ -1262,7 +1363,7 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) return cmd; } -static void do_sftp_init(void) +static int do_sftp_init(void) { /* * Do protocol initialisation. @@ -1270,7 +1371,7 @@ static void do_sftp_init(void) if (!fxp_init()) { fprintf(stderr, "Fatal: unable to initialise SFTP: %s\n", fxp_error()); - return; + return 1; /* failure */ } /* @@ -1286,6 +1387,7 @@ static void do_sftp_init(void) printf("Remote working directory is %s\n", homedir); } pwd = dupstr(homedir); + return 0; } void do_sftp(int mode, int modeflags, char *batchfile) @@ -1339,179 +1441,6 @@ void do_sftp(int mode, int modeflags, char *batchfile) static int verbose = 0; -void verify_ssh_host_key(char *host, int port, char *keytype, - char *keystr, char *fingerprint) -{ - int ret; - HANDLE hin; - DWORD savemode, i; - - static const char absentmsg[] = - "The server's host key is not cached in the registry. You\n" - "have no guarantee that the server is the computer you\n" - "think it is.\n" - "The server's key fingerprint is:\n" - "%s\n" - "If you trust this host, enter \"y\" to add the key to\n" - "PuTTY's cache and carry on connecting.\n" - "If you want to carry on connecting just once, without\n" - "adding the key to the cache, enter \"n\".\n" - "If you do not trust this host, press Return to abandon the\n" - "connection.\n" - "Store key in cache? (y/n) "; - - static const char wrongmsg[] = - "WARNING - POTENTIAL SECURITY BREACH!\n" - "The server's host key does not match the one PuTTY has\n" - "cached in the registry. This means that either the\n" - "server administrator has changed the host key, or you\n" - "have actually connected to another computer pretending\n" - "to be the server.\n" - "The new key fingerprint is:\n" - "%s\n" - "If you were expecting this change and trust the new key,\n" - "enter \"y\" to update PuTTY's cache and continue connecting.\n" - "If you want to carry on connecting but without updating\n" - "the cache, enter \"n\".\n" - "If you want to abandon the connection completely, press\n" - "Return to cancel. Pressing Return is the ONLY guaranteed\n" - "safe choice.\n" - "Update cached key? (y/n, Return cancels connection) "; - - static const char abandoned[] = "Connection abandoned.\n"; - - char line[32]; - - /* - * Verify the key against the registry. - */ - ret = verify_host_key(host, port, keytype, keystr); - - if (ret == 0) /* success - key matched OK */ - return; - - if (ret == 2) { /* key was different */ - fprintf(stderr, wrongmsg, fingerprint); - fflush(stderr); - } - if (ret == 1) { /* key was absent */ - fprintf(stderr, absentmsg, fingerprint); - fflush(stderr); - } - - hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); - ReadFile(hin, line, sizeof(line) - 1, &i, NULL); - SetConsoleMode(hin, savemode); - - if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') { - if (line[0] == 'y' || line[0] == 'Y') - store_host_key(host, port, keytype, keystr); - } else { - fprintf(stderr, abandoned); - exit(0); - } -} - -/* - * Ask whether the selected cipher is acceptable (since it was - * below the configured 'warn' threshold). - * cs: 0 = both ways, 1 = client->server, 2 = server->client - */ -void askcipher(char *ciphername, int cs) -{ - HANDLE hin; - DWORD savemode, i; - - static const char msg[] = - "The first %scipher supported by the server is\n" - "%s, which is below the configured warning threshold.\n" - "Continue with connection? (y/n) "; - static const char abandoned[] = "Connection abandoned.\n"; - - char line[32]; - - fprintf(stderr, msg, - (cs == 0) ? "" : - (cs == 1) ? "client-to-server " : - "server-to-client ", - ciphername); - fflush(stderr); - - hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); - ReadFile(hin, line, sizeof(line) - 1, &i, NULL); - SetConsoleMode(hin, savemode); - - if (line[0] == 'y' || line[0] == 'Y') { - return; - } else { - fprintf(stderr, abandoned); - exit(0); - } -} - -/* - * Ask whether to wipe a session log file before writing to it. - * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). - */ -int askappend(char *filename) -{ - HANDLE hin; - DWORD savemode, i; - - static const char msgtemplate[] = - "The session log file \"%.*s\" already exists.\n" - "You can overwrite it with a new session log,\n" - "append your session log to the end of it,\n" - "or disable session logging for this session.\n" - "Enter \"y\" to wipe the file, \"n\" to append to it,\n" - "or just press Return to disable logging.\n" - "Wipe the log file? (y/n, Return cancels logging) "; - - char line[32]; - - fprintf(stderr, msgtemplate, FILENAME_MAX, filename); - fflush(stderr); - - hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &savemode); - SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | - ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); - ReadFile(hin, line, sizeof(line) - 1, &i, NULL); - SetConsoleMode(hin, savemode); - - if (line[0] == 'y' || line[0] == 'Y') - return 2; - else if (line[0] == 'n' || line[0] == 'N') - return 1; - else - return 0; -} - -/* - * Warn about the obsolescent key file format. - */ -void old_keyfile_warning(void) -{ - static const char message[] = - "You are loading an SSH 2 private key which has an\n" - "old version of the file format. This means your key\n" - "file is not fully tamperproof. Future versions of\n" - "PuTTY may stop supporting this private key format,\n" - "so we recommend you convert your key to the new\n" - "format.\n" - "\n" - "Once the key is loaded into PuTTYgen, you can perform\n" - "this conversion simply by saving it again.\n"; - - fputs(message, stderr); -} - /* * Print an error message and perform a fatal exit. */ @@ -1524,9 +1453,9 @@ void fatalbox(char *fmt, ...) vsprintf(str + strlen(str), fmt, ap); va_end(ap); strcat(str, "\n"); - fputs(stderr, str); + fputs(str, stderr); - exit(1); + cleanup_exit(1); } void connection_fatal(char *fmt, ...) { @@ -1537,13 +1466,9 @@ void connection_fatal(char *fmt, ...) vsprintf(str + strlen(str), fmt, ap); va_end(ap); strcat(str, "\n"); - fputs(stderr, str); - - exit(1); -} + fputs(str, stderr); -void logevent(char *string) -{ + cleanup_exit(1); } void ldisc_send(char *buf, int len, int interactive) @@ -1589,6 +1514,8 @@ int from_backend(int is_stderr, char *data, int datalen) unsigned char *p = (unsigned char *) data; unsigned len = (unsigned) datalen; + assert(len > 0); + /* * stderr data is just spouted to local stderr and otherwise * ignored. @@ -1691,57 +1618,6 @@ static void ssh_sftp_init(void) } } -static char *password = NULL; -static int get_line(const char *prompt, char *str, int maxlen, int is_pw) -{ - HANDLE hin, hout; - DWORD savemode, newmode, i; - - if (password) { - static int tried_once = 0; - - if (tried_once) { - return 0; - } else { - strncpy(str, password, maxlen); - str[maxlen - 1] = '\0'; - tried_once = 1; - return 1; - } - } - - hin = GetStdHandle(STD_INPUT_HANDLE); - hout = GetStdHandle(STD_OUTPUT_HANDLE); - if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) { - fprintf(stderr, "Cannot get standard input/output handles\n"); - exit(1); - } - - GetConsoleMode(hin, &savemode); - newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT; - if (is_pw) - newmode &= ~ENABLE_ECHO_INPUT; - else - newmode |= ENABLE_ECHO_INPUT; - SetConsoleMode(hin, newmode); - - WriteFile(hout, prompt, strlen(prompt), &i, NULL); - ReadFile(hin, str, maxlen - 1, &i, NULL); - - SetConsoleMode(hin, savemode); - - if ((int) i > maxlen) - i = maxlen - 1; - else - i = i - 2; - str[i] = '\0'; - - if (is_pw) - WriteFile(hout, "\r\n", 2, &i, NULL); - - return 1; -} - /* * Initialize the Win$ock driver. */ @@ -1753,11 +1629,11 @@ static void init_winsock(void) winsock_ver = MAKEWORD(1, 1); if (WSAStartup(winsock_ver, &wsadata)) { fprintf(stderr, "Unable to initialise WinSock"); - exit(1); + cleanup_exit(1); } if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) { fprintf(stderr, "WinSock version is incompatible with 1.1"); - exit(1); + cleanup_exit(1); } } @@ -1776,7 +1652,7 @@ static void usage(void) printf(" -v show verbose messages\n"); printf(" -P port connect to specified port\n"); printf(" -pw passw login with specified password\n"); - exit(1); + cleanup_exit(1); } /* @@ -1846,7 +1722,7 @@ static int psftp_connect(char *userhost, char *user, int portnumber) printf("login as: "); if (!fgets(cfg.username, sizeof(cfg.username), stdin)) { fprintf(stderr, "psftp: aborting\n"); - exit(1); + cleanup_exit(1); } else { int len = strlen(cfg.username); if (cfg.username[len - 1] == '\n') @@ -1926,7 +1802,7 @@ int main(int argc, char *argv[]) char *batchfile = NULL; flags = FLAG_STDERR | FLAG_INTERACTIVE; - ssh_get_line = &get_line; + ssh_get_line = &console_get_line; init_winsock(); sk_init(); @@ -1948,12 +1824,14 @@ int main(int argc, char *argv[]) } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) { portnumber = atoi(argv[++i]); } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) { - password = argv[++i]; + console_password = argv[++i]; } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) { mode = 1; batchfile = argv[++i]; } else if (strcmp(argv[i], "-bc") == 0) { modeflags = modeflags | 1; + } else if (strcmp(argv[i], "-batch") == 0) { + console_batch_mode = TRUE; } else if (strcmp(argv[i], "-be") == 0) { modeflags = modeflags | 2; } else if (strcmp(argv[i], "--") == 0) { @@ -1974,7 +1852,8 @@ int main(int argc, char *argv[]) if (userhost) { if (psftp_connect(userhost, user, portnumber)) return 1; - do_sftp_init(); + if (do_sftp_init()) + return 1; } else { printf("psftp: no hostname specified; use \"open host.name\"" " to connect\n");