Makefile.flags: restrict Wno-constant-logical-operand and Wno-string-plus-int options...
[oweals/busybox.git] / networking / ftpd.c
index c63b9319e199f430154d26b2b1ca433e653ab141..6ca231c90b203d4ee54a6fb7fe46c5188b61441b 100644 (file)
@@ -4,15 +4,85 @@
  *
  * Author: Adam Tkac <vonsch@gmail.com>
  *
- * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2, see file LICENSE in this source tree.
  *
  * Only subset of FTP protocol is implemented but vast majority of clients
  * should not have any problem.
  *
  * You have to run this daemon via inetd.
  */
+//config:config FTPD
+//config:      bool "ftpd (30 kb)"
+//config:      default y
+//config:      help
+//config:      Simple FTP daemon. You have to run it via inetd.
+//config:
+//config:config FEATURE_FTPD_WRITE
+//config:      bool "Enable -w (upload commands)"
+//config:      default y
+//config:      depends on FTPD
+//config:      help
+//config:      Enable -w option. "ftpd -w" will accept upload commands
+//config:      such as STOR, STOU, APPE, DELE, MKD, RMD, rename commands.
+//config:
+//config:config FEATURE_FTPD_ACCEPT_BROKEN_LIST
+//config:      bool "Enable workaround for RFC-violating clients"
+//config:      default y
+//config:      depends on FTPD
+//config:      help
+//config:      Some ftp clients (among them KDE's Konqueror) issue illegal
+//config:      "LIST -l" requests. This option works around such problems.
+//config:      It might prevent you from listing files starting with "-" and
+//config:      it increases the code size by ~40 bytes.
+//config:      Most other ftp servers seem to behave similar to this.
+//config:
+//config:config FEATURE_FTPD_AUTHENTICATION
+//config:      bool "Enable authentication"
+//config:      default y
+//config:      depends on FTPD
+//config:      help
+//config:      Require login, and change to logged in user's UID:GID before
+//config:      accessing any files. Option "-a USER" allows "anonymous"
+//config:      logins (treats them as if USER logged in).
+//config:
+//config:      If this option is not selected, ftpd runs with the rights
+//config:      of the user it was started under, and does not require login.
+//config:      Take care to not launch it under root.
+
+//applet:IF_FTPD(APPLET(ftpd, BB_DIR_USR_SBIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_FTPD) += ftpd.o
+
+//usage:#define ftpd_trivial_usage
+//usage:       "[-wvS]"IF_FEATURE_FTPD_AUTHENTICATION(" [-a USER]")" [-t N] [-T N] [DIR]"
+//usage:#define ftpd_full_usage "\n\n"
+//usage:       IF_NOT_FEATURE_FTPD_AUTHENTICATION(
+//usage:       "Anonymous FTP server. Client access occurs under ftpd's UID.\n"
+//usage:       )
+//usage:       IF_FEATURE_FTPD_AUTHENTICATION(
+//usage:       "FTP server. "
+//usage:       )
+//usage:       "Chroots to DIR, if this fails (run by non-root), cds to it.\n"
+//usage:       "Should be used as inetd service, inetd.conf line:\n"
+//usage:       "       21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
+//usage:       "Can be run from tcpsvd:\n"
+//usage:       "       tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve"
+//usage:     "\n"
+//usage:     "\n       -w      Allow upload"
+//usage:       IF_FEATURE_FTPD_AUTHENTICATION(
+//usage:     "\n       -A      No login required, client access occurs under ftpd's UID"
+//
+// if !FTPD_AUTHENTICATION, -A is accepted too, but not shown in --help
+// since it's the only supported mode in that configuration
+//
+//usage:     "\n       -a USER Enable 'anonymous' login and map it to USER"
+//usage:       )
+//usage:     "\n       -v      Log errors to stderr. -vv: verbose log"
+//usage:     "\n       -S      Log errors to syslog. -SS: verbose log"
+//usage:     "\n       -t,-T N Idle and absolute timeout"
 
 #include "libbb.h"
+#include "common_bufsiz.h"
 #include <syslog.h>
 #include <netinet/tcp.h>
 
