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