1/* MMS extension for IP connection tracking
2 * (C) 2002 by Filip Sneppe <filip.sneppe@cronos.be>
3 * based on ip_conntrack_ftp.c and ip_conntrack_irc.c
4 *
5 * ip_conntrack_mms.c v0.3 2002-09-22
6 *
7 *      This program is free software; you can redistribute it and/or
8 *      modify it under the terms of the GNU General Public License
9 *      as published by the Free Software Foundation; either version
10 *      2 of the License, or (at your option) any later version.
11 *
12 *      Module load syntax:
13 *      insmod ip_conntrack_mms.o ports=port1,port2,...port<MAX_PORTS>
14 *
15 *      Please give the ports of all MMS servers You wish to connect to.
16 *      If you don't specify ports, the default will be TCP port 1755.
17 *
18 *      More info on MMS protocol, firewalls and NAT:
19 *      http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwmt/html/MMSFirewall.asp
20 *      http://www.microsoft.com/windows/windowsmedia/serve/firewall.asp
21 *
22 *      The SDP project people are reverse-engineering MMS:
23 *      http://get.to/sdp
24 */
25
26#include <linux/config.h>
27#include <linux/module.h>
28#include <linux/netfilter.h>
29#include <linux/ip.h>
30#include <linux/ctype.h>
31#include <net/checksum.h>
32#include <net/tcp.h>
33
34#include <linux/netfilter_ipv4/lockhelp.h>
35#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
36#include <linux/netfilter_ipv4/ip_conntrack_mms.h>
37
38DECLARE_LOCK(ip_mms_lock);
39struct module *ip_conntrack_mms = THIS_MODULE;
40
41#define MAX_PORTS 8
42static int ports[MAX_PORTS];
43static int ports_c;
44#ifdef MODULE_PARM
45MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i");
46#endif
47
48#define DEBUGP(format, args...)
49
50#ifdef CONFIG_IP_NF_NAT_NEEDED
51EXPORT_SYMBOL(ip_mms_lock);
52#endif
53
54MODULE_AUTHOR("Filip Sneppe <filip.sneppe@cronos.be>");
55MODULE_DESCRIPTION("Microsoft Windows Media Services (MMS) connection tracking module");
56MODULE_LICENSE("GPL");
57
58/* #define isdigit(c) (c >= '0' && c <= '9') */
59
60/* copied from drivers/usb/serial/io_edgeport.c - not perfect but will do the trick */
61static void unicode_to_ascii (char *string, short *unicode, int unicode_size)
62{
63	int i;
64	for (i = 0; i < unicode_size; ++i) {
65		string[i] = (char)(unicode[i]);
66	}
67	string[unicode_size] = 0x00;
68}
69
70__inline static int atoi(char *s)
71{
72	int i=0;
73	while (isdigit(*s)) {
74		i = i*10 + *(s++) - '0';
75	}
76	return i;
77}
78
79/* convert ip address string like "192.168.0.10" to unsigned int */
80__inline static u_int32_t asciiiptoi(char *s)
81{
82	unsigned int i, j, k;
83
84	for(i=k=0; k<3; ++k, ++s, i<<=8) {
85		i+=atoi(s);
86		for(j=0; (*(++s) != '.') && (j<3); ++j)
87			;
88	}
89	i+=atoi(s);
90	return ntohl(i);
91}
92
93int parse_mms(const char *data,
94	      const unsigned int datalen,
95	      u_int32_t *mms_ip,
96	      u_int16_t *mms_proto,
97	      u_int16_t *mms_port,
98	      char **mms_string_b,
99	      char **mms_string_e,
100	      char **mms_padding_e)
101{
102	int unicode_size, i;
103	char tempstring[28];       /* "\\255.255.255.255\UDP\65535" */
104	char getlengthstring[28];
105
106	for(unicode_size=0;
107	    (char) *(data+(MMS_SRV_UNICODE_STRING_OFFSET+unicode_size*2)) != (char)0;
108	    unicode_size++)
109		if ((unicode_size == 28) || (MMS_SRV_UNICODE_STRING_OFFSET+unicode_size*2 >= datalen))
110			return -1; /* out of bounds - incomplete packet */
111
112	unicode_to_ascii(tempstring, (short *)(data+MMS_SRV_UNICODE_STRING_OFFSET), unicode_size);
113	DEBUGP("ip_conntrack_mms: offset 60: %s\n", (const char *)(tempstring));
114
115	/* IP address ? */
116	*mms_ip = asciiiptoi(tempstring+2);
117
118	i=sprintf(getlengthstring, "%u.%u.%u.%u", HIPQUAD(*mms_ip));
119
120	/* protocol ? */
121	if(strncmp(tempstring+3+i, "TCP", 3)==0)
122		*mms_proto = IPPROTO_TCP;
123	else if(strncmp(tempstring+3+i, "UDP", 3)==0)
124		*mms_proto = IPPROTO_UDP;
125
126	/* port ? */
127	*mms_port = atoi(tempstring+7+i);
128
129	/* we store a pointer to the beginning of the "\\a.b.c.d\proto\port"
130	   unicode string, one to the end of the string, and one to the end
131	   of the packet, since we must keep track of the number of bytes
132	   between end of the unicode string and the end of packet (padding) */
133	*mms_string_b  = (char *)(data + MMS_SRV_UNICODE_STRING_OFFSET);
134	*mms_string_e  = (char *)(data + MMS_SRV_UNICODE_STRING_OFFSET + unicode_size * 2);
135	*mms_padding_e = (char *)(data + datalen); /* looks funny, doesn't it */
136	return 0;
137}
138
139
140static int help(const struct iphdr *iph, size_t len,
141		struct ip_conntrack *ct,
142		enum ip_conntrack_info ctinfo)
143{
144	/* tcplen not negative guaranteed by ip_conntrack_tcp.c */
145	struct tcphdr *tcph = (void *)iph + iph->ihl * 4;
146	const char *data = (const char *)tcph + tcph->doff * 4;
147	unsigned int tcplen = len - iph->ihl * 4;
148	unsigned int datalen = tcplen - tcph->doff * 4;
149	int dir = CTINFO2DIR(ctinfo);
150	struct ip_conntrack_expect expect, *exp = &expect;
151	struct ip_ct_mms_expect *exp_mms_info = &exp->help.exp_mms_info;
152
153	u_int32_t mms_ip;
154	u_int16_t mms_proto;
155	char mms_proto_string[8];
156	u_int16_t mms_port;
157	char *mms_string_b, *mms_string_e, *mms_padding_e;
158
159	/* Until there's been traffic both ways, don't look in packets. */
160	if (ctinfo != IP_CT_ESTABLISHED
161	    && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
162		DEBUGP("ip_conntrack_mms: Conntrackinfo = %u\n", ctinfo);
163		return NF_ACCEPT;
164	}
165
166	/* Not whole TCP header? */
167	if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff*4) {
168		DEBUGP("ip_conntrack_mms: tcplen = %u\n", (unsigned)tcplen);
169		return NF_ACCEPT;
170	}
171
172	/* Checksum invalid?  Ignore. */
173	if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
174	    csum_partial((char *)tcph, tcplen, 0))) {
175		DEBUGP("mms_help: bad csum: %p %u %u.%u.%u.%u %u.%u.%u.%u\n",
176		       tcph, tcplen, NIPQUAD(iph->saddr),
177		       NIPQUAD(iph->daddr));
178		return NF_ACCEPT;
179	}
180
181	/* Only look at packets with 0x00030002/196610 on bytes 36->39 of TCP payload */
182	if( (MMS_SRV_MSG_OFFSET < datalen) &&
183	    ((*(u32 *)(data+MMS_SRV_MSG_OFFSET)) == MMS_SRV_MSG_ID)) {
184		DEBUGP("ip_conntrack_mms: offset 37: %u %u %u %u, datalen:%u\n",
185		       (u8)*(data+36), (u8)*(data+37),
186		       (u8)*(data+38), (u8)*(data+39),
187		       datalen);
188		if(parse_mms(data, datalen, &mms_ip, &mms_proto, &mms_port,
189		             &mms_string_b, &mms_string_e, &mms_padding_e))
190			if(net_ratelimit())
191				printk(KERN_WARNING
192				       "ip_conntrack_mms: Unable to parse data payload\n");
193
194		memset(&expect, 0, sizeof(expect));
195
196		sprintf(mms_proto_string, "(%u)", mms_proto);
197		DEBUGP("ip_conntrack_mms: adding %s expectation %u.%u.%u.%u -> %u.%u.%u.%u:%u\n",
198		       mms_proto == IPPROTO_TCP ? "TCP"
199		       : mms_proto == IPPROTO_UDP ? "UDP":mms_proto_string,
200		       NIPQUAD(ct->tuplehash[!dir].tuple.src.ip),
201		       NIPQUAD(mms_ip),
202		       mms_port);
203
204		/* it's possible that the client will just ask the server to tunnel
205		   the stream over the same TCP session (from port 1755): there's
206		   shouldn't be a need to add an expectation in that case, but it
207		   makes NAT packet mangling so much easier */
208		LOCK_BH(&ip_mms_lock);
209
210		DEBUGP("ip_conntrack_mms: tcph->seq = %u\n", tcph->seq);
211
212		exp->seq = ntohl(tcph->seq) + (mms_string_b - data);
213		exp_mms_info->len     = (mms_string_e  - mms_string_b);
214		exp_mms_info->padding = (mms_padding_e - mms_string_e);
215		exp_mms_info->port    = mms_port;
216
217		DEBUGP("ip_conntrack_mms: wrote info seq=%u (ofs=%u), len=%d, padding=%u\n",
218		       exp->seq, (mms_string_e - data), exp_mms_info->len, exp_mms_info->padding);
219
220		exp->tuple = ((struct ip_conntrack_tuple)
221		              { { ct->tuplehash[!dir].tuple.src.ip, { 0 } },
222		              { mms_ip,
223		                { (__u16) ntohs(mms_port) },
224		                mms_proto } }
225		             );
226		exp->mask  = ((struct ip_conntrack_tuple)
227		             { { 0xFFFFFFFF, { 0 } },
228		               { 0xFFFFFFFF, { 0xFFFF }, 0xFFFF }});
229		exp->expectfn = NULL;
230		ip_conntrack_expect_related(ct, &expect);
231		UNLOCK_BH(&ip_mms_lock);
232	}
233
234	return NF_ACCEPT;
235}
236
237static struct ip_conntrack_helper mms[MAX_PORTS];
238static char mms_names[MAX_PORTS][10];
239
240/* Not __exit: called from init() */
241static void fini(void)
242{
243	int i;
244	for (i = 0; (i < MAX_PORTS) && ports[i]; i++) {
245		DEBUGP("ip_conntrack_mms: unregistering helper for port %d\n",
246				ports[i]);
247		ip_conntrack_helper_unregister(&mms[i]);
248	}
249}
250
251static int __init init(void)
252{
253	int i, ret;
254	char *tmpname;
255
256	if (ports[0] == 0)
257		ports[0] = MMS_PORT;
258
259	for (i = 0; (i < MAX_PORTS) && ports[i]; i++) {
260		memset(&mms[i], 0, sizeof(struct ip_conntrack_helper));
261		mms[i].tuple.src.u.tcp.port = htons(ports[i]);
262		mms[i].tuple.dst.protonum = IPPROTO_TCP;
263		mms[i].mask.src.u.tcp.port = 0xFFFF;
264		mms[i].mask.dst.protonum = 0xFFFF;
265		mms[i].max_expected = 1;
266		mms[i].timeout = 0;
267		mms[i].flags = IP_CT_HELPER_F_REUSE_EXPECT;
268		mms[i].me = THIS_MODULE;
269		mms[i].help = help;
270
271		tmpname = &mms_names[i][0];
272		if (ports[i] == MMS_PORT)
273			sprintf(tmpname, "mms");
274		else
275			sprintf(tmpname, "mms-%d", ports[i]);
276		mms[i].name = tmpname;
277
278		DEBUGP("ip_conntrack_mms: registering helper for port %d\n",
279				ports[i]);
280		ret = ip_conntrack_helper_register(&mms[i]);
281
282		if (ret) {
283			fini();
284			return ret;
285		}
286		ports_c++;
287	}
288	return 0;
289}
290
291module_init(init);
292module_exit(fini);
293