@@ -100,15 +170,20 @@ struct globals {
        len_and_sockaddr *port_addr;
        char *ftp_cmd;
        char *ftp_arg;
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
        char *rnfr_filename;
 #endif
        /* We need these aligned to uint32_t */
        char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
        char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
 } FIX_ALIASING;
-#define G (*(struct globals*)&bb_common_bufsiz1)
+#define G (*ptr_to_globals)
+/* ^^^ about 75 bytes smaller code than this: */
+//#define G (*(struct globals*)bb_common_bufsiz1)
 #define INIT_G() do { \
+       SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+       /*setup_common_bufsiz();*/ \
+       \
        /* Moved to main */ \
        /*strcpy(G.msg_ok  + 4, MSG_OK );*/ \
        /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
@@ -193,7 +268,7 @@ cmdio_write(uint32_t status_str, const char *str)
 static void
 cmdio_write_ok(unsigned status)
 {
-       *(uint32_t *) G.msg_ok = status;
+       *(bb__aliased_uint32_t *) G.msg_ok = status;
        xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
        if (G.verbose > 1)
                verbose_log(G.msg_ok);
@@ -204,9 +279,9 @@ cmdio_write_ok(unsigned status)
 static void
 cmdio_write_error(unsigned status)
 {
-       *(uint32_t *) G.msg_err = status;
+       *(bb__aliased_uint32_t *) G.msg_err = status;
        xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
-       if (G.verbose > 1)
+       if (G.verbose > 0)
                verbose_log(G.msg_err);
 }
 #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
@@ -361,7 +436,7 @@ ftpdataio_get_pasv_fd(void)
                return remote_fd;
        }
 
-       setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       setsockopt_keepalive(remote_fd);
        return remote_fd;
 }
 
@@ -416,7 +491,7 @@ bind_for_passive_mode(void)
        G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
        setsockopt_reuseaddr(fd);
 
-       set_nport(G.local_addr, 0);
+       set_nport(&G.local_addr->u.sa, 0);
        xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
        xlisten(fd, 1);
        getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
@@ -525,7 +600,7 @@ handle_port(void)
        G.port_addr = xdotted2sockaddr(raw, port);
 #else
        G.port_addr = get_peer_lsa(STDIN_FILENO);
-       set_nport(G.port_addr, htons(port));
+       set_nport(&G.port_addr->u.sa, htons(port));
 #endif
        WRITE_OK(FTP_PORTOK);
 }
@@ -534,7 +609,7 @@ static void
 handle_rest(void)
 {
        /* When ftp_arg == NULL simply restart from beginning */
-       G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
+       G.restart_pos = G.ftp_arg ? XATOOFF(G.ftp_arg) : 0;
        WRITE_OK(FTP_RESTOK);
 }
 
@@ -606,14 +681,8 @@ popen_ls(const char *opt)
        pid_t pid;
 
        argv[0] = "ftpd";
-       argv[1] = opt; /* "-l" or "-1" */
-#if BB_MMU
+       argv[1] = opt; /* "-lA" or "-1A" */
        argv[2] = "--";
-#else
-       /* NOMMU ftpd ls helper chdirs to argv[2],
-        * preventing peer from seeing real root. */
-       argv[2] = xrealloc_getcwd_or_warn(NULL);
-#endif
        argv[3] = G.ftp_arg;
        argv[4] = NULL;
 
@@ -632,22 +701,12 @@ popen_ls(const char *opt)
        xpiped_pair(outfd);
 
        /*fflush_all(); - so far we dont use stdio on output */
