1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#include <fcntl.h>
28#include <arpa/tftp.h>
29#include "snoop.h"
30
31struct tftp_options {
32	int blksize;
33	int tsize;
34};
35
36extern char *dlc_header;
37static char *tftperror(unsigned short);
38static char *show_type(int);
39static char *tftp_parse_oack(char *, size_t, struct tftp_options *);
40
41int
42interpret_tftp(int flags, void *data, int fraglen)
43{
44	char *name, *mode;
45	extern int src_port, dst_port;
46	int blocksize = fraglen - 4;
47	struct tftp_options opts;
48	struct ttable *tt;
49	struct tftphdr *tftp = data;
50
51	opts.tsize = 0;
52	opts.blksize = 512;
53
54	switch (ntohs(tftp->th_opcode)) {
55	case RRQ:
56	case WRQ:
57		add_transient(src_port, interpret_tftp);
58		break;
59	case ERROR:
60		del_transient(src_port);
61		break;
62	case OACK:
63		tt = is_transient(dst_port);
64		if (tt != NULL)
65			tt->blksize = opts.blksize;
66		break;
67	case DATA:
68		tt = is_transient(dst_port);
69		if (tt != NULL)
70			opts.blksize = tt->blksize;
71		break;
72	default:
73		break;
74	}
75
76	if (flags & F_SUM) {
77		switch (ntohs(tftp->th_opcode)) {
78		case RRQ:
79			name = (char *)&tftp->th_stuff;
80			mode = name + (strlen(name) + 1);
81			(void) sprintf(get_sum_line(),
82			    "TFTP Read \"%s\" (%s)", name, mode);
83			break;
84		case WRQ:
85			name = (char *)&tftp->th_stuff;
86			mode = name + (strlen(name) + 1);
87			(void) sprintf(get_sum_line(),
88			    "TFTP Write \"%s\" (%s)", name, mode);
89			break;
90		case DATA:
91			(void) sprintf(get_sum_line(),
92			    "TFTP Data block %u (%d bytes)%s",
93			    ntohs(tftp->th_block), blocksize,
94			    blocksize < opts.blksize ? " (last block)":"");
95			break;
96		case ACK:
97			(void) sprintf(get_sum_line(), "TFTP Ack block %d",
98			    ntohs(tftp->th_block));
99			break;
100		case ERROR:
101			(void) sprintf(get_sum_line(), "TFTP Error: %s",
102			    tftperror(ntohs(tftp->th_code)));
103			break;
104		case OACK:
105			(void) sprintf(get_sum_line(), "TFTP OACK: %s",
106			    tftp_parse_oack((char *)&tftp->th_stuff,
107			    fraglen - sizeof (tftp->th_opcode), &opts));
108			if (tt != NULL)
109				tt->blksize = opts.blksize;
110			break;
111		}
112	}
113
114	if (flags & F_DTAIL) {
115		show_header("TFTP:  ", "Trivial File Transfer Protocol",
116		    fraglen);
117		show_space();
118		(void) sprintf(get_line((char *)(uintptr_t)tftp->th_opcode -
119		    dlc_header, 2), "Opcode = %d (%s)", ntohs(tftp->th_opcode),
120		    show_type(ntohs(tftp->th_opcode)));
121
122		switch (ntohs(tftp->th_opcode)) {
123		case RRQ:
124		case WRQ:
125			name = (char *)&tftp->th_stuff;
126			mode = name + (strlen(name) + 1);
127			(void) sprintf(
128			    get_line(name - dlc_header, strlen(name) + 1),
129			    "File name = \"%s\"", name);
130			(void) sprintf(
131			    get_line(mode - dlc_header, strlen(mode) + 1),
132			    "Transfer mode = %s", mode);
133			break;
134
135		case DATA:
136			(void) sprintf(get_line(
137			    (char *)(uintptr_t)tftp->th_block - dlc_header, 2),
138			    "Data block = %d%s", ntohs(tftp->th_block),
139			    blocksize < opts.blksize ? " (last block)" : "");
140			(void) sprintf(get_line(
141			    (char *)(uintptr_t)tftp->th_data - dlc_header,
142			    blocksize), "[ %d bytes of data ]", blocksize);
143			break;
144
145		case ACK:
146			(void) sprintf(get_line(
147			    (char *)(uintptr_t)tftp->th_block - dlc_header, 2),
148			    "Acknowledge block = %d", ntohs(tftp->th_block));
149			break;
150
151		case ERROR:
152			(void) sprintf(get_line(
153			    (char *)(uintptr_t)tftp->th_code - dlc_header, 2),
154			    "Error = %d (%s)", ntohs(tftp->th_code),
155			    tftperror(ntohs(tftp->th_code)));
156			(void) sprintf(get_line(
157			    (char *)(uintptr_t)tftp->th_data -
158			    dlc_header, strlen(tftp->th_data) + 1),
159			    "Error string = \"%s\"", tftp->th_data);
160			break;
161		case OACK:
162			(void) sprintf(get_line(
163			    (char *)(uintptr_t)tftp->th_code - dlc_header, 2),
164			    "TFTP OACK: %s",
165			    tftp_parse_oack((char *)&tftp->th_stuff,
166			    fraglen - sizeof (tftp->th_opcode), &opts));
167			if (tt != NULL)
168				tt->blksize = opts.blksize;
169			break;
170		}
171	}
172
173	return (fraglen);
174}
175
176static char *
177show_type(int t)
178{
179	switch (t) {
180	case RRQ:	return ("read request");
181	case WRQ:	return ("write request");
182	case DATA:	return ("data packet");
183	case ACK:	return ("acknowledgement");
184	case ERROR:	return ("error");
185	case OACK:	return ("option acknowledgement");
186	}
187	return ("?");
188}
189
190static char *
191tftperror(unsigned short code)
192{
193	static char buf[128];
194
195	switch (code) {
196	case EUNDEF:	return ("not defined");
197	case ENOTFOUND:	return ("file not found");
198	case EACCESS:	return ("access violation");
199	case ENOSPACE:	return ("disk full or allocation exceeded");
200	case EBADOP:	return ("illegal TFTP operation");
201	case EBADID:	return ("unknown transfer ID");
202	case EEXISTS:	return ("file already exists");
203	case ENOUSER:	return ("no such user");
204	}
205	(void) sprintf(buf, "%d", code);
206
207	return (buf);
208}
209
210static char *
211tftp_parse_oack(char *buf, size_t size, struct tftp_options *opts)
212{
213	static char tftp_options[128];
214	int i, idx;
215
216	tftp_options[0] = '\0';
217	idx = 0;
218
219	while (size > 0 && idx < sizeof (tftp_options)) {
220		if (idx > 0) {
221			tftp_options[idx++] = ' ';
222			tftp_options[idx] = '\0';
223		}
224
225		/* get name */
226		if (idx + strnlen(buf, size) + 1 > sizeof (tftp_options))
227			break;
228		for (i = 0; i < size; i++) {
229			tftp_options[idx] = buf[i];
230			if (tftp_options[idx] == '\0') {
231				i++;
232				break;
233			}
234			idx++;
235		}
236		size -= i;
237		/*
238		 * RFC 2348 requires this case in-sensitive.
239		 */
240		if (strcasecmp(buf, "blksize") == 0) {
241			int blksize = strtol(buf + i, NULL, 0);
242
243			if (blksize >= 8)
244				opts->blksize = blksize;
245		}
246		buf += i;
247
248		/* can we store separator? */
249		if (idx + 3 > sizeof (tftp_options))
250			break;
251		strcat(tftp_options, ": ");
252		idx += 2;
253
254		/* get value */
255		if (idx + strnlen(buf, size) + 1 > sizeof (tftp_options))
256			break;
257
258		for (i = 0; i < size; i++) {
259			tftp_options[idx] = buf[i];
260			if (tftp_options[idx] == '\0') {
261				i++;
262				break;
263			}
264			idx++;
265		}
266		size -= i;
267		buf += i;
268	}
269	return (tftp_options);
270}
271