From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.33) id 1BwQ5L-00051T-Kg for qemu-devel@nongnu.org; Sun, 15 Aug 2004 14:50:27 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.33) id 1BwQ5J-00050D-Lm for qemu-devel@nongnu.org; Sun, 15 Aug 2004 14:50:26 -0400 Received: from [199.232.76.173] (helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.33) id 1BwQ5J-0004zj-H8 for qemu-devel@nongnu.org; Sun, 15 Aug 2004 14:50:25 -0400 Received: from [213.80.72.10] (helo=kubrik.opensource.se) by monty-python.gnu.org with esmtp (Exim 4.34) id 1BwQ0x-0003kp-Di for qemu-devel@nongnu.org; Sun, 15 Aug 2004 14:45:56 -0400 Received: from 192.168.1.16 (unknown [213.80.72.14]) by kubrik.opensource.se (Postfix) with ESMTP id 2AD583752C for ; Sun, 15 Aug 2004 20:34:05 +0200 (CEST) From: Magnus Damm Content-Type: multipart/mixed; boundary="=-at1S8EL7oc6ErPCqgWVP" Message-Id: <1092595952.19341.28.camel@kubu.opensource.se> Mime-Version: 1.0 Date: Sun, 15 Aug 2004 20:52:32 +0200 Subject: [Qemu-devel] [PATCH] tftp server, second try Reply-To: qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org --=-at1S8EL7oc6ErPCqgWVP Content-Type: text/plain Content-Transfer-Encoding: 7bit Hi again, Attached is a patch that adds a tftp server to the slirp networking code, ie the patch makes it possible for the guest to read files from the host if the user network implementation enabled with -user-net. This is the second try, the first patch contained a fatal bug that did not show on my PowerPC box (misplaced parenthesis, aargh) - I recently tried the code on a x86 box and realized that all read requests were treated as access violations... Sorry about that. Please try this patch instead, it has been tested on CVS today and 0.60.0 under Linux/PPC and 0.60.0 under Linux/x86. This patch applies both to CVS and 0.60.0. The option -tftp is used to tell the tftp server which files that should be exported as read only data. Default is no exports, "-tftp /" will make all files on your host available to your guest. The tftp server only accepts absolute filenames, no relative paths are allowed. If someone wants to test the code I would be happy to hand over a precompiled x86 image containing a tftp client useful for testing. Thanks. / magnus --=-at1S8EL7oc6ErPCqgWVP Content-Disposition: attachment; filename=qemu-0.6.0-tftp_20040815.patch Content-Type: text/x-patch; name=qemu-0.6.0-tftp_20040815.patch; charset=UTF-8 Content-Transfer-Encoding: 7bit diff -urN qemu-0.6.0/Makefile.target qemu-0.6.0-tftp/Makefile.target --- qemu-0.6.0/Makefile.target 2004-07-10 20:20:09.000000000 +0200 +++ qemu-0.6.0-tftp/Makefile.target 2004-08-09 20:09:08.000000000 +0200 @@ -260,7 +260,7 @@ DEFINES+=-I$(SRC_PATH)/slirp SLIRP_OBJS=cksum.o if.o ip_icmp.o ip_input.o ip_output.o \ slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o tcp_output.o \ -tcp_subr.o tcp_timer.o udp.o bootp.o debug.o +tcp_subr.o tcp_timer.o udp.o bootp.o debug.o tftp.o VL_OBJS+=$(addprefix slirp/, $(SLIRP_OBJS)) endif diff -urN qemu-0.6.0/slirp/slirp.h qemu-0.6.0-tftp/slirp/slirp.h --- qemu-0.6.0/slirp/slirp.h 2004-07-10 20:20:09.000000000 +0200 +++ qemu-0.6.0-tftp/slirp/slirp.h 2004-08-09 20:09:08.000000000 +0200 @@ -195,6 +195,7 @@ #endif #include "bootp.h" +#include "tftp.h" #include "libslirp.h" extern struct ttys *ttys_unit[MAX_INTERFACES]; diff -urN qemu-0.6.0/slirp/tftp.c qemu-0.6.0-tftp/slirp/tftp.c --- qemu-0.6.0/slirp/tftp.c 1970-01-01 01:00:00.000000000 +0100 +++ qemu-0.6.0-tftp/slirp/tftp.c 2004-08-09 20:50:28.000000000 +0200 @@ -0,0 +1,343 @@ +/* + * tftp.c - a simple, read-only tftp server for qemu + * + * Copyright (c) 2004 Magnus Damm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +struct tftp_session { + int in_use; + unsigned char filename[TFTP_FILENAME_MAX]; + + struct in_addr client_ip; + u_int16_t client_port; + + struct timeval timestamp; +}; + +struct tftp_session tftp_sessions[TFTP_SESSIONS_MAX]; + +char *tftp_prefix; + +static void tftp_session_update(struct tftp_session *spt) +{ + gettimeofday(&spt->timestamp, 0); + spt->in_use = 1; +} + +static void tftp_session_terminate(struct tftp_session *spt) +{ + spt->in_use = 0; +} + +static int tftp_session_allocate(struct tftp_t *tp) +{ + struct tftp_session *spt; + struct timeval tv; + int k; + + gettimeofday(&tv, 0); + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &tftp_sessions[k]; + + if (!spt->in_use) { + goto found; + } + + /* sessions time out after 5 inactive seconds */ + + if (tv.tv_sec > (spt->timestamp.tv_sec + 5)) { + goto found; + } + } + + return -1; + + found: + memset(spt, 0, sizeof(*spt)); + memcpy(&spt->client_ip, &tp->ip.ip_src, sizeof(spt->client_ip)); + spt->client_port = tp->udp.uh_sport; + + tftp_session_update(spt); + + return k; +} + +static int tftp_session_find(struct tftp_t *tp) +{ + struct tftp_session *spt; + int k; + + for (k = 0; k < TFTP_SESSIONS_MAX; k++) { + spt = &tftp_sessions[k]; + + if (spt->in_use) { + if (!memcmp(&spt->client_ip, &tp->ip.ip_src, sizeof(spt->client_ip))) { + if (spt->client_port == tp->udp.uh_sport) { + return k; + } + } + } + } + + return -1; +} + +static int tftp_read_data(struct tftp_session *spt, u_int16_t block_nr, + u_int8_t *buf, int len) +{ + int fd; + int bytes_read = 0; + + fd = open(spt->filename, O_RDONLY); + + if (fd < 0) { + return -1; + } + + if (len) { + lseek(fd, block_nr * 512, SEEK_SET); + + bytes_read = read(fd, buf, len); + } + + close(fd); + + return bytes_read; +} + +static int tftp_send_error(struct tftp_session *spt, + u_int16_t errorcode, + struct tftp_t *recv_tp) +{ + struct sockaddr_in saddr, daddr; + struct mbuf *m; + struct tftp_t *tp; + int nobytes; + + m = m_get(); + + if (!m) { + return -1; + } + + memset(m->m_data, 0, m->m_size); + + m->m_data += if_maxlinkhdr; + tp = (void *)m->m_data; + m->m_data += sizeof(struct udpiphdr); + + tp->tp_op = htons(TFTP_ERROR); + tp->x.tp_data.tp_block_nr = htons(errorcode); + + saddr.sin_addr = recv_tp->ip.ip_dst; + saddr.sin_port = recv_tp->udp.uh_dport; + + daddr.sin_addr = spt->client_ip; + daddr.sin_port = spt->client_port; + + nobytes = 2; + + m->m_len = sizeof(struct tftp_t) - (512 - 2) - + sizeof(struct ip) - sizeof(struct udphdr); + + udp_output2(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); + + tftp_session_terminate(spt); + + return 0; +} + +static int tftp_send_data(struct tftp_session *spt, + u_int16_t block_nr, + struct tftp_t *recv_tp) +{ + struct sockaddr_in saddr, daddr; + struct mbuf *m; + struct tftp_t *tp; + int nobytes; + + if (block_nr < 1) { + return -1; + } + + m = m_get(); + + if (!m) { + return -1; + } + + memset(m->m_data, 0, m->m_size); + + m->m_data += if_maxlinkhdr; + tp = (void *)m->m_data; + m->m_data += sizeof(struct udpiphdr); + + tp->tp_op = htons(TFTP_DATA); + tp->x.tp_data.tp_block_nr = htons(block_nr); + + saddr.sin_addr = recv_tp->ip.ip_dst; + saddr.sin_port = recv_tp->udp.uh_dport; + + daddr.sin_addr = spt->client_ip; + daddr.sin_port = spt->client_port; + + nobytes = tftp_read_data(spt, block_nr - 1, tp->x.tp_data.tp_buf, 512); + + if (nobytes < 0) { + m_free(m); + + /* send "file not found" error back */ + + tftp_send_error(spt, 1, tp); + + return -1; + } + + m->m_len = sizeof(struct tftp_t) - (512 - nobytes) - + sizeof(struct ip) - sizeof(struct udphdr); + + udp_output2(NULL, m, &saddr, &daddr, IPTOS_LOWDELAY); + + if (nobytes == 512) { + tftp_session_update(spt); + } + else { + tftp_session_terminate(spt); + } + + return 0; +} + +static void tftp_handle_rrq(struct tftp_t *tp, int pktlen) +{ + struct tftp_session *spt; + int s, k, n; + u_int8_t *src, *dst; + + s = tftp_session_allocate(tp); + + if (s < 0) { + return; + } + + spt = &tftp_sessions[s]; + + src = tp->x.tp_buf; + dst = spt->filename; + n = pktlen - ((void *)&tp->x.tp_buf[0] - (void *)tp); + + /* get name */ + + for (k = 0; k < n; k++) { + if (k < TFTP_FILENAME_MAX) { + dst[k] = src[k]; + } + else { + return; + } + + if (src[k] == '\0') { + break; + } + } + + if (k >= n) { + return; + } + + k++; + + /* check mode */ + + if ((n - k) < 6) { + return; + } + + if (memcmp(&src[k], "octet\0", 6) != 0) { + return; + } + + /* do sanity checks on the filename */ + + if ((spt->filename[0] != '/') + || (spt->filename[strlen(spt->filename) - 1] == '/') + || strstr(spt->filename, "/../")) { + n = 2; /* access violation */ + goto error_n; + } + + /* only allow exported prefixes */ + + if (!tftp_prefix + || (strncmp(spt->filename, tftp_prefix, strlen(tftp_prefix)) != 0)) { + n = 2; /* access violation */ + goto error_n; + } + + /* check if the file exists */ + + if (tftp_read_data(spt, 0, spt->filename, 0) < 0) { + n = 1; /* file not found */ + goto error_n; + } + + tftp_send_data(spt, 1, tp); + + return; + + error_n: + tftp_send_error(&tftp_sessions[s], n, tp); +} + +static void tftp_handle_ack(struct tftp_t *tp, int pktlen) +{ + int s; + + s = tftp_session_find(tp); + + if (s < 0) { + return; + } + + if (tftp_send_data(&tftp_sessions[s], + ntohs(tp->x.tp_data.tp_block_nr) + 1, + tp) < 0) { + return; + } +} + +void tftp_input(struct mbuf *m) +{ + struct tftp_t *tp = (struct tftp_t *)m->m_data; + + switch(ntohs(tp->tp_op)) { + case TFTP_RRQ: + tftp_handle_rrq(tp, m->m_len); + break; + + case TFTP_ACK: + tftp_handle_ack(tp, m->m_len); + break; + } +} diff -urN qemu-0.6.0/slirp/tftp.h qemu-0.6.0-tftp/slirp/tftp.h --- qemu-0.6.0/slirp/tftp.h 1970-01-01 01:00:00.000000000 +0100 +++ qemu-0.6.0-tftp/slirp/tftp.h 2004-08-09 20:09:08.000000000 +0200 @@ -0,0 +1,30 @@ +/* tftp defines */ + +#define TFTP_SESSIONS_MAX 3 + +#define TFTP_SERVER 69 + +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 + +#define TFTP_FILENAME_MAX 512 + +struct tftp_t { + struct ip ip; + struct udphdr udp; + u_int16_t tp_op; + union { + struct { + u_int16_t tp_block_nr; + u_int8_t tp_buf[512]; + } tp_data; + u_int8_t tp_buf[512 + 2]; + } x; +}; + +extern char *tftp_prefix; + +void tftp_input(struct mbuf *m); diff -urN qemu-0.6.0/slirp/udp.c qemu-0.6.0-tftp/slirp/udp.c --- qemu-0.6.0/slirp/udp.c 2004-07-10 20:20:09.000000000 +0200 +++ qemu-0.6.0-tftp/slirp/udp.c 2004-08-09 20:09:08.000000000 +0200 @@ -153,6 +153,14 @@ goto bad; } + /* + * handle TFTP + */ + if (ntohs(uh->uh_dport) == TFTP_SERVER) { + tftp_input(m); + goto bad; + } + /* * Locate pcb for datagram. */ diff -urN qemu-0.6.0/vl.c qemu-0.6.0-tftp/vl.c --- qemu-0.6.0/vl.c 2004-07-10 20:20:09.000000000 +0200 +++ qemu-0.6.0-tftp/vl.c 2004-08-09 20:09:08.000000000 +0200 @@ -2097,6 +2097,7 @@ "-tun-fd fd use this fd as already opened tap/tun interface\n" #ifdef CONFIG_SLIRP "-user-net use user mode network stack [default if no tap/tun script]\n" + "-tftp prefix allow tftp access to files starting with prefix [only with -user-net enabled]\n" #endif "-dummy-net use dummy network stack\n" "\n" @@ -2164,6 +2165,7 @@ QEMU_OPTION_n, QEMU_OPTION_tun_fd, QEMU_OPTION_user_net, + QEMU_OPTION_tftp, QEMU_OPTION_dummy_net, QEMU_OPTION_kernel, @@ -2214,6 +2216,7 @@ { "tun-fd", HAS_ARG, QEMU_OPTION_tun_fd }, #ifdef CONFIG_SLIRP { "user-net", 0, QEMU_OPTION_user_net }, + { "tftp", HAS_ARG, QEMU_OPTION_tftp }, #endif { "dummy-net", 0, QEMU_OPTION_dummy_net }, @@ -2454,9 +2457,17 @@ } } break; +#ifdef CONFIG_SLIRP + case QEMU_OPTION_tftp: + { + extern const char *tftp_prefix; + tftp_prefix = optarg; + } + break; case QEMU_OPTION_user_net: net_if_type = NET_IF_USER; break; +#endif case QEMU_OPTION_dummy_net: net_if_type = NET_IF_DUMMY; break; --=-at1S8EL7oc6ErPCqgWVP--