[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>
This commit is contained in:
Patrick McHardy 2008-03-25 20:18:57 -07:00 committed by David S. Miller
parent ac3677406d
commit ea45f12a27
3 changed files with 152 additions and 169 deletions

View File

@ -5,14 +5,6 @@
#define SIP_PORT 5060
#define SIP_TIMEOUT 3600
enum sip_header_pos {
POS_FROM,
POS_TO,
POS_VIA,
POS_CONTACT,
POS_CONTENT,
};
struct sip_header {
const char *name;
const char *cname;
@ -36,9 +28,20 @@ struct sip_header {
.match_len = (__match), \
}
#define SIP_HDR(__name, __cname, __search, __match) \
__SIP_HDR(__name, __cname, __search, __match)
#define SDP_HDR(__name, __search, __match) \
__SIP_HDR(__name, NULL, __search, __match)
enum sip_header_types {
SIP_HDR_FROM,
SIP_HDR_TO,
SIP_HDR_CONTACT,
SIP_HDR_VIA,
SIP_HDR_CONTENT_LENGTH,
};
enum sdp_header_types {
SDP_HDR_UNSPEC,
SDP_HDR_VERSION,
@ -60,13 +63,10 @@ extern unsigned int (*nf_nat_sdp_hook)(struct sk_buff *skb,
extern int ct_sip_parse_request(const struct nf_conn *ct,
const char *dptr, unsigned int datalen,
unsigned int *matchoff, unsigned int *matchlen);
extern int ct_sip_get_info(const struct nf_conn *ct, const char *dptr,
size_t dlen, unsigned int *matchoff,
unsigned int *matchlen, enum sip_header_pos pos);
extern int ct_sip_lnlen(const char *line, const char *limit);
extern const char *ct_sip_search(const char *needle, const char *haystack,
size_t needle_len, size_t haystack_len,
int case_sensitive);
extern int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sip_header_types type,
unsigned int *matchoff, unsigned int *matchlen);
extern int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,

View File

@ -108,14 +108,14 @@ static int map_addr(struct sk_buff *skb,
static int map_sip_addr(struct sk_buff *skb,
const char **dptr, unsigned int *datalen,
enum sip_header_pos pos, struct addr_map *map)
enum sip_header_types type, struct addr_map *map)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
unsigned int matchlen, matchoff;
if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, &matchlen,
pos) <= 0)
if (ct_sip_get_header(ct, *dptr, 0, *datalen, type,
&matchoff, &matchlen) <= 0)
return 1;
return map_addr(skb, dptr, datalen, matchoff, matchlen, map);
}
@ -141,10 +141,10 @@ static unsigned int ip_nat_sip(struct sk_buff *skb,
return NF_DROP;
}
if (!map_sip_addr(skb, dptr, datalen, POS_FROM, &map) ||
!map_sip_addr(skb, dptr, datalen, POS_TO, &map) ||
!map_sip_addr(skb, dptr, datalen, POS_VIA, &map) ||
!map_sip_addr(skb, dptr, datalen, POS_CONTACT, &map))
if (!map_sip_addr(skb, dptr, datalen, SIP_HDR_FROM, &map) ||
!map_sip_addr(skb, dptr, datalen, SIP_HDR_TO, &map) ||
!map_sip_addr(skb, dptr, datalen, SIP_HDR_VIA, &map) ||
!map_sip_addr(skb, dptr, datalen, SIP_HDR_CONTACT, &map))
return NF_DROP;
return NF_ACCEPT;
}
@ -166,8 +166,8 @@ static int mangle_content_len(struct sk_buff *skb,
c_len = *datalen - matchoff + strlen("v=");
/* Now, update SDP length */
if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, &matchlen,
POS_CONTENT) <= 0)
if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CONTENT_LENGTH,
&matchoff, &matchlen) <= 0)
return 0;
buflen = sprintf(buffer, "%u", c_len);

View File

