linux/net/netfilter/nf_conntrack_ftp.c
<<
>>
Prefs
   1/* FTP extension for connection tracking. */
   2
   3/* (C) 1999-2001 Paul `Rusty' Russell
   4 * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
   5 * (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.org>
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10 */
  11
  12#include <linux/module.h>
  13#include <linux/moduleparam.h>
  14#include <linux/netfilter.h>
  15#include <linux/ip.h>
  16#include <linux/ipv6.h>
  17#include <linux/ctype.h>
  18#include <linux/inet.h>
  19#include <net/checksum.h>
  20#include <net/tcp.h>
  21
  22#include <net/netfilter/nf_conntrack.h>
  23#include <net/netfilter/nf_conntrack_expect.h>
  24#include <net/netfilter/nf_conntrack_ecache.h>
  25#include <net/netfilter/nf_conntrack_helper.h>
  26#include <linux/netfilter/nf_conntrack_ftp.h>
  27
  28MODULE_LICENSE("GPL");
  29MODULE_AUTHOR("Rusty Russell <rusty@rustcorp.com.au>");
  30MODULE_DESCRIPTION("ftp connection tracking helper");
  31MODULE_ALIAS("ip_conntrack_ftp");
  32
  33/* This is slow, but it's simple. --RR */
  34static char *ftp_buffer;
  35
  36static DEFINE_SPINLOCK(nf_ftp_lock);
  37
  38#define MAX_PORTS 8
  39static u_int16_t ports[MAX_PORTS];
  40static unsigned int ports_c;
  41module_param_array(ports, ushort, &ports_c, 0400);
  42
  43static int loose;
  44module_param(loose, bool, 0600);
  45
  46unsigned int (*nf_nat_ftp_hook)(struct sk_buff *skb,
  47                                enum ip_conntrack_info ctinfo,
  48                                enum nf_ct_ftp_type type,
  49                                unsigned int matchoff,
  50                                unsigned int matchlen,
  51                                struct nf_conntrack_expect *exp);
  52EXPORT_SYMBOL_GPL(nf_nat_ftp_hook);
  53
  54static int try_rfc959(const char *, size_t, struct nf_conntrack_man *, char);
  55static int try_eprt(const char *, size_t, struct nf_conntrack_man *, char);
  56static int try_epsv_response(const char *, size_t, struct nf_conntrack_man *,
  57                             char);
  58
  59static struct ftp_search {
  60        const char *pattern;
  61        size_t plen;
  62        char skip;
  63        char term;
  64        enum nf_ct_ftp_type ftptype;
  65        int (*getnum)(const char *, size_t, struct nf_conntrack_man *, char);
  66} search[IP_CT_DIR_MAX][2] = {
  67        [IP_CT_DIR_ORIGINAL] = {
  68                {
  69                        .pattern        = "PORT",
  70                        .plen           = sizeof("PORT") - 1,
  71                        .skip           = ' ',
  72                        .term           = '\r',
  73                        .ftptype        = NF_CT_FTP_PORT,
  74                        .getnum         = try_rfc959,
  75                },
  76                {
  77                        .pattern        = "EPRT",
  78                        .plen           = sizeof("EPRT") - 1,
  79                        .skip           = ' ',
  80                        .term           = '\r',
  81                        .ftptype        = NF_CT_FTP_EPRT,
  82                        .getnum         = try_eprt,
  83                },
  84        },
  85        [IP_CT_DIR_REPLY] = {
  86                {
  87                        .pattern        = "227 ",
  88                        .plen           = sizeof("227 ") - 1,
  89                        .skip           = '(',
  90                        .term           = ')',
  91                        .ftptype        = NF_CT_FTP_PASV,
  92                        .getnum         = try_rfc959,
  93                },
  94                {
  95                        .pattern        = "229 ",
  96                        .plen           = sizeof("229 ") - 1,
  97                        .skip           = '(',
  98                        .term           = ')',
  99                        .ftptype        = NF_CT_FTP_EPSV,
 100                        .getnum         = try_epsv_response,
 101                },
 102        },
 103};
 104
 105static int
 106get_ipv6_addr(const char *src, size_t dlen, struct in6_addr *dst, u_int8_t term)
 107{
 108        const char *end;
 109        int ret = in6_pton(src, min_t(size_t, dlen, 0xffff), (u8 *)dst, term, &end);
 110        if (ret > 0)
 111                return (int)(end - src);
 112        return 0;
 113}
 114
 115static int try_number(const char *data, size_t dlen, u_int32_t array[],
 116                      int array_size, char sep, char term)
 117{
 118        u_int32_t i, len;
 119
 120        memset(array, 0, sizeof(array[0])*array_size);
 121
 122        /* Keep data pointing at next char. */
 123        for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) {
 124                if (*data >= '0' && *data <= '9') {
 125                        array[i] = array[i]*10 + *data - '0';
 126                }
 127                else if (*data == sep)
 128                        i++;
 129                else {
 130                        /* Unexpected character; true if it's the
 131                           terminator and we're finished. */
 132                        if (*data == term && i == array_size - 1)
 133                                return len;
 134
 135                        pr_debug("Char %u (got %u nums) `%u' unexpected\n",
 136                                 len, i, *data);
 137                        return 0;
 138                }
 139        }
 140        pr_debug("Failed to fill %u numbers separated by %c\n",
 141                 array_size, sep);
 142        return 0;
 143}
 144
 145/* Returns 0, or length of numbers: 192,168,1,1,5,6 */
 146static int try_rfc959(const char *data, size_t dlen,
 147                      struct nf_conntrack_man *cmd, char term)
 148{
 149        int length;
 150        u_int32_t array[6];
 151
 152        length = try_number(data, dlen, array, 6, ',', term);
 153        if (length == 0)
 154                return 0;
 155
 156        cmd->u3.ip =  htonl((array[0] << 24) | (array[1] << 16) |
 157                                    (array[2] << 8) | array[3]);
 158        cmd->u.tcp.port = htons((array[4] << 8) | array[5]);
 159        return length;
 160}
 161
 162/* Grab port: number up to delimiter */
 163static int get_port(const char *data, int start, size_t dlen, char delim,
 164                    __be16 *port)
 165{
 166        u_int16_t tmp_port = 0;
 167        int i;
 168
 169        for (i = start; i < dlen; i++) {
 170                /* Finished? */
 171                if (data[i] == delim) {
 172                        if (tmp_port == 0)
 173                                break;
 174                        *port = htons(tmp_port);
 175                        pr_debug("get_port: return %d\n", tmp_port);
 176                        return i + 1;
 177                }
 178                else if (data[i] >= '0' && data[i] <= '9')
 179                        tmp_port = tmp_port*10 + data[i] - '0';
 180                else { /* Some other crap */
 181                        pr_debug("get_port: invalid char.\n");
 182                        break;
 183                }
 184        }
 185        return 0;
 186}
 187
 188/* Returns 0, or length of numbers: |1|132.235.1.2|6275| or |2|3ffe::1|6275| */
 189static int try_eprt(const char *data, size_t dlen, struct nf_conntrack_man *cmd,
 190                    char term)
 191{
 192        char delim;
 193        int length;
 194
 195        /* First character is delimiter, then "1" for IPv4 or "2" for IPv6,
 196           then delimiter again. */
 197        if (dlen <= 3) {
 198                pr_debug("EPRT: too short\n");
 199                return 0;
 200        }
 201        delim = data[0];
 202        if (isdigit(delim) || delim < 33 || delim > 126 || data[2] != delim) {
 203                pr_debug("try_eprt: invalid delimitter.\n");
 204                return 0;
 205        }
 206
 207        if ((cmd->l3num == PF_INET && data[1] != '1') ||
 208            (cmd->l3num == PF_INET6 && data[1] != '2')) {
 209                pr_debug("EPRT: invalid protocol number.\n");
 210                return 0;
 211        }
 212
 213        pr_debug("EPRT: Got %c%c%c\n", delim, data[1], delim);
 214
 215        if (data[1] == '1') {
 216                u_int32_t array[4];
 217
 218                /* Now we have IP address. */
 219                length = try_number(data + 3, dlen - 3, array, 4, '.', delim);
 220                if (length != 0)
 221                        cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16)
 222                                           | (array[2] << 8) | array[3]);
 223        } else {
 224                /* Now we have IPv6 address. */
 225                length = get_ipv6_addr(data + 3, dlen - 3,
 226                                       (struct in6_addr *)cmd->u3.ip6, delim);
 227        }
 228
 229        if (length == 0)
 230                return 0;
 231        pr_debug("EPRT: Got IP address!\n");
 232        /* Start offset includes initial "|1|", and trailing delimiter */
 233        return get_port(data, 3 + length + 1, dlen, delim, &cmd->u.tcp.port);
 234}
 235
 236/* Returns 0, or length of numbers: |||6446| */
 237static int try_epsv_response(const char *data, size_t dlen,
 238                             struct nf_conntrack_man *cmd, char term)
 239{
 240        char delim;
 241
 242        /* Three delimiters. */
 243        if (dlen <= 3) return 0;
 244        delim = data[0];
 245        if (isdigit(delim) || delim < 33 || delim > 126
 246            || data[1] != delim || data[2] != delim)
 247                return 0;
 248
 249        return get_port(data, 3, dlen, delim, &cmd->u.tcp.port);
 250}
 251
 252/* Return 1 for match, 0 for accept, -1 for partial. */
 253static int find_pattern(const char *data, size_t dlen,
 254                        const char *pattern, size_t plen,
 255                        char skip, char term,
 256                        unsigned int *numoff,
 257                        unsigned int *numlen,
 258                        struct nf_conntrack_man *cmd,
 259                        int (*getnum)(const char *, size_t,
 260                                      struct nf_conntrack_man *, char))
 261{
 262        size_t i;
 263
 264        pr_debug("find_pattern `%s': dlen = %Zu\n", pattern, dlen);
 265        if (dlen == 0)
 266                return 0;
 267
 268        if (dlen <= plen) {
 269                /* Short packet: try for partial? */
 270                if (strnicmp(data, pattern, dlen) == 0)
 271                        return -1;
 272                else return 0;
 273        }
 274
 275        if (strnicmp(data, pattern, plen) != 0) {
 276#if 0
 277                size_t i;
 278
 279                pr_debug("ftp: string mismatch\n");
 280                for (i = 0; i < plen; i++) {
 281                        pr_debug("ftp:char %u `%c'(%u) vs `%c'(%u)\n",
 282                                 i, data[i], data[i],
 283                                 pattern[i], pattern[i]);
 284                }
 285#endif
 286                return 0;
 287        }
 288
 289        pr_debug("Pattern matches!\n");
 290        /* Now we've found the constant string, try to skip
 291           to the 'skip' character */
 292        for (i = plen; data[i] != skip; i++)
 293                if (i == dlen - 1) return -1;
 294
 295        /* Skip over the last character */
 296        i++;
 297
 298        pr_debug("Skipped up to `%c'!\n", skip);
 299
 300        *numoff = i;
 301        *numlen = getnum(data + i, dlen - i, cmd, term);
 302        if (!*numlen)
 303                return -1;
 304
 305        pr_debug("Match succeeded!\n");
 306        return 1;
 307}
 308
 309/* Look up to see if we're just after a \n. */
 310static int find_nl_seq(u32 seq, const struct nf_ct_ftp_master *info, int dir)
 311{
 312        unsigned int i;
 313
 314        for (i = 0; i < info->seq_aft_nl_num[dir]; i++)
 315                if (info->seq_aft_nl[dir][i] == seq)
 316                        return 1;
 317        return 0;
 318}
 319
 320/* We don't update if it's older than what we have. */
 321static void update_nl_seq(struct nf_conn *ct, u32 nl_seq,
 322                          struct nf_ct_ftp_master *info, int dir,
 323                          struct sk_buff *skb)
 324{
 325        unsigned int i, oldest = NUM_SEQ_TO_REMEMBER;
 326
 327        /* Look for oldest: if we find exact match, we're done. */
 328        for (i = 0; i < info->seq_aft_nl_num[dir]; i++) {
 329                if (info->seq_aft_nl[dir][i] == nl_seq)
 330                        return;
 331
 332                if (oldest == info->seq_aft_nl_num[dir] ||
 333                    before(info->seq_aft_nl[dir][i],
 334                           info->seq_aft_nl[dir][oldest]))
 335                        oldest = i;
 336        }
 337
 338        if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) {
 339                info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq;
 340                nf_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, ct);
 341        } else if (oldest != NUM_SEQ_TO_REMEMBER &&
 342                   after(nl_seq, info->seq_aft_nl[dir][oldest])) {
 343                info->seq_aft_nl[dir][oldest] = nl_seq;
 344                nf_conntrack_event_cache(IPCT_HELPINFO_VOLATILE, ct);
 345        }
 346}
 347
 348static int help(struct sk_buff *skb,
 349                unsigned int protoff,
 350                struct nf_conn *ct,
 351                enum ip_conntrack_info ctinfo)
 352{
 353        unsigned int dataoff, datalen;
 354        const struct tcphdr *th;
 355        struct tcphdr _tcph;
 356        const char *fb_ptr;
 357        int ret;
 358        u32 seq;
 359        int dir = CTINFO2DIR(ctinfo);
 360        unsigned int matchlen, matchoff;
 361        struct nf_ct_ftp_master *ct_ftp_info = &nfct_help(ct)->help.ct_ftp_info;
 362        struct nf_conntrack_expect *exp;
 363        union nf_inet_addr *daddr;
 364        struct nf_conntrack_man cmd = {};
 365        unsigned int i;
 366        int found = 0, ends_in_nl;
 367        typeof(nf_nat_ftp_hook) nf_nat_ftp;
 368
 369        /* Until there's been traffic both ways, don't look in packets. */
 370        if (ctinfo != IP_CT_ESTABLISHED
 371            && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
 372                pr_debug("ftp: Conntrackinfo = %u\n", ctinfo);
 373                return NF_ACCEPT;
 374        }
 375
 376        th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph);
 377        if (th == NULL)
 378                return NF_ACCEPT;
 379
 380        dataoff = protoff + th->doff * 4;
 381        /* No data? */
 382        if (dataoff >= skb->len) {
 383                pr_debug("ftp: dataoff(%u) >= skblen(%u)\n", dataoff,
 384                         skb->len);
 385                return NF_ACCEPT;
 386        }
 387        datalen = skb->len - dataoff;
 388
 389        spin_lock_bh(&nf_ftp_lock);
 390        fb_ptr = skb_header_pointer(skb, dataoff, datalen, ftp_buffer);
 391        BUG_ON(fb_ptr == NULL);
 392
 393        ends_in_nl = (fb_ptr[datalen - 1] == '\n');
 394        seq = ntohl(th->seq) + datalen;
 395
 396        /* Look up to see if we're just after a \n. */
 397        if (!find_nl_seq(ntohl(th->seq), ct_ftp_info, dir)) {
 398                /* Now if this ends in \n, update ftp info. */
 399                pr_debug("nf_conntrack_ftp: wrong seq pos %s(%u) or %s(%u)\n",
 400                         ct_ftp_info->seq_aft_nl_num[dir] > 0 ? "" : "(UNSET)",
 401                         ct_ftp_info->seq_aft_nl[dir][0],
 402                         ct_ftp_info->seq_aft_nl_num[dir] > 1 ? "" : "(UNSET)",
 403                         ct_ftp_info->seq_aft_nl[dir][1]);
 404                ret = NF_ACCEPT;
 405                goto out_update_nl;
 406        }
 407
 408        /* Initialize IP/IPv6 addr to expected address (it's not mentioned
 409           in EPSV responses) */
 410        cmd.l3num = nf_ct_l3num(ct);
 411        memcpy(cmd.u3.all, &ct->tuplehash[dir].tuple.src.u3.all,
 412               sizeof(cmd.u3.all));
 413
 414        for (i = 0; i < ARRAY_SIZE(search[dir]); i++) {
 415                found = find_pattern(fb_ptr, datalen,
 416                                     search[dir][i].pattern,
 417                                     search[dir][i].plen,
 418                                     search[dir][i].skip,
 419                                     search[dir][i].term,
 420                                     &matchoff, &matchlen,
 421                                     &cmd,
 422                                     search[dir][i].getnum);
 423                if (found) break;
 424        }
 425        if (found == -1) {
 426                /* We don't usually drop packets.  After all, this is
 427                   connection tracking, not packet filtering.
 428                   However, it is necessary for accurate tracking in
 429                   this case. */
 430                if (net_ratelimit())
 431                        printk("conntrack_ftp: partial %s %u+%u\n",
 432                               search[dir][i].pattern,
 433                               ntohl(th->seq), datalen);
 434                ret = NF_DROP;
 435                goto out;
 436        } else if (found == 0) { /* No match */
 437                ret = NF_ACCEPT;
 438                goto out_update_nl;
 439        }
 440
 441        pr_debug("conntrack_ftp: match `%.*s' (%u bytes at %u)\n",
 442                 matchlen, fb_ptr + matchoff,
 443                 matchlen, ntohl(th->seq) + matchoff);
 444
 445        exp = nf_ct_expect_alloc(ct);
 446        if (exp == NULL) {
 447                ret = NF_DROP;
 448                goto out;
 449        }
 450
 451        /* We refer to the reverse direction ("!dir") tuples here,
 452         * because we're expecting something in the other direction.
 453         * Doesn't matter unless NAT is happening.  */
 454        daddr = &ct->tuplehash[!dir].tuple.dst.u3;
 455
 456        /* Update the ftp info */
 457        if ((cmd.l3num == nf_ct_l3num(ct)) &&
 458            memcmp(&cmd.u3.all, &ct->tuplehash[dir].tuple.src.u3.all,
 459                     sizeof(cmd.u3.all))) {
 460                /* Enrico Scholz's passive FTP to partially RNAT'd ftp
 461                   server: it really wants us to connect to a
 462                   different IP address.  Simply don't record it for
 463                   NAT. */
 464                if (cmd.l3num == PF_INET) {
 465                        pr_debug("conntrack_ftp: NOT RECORDING: " NIPQUAD_FMT
 466                                 " != " NIPQUAD_FMT "\n",
 467                                 NIPQUAD(cmd.u3.ip),
 468                                 NIPQUAD(ct->tuplehash[dir].tuple.src.u3.ip));
 469                } else {
 470                        pr_debug("conntrack_ftp: NOT RECORDING: " NIP6_FMT
 471                                 " != " NIP6_FMT "\n",
 472                                 NIP6(*((struct in6_addr *)cmd.u3.ip6)),
 473                                 NIP6(*((struct in6_addr *)
 474                                        ct->tuplehash[dir].tuple.src.u3.ip6)));
 475                }
 476
 477                /* Thanks to Cristiano Lincoln Mattos
 478                   <lincoln@cesar.org.br> for reporting this potential
 479                   problem (DMZ machines opening holes to internal
 480                   networks, or the packet filter itself). */
 481                if (!loose) {
 482                        ret = NF_ACCEPT;
 483                        goto out_put_expect;
 484                }
 485                daddr = &cmd.u3;
 486        }
 487
 488        nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, cmd.l3num,
 489                          &ct->tuplehash[!dir].tuple.src.u3, daddr,
 490                          IPPROTO_TCP, NULL, &cmd.u.tcp.port);
 491
 492        /* Now, NAT might want to mangle the packet, and register the
 493         * (possibly changed) expectation itself. */
 494        nf_nat_ftp = rcu_dereference(nf_nat_ftp_hook);
 495        if (nf_nat_ftp && ct->status & IPS_NAT_MASK)
 496                ret = nf_nat_ftp(skb, ctinfo, search[dir][i].ftptype,
 497                                 matchoff, matchlen, exp);
 498        else {
 499                /* Can't expect this?  Best to drop packet now. */
 500                if (nf_ct_expect_related(exp) != 0)
 501                        ret = NF_DROP;
 502                else
 503                        ret = NF_ACCEPT;
 504        }
 505
 506out_put_expect:
 507        nf_ct_expect_put(exp);
 508
 509out_update_nl:
 510        /* Now if this ends in \n, update ftp info.  Seq may have been
 511         * adjusted by NAT code. */
 512        if (ends_in_nl)
 513                update_nl_seq(ct, seq, ct_ftp_info, dir, skb);
 514 out:
 515        spin_unlock_bh(&nf_ftp_lock);
 516        return ret;
 517}
 518
 519static struct nf_conntrack_helper ftp[MAX_PORTS][2] __read_mostly;
 520static char ftp_names[MAX_PORTS][2][sizeof("ftp-65535")] __read_mostly;
 521
 522static const struct nf_conntrack_expect_policy ftp_exp_policy = {
 523        .max_expected   = 1,
 524        .timeout        = 5 * 60,
 525};
 526
 527/* don't make this __exit, since it's called from __init ! */
 528static void nf_conntrack_ftp_fini(void)
 529{
 530        int i, j;
 531        for (i = 0; i < ports_c; i++) {
 532                for (j = 0; j < 2; j++) {
 533                        if (ftp[i][j].me == NULL)
 534                                continue;
 535
 536                        pr_debug("nf_ct_ftp: unregistering helper for pf: %d "
 537                                 "port: %d\n",
 538                                 ftp[i][j].tuple.src.l3num, ports[i]);
 539                        nf_conntrack_helper_unregister(&ftp[i][j]);
 540                }
 541        }
 542
 543        kfree(ftp_buffer);
 544}
 545
 546static int __init nf_conntrack_ftp_init(void)
 547{
 548        int i, j = -1, ret = 0;
 549        char *tmpname;
 550
 551        ftp_buffer = kmalloc(65536, GFP_KERNEL);
 552        if (!ftp_buffer)
 553                return -ENOMEM;
 554
 555        if (ports_c == 0)
 556                ports[ports_c++] = FTP_PORT;
 557
 558        /* FIXME should be configurable whether IPv4 and IPv6 FTP connections
 559                 are tracked or not - YK */
 560        for (i = 0; i < ports_c; i++) {
 561                ftp[i][0].tuple.src.l3num = PF_INET;
 562                ftp[i][1].tuple.src.l3num = PF_INET6;
 563                for (j = 0; j < 2; j++) {
 564                        ftp[i][j].tuple.src.u.tcp.port = htons(ports[i]);
 565                        ftp[i][j].tuple.dst.protonum = IPPROTO_TCP;
 566                        ftp[i][j].expect_policy = &ftp_exp_policy;
 567                        ftp[i][j].me = THIS_MODULE;
 568                        ftp[i][j].help = help;
 569                        tmpname = &ftp_names[i][j][0];
 570                        if (ports[i] == FTP_PORT)
 571                                sprintf(tmpname, "ftp");
 572                        else
 573                                sprintf(tmpname, "ftp-%d", ports[i]);
 574                        ftp[i][j].name = tmpname;
 575
 576                        pr_debug("nf_ct_ftp: registering helper for pf: %d "
 577                                 "port: %d\n",
 578                                 ftp[i][j].tuple.src.l3num, ports[i]);
 579                        ret = nf_conntrack_helper_register(&ftp[i][j]);
 580                        if (ret) {
 581                                printk("nf_ct_ftp: failed to register helper "
 582                                       " for pf: %d port: %d\n",
 583                                        ftp[i][j].tuple.src.l3num, ports[i]);
 584                                nf_conntrack_ftp_fini();
 585                                return ret;
 586                        }
 587                }
 588        }
 589
 590        return 0;
 591}
 592
 593module_init(nf_conntrack_ftp_init);
 594module_exit(nf_conntrack_ftp_fini);
 595