tftpd: new applet (mostly using existing code for tftp)
authorDenis Vlasenko <vda.linux@googlemail.com>
Mon, 17 Mar 2008 09:10:39 +0000 (09:10 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Mon, 17 Mar 2008 09:10:39 +0000 (09:10 -0000)
function                                             old     new   delta
tftp_protocol                                          -    1173   +1173
tftpd_main                                             -     500    +500
tftp_option_get                                        -     102    +102
packed_usage                                       23650   23662     +12
applet_names                                        1809    1815      +6
applet_main                                         1100    1104      +4
applet_nameofs                                       550     552      +2
tftp_main                                            302     301      -1
get_nport                                             32       -     -32
tftp                                                1172       -   -1172
------------------------------------------------------------------------------
(add/remove: 3/2 grow/shrink: 4/1 up/down: 1799/-1205)        Total: 594 bytes
   text    data     bss     dec     hex filename
 796479     662    7420  804561   c46d1 busybox_old
 797153     662    7420  805235   c4973 busybox_unstripped

TODO_config_nommu
include/applets.h
include/usage.h
ipsvd/Config.in
ipsvd/tcpudp.c
networking/Config.in
networking/Kbuild
networking/tftp.c
printutils/Config.in

index 8bf7b21..042f622 100644 (file)
@@ -835,14 +835,14 @@ CONFIG_SOFTLIMIT=y
 # CONFIG_SESTATUS is not set
 
 #
-# print support
+# Print Utilities
 #
 CONFIG_LPD=y
 CONFIG_LPR=y
 CONFIG_LPQ=y
 
 #
-# ipsvd utilities
+# ipsvd Utilities
 #
 CONFIG_TCPSVD=y
 CONFIG_UDPSVD=y
index d4b6dbe..c2a089c 100644 (file)
@@ -355,6 +355,7 @@ USE_TELNETD(APPLET(telnetd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER))
 USE_TEST(APPLET_NOEXEC(test, test, _BB_DIR_USR_BIN, _BB_SUID_NEVER, test))
 #if ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT
 USE_TFTP(APPLET(tftp, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_TFTPD(APPLET(tftpd, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 #endif
 USE_TIME(APPLET(time, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_TOP(APPLET(top, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
index 507a52d..8f563f5 100644 (file)
 #define tftp_trivial_usage \
        "[OPTION]... HOST [PORT]"
 #define tftp_full_usage \
-       "Transfer a file from/to tftp server using \"octet\" mode\n" \
+       "Transfer a file from/to tftp server\n" \
      "\nOptions:" \
      "\n       -l FILE Local FILE" \
      "\n       -r FILE Remote FILE" \
        USE_FEATURE_TFTP_BLOCKSIZE( \
      "\n       -b SIZE Transfer blocks of SIZE octets" \
        )
+
+#define tftpd_trivial_usage \
+       "[DIR]"
+#define tftpd_full_usage \
+       "Transfer a file on request from a tftp client" \
+
 #define time_trivial_usage \
        "[OPTION]... COMMAND [ARGS...]"
 #define time_full_usage \
index 8522ef9..0cb8c62 100644 (file)
@@ -3,7 +3,7 @@
 # see scripts/kbuild/config-language.txt.
 #
 
-menu "ipsvd utilities"
+menu "ipsvd Utilities"
 
 config TCPSVD
        bool "tcpsvd"
index 8f6616f..729b7bc 100644 (file)
@@ -322,7 +322,7 @@ int tcpudpsvd_main(int argc ATTRIBUTE_UNUSED, char **argv)
                /* In case recv_from_to won't be able to recover local addr.
                 * Also sets port - recv_from_to is unable to do it. */
                local = *lsa;
-               conn = recv_from_to(sock, NULL, 0, MSG_DONTWAIT | MSG_PEEK,
+               conn = recv_from_to(sock, NULL, 0, MSG_PEEK,
                                &remote.u.sa, &local.u.sa, sa_len);
        }
        sig_block(SIGCHLD);
index 3c53c11..729cca1 100644 (file)
@@ -774,28 +774,41 @@ config TFTP
          is usually used for simple, small transfers such as a root image
          for a network-enabled bootloader.
 
+config TFTPD
+       bool "tftpd"
+       default n
+       help
+         This enables the Trivial File Transfer Protocol server program.
+         It expects that stdin is a datagram socket and a packet
+         is already pending on it. It will exit after one transfer.
+         In other words: it should be run from inetd in nowait mode,
+         or from udpsvd. Example: "udpsvd -E 0 69 tftpd DIR"
+
 config FEATURE_TFTP_GET
        bool "Enable \"get\" command"
        default y
-       depends on TFTP
+       depends on TFTP || TFTPD
        help
          Add support for the GET command within the TFTP client.  This allows
          a client to retrieve a file from a TFTP server.
+         Also enable upload support in tftpd, if tftpd is selected.
 
 config FEATURE_TFTP_PUT
        bool "Enable \"put\" command"
        default y
-       depends on TFTP
+       depends on TFTP || TFTPD
        help
          Add support for the PUT command within the TFTP client.  This allows
          a client to transfer a file to a TFTP server.
+         Also enable download support in tftpd, if tftpd is selected.
 
 config FEATURE_TFTP_BLOCKSIZE
-       bool "Enable \"blocksize\" command"
+       bool "Enable \"blksize\" protocol option"
        default n
-       depends on TFTP
+       depends on TFTP || TFTPD
        help
-         Allow the client to specify the desired block size for transfers.
+         Allow tftp to specify block size, and tftpd to understand
+         "blksize" option.
 
 config DEBUG_TFTP
        bool "Enable debug"
index 44258e9..bf9ba99 100644 (file)
@@ -35,6 +35,7 @@ lib-$(CONFIG_SLATTACH)     += slattach.o
 lib-$(CONFIG_TELNET)       += telnet.o
 lib-$(CONFIG_TELNETD)      += telnetd.o
 lib-$(CONFIG_TFTP)         += tftp.o
+lib-$(CONFIG_TFTPD)        += tftp.o
 lib-$(CONFIG_TRACEROUTE)   += traceroute.o
 lib-$(CONFIG_VCONFIG)      += vconfig.o
 lib-$(CONFIG_WGET)         += wget.o
index 1872615..23a2413 100644 (file)
@@ -2,7 +2,7 @@
 /* -------------------------------------------------------------------------
  * tftp.c
  *
- * A simple tftp client for busybox.
+ * A simple tftp client/server for busybox.
  * Tries to follow RFC1350.
  * Only "octet" mode supported.
  * Optional blocksize negotiation (RFC2347 + RFC2348)
@@ -16,6 +16,8 @@
  *
  * utftp:  Copyright (C) 1999 Uwe Ohse <uwe@ohse.de>
  *
+ * tftpd added by Denys Vlasenko
+ *
  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
  * ------------------------------------------------------------------------- */
 
@@ -46,7 +48,7 @@
 #define CMD_PUT(cmd) 1
 #else
 #define USE_GETPUT(...) __VA_ARGS__
-/* masks coming from getpot32 */
+/* masks coming from getopt32 */
 #define CMD_GET(cmd) ((cmd) & 1)
 #define CMD_PUT(cmd) ((cmd) & 2)
 #endif
@@ -64,14 +66,12 @@ static int tftp_blocksize_check(int blocksize, int bufsize)
         * but our implementation makes it impossible
         * to use blocksizes smaller than 22 octets.
         */
-
        if ((bufsize && (blocksize > bufsize))
-        || (blocksize < 8) || (blocksize > 65564)
+        || (blocksize < 24) || (blocksize > 65564)
        ) {
                bb_error_msg("bad blocksize");
                return 0;
        }
-
        return blocksize;
 }
 
@@ -81,8 +81,11 @@ static char *tftp_option_get(char *buf, int len, const char *option)
        int opt_found = 0;
        int k;
 
+       /* buf points to:
+        * "opt_name<NUL>opt_val<NUL>opt_name2<NUL>opt_val2<NUL>..." */
+
        while (len > 0) {
-               /* Make sure the options are terminated correctly */
+               /* Make sure options are terminated correctly */
                for (k = 0; k < len; k++) {
                        if (buf[k] == '\0') {
                                goto nul_found;
@@ -90,7 +93,7 @@ static char *tftp_option_get(char *buf, int len, const char *option)
                }
                return NULL;
  nul_found:
-               if (opt_val == 0) {
+               if (opt_val == 0) { /* it's "name" part */
                        if (strcasecmp(buf, option) == 0) {
                                opt_found = 1;
                        }
@@ -109,77 +112,116 @@ static char *tftp_option_get(char *buf, int len, const char *option)
 
 #endif
 
-static int tftp( USE_GETPUT(const int cmd,)
+static int tftp_protocol(
+               USE_GETPUT(int cmd,)
+               len_and_sockaddr *our_lsa,
                len_and_sockaddr *peer_lsa,
-               const char *remotefile, const int localfd,
-               unsigned port, int tftp_bufsize)
+               USE_TFTP(const char *remote_file,)
+               int local_fd,
+               int blocksize)
 {
+#if !ENABLE_TFTP
+#define remote_file NULL
+#endif
        struct pollfd pfd[1];
-#define socketfd (pfd[0].fd)
+#define socket_fd (pfd[0].fd)
        int len;
        int send_len;
        USE_FEATURE_TFTP_BLOCKSIZE(smallint want_option_ack = 0;)
        smallint finished = 0;
        uint16_t opcode;
-       uint16_t block_nr = 1;
+       uint16_t block_nr;
        uint16_t recv_blk;
        int retries, waittime_ms;
+       int tftp_bufsize = blocksize + 4;
        char *cp;
-
-       unsigned org_port;
-       len_and_sockaddr *const from = alloca(LSA_LEN_SIZE + peer_lsa->len);
-
        /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
         * size varies meaning BUFFERS_GO_ON_STACK would fail */
        /* We must keep the transmit and receive buffers seperate */
        /* In case we rcv a garbage pkt and we need to rexmit the last pkt */
-       char *xbuf = xmalloc(tftp_bufsize += 4);
+       char *xbuf = xmalloc(tftp_bufsize);
        char *rbuf = xmalloc(tftp_bufsize);
 
-       port = org_port = htons(port);
+       socket_fd = xsocket(peer_lsa->u.sa.sa_family, SOCK_DGRAM, 0);
+       setsockopt_reuseaddr(socket_fd);
 
-       socketfd = xsocket(peer_lsa->u.sa.sa_family, SOCK_DGRAM, 0);
+       if (!ENABLE_TFTP || our_lsa) {
+               /* tftpd */
+               block_nr = 0;
 
-       /* build opcode */
-       opcode = TFTP_WRQ;
-       if (CMD_GET(cmd)) {
-               opcode = TFTP_RRQ;
-       }
-       cp = xbuf + 2;
-       /* add filename and mode */
-       /* fill in packet if the filename fits into xbuf */
-       len = strlen(remotefile) + 1;
-       if (2 + len + sizeof("octet") >= tftp_bufsize) {
-               bb_error_msg("remote filename is too long");
-               goto ret;
-       }
-       strcpy(cp, remotefile);
-       cp += len;
-       /* add "mode" part of the package */
-       strcpy(cp, "octet");
-       cp += sizeof("octet");
+               /* Create a socket which is:
+                * 1. bound to IP:port peer sent 1st datagram to,
+                * 2. connected to peer's IP:port
+                * This way we will answer from the IP:port peer
+                * expects, will not get any other packets on
+                * the socket, and also plain read/write will work. */
+               xbind(socket_fd, &our_lsa->u.sa, our_lsa->len);
+               xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len);
 
 #if ENABLE_FEATURE_TFTP_BLOCKSIZE
-       len = tftp_bufsize - 4; /* data block size */
-       if (len != TFTP_BLOCKSIZE_DEFAULT) {
-               /* rfc2348 says that 65464 is a max allowed value */
-               if ((&xbuf[tftp_bufsize - 1] - cp) < sizeof("blksize NNNNN")) {
+               if (blocksize != TFTP_BLOCKSIZE_DEFAULT) {
+                       /* Create and send OACK packet */
+                       opcode = TFTP_OACK;
+                       cp = xbuf + 2;
+                       goto add_blksize_opt;
+               }
+               /* else: just fall into while (1) loop below */
+#endif
+       } else {
+               /* tftp */
+               block_nr = 1;
+
+               /* We can't (and don't really need to) bind the socket:
+                * we don't know from which local IP datagrams will be sent,
+                * but kernel will pick the same IP every time (unless routing
+                * table is changed), thus peer will see dgrams consistently
+                * coming from the same IP.
+                * We would like to connect the socket, but since peer's
+                * UDP code can be less perfect than ours, _peer's_ IP:port
+                * in replies may differ from IP:port we used to send
+                * our first packet. We can connect() only when we get
+                * first reply. */
+
+               /* build opcode */
+               opcode = TFTP_WRQ;
+               if (CMD_GET(cmd)) {
+                       opcode = TFTP_RRQ;
+               }
+               cp = xbuf + 2;
+               /* add filename and mode */
+               /* fill in packet if the filename fits into xbuf */
+               len = strlen(remote_file) + 1;
+               if (2 + len + sizeof("octet") >= tftp_bufsize) {
                        bb_error_msg("remote filename is too long");
                        goto ret;
                }
-               /* add "blksize", <nul>, blocksize */
-               strcpy(cp, "blksize");
-               cp += sizeof("blksize");
-               cp += snprintf(cp, 6, "%d", len) + 1;
-               want_option_ack = 1;
-       }
+               strcpy(cp, remote_file);
+               cp += len;
+               /* add "mode" part of the package */
+               strcpy(cp, "octet");
+               cp += sizeof("octet");
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+               if (blocksize != TFTP_BLOCKSIZE_DEFAULT) {
+                       /* rfc2348 says that 65464 is a max allowed value */
+                       if ((&xbuf[tftp_bufsize - 1] - cp) < sizeof("blksize NNNNN")) {
+                               bb_error_msg("remote filename is too long");
+                               goto ret;
+                       }
+                       want_option_ack = 1;
+ add_blksize_opt:
+                       /* add "blksize", <nul>, blocksize, <nul> */
+                       strcpy(cp, "blksize");
+                       cp += sizeof("blksize");
+                       cp += snprintf(cp, 6, "%d", blocksize) + 1;
+               }
 #endif
-       /* First packet is built, so skip packet generation */
-       goto send_pkt;
+               /* First packet is built, so skip packet generation */
+               goto send_pkt;
+       }
 
        /* Using mostly goto's - continue/break will be less clear
         * in where we actually jump to */
-
        while (1) {
                /* Build ACK or DATA */
                cp = xbuf + 2;
@@ -189,7 +231,7 @@ static int tftp( USE_GETPUT(const int cmd,)
                opcode = TFTP_ACK;
                if (CMD_PUT(cmd)) {
                        opcode = TFTP_DATA;
-                       len = full_read(localfd, cp, tftp_bufsize - 4);
+                       len = full_read(local_fd, cp, tftp_bufsize - 4);
                        if (len < 0) {
                                bb_perror_msg(bb_msg_read_error);
                                goto ret;
@@ -216,36 +258,36 @@ static int tftp( USE_GETPUT(const int cmd,)
                        fprintf(stderr, "%02x ", (unsigned char) *cp);
                fprintf(stderr, "\n");
 #endif
-               xsendto(socketfd, xbuf, send_len, &peer_lsa->u.sa, peer_lsa->len);
+               xsendto(socket_fd, xbuf, send_len, &peer_lsa->u.sa, peer_lsa->len);
                /* Was it final ACK? then exit */
                if (finished && (opcode == TFTP_ACK))
                        goto ret;
 
  recv_again:
                /* Receive packet */
-               /*pfd[0].fd = socketfd;*/
+               /*pfd[0].fd = socket_fd;*/
                pfd[0].events = POLLIN;
                switch (safe_poll(pfd, 1, waittime_ms)) {
-                       unsigned from_port;
                case 1:
-                       from->len = peer_lsa->len;
-                       memset(&from->u.sa, 0, peer_lsa->len);
-                       len = recvfrom(socketfd, rbuf, tftp_bufsize, 0,
-                                               &from->u.sa, &from->len);
+                       if (!our_lsa) {
+                               /* tftp (not tftpd!) receiving 1st packet */
+                               our_lsa = ((void*)(ptrdiff_t)-1); /* not NULL */
+                               len = recvfrom(socket_fd, rbuf, tftp_bufsize, 0,
+                                               &peer_lsa->u.sa, &peer_lsa->len);
+                               /* Our first dgram went to port 69
+                                * but reply may come from different one.
+                                * Remember and use this new port (and IP) */
+                               if (len >= 0)
+                                       xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len);
+                       } else {
+                               /* tftpd, or not the very first packet:
+                                * socket is connect()ed, can just read from it. */
+                               len = safe_read(socket_fd, rbuf, tftp_bufsize);
+                       }
                        if (len < 0) {
-                               bb_perror_msg("recvfrom");
+                               bb_perror_msg("read");
                                goto ret;
                        }
-                       from_port = get_nport(&from->u.sa);
-                       if (port == org_port) {
-                               /* Our first query went to port 69
-                                * but reply will come from different one.
-                                * Remember and use this new port */
-                               port = from_port;
-                               set_nport(peer_lsa, from_port);
-                       }
-                       if (port != from_port)
-                               goto recv_again;
                        goto process_pkt;
                case 0:
                        retries--;
@@ -316,7 +358,7 @@ static int tftp( USE_GETPUT(const int cmd,)
                                                /*static const uint16_t error_8[2] = { htons(TFTP_ERROR), htons(8) };*/
                                                /* thus we open-code big-endian layout */
                                                static const uint8_t error_8[4] = { 0,TFTP_ERROR, 0,8 };
-                                               xsendto(socketfd, error_8, 4, &peer_lsa->u.sa, peer_lsa->len);
+                                               xsendto(socket_fd, error_8, 4, &peer_lsa->u.sa, peer_lsa->len);
                                                bb_error_msg("server proposes bad blksize %d, exiting", blksize);
                                                goto ret;
                                        }
@@ -345,7 +387,7 @@ static int tftp( USE_GETPUT(const int cmd,)
 
                if (CMD_GET(cmd) && (opcode == TFTP_DATA)) {
                        if (recv_blk == block_nr) {
-                               len = full_write(localfd, &rbuf[4], len - 4);
+                               len = full_write(local_fd, &rbuf[4], len - 4);
                                if (len < 0) {
                                        bb_perror_msg(bb_msg_write_error);
                                        goto ret;
@@ -363,7 +405,7 @@ static int tftp( USE_GETPUT(const int cmd,)
                }
 
                if (CMD_PUT(cmd) && (opcode == TFTP_ACK)) {
-                       /* did server ACK our last DATA pkt? */
+                       /* did peer ACK our last DATA pkt? */
                        if (recv_blk == (uint16_t) (block_nr - 1)) {
                                if (finished)
                                        goto ret;
@@ -381,25 +423,27 @@ static int tftp( USE_GETPUT(const int cmd,)
                 * See:
                 * http://en.wikipedia.org/wiki/Sorcerer's_Apprentice_Syndrome
                 */
-       }
+       } /* end of "while (1)" */
  ret:
        if (ENABLE_FEATURE_CLEAN_UP) {
-               close(socketfd);
+               close(socket_fd);
                free(xbuf);
                free(rbuf);
        }
        return finished == 0; /* returns 1 on failure */
 }
 
+#if ENABLE_TFTP
+
 int tftp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 int tftp_main(int argc ATTRIBUTE_UNUSED, char **argv)
 {
        len_and_sockaddr *peer_lsa;
-       const char *localfile = NULL;
-       const char *remotefile = NULL;
+       const char *local_file = NULL;
+       const char *remote_file = NULL;
        int port;
        USE_GETPUT(int cmd;)
-       int fd = -1;
+       int local_fd;
        int flags = 0;
        int result;
        int blocksize = TFTP_BLOCKSIZE_DEFAULT;
@@ -412,7 +456,7 @@ int tftp_main(int argc ATTRIBUTE_UNUSED, char **argv)
        USE_GETPUT(cmd =) getopt32(argv,
                        USE_FEATURE_TFTP_GET("g") USE_FEATURE_TFTP_PUT("p")
                                "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"),
-                       &localfile, &remotefile
+                       &local_file, &remote_file
                        USE_FEATURE_TFTP_BLOCKSIZE(, &blocksize));
        argv += optind;
 
@@ -425,36 +469,142 @@ int tftp_main(int argc ATTRIBUTE_UNUSED, char **argv)
                return EXIT_FAILURE;
 #endif
 
-       if (!localfile)
-               localfile = remotefile;
-       if (!remotefile)
-               remotefile = localfile;
+       if (!local_file)
+               local_file = remote_file;
+       if (!remote_file)
+               remote_file = local_file;
        /* Error if filename or host is not known */
-       if (!remotefile || !argv[0])
+       if (!remote_file || !argv[0])
                bb_show_usage();
 
-       fd = CMD_GET(cmd) ? STDOUT_FILENO : STDIN_FILENO;
-       if (!LONE_DASH(localfile)) {
-               fd = xopen(localfile, flags);
+       local_fd = CMD_GET(cmd) ? STDOUT_FILENO : STDIN_FILENO;
+       if (!LONE_DASH(local_file)) {
+               local_fd = xopen(local_file, flags);
        }
 
        port = bb_lookup_port(argv[1], "udp", 69);
        peer_lsa = xhost2sockaddr(argv[0], port);
 
 #if ENABLE_DEBUG_TFTP
-       fprintf(stderr, "using server '%s', remotefile '%s', localfile '%s'\n",
+       fprintf(stderr, "using server '%s', remote_file '%s', local_file '%s'\n",
                        xmalloc_sockaddr2dotted(&peer_lsa->u.sa),
-                       remotefile, localfile);
+                       remote_file, local_file);
 #endif
 
-       result = tftp( USE_GETPUT(cmd,) peer_lsa, remotefile, fd, port, blocksize);
+       result = tftp_protocol(
+                       USE_GETPUT(cmd,)
+                       NULL /* our_lsa*/,
+                       peer_lsa,
+                       remote_file, local_fd, blocksize);
 
        if (ENABLE_FEATURE_CLEAN_UP)
-               close(fd);
-       if (result != EXIT_SUCCESS && !LONE_DASH(localfile) && CMD_GET(cmd)) {
-               unlink(localfile);
+               close(local_fd);
+       if (result != EXIT_SUCCESS && !LONE_DASH(local_file) && CMD_GET(cmd)) {
+               unlink(local_file);
        }
        return result;
 }
 
+#endif /* ENABLE_TFTP */
+
+#if ENABLE_TFTPD
+
+/* TODO: libbb candidate? */
+static len_and_sockaddr *get_sock_lsa(int s)
+{
+       len_and_sockaddr *lsa;
+       socklen_t len = 0;
+
+       if (getsockname(s, NULL, &len) != 0)
+               return NULL;
+       lsa = xzalloc(LSA_LEN_SIZE + len);
+       lsa->len = len;
+       getsockname(s, &lsa->u.sa, &lsa->len);
+       return lsa;
+}
+
+int tftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tftpd_main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
+{
+       struct stat statbuf;
+       char block_buf[TFTP_BLOCKSIZE_DEFAULT];
+       len_and_sockaddr *our_lsa;
+       len_and_sockaddr *peer_lsa;
+       char *filename, *mode, *opt_str;
+       int result, opcode, cmd, req_modebits, open_mode, local_fd, blksize;
+
+       our_lsa = get_sock_lsa(STDIN_FILENO);
+       if (!our_lsa)
+               bb_perror_msg_and_die("stdin is not a socket");
+       peer_lsa = xzalloc(LSA_LEN_SIZE + our_lsa->len);
+       peer_lsa->len = our_lsa->len;
+
+       if (argv[1])
+               xchdir(argv[1]);
+
+       result = recv_from_to(STDIN_FILENO, block_buf, sizeof(block_buf),
+                       0 /* flags */,
+                       &peer_lsa->u.sa, &our_lsa->u.sa, our_lsa->len);
+
+       opcode = ntohs(*(uint16_t*)block_buf);
+       if (result < 4 || result >= sizeof(block_buf)
+        || block_buf[result-1] != '\0'
+        || (opcode != TFTP_RRQ && opcode != TFTP_WRQ)
+       ) {
+               bb_error_msg_and_die("malformed packet");
+       }
+       filename = block_buf + 2;
+       if (filename[0] == '.' || strstr(filename, "/.")) {
+               bb_error_msg_and_die("dot in filename");
+       }
+       mode = filename + strlen(filename) + 1;
+       if (mode >= block_buf + sizeof(block_buf)
+        || strcmp(mode, "octet") != 0
+       ) {
+               bb_error_msg_and_die("malformed packet");
+       }
+       blksize = TFTP_BLOCKSIZE_DEFAULT;
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+       opt_str = mode + 6;
+       if (opt_str < block_buf + sizeof(block_buf)) {
+               char *res = tftp_option_get(opt_str, block_buf + sizeof(block_buf) - opt_str, "blksize");
+               if (res) {
+                       int sz = xatoi_u(res);
+                       if (tftp_blocksize_check(sz, 0))
+                               blksize = sz;
+               }
+       }
+#endif
+       xstat(filename, &statbuf);
+       /* if opcode == TFTP_WRQ: */
+       cmd = 1; /* CMD_GET: we will receive file's data */
+       req_modebits = 0222; /* writable by anyone */
+       open_mode = O_WRONLY | O_TRUNC;
+       if (opcode == TFTP_RRQ) {
+               cmd = 2; /* CMD_PUT */
+               req_modebits = 0444; /* readable by anyone */
+               open_mode = O_RDONLY;
+       }
+       if (!S_ISREG(statbuf.st_mode)
+        || (statbuf.st_mode & req_modebits) != req_modebits
+       ) {
+               bb_error_msg_and_die("access to '%s' is denied", filename);
+       }
+       local_fd = xopen(filename, open_mode);
+
+       close(STDIN_FILENO); /* close old, possibly wildcard socket */
+       /* tftp_protocol() will create new one, bound to particular local IP */
+       result = tftp_protocol(
+               USE_GETPUT(cmd,)
+               our_lsa, peer_lsa,
+               USE_TFTP(NULL /*remote_file*/,)
+               local_fd,
+               blksize
+       );
+
+       return result;
+}
+
+#endif /* ENABLE_TFTPD */
+
 #endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */
index e5f1864..e0bf71b 100644 (file)
@@ -1,4 +1,4 @@
-menu "print support"
+menu "Print Utilities"
 
 config LPD
        bool "lpd"