-       pid = BB_MMU ? fork() : vfork();
-       if (pid < 0)
-               bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork");
-
+       pid = BB_MMU ? xfork() : xvfork();
        if (pid == 0) {
-               /* child */
 #if !BB_MMU
-               /* On NOMMU, we want to execute a child - copy of ourself.
-                * In chroot we usually can't do it. Thus we chdir
-                * out of the chroot back to original root,
-                * and (see later below) execute bb_busybox_exec_path
-                * relative to current directory */
-               if (fchdir(G.root_fd) != 0)
-                       _exit(127);
-               /*close(G.root_fd); - close_on_exec_on() took care of this */
+               int cur_fd;
 #endif
+               /* child */
                /* NB: close _first_, then move fd! */
                close(outfd.rd);
                xmove_fd(outfd.wr, STDOUT_FILENO);
@@ -659,21 +718,28 @@ popen_ls(const char *opt)
                dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
 #if BB_MMU
                /* memset(&G, 0, sizeof(G)); - ls_main does it */
-               exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
+               exit(ls_main(/*argc_unused*/ 0, (char**) argv));
 #else
-               /* + 1: we must use relative path here if in chroot.
-                * For example, execv("/proc/self/exe") will fail, since
-                * it looks for "/proc/self/exe" _relative to chroot!_ */
-               execv(bb_busybox_exec_path + 1, (char**) argv);
+               cur_fd = xopen(".", O_RDONLY | O_DIRECTORY);
+               /* On NOMMU, we want to execute a child - copy of ourself
+                * in order to unblock parent after vfork.
+                * In chroot we usually can't re-exec. Thus we escape
+                * out of the chroot back to original root.
+                */
+               if (G.root_fd >= 0) {
+                       if (fchdir(G.root_fd) != 0 || chroot(".") != 0)
+                               _exit(127);
+                       /*close(G.root_fd); - close_on_exec_on() took care of this */
+               }
+               /* Child expects directory to list on fd #3 */
+               xmove_fd(cur_fd, 3);
+               execv(bb_busybox_exec_path, (char**) argv);
                _exit(127);
 #endif
        }
 
        /* parent */
        close(outfd.wr);
-#if !BB_MMU
-       free((char*)argv[2]);
-#endif
        return outfd.rd;
 }
 
@@ -692,10 +758,9 @@ handle_dir_common(int opts)
        if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
                return; /* port_or_pasv_was_seen emitted error response */
 
-       /* -n prevents user/groupname display,
-        * which can be problematic in chroot */
-       ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
+       ls_fd = popen_ls((opts & LONG_LISTING) ? "-lA" : "-1A");
        ls_fp = xfdopen_for_read(ls_fd);