@ -47,109 +47,6 @@ unsigned int (*nf_nat_sdp_hook)(struct sk_buff *skb,
struct nf_conntrack_expect *exp) __read_mostly;
EXPORT_SYMBOL_GPL(nf_nat_sdp_hook);
static int digits_len(const struct nf_conn *, const char *, const char *, int *);
static int epaddr_len(const struct nf_conn *, const char *, const char *, int *);
static int skp_digits_len(const struct nf_conn *, const char *, const char *, int *);
static int skp_epaddr_len(const struct nf_conn *, const char *, const char *, int *);
struct sip_header_nfo {
const char *lname;
const char *sname;
const char *ln_str;
size_t lnlen;
size_t snlen;
size_t ln_strlen;
int case_sensitive;
int (*match_len)(const struct nf_conn *, const char *,
const char *, int *);
};
static const struct sip_header_nfo ct_sip_hdrs[] = {
[POS_FROM] = { /* SIP From header */
.lname = "From:",
.lnlen = sizeof("From:") - 1,
.sname = "\r\nf:",
.snlen = sizeof("\r\nf:") - 1,
.ln_str = "sip:",
.ln_strlen = sizeof("sip:") - 1,
.match_len = skp_epaddr_len,
},
[POS_TO] = { /* SIP To header */
.lname = "To:",
.lnlen = sizeof("To:") - 1,
.sname = "\r\nt:",
.snlen = sizeof("\r\nt:") - 1,
.ln_str = "sip:",
.ln_strlen = sizeof("sip:") - 1,
.match_len = skp_epaddr_len
},
[POS_VIA] = { /* SIP Via header */
.lname = "Via:",
.lnlen = sizeof("Via:") - 1,
.sname = "\r\nv:",
.snlen = sizeof("\r\nv:") - 1, /* rfc3261 "\r\n" */
.ln_str = "UDP ",
.ln_strlen = sizeof("UDP ") - 1,
.match_len = epaddr_len,
},
[POS_CONTACT] = { /* SIP Contact header */
.lname = "Contact:",
.lnlen = sizeof("Contact:") - 1,
.sname = "\r\nm:",
.snlen = sizeof("\r\nm:") - 1,
.ln_str = "sip:",
.ln_strlen = sizeof("sip:") - 1,
.match_len = skp_epaddr_len
},
[POS_CONTENT] = { /* SIP Content length header */
.lname = "Content-Length:",
.lnlen = sizeof("Content-Length:") - 1,
.sname = "\r\nl:",
.snlen = sizeof("\r\nl:") - 1,
.ln_str = ":",
.ln_strlen = sizeof(":") - 1,
.match_len = skp_digits_len
},
};
/* get line length until first CR or LF seen. */
int ct_sip_lnlen(const char *line, const char *limit)
{
const char *k = line;
while ((line < limit) && (*line == '\r' || *line == '\n'))
line++;
while (line < limit) {
if (*line == '\r' || *line == '\n')
break;
line++;
}
return line - k;
}
EXPORT_SYMBOL_GPL(ct_sip_lnlen);
/* Linear string search, case sensitive. */
const char *ct_sip_search(const char *needle, const char *haystack,
size_t needle_len, size_t haystack_len,
int case_sensitive)
{
const char *limit = haystack + (haystack_len - needle_len);
while (haystack < limit) {
if (case_sensitive) {
if (strncmp(haystack, needle, needle_len) == 0)
return haystack;
} else {
if (strnicmp(haystack, needle, needle_len) == 0)
return haystack;
}
haystack++;
}
return NULL;
}
EXPORT_SYMBOL_GPL(ct_sip_search);
static int string_len(const struct nf_conn *ct, const char *dptr,
const char *limit, int *shift)
{
@ -173,16 +70,6 @@ static int digits_len(const struct nf_conn *ct, const char *dptr,
return len;
}
/* get digits length, skipping blank spaces. */
static int skp_digits_len(const struct nf_conn *ct, const char *dptr,
const char *limit, int *shift)
{
for (; dptr < limit && *dptr == ' '; dptr++)
(*shift)++;
return digits_len(ct, dptr, limit, shift);
}
static int parse_addr(const struct nf_conn *ct, const char *cp,
const char **endp, union nf_inet_addr *addr,
const char *limit)
@ -294,50 +181,146 @@ int ct_sip_parse_request(const struct nf_conn *ct,
}
EXPORT_SYMBOL_GPL(ct_sip_parse_request);
/* Returns 0 if not found, -1 error parsing. */
int ct_sip_get_info(const struct nf_conn *ct,
const char *dptr, size_t dlen,
unsigned int *matchoff,
unsigned int *matchlen,
enum sip_header_pos pos)
/* SIP header parsing: SIP headers are located at the beginning of a line, but
* may span several lines, in which case the continuation lines begin with a
* whitespace character. RFC 2543 allows lines to be terminated with CR, LF or
* CRLF, RFC 3261 allows only CRLF, we support both.
*
* Headers are followed by (optionally) whitespace, a colon, again (optionally)
* whitespace and the values. Whitespace in this context means any amount of
* tabs, spaces and continuation lines, which are treated as a single whitespace
* character.
*/
static const struct sip_header ct_sip_hdrs[] = {
[SIP_HDR_FROM] = SIP_HDR("From", "f", "sip:", skp_epaddr_len),
[SIP_HDR_TO] = SIP_HDR("To", "t", "sip:", skp_epaddr_len),
[SIP_HDR_CONTACT] = SIP_HDR("Contact", "m", "sip:", skp_epaddr_len),
[SIP_HDR_VIA] = SIP_HDR("Via", "v", "UDP ", epaddr_len),
[SIP_HDR_CONTENT_LENGTH] = SIP_HDR("Content-Length", "l", NULL, digits_len),
};
static const char *sip_follow_continuation(const char *dptr, const char *limit)
{
const struct sip_header_nfo *hnfo = &ct_sip_hdrs[pos];
const char *limit, *aux, *k = dptr;
int shift = 0;
/* Walk past newline */
if (++dptr >= limit)
return NULL;
limit = dptr + (dlen - hnfo->lnlen);
/* Skip '\n' in CR LF */
if (*(dptr - 1) == '\r' && *dptr == '\n') {
if (++dptr >= limit)
return NULL;
}
while (dptr < limit) {
if ((strncmp(dptr, hnfo->lname, hnfo->lnlen) != 0) &&
(hnfo->sname == NULL ||
strncmp(dptr, hnfo->sname, hnfo->snlen) != 0)) {
dptr++;
/* Continuation line? */
if (*dptr != ' ' && *dptr != '\t')
return NULL;
/* skip leading whitespace */
for (; dptr < limit; dptr++) {
if (*dptr != ' ' && *dptr != '\t')
break;
}
return dptr;
}
static const char *sip_skip_whitespace(const char *dptr, const char *limit)
{
for (; dptr < limit; dptr++) {
if (*dptr == ' ')
continue;
if (*dptr != '\r' && *dptr != '\n')
break;
dptr = sip_follow_continuation(dptr, limit);
if (dptr == NULL)
return NULL;
}
return dptr;
}
/* Search within a SIP header value, dealing with continuation lines */
static const char *ct_sip_header_search(const char *dptr, const char *limit,
const char *needle, unsigned int len)
{
for (limit -= len; dptr < limit; dptr++) {
if (*dptr == '\r' || *dptr == '\n') {
dptr = sip_follow_continuation(dptr, limit);
if (dptr == NULL)
break;
continue;
}
aux = ct_sip_search(hnfo->ln_str, dptr, hnfo->ln_strlen,
ct_sip_lnlen(dptr, limit),
hnfo->case_sensitive);
if (!aux) {
pr_debug("'%s' not found in '%s'.\n", hnfo->ln_str,
hnfo->lname);
return -1;
}
aux += hnfo->ln_strlen;
*matchlen = hnfo->match_len(ct, aux, limit, &shift);
if (strnicmp(dptr, needle, len) == 0)
return dptr;
}
return NULL;
}
int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
unsigned int dataoff, unsigned int datalen,
enum sip_header_types type,
unsigned int *matchoff, unsigned int *matchlen)
{
const struct sip_header *hdr = &ct_sip_hdrs[type];
const char *start = dptr, *limit = dptr + datalen;
int shift = 0;
for (dptr += dataoff; dptr < limit; dptr++) {
/* Find beginning of line */
if (*dptr != '\r' && *dptr != '\n')
continue;
if (++dptr >= limit)
break;
if (*(dptr - 1) == '\r' && *dptr == '\n') {
if (++dptr >= limit)
break;
}
/* Skip continuation lines */
if (*dptr == ' ' || *dptr == '\t')
continue;
/* Find header. Compact headers must be followed by a
* non-alphabetic character to avoid mismatches. */
if (limit - dptr >= hdr->len &&
strnicmp(dptr, hdr->name, hdr->len) == 0)
dptr += hdr->len;
else if (hdr->cname && limit - dptr >= hdr->clen + 1 &&
strnicmp(dptr, hdr->cname, hdr->clen) == 0 &&
!isalpha(*(dptr + hdr->clen + 1)))
dptr += hdr->clen;
else
continue;
/* Find and skip colon */
dptr = sip_skip_whitespace(dptr, limit);
if (dptr == NULL)
break;
if (*dptr != ':' || ++dptr >= limit)
break;
/* Skip whitespace after colon */
dptr = sip_skip_whitespace(dptr, limit);
if (dptr == NULL)
break;
*matchoff = dptr - start;
if (hdr->search) {
dptr = ct_sip_header_search(dptr, limit, hdr->search,
hdr->slen);
if (!dptr)
return -1;
dptr += hdr->slen;
}
*matchlen = hdr->match_len(ct, dptr, limit, &shift);
if (!*matchlen)
return -1;
*matchoff = (aux - k) + shift;
pr_debug("%s match succeeded! - len: %u\n", hnfo->lname,
*matchlen);
*matchoff = dptr - start + shift;
return 1;
}
pr_debug("%s header not found.\n", hnfo->lname);
return 0;
}
EXPORT_SYMBOL_GPL(ct_sip_get_info);
EXPORT_SYMBOL_GPL(ct_sip_get_header);
/* SDP header parsing: a SDP session description contains an ordered set of
* headers, starting with a section containing general session parameters,