diff options
author | Patrick McHardy <kaber@trash.net> | 2008-03-25 23:18:57 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-03-25 23:18:57 -0400 |
commit | ea45f12a2766dae54e5426a23e8f4bafdbe2782e (patch) | |
tree | eb3096bee1e0053c545424f9cea5d0c91b114fe4 | |
parent | ac3677406d4e36e86b1eb5a453997a3b3e0c089a (diff) |
[NETFILTER]: nf_conntrack_sip: parse SIP headers properly
Introduce new function for SIP header parsing that properly deals with
continuation lines and whitespace in headers and use it.
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/netfilter/nf_conntrack_sip.h | 30 | ||||
-rw-r--r-- | net/ipv4/netfilter/nf_nat_sip.c | 18 | ||||
-rw-r--r-- | net/netfilter/nf_conntrack_sip.c | 271 |
3 files changed, 151 insertions, 168 deletions
diff --git a/include/linux/netfilter/nf_conntrack_sip.h b/include/linux/netfilter/nf_conntrack_sip.h index 480b26f40ce4..ccc701422963 100644 --- a/include/linux/netfilter/nf_conntrack_sip.h +++ b/include/linux/netfilter/nf_conntrack_sip.h | |||
@@ -5,14 +5,6 @@ | |||
5 | #define SIP_PORT 5060 | 5 | #define SIP_PORT 5060 |
6 | #define SIP_TIMEOUT 3600 | 6 | #define SIP_TIMEOUT 3600 |
7 | 7 | ||
8 | enum sip_header_pos { | ||
9 | POS_FROM, | ||
10 | POS_TO, | ||
11 | POS_VIA, | ||
12 | POS_CONTACT, | ||
13 | POS_CONTENT, | ||
14 | }; | ||
15 | |||
16 | struct sip_header { | 8 | struct sip_header { |
17 | const char *name; | 9 | const char *name; |
18 | const char *cname; | 10 | const char *cname; |
@@ -36,9 +28,20 @@ struct sip_header { | |||
36 | .match_len = (__match), \ | 28 | .match_len = (__match), \ |
37 | } | 29 | } |
38 | 30 | ||
31 | #define SIP_HDR(__name, __cname, __search, __match) \ | ||
32 | __SIP_HDR(__name, __cname, __search, __match) | ||
33 | |||
39 | #define SDP_HDR(__name, __search, __match) \ | 34 | #define SDP_HDR(__name, __search, __match) \ |
40 | __SIP_HDR(__name, NULL, __search, __match) | 35 | __SIP_HDR(__name, NULL, __search, __match) |
41 | 36 | ||
37 | enum sip_header_types { | ||
38 | SIP_HDR_FROM, | ||
39 | SIP_HDR_TO, | ||
40 | SIP_HDR_CONTACT, | ||
41 | SIP_HDR_VIA, | ||
42 | SIP_HDR_CONTENT_LENGTH, | ||
43 | }; | ||
44 | |||
42 | enum sdp_header_types { | 45 | enum sdp_header_types { |
43 | SDP_HDR_UNSPEC, | 46 | SDP_HDR_UNSPEC, |
44 | SDP_HDR_VERSION, | 47 | SDP_HDR_VERSION, |
@@ -60,13 +63,10 @@ extern unsigned int (*nf_nat_sdp_hook)(struct sk_buff *skb, | |||
60 | extern int ct_sip_parse_request(const struct nf_conn *ct, | 63 | extern int ct_sip_parse_request(const struct nf_conn *ct, |
61 | const char *dptr, unsigned int datalen, | 64 | const char *dptr, unsigned int datalen, |
62 | unsigned int *matchoff, unsigned int *matchlen); | 65 | unsigned int *matchoff, unsigned int *matchlen); |
63 | extern int ct_sip_get_info(const struct nf_conn *ct, const char *dptr, | 66 | extern int ct_sip_get_header(const struct nf_conn *ct, const char *dptr, |
64 | size_t dlen, unsigned int *matchoff, | 67 | unsigned int dataoff, unsigned int datalen, |
65 | unsigned int *matchlen, enum sip_header_pos pos); | 68 | enum sip_header_types type, |
66 | extern int ct_sip_lnlen(const char *line, const char *limit); | 69 | unsigned int *matchoff, unsigned int *matchlen); |
67 | extern const char *ct_sip_search(const char *needle, const char *haystack, | ||
68 | size_t needle_len, size_t haystack_len, | ||
69 | int case_sensitive); | ||
70 | 70 | ||
71 | extern int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr, | 71 | extern int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr, |
72 | unsigned int dataoff, unsigned int datalen, | 72 | unsigned int dataoff, unsigned int datalen, |
diff --git a/net/ipv4/netfilter/nf_nat_sip.c b/net/ipv4/netfilter/nf_nat_sip.c index 60151b5901a5..c13e43862361 100644 --- a/net/ipv4/netfilter/nf_nat_sip.c +++ b/net/ipv4/netfilter/nf_nat_sip.c | |||
@@ -108,14 +108,14 @@ static int map_addr(struct sk_buff *skb, | |||
108 | 108 | ||
109 | static int map_sip_addr(struct sk_buff *skb, | 109 | static int map_sip_addr(struct sk_buff *skb, |
110 | const char **dptr, unsigned int *datalen, | 110 | const char **dptr, unsigned int *datalen, |
111 | enum sip_header_pos pos, struct addr_map *map) | 111 | enum sip_header_types type, struct addr_map *map) |
112 | { | 112 | { |
113 | enum ip_conntrack_info ctinfo; | 113 | enum ip_conntrack_info ctinfo; |
114 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); | 114 | struct nf_conn *ct = nf_ct_get(skb, &ctinfo); |
115 | unsigned int matchlen, matchoff; | 115 | unsigned int matchlen, matchoff; |
116 | 116 | ||
117 | if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, &matchlen, | 117 | if (ct_sip_get_header(ct, *dptr, 0, *datalen, type, |
118 | pos) <= 0) | 118 | &matchoff, &matchlen) <= 0) |
119 | return 1; | 119 | return 1; |
120 | return map_addr(skb, dptr, datalen, matchoff, matchlen, map); | 120 | return map_addr(skb, dptr, datalen, matchoff, matchlen, map); |
121 | } | 121 | } |
@@ -141,10 +141,10 @@ static unsigned int ip_nat_sip(struct sk_buff *skb, | |||
141 | return NF_DROP; | 141 | return NF_DROP; |
142 | } | 142 | } |
143 | 143 | ||
144 | if (!map_sip_addr(skb, dptr, datalen, POS_FROM, &map) || | 144 | if (!map_sip_addr(skb, dptr, datalen, SIP_HDR_FROM, &map) || |
145 | !map_sip_addr(skb, dptr, datalen, POS_TO, &map) || | 145 | !map_sip_addr(skb, dptr, datalen, SIP_HDR_TO, &map) || |
146 | !map_sip_addr(skb, dptr, datalen, POS_VIA, &map) || | 146 | !map_sip_addr(skb, dptr, datalen, SIP_HDR_VIA, &map) || |
147 | !map_sip_addr(skb, dptr, datalen, POS_CONTACT, &map)) | 147 | !map_sip_addr(skb, dptr, datalen, SIP_HDR_CONTACT, &map)) |
148 | return NF_DROP; | 148 | return NF_DROP; |
149 | return NF_ACCEPT; | 149 | return NF_ACCEPT; |
150 | } | 150 | } |
@@ -166,8 +166,8 @@ static int mangle_content_len(struct sk_buff *skb, | |||
166 | c_len = *datalen - matchoff + strlen("v="); | 166 | c_len = *datalen - matchoff + strlen("v="); |
167 | 167 | ||
168 | /* Now, update SDP length */ | 168 | /* Now, update SDP length */ |
169 | if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, &matchlen, | 169 | if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CONTENT_LENGTH, |
170 | POS_CONTENT) <= 0) | 170 | &matchoff, &matchlen) <= 0) |
171 | return 0; | 171 | return 0; |
172 | 172 | ||
173 | buflen = sprintf(buffer, "%u", c_len); | 173 | buflen = sprintf(buffer, "%u", c_len); |
diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index bb4396155681..cbc91598acee 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c | |||
@@ -47,109 +47,6 @@ unsigned int (*nf_nat_sdp_hook)(struct sk_buff *skb, | |||
47 | struct nf_conntrack_expect *exp) __read_mostly; | 47 | struct nf_conntrack_expect *exp) __read_mostly; |
48 | EXPORT_SYMBOL_GPL(nf_nat_sdp_hook); | 48 | EXPORT_SYMBOL_GPL(nf_nat_sdp_hook); |
49 | 49 | ||
50 | static int digits_len(const struct nf_conn *, const char *, const char *, int *); | ||
51 | static int epaddr_len(const struct nf_conn *, const char *, const char *, int *); | ||
52 | static int skp_digits_len(const struct nf_conn *, const char *, const char *, int *); | ||
53 | static int skp_epaddr_len(const struct nf_conn *, const char *, const char *, int *); | ||
54 | |||
55 | struct sip_header_nfo { | ||
56 | const char *lname; | ||
57 | const char *sname; | ||
58 | const char *ln_str; | ||
59 | size_t lnlen; | ||
60 | size_t snlen; | ||
61 | size_t ln_strlen; | ||
62 | int case_sensitive; | ||
63 | int (*match_len)(const struct nf_conn *, const char *, | ||
64 | const char *, int *); | ||
65 | }; | ||
66 | |||
67 | static const struct sip_header_nfo ct_sip_hdrs[] = { | ||
68 | [POS_FROM] = { /* SIP From header */ | ||
69 | .lname = "From:", | ||
70 | .lnlen = sizeof("From:") - 1, | ||
71 | .sname = "\r\nf:", | ||
72 | .snlen = sizeof("\r\nf:") - 1, | ||
73 | .ln_str = "sip:", | ||
74 | .ln_strlen = sizeof("sip:") - 1, | ||
75 | .match_len = skp_epaddr_len, | ||
76 | }, | ||
77 | [POS_TO] = { /* SIP To header */ | ||
78 | .lname = "To:", | ||
79 | .lnlen = sizeof("To:") - 1, | ||
80 | .sname = "\r\nt:", | ||
81 | .snlen = sizeof("\r\nt:") - 1, | ||
82 | .ln_str = "sip:", | ||
83 | .ln_strlen = sizeof("sip:") - 1, | ||
84 | .match_len = skp_epaddr_len | ||
85 | }, | ||
86 | [POS_VIA] = { /* SIP Via header */ | ||
87 | .lname = "Via:", | ||
88 | .lnlen = sizeof("Via:") - 1, | ||
89 | .sname = "\r\nv:", | ||
90 | .snlen = sizeof("\r\nv:") - 1, /* rfc3261 "\r\n" */ | ||
91 | .ln_str = "UDP ", | ||
92 | .ln_strlen = sizeof("UDP ") - 1, | ||
93 | .match_len = epaddr_len, | ||
94 | }, | ||
95 | [POS_CONTACT] = { /* SIP Contact header */ | ||
96 | .lname = "Contact:", | ||
97 | .lnlen = sizeof("Contact:") - 1, | ||
98 | .sname = "\r\nm:", | ||
99 | .snlen = sizeof("\r\nm:") - 1, | ||
100 | .ln_str = "sip:", | ||
101 | .ln_strlen = sizeof("sip:") - 1, | ||
102 | .match_len = skp_epaddr_len | ||
103 | }, | ||
104 | [POS_CONTENT] = { /* SIP Content length header */ | ||
105 | .lname = "Content-Length:", | ||
106 | .lnlen = sizeof("Content-Length:") - 1, | ||
107 | .sname = "\r\nl:", | ||
108 | .snlen = sizeof("\r\nl:") - 1, | ||
109 | .ln_str = ":", | ||
110 | .ln_strlen = sizeof(":") - 1, | ||
111 | .match_len = skp_digits_len | ||
112 | }, | ||
113 | }; | ||
114 | |||
115 | /* get line length until first CR or LF seen. */ | ||
116 | int ct_sip_lnlen(const char *line, const char *limit) | ||
117 | { | ||
118 | const char *k = line; | ||
119 | |||
120 | while ((line < limit) && (*line == '\r' || *line == '\n')) | ||
121 | line++; | ||
122 | |||
123 | while (line < limit) { | ||
124 | if (*line == '\r' || *line == '\n') | ||
125 | break; | ||
126 | line++; | ||
127 | } | ||
128 | return line - k; | ||
129 | } | ||
130 | EXPORT_SYMBOL_GPL(ct_sip_lnlen); | ||
131 | |||
132 | /* Linear string search, case sensitive. */ | ||
133 | const char *ct_sip_search(const char *needle, const char *haystack, | ||
134 | size_t needle_len, size_t haystack_len, | ||
135 | int case_sensitive) | ||
136 | { | ||
137 | const char *limit = haystack + (haystack_len - needle_len); | ||
138 | |||
139 | while (haystack < limit) { | ||
140 | if (case_sensitive) { | ||
141 | if (strncmp(haystack, needle, needle_len) == 0) | ||
142 | return haystack; | ||
143 | } else { | ||
144 | if (strnicmp(haystack, needle, needle_len) == 0) | ||
145 | return haystack; | ||
146 | } | ||
147 | haystack++; | ||
148 | } | ||
149 | return NULL; | ||
150 | } | ||
151 | EXPORT_SYMBOL_GPL(ct_sip_search); | ||
152 | |||
153 | static int string_len(const struct nf_conn *ct, const char *dptr, | 50 | static int string_len(const struct nf_conn *ct, const char *dptr, |
154 | const char *limit, int *shift) | 51 | const char *limit, int *shift) |
155 | { | 52 | { |
@@ -173,16 +70,6 @@ static int digits_len(const struct nf_conn *ct, const char *dptr, | |||
173 | return len; | 70 | return len; |
174 | } | 71 | } |
175 | 72 | ||
176 | /* get digits length, skipping blank spaces. */ | ||
177 | static int skp_digits_len(const struct nf_conn *ct, const char *dptr, | ||
178 | const char *limit, int *shift) | ||
179 | { | ||
180 | for (; dptr < limit && *dptr == ' '; dptr++) | ||
181 | (*shift)++; | ||
182 | |||
183 | return digits_len(ct, dptr, limit, shift); | ||
184 | } | ||
185 | |||
186 | static int parse_addr(const struct nf_conn *ct, const char *cp, | 73 | static int parse_addr(const struct nf_conn *ct, const char *cp, |
187 | const char **endp, union nf_inet_addr *addr, | 74 | const char **endp, union nf_inet_addr *addr, |
188 | const char *limit) | 75 | const char *limit) |
@@ -294,50 +181,146 @@ int ct_sip_parse_request(const struct nf_conn *ct, | |||
294 | } | 181 | } |
295 | EXPORT_SYMBOL_GPL(ct_sip_parse_request); | 182 | EXPORT_SYMBOL_GPL(ct_sip_parse_request); |
296 | 183 | ||
297 | /* Returns 0 if not found, -1 error parsing. */ | 184 | /* SIP header parsing: SIP headers are located at the beginning of a line, but |
298 | int ct_sip_get_info(const struct nf_conn *ct, | 185 | * may span several lines, in which case the continuation lines begin with a |
299 | const char *dptr, size_t dlen, | 186 | * whitespace character. RFC 2543 allows lines to be terminated with CR, LF or |
300 | unsigned int *matchoff, | 187 | * CRLF, RFC 3261 allows only CRLF, we support both. |
301 | unsigned int *matchlen, | 188 | * |
302 | enum sip_header_pos pos) | 189 | * Headers are followed by (optionally) whitespace, a colon, again (optionally) |
190 | * whitespace and the values. Whitespace in this context means any amount of | ||
191 | * tabs, spaces and continuation lines, which are treated as a single whitespace | ||
192 | * character. | ||
193 | */ | ||
194 | static const struct sip_header ct_sip_hdrs[] = { | ||
195 | [SIP_HDR_FROM] = SIP_HDR("From", "f", "sip:", skp_epaddr_len), | ||
196 | [SIP_HDR_TO] = SIP_HDR("To", "t", "sip:", skp_epaddr_len), | ||
197 | [SIP_HDR_CONTACT] = SIP_HDR("Contact", "m", "sip:", skp_epaddr_len), | ||
198 | [SIP_HDR_VIA] = SIP_HDR("Via", "v", "UDP ", epaddr_len), | ||
199 | [SIP_HDR_CONTENT_LENGTH] = SIP_HDR("Content-Length", "l", NULL, digits_len), | ||
200 | }; | ||
201 | |||
202 | static const char *sip_follow_continuation(const char *dptr, const char *limit) | ||
303 | { | 203 | { |
304 | const struct sip_header_nfo *hnfo = &ct_sip_hdrs[pos]; | 204 | /* Walk past newline */ |
305 | const char *limit, *aux, *k = dptr; | 205 | if (++dptr >= limit) |
306 | int shift = 0; | 206 | return NULL; |
207 | |||
208 | /* Skip '\n' in CR LF */ | ||
209 | if (*(dptr - 1) == '\r' && *dptr == '\n') { | ||
210 | if (++dptr >= limit) | ||
211 | return NULL; | ||
212 | } | ||
213 | |||
214 | /* Continuation line? */ | ||
215 | if (*dptr != ' ' && *dptr != '\t') | ||
216 | return NULL; | ||
217 | |||
218 | /* skip leading whitespace */ | ||
219 | for (; dptr < limit; dptr++) { | ||
220 | if (*dptr != ' ' && *dptr != '\t') | ||
221 | break; | ||
222 | } | ||
223 | return dptr; | ||
224 | } | ||
225 | |||
226 | static const char *sip_skip_whitespace(const char *dptr, const char *limit) | ||
227 | { | ||
228 | for (; dptr < limit; dptr++) { | ||
229 | if (*dptr == ' ') | ||
230 | continue; | ||
231 | if (*dptr != '\r' && *dptr != '\n') | ||
232 | break; | ||
233 | dptr = sip_follow_continuation(dptr, limit); | ||
234 | if (dptr == NULL) | ||
235 | return NULL; | ||
236 | } | ||
237 | return dptr; | ||
238 | } | ||
307 | 239 | ||
308 | limit = dptr + (dlen - hnfo->lnlen); | 240 | /* Search within a SIP header value, dealing with continuation lines */ |
241 | static const char *ct_sip_header_search(const char *dptr, const char *limit, | ||
242 | const char *needle, unsigned int len) | ||
243 | { | ||
244 | for (limit -= len; dptr < limit; dptr++) { | ||
245 | if (*dptr == '\r' || *dptr == '\n') { | ||
246 | dptr = sip_follow_continuation(dptr, limit); | ||
247 | if (dptr == NULL) | ||
248 | break; | ||
249 | continue; | ||
250 | } | ||
309 | 251 | ||
310 | while (dptr < limit) { | 252 | if (strnicmp(dptr, needle, len) == 0) |
311 | if ((strncmp(dptr, hnfo->lname, hnfo->lnlen) != 0) && | 253 | return dptr; |
312 | (hnfo->sname == NULL || | 254 | } |
313 | strncmp(dptr, hnfo->sname, hnfo->snlen) != 0)) { | 255 | return NULL; |
314 | dptr++; | 256 | } |
257 | |||
258 | int ct_sip_get_header(const struct nf_conn *ct, const char *dptr, | ||
259 | unsigned int dataoff, unsigned int datalen, | ||
260 | enum sip_header_types type, | ||
261 | unsigned int *matchoff, unsigned int *matchlen) | ||
262 | { | ||
263 | const struct sip_header *hdr = &ct_sip_hdrs[type]; | ||
264 | const char *start = dptr, *limit = dptr + datalen; | ||
265 | int shift = 0; | ||
266 | |||
267 | for (dptr += dataoff; dptr < limit; dptr++) { | ||
268 | /* Find beginning of line */ | ||
269 | if (*dptr != '\r' && *dptr != '\n') | ||
315 | continue; | 270 | continue; |
271 | if (++dptr >= limit) | ||
272 | break; | ||
273 | if (*(dptr - 1) == '\r' && *dptr == '\n') { | ||
274 | if (++dptr >= limit) | ||
275 | break; | ||
316 | } | 276 | } |
317 | aux = ct_sip_search(hnfo->ln_str, dptr, hnfo->ln_strlen, | 277 | |
318 | ct_sip_lnlen(dptr, limit), | 278 | /* Skip continuation lines */ |
319 | hnfo->case_sensitive); | 279 | if (*dptr == ' ' || *dptr == '\t') |
320 | if (!aux) { | 280 | continue; |
321 | pr_debug("'%s' not found in '%s'.\n", hnfo->ln_str, | 281 | |
322 | hnfo->lname); | 282 | /* Find header. Compact headers must be followed by a |
323 | return -1; | 283 | * non-alphabetic character to avoid mismatches. */ |
284 | if (limit - dptr >= hdr->len && | ||
285 | strnicmp(dptr, hdr->name, hdr->len) == 0) | ||
286 | dptr += hdr->len; | ||
287 | else if (hdr->cname && limit - dptr >= hdr->clen + 1 && | ||
288 | strnicmp(dptr, hdr->cname, hdr->clen) == 0 && | ||
289 | !isalpha(*(dptr + hdr->clen + 1))) | ||
290 | dptr += hdr->clen; | ||
291 | else | ||
292 | continue; | ||
293 | |||
294 | /* Find and skip colon */ | ||
295 | dptr = sip_skip_whitespace(dptr, limit); | ||
296 | if (dptr == NULL) | ||
297 | break; | ||
298 | if (*dptr != ':' || ++dptr >= limit) | ||
299 | break; | ||
300 | |||
301 | /* Skip whitespace after colon */ | ||
302 | dptr = sip_skip_whitespace(dptr, limit); | ||
303 | if (dptr == NULL) | ||
304 | break; | ||
305 | |||
306 | *matchoff = dptr - start; | ||
307 | if (hdr->search) { | ||
308 | dptr = ct_sip_header_search(dptr, limit, hdr->search, | ||
309 | hdr->slen); | ||
310 | if (!dptr) | ||
311 | return -1; | ||
312 | dptr += hdr->slen; | ||
324 | } | 313 | } |
325 | aux += hnfo->ln_strlen; | ||
326 | 314 | ||
327 | *matchlen = hnfo->match_len(ct, aux, limit, &shift); | 315 | *matchlen = hdr->match_len(ct, dptr, limit, &shift); |
328 | if (!*matchlen) | 316 | if (!*matchlen) |
329 | return -1; | 317 | return -1; |
330 | 318 | *matchoff = dptr - start + shift; | |
331 | *matchoff = (aux - k) + shift; | ||
332 | |||
333 | pr_debug("%s match succeeded! - len: %u\n", hnfo->lname, | ||
334 | *matchlen); | ||
335 | return 1; | 319 | return 1; |
336 | } | 320 | } |
337 | pr_debug("%s header not found.\n", hnfo->lname); | ||
338 | return 0; | 321 | return 0; |
339 | } | 322 | } |
340 | EXPORT_SYMBOL_GPL(ct_sip_get_info); | 323 | EXPORT_SYMBOL_GPL(ct_sip_get_header); |
341 | 324 | ||
342 | /* SDP header parsing: a SDP session description contains an ordered set of | 325 | /* SDP header parsing: a SDP session description contains an ordered set of |
343 | * headers, starting with a section containing general session parameters, | 326 | * headers, starting with a section containing general session parameters, |