+/* FIXME: filenames with embedded newlines are mishandled */
 
        if (opts & USE_CTRL_CONN) {
                /* STAT <filename> */
@@ -716,16 +781,20 @@ handle_dir_common(int opts)
                int remote_fd = get_remote_transfer_fd(" Directory listing");
                if (remote_fd >= 0) {
                        while (1) {
-                               line = xmalloc_fgetline(ls_fp);
+                               unsigned len;
+
+                               line = xmalloc_fgets(ls_fp);
                                if (!line)
                                        break;
                                /* I've seen clients complaining when they
                                 * are fed with ls output with bare '\n'.
-                                * Pity... that would be much simpler.
+                                * Replace trailing "\n\0" with "\r\n".
                                 */
-/* TODO: need to s/LF/NUL/g here */
-                               xwrite_str(remote_fd, line);
-                               xwrite(remote_fd, "\r\n", 2);
+                               len = strlen(line);
+                               if (len != 0) /* paranoia check */
+                                       line[len - 1] = '\r';
+                               line[len] = '\n';
+                               xwrite(remote_fd, line, len + 1);
                                free(line);
                        }
                }
@@ -808,7 +877,7 @@ handle_size_or_mdtm(int need_size)
                gmtime_r(&statbuf.st_mtime, &broken_out);
                sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
                        broken_out.tm_year + 1900,
-                       broken_out.tm_mon,
+                       broken_out.tm_mon + 1,
                        broken_out.tm_mday,
                        broken_out.tm_hour,
                        broken_out.tm_min,
@@ -819,7 +888,7 @@ handle_size_or_mdtm(int need_size)
 
 /* Upload commands */
 
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
 static void
 handle_mkd(void)
 {
@@ -914,6 +983,7 @@ handle_upload_common(int is_append, int is_unique)
         || fstat(local_file_fd, &statbuf) != 0
         || !S_ISREG(statbuf.st_mode)
        ) {
+               free(tempname);
                WRITE_ERR(FTP_UPLOADFAIL);
                if (local_file_fd >= 0)
                        goto close_local_and_bail;
@@ -961,7 +1031,7 @@ handle_stou(void)
        G.restart_pos = 0;
        handle_upload_common(0, 1);
 }
-#endif /* ENABLE_FEATURE_FTP_WRITE */
+#endif /* ENABLE_FEATURE_FTPD_WRITE */
 
 static uint32_t
 cmdio_get_cmd_and_arg(void)
@@ -1071,6 +1141,8 @@ enum {
        const_PASV = mk_const4('P', 'A', 'S', 'V'),
        const_PORT = mk_const4('P', 'O', 'R', 'T'),
        const_PWD  = mk_const3('P', 'W', 'D'),
+       /* Same as PWD. Reportedly used by windows ftp client */
+       const_XPWD = mk_const4('X', 'P', 'W', 'D'),
        const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
        const_REST = mk_const4('R', 'E', 'S', 'T'),
        const_RETR = mk_const4('R', 'E', 'T', 'R'),
@@ -1090,18 +1162,20 @@ enum {
        OPT_l = (1 << 0),
        OPT_1 = (1 << 1),
 #endif
-       OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
-       OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
-       OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
+       BIT_A =        (!BB_MMU) * 2,
+       OPT_A = (1 << (BIT_A + 0)),
+       OPT_v = (1 << (BIT_A + 1)),
+       OPT_S = (1 << (BIT_A + 2)),
+       OPT_w = (1 << (BIT_A + 3)) * ENABLE_FEATURE_FTPD_WRITE,
 };
 
 int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-#if !BB_MMU
-int ftpd_main(int argc, char **argv)
-#else
 int ftpd_main(int argc UNUSED_PARAM, char **argv)
-#endif
 {
+#if ENABLE_FEATURE_FTPD_AUTHENTICATION
+       struct passwd *pw = NULL;
+       char *anon_opt = NULL;
+#endif
        unsigned abs_timeout;
        unsigned verbose_S;
        smallint opts;
@@ -1111,20 +1185,27 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
        abs_timeout = 1 * 60 * 60;
        verbose_S = 0;
        G.timeout = 2 * 60;
-       opt_complementary = "t+:T+:vv:SS";
 #if BB_MMU
-       opts = getopt32(argv,   "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
+       opts = getopt32(argv, "^"   "AvS" IF_FEATURE_FTPD_WRITE("w")
+               "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
+               "\0" "vv:SS",
+               &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
+               &G.verbose, &verbose_S
+       );
 #else
-       opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
+       opts = getopt32(argv, "^" "l1AvS" IF_FEATURE_FTPD_WRITE("w")
+               "t:+T:+" IF_FEATURE_FTPD_AUTHENTICATION("a:")
+               "\0" "vv:SS",
+               &G.timeout, &abs_timeout, IF_FEATURE_FTPD_AUTHENTICATION(&anon_opt,)
+               &G.verbose, &verbose_S
+       );
        if (opts & (OPT_l|OPT_1)) {
-               /* Our secret backdoor to ls */
-/* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
-/* TODO: pass -A? It shows dot files */
-/* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
-               xchdir(argv[2]);
-               argv[2] = (char*)"--";
+               /* Our secret backdoor to ls: see popen_ls() */
+               if (fchdir(3) != 0)
+                       _exit(127);
                /* memset(&G, 0, sizeof(G)); - ls_main does it */
-               return ls_main(argc, argv);
+               /* NB: in this case -A has a different meaning: like "ls -A" */
+               return ls_main(/*argc_unused*/ 0, argv);
        }
 #endif
        if (G.verbose < verbose_S)
@@ -1160,57 +1241,87 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
        if (logmode)
                applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
 
-#if !BB_MMU
-       G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
-       close_on_exec_on(G.root_fd);
-#endif
-
-       if (argv[optind]) {
-               xchdir(argv[optind]);
-               chroot(".");
-       }
-
        //umask(077); - admin can set umask before starting us
 
-       /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
-       signal(SIGPIPE, SIG_IGN);
+       /* Signals */
+       bb_signals(0
+               /* We'll always take EPIPE rather than a rude signal, thanks */
+               + (1 << SIGPIPE)
+               /* LIST command spawns chilren. Prevent zombies */
+               + (1 << SIGCHLD)
+               , SIG_IGN);
 
        /* Set up options on the command socket (do we need these all? why?) */
-       setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
-       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+       setsockopt_1(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY);
+       setsockopt_keepalive(STDIN_FILENO);
        /* Telnet protocol over command link may send "urgent" data,
         * we prefer it to be received in the "normal" data stream: */
-       setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
+       setsockopt_1(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE);
 
        WRITE_OK(FTP_GREET);
        signal(SIGALRM, timeout_handler);
 
-#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
-       {
-               smallint user_was_specified = 0;
+#if ENABLE_FEATURE_FTPD_AUTHENTICATION
+       if (!(opts & OPT_A)) {
                while (1) {
                        uint32_t cmdval = cmdio_get_cmd_and_arg();
-
                        if (cmdval == const_USER) {
-                               if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
-                                       cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
-                               else {
-                                       user_was_specified = 1;
-                                       cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
+                               if (anon_opt && strcmp(G.ftp_arg, "anonymous") == 0) {
+                                       pw = getpwnam(anon_opt);
+                                       if (pw)
+                                               break; /* does not even ask for password */
                                }
+                               pw = getpwnam(G.ftp_arg);
+                               cmdio_write_raw(STR(FTP_GIVEPWORD)" Specify password\r\n");
                        } else if (cmdval == const_PASS) {
-                               if (user_was_specified)
-                                       break;
-                               cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
+                               if (check_password(pw, G.ftp_arg) > 0) {
+                                       break;  /* login success */
+                               }
+                               cmdio_write_raw(STR(FTP_LOGINERR)" Login failed\r\n");
+                               pw = NULL;
                        } else if (cmdval == const_QUIT) {
                                WRITE_OK(FTP_GOODBYE);
                                return 0;
                        } else {
-                               cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
+                               cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER+PASS\r\n");
                        }
                }
+               WRITE_OK(FTP_LOGINOK);
        }
-       WRITE_OK(FTP_LOGINOK);
+#endif
+
+       /* Do this after auth, else /etc/passwd is not accessible */
+#if !BB_MMU
+       G.root_fd = -1;
+#endif
+       argv += optind;
+       if (argv[0]) {
+               const char *basedir = argv[0];
+#if !BB_MMU
+               G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
+               close_on_exec_on(G.root_fd);
+#endif
+               if (chroot(basedir) == 0)
+                       basedir = "/";
+#if !BB_MMU
+               else {
+                       close(G.root_fd);
+                       G.root_fd = -1;
+               }
+#endif
+               /*
+                * If chroot failed, assume that we aren't root,
+                * and at least chdir to the specified DIR
+                * (older versions were dying with error message).
+                * If chroot worked, move current dir to new "/":
+                */
+               xchdir(basedir);
+       }
+
+#if ENABLE_FEATURE_FTPD_AUTHENTICATION
+       if (pw)
+               change_identity(pw);
+       /* else: -A is in effect */
 #endif
 
        /* RFC-959 Section 5.1
@@ -1279,7 +1390,7 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
                        WRITE_OK(FTP_ALLOOK);
                else if (cmdval == const_SYST)
                        cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
-               else if (cmdval == const_PWD)
+               else if (cmdval == const_PWD || cmdval == const_XPWD)
                        handle_pwd();
                else if (cmdval == const_CWD)
                        handle_cwd();
@@ -1316,7 +1427,7 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
                        handle_port();
                else if (cmdval == const_REST)
                        handle_rest();
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
                else if (opts & OPT_w) {
                        if (cmdval == const_STOR)
                                handle_stor();
@@ -1356,7 +1467,7 @@ int ftpd_main(int argc UNUSED_PARAM, char **argv)
                         * (doesn't necessarily mean "we must support them")
                         * foo 1.2.3: XXXX - comment
                         */
-#if ENABLE_FEATURE_FTP_WRITE
+#if ENABLE_FEATURE_FTPD_WRITE
  bad_cmd:
 #endif
                        cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");