1254401Scy/* 2254401Scy * Copyright (C) 2012 by Darren Reed. 3254401Scy * 4254401Scy * See the IPFILTER.LICENCE file for details on licencing. 5254401Scy * 6254401Scy * $Id: ip_tftp_pxy.c,v 1.1.2.9 2012/07/22 08:04:23 darren_r Exp $ 7254401Scy */ 8254401Scy 9254401Scy#define IPF_TFTP_PROXY 10254401Scy 11254401Scytypedef struct ipf_tftp_softc_s { 12254401Scy int ipf_p_tftp_readonly; 13254401Scy ipftuneable_t *ipf_p_tftp_tune; 14254401Scy} ipf_tftp_softc_t; 15254401Scy 16254401Scyint ipf_p_tftp_backchannel __P((fr_info_t *, ap_session_t *, nat_t *)); 17254401Scyint ipf_p_tftp_client __P((ipf_tftp_softc_t *, fr_info_t *, ap_session_t *, 18254401Scy nat_t *)); 19254401Scyint ipf_p_tftp_in __P((void *, fr_info_t *, ap_session_t *, nat_t *)); 20254401Scyvoid ipf_p_tftp_main_load __P((void)); 21254401Scyvoid ipf_p_tftp_main_unload __P((void)); 22254401Scyint ipf_p_tftp_new __P((void *, fr_info_t *, ap_session_t *, nat_t *)); 23254401Scyvoid ipf_p_tftp_del __P((ipf_main_softc_t *, ap_session_t *)); 24254401Scyint ipf_p_tftp_out __P((void *, fr_info_t *, ap_session_t *, nat_t *)); 25254401Scyint ipf_p_tftp_server __P((ipf_tftp_softc_t *, fr_info_t *, ap_session_t *, 26254401Scy nat_t *)); 27254401Scyvoid *ipf_p_tftp_soft_create __P((ipf_main_softc_t *)); 28254401Scyvoid ipf_p_tftp_soft_destroy __P((ipf_main_softc_t *, void *)); 29254401Scy 30254401Scystatic frentry_t tftpfr; 31254401Scystatic int tftp_proxy_init = 0; 32254401Scy 33254401Scytypedef enum tftp_cmd_e { 34254401Scy TFTP_CMD_READ = 1, 35254401Scy TFTP_CMD_WRITE = 2, 36254401Scy TFTP_CMD_DATA = 3, 37254401Scy TFTP_CMD_ACK = 4, 38254401Scy TFTP_CMD_ERROR = 5 39254401Scy} tftp_cmd_t; 40254401Scy 41254401Scytypedef struct tftpinfo { 42254401Scy tftp_cmd_t ti_lastcmd; 43254401Scy int ti_nextblk; 44254401Scy int ti_lastblk; 45254401Scy int ti_lasterror; 46254401Scy char ti_filename[80]; 47254401Scy ipnat_t *ti_rule; 48254401Scy} tftpinfo_t; 49254401Scy 50254401Scystatic ipftuneable_t ipf_tftp_tuneables[] = { 51254401Scy { { (void *)offsetof(ipf_tftp_softc_t, ipf_p_tftp_readonly) }, 52254401Scy "tftp_read_only", 0, 1, 53254401Scy stsizeof(ipf_tftp_softc_t, ipf_p_tftp_readonly), 54254401Scy 0, NULL, NULL }, 55254401Scy { { NULL }, NULL, 0, 0, 0, 0, NULL, NULL } 56254401Scy}; 57254401Scy 58254401Scy 59254401Scy/* 60254401Scy * TFTP application proxy initialization. 61254401Scy */ 62254401Scyvoid 63254401Scyipf_p_tftp_main_load() 64254401Scy{ 65254401Scy 66254401Scy bzero((char *)&tftpfr, sizeof(tftpfr)); 67254401Scy tftpfr.fr_ref = 1; 68254401Scy tftpfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE; 69254401Scy MUTEX_INIT(&tftpfr.fr_lock, "TFTP proxy rule lock"); 70254401Scy tftp_proxy_init = 1; 71254401Scy} 72254401Scy 73254401Scy 74254401Scyvoid 75254401Scyipf_p_tftp_main_unload() 76254401Scy{ 77254401Scy 78254401Scy if (tftp_proxy_init == 1) { 79254401Scy MUTEX_DESTROY(&tftpfr.fr_lock); 80254401Scy tftp_proxy_init = 0; 81254401Scy } 82254401Scy} 83254401Scy 84254401Scy 85254401Scyvoid * 86254401Scyipf_p_tftp_soft_create(softc) 87254401Scy ipf_main_softc_t *softc; 88254401Scy{ 89254401Scy ipf_tftp_softc_t *softt; 90254401Scy 91254401Scy KMALLOC(softt, ipf_tftp_softc_t *); 92254401Scy if (softt == NULL) 93254401Scy return NULL; 94254401Scy 95254401Scy bzero((char *)softt, sizeof(*softt)); 96254401Scy 97254401Scy softt->ipf_p_tftp_tune = ipf_tune_array_copy(softt, 98254401Scy sizeof(ipf_tftp_tuneables), 99254401Scy ipf_tftp_tuneables); 100254401Scy if (softt->ipf_p_tftp_tune == NULL) { 101254401Scy ipf_p_tftp_soft_destroy(softc, softt); 102254401Scy return NULL; 103254401Scy } 104254401Scy if (ipf_tune_array_link(softc, softt->ipf_p_tftp_tune) == -1) { 105254401Scy ipf_p_tftp_soft_destroy(softc, softt); 106254401Scy return NULL; 107254401Scy } 108254401Scy 109254401Scy softt->ipf_p_tftp_readonly = 1; 110254401Scy 111254401Scy return softt; 112254401Scy} 113254401Scy 114254401Scy 115254401Scyvoid 116254401Scyipf_p_tftp_soft_destroy(softc, arg) 117254401Scy ipf_main_softc_t *softc; 118254401Scy void *arg; 119254401Scy{ 120254401Scy ipf_tftp_softc_t *softt = arg; 121254401Scy 122254401Scy if (softt->ipf_p_tftp_tune != NULL) { 123254401Scy ipf_tune_array_unlink(softc, softt->ipf_p_tftp_tune); 124254401Scy KFREES(softt->ipf_p_tftp_tune, sizeof(ipf_tftp_tuneables)); 125254401Scy softt->ipf_p_tftp_tune = NULL; 126254401Scy } 127254401Scy 128254401Scy KFREE(softt); 129254401Scy} 130254401Scy 131254401Scy 132254401Scyint 133254401Scyipf_p_tftp_out(arg, fin, aps, nat) 134254401Scy void *arg; 135254401Scy fr_info_t *fin; 136254401Scy ap_session_t *aps; 137254401Scy nat_t *nat; 138254401Scy{ 139254401Scy ipf_tftp_softc_t *softt = arg; 140254401Scy 141254401Scy fin->fin_flx |= FI_NOWILD; 142254401Scy if (nat->nat_dir == NAT_OUTBOUND) 143254401Scy return ipf_p_tftp_client(softt, fin, aps, nat); 144254401Scy return ipf_p_tftp_server(softt, fin, aps, nat); 145254401Scy} 146254401Scy 147254401Scy 148254401Scyint 149254401Scyipf_p_tftp_in(arg, fin, aps, nat) 150254401Scy void *arg; 151254401Scy fr_info_t *fin; 152254401Scy ap_session_t *aps; 153254401Scy nat_t *nat; 154254401Scy{ 155254401Scy ipf_tftp_softc_t *softt = arg; 156254401Scy 157254401Scy fin->fin_flx |= FI_NOWILD; 158254401Scy if (nat->nat_dir == NAT_INBOUND) 159254401Scy return ipf_p_tftp_client(softt, fin, aps, nat); 160254401Scy return ipf_p_tftp_server(softt, fin, aps, nat); 161254401Scy} 162254401Scy 163254401Scy 164254401Scyint 165254401Scyipf_p_tftp_new(arg, fin, aps, nat) 166254401Scy void *arg; 167254401Scy fr_info_t *fin; 168254401Scy ap_session_t *aps; 169254401Scy nat_t *nat; 170254401Scy{ 171254401Scy udphdr_t *udp; 172254401Scy tftpinfo_t *ti; 173254401Scy ipnat_t *ipn; 174254401Scy ipnat_t *np; 175254401Scy int size; 176254401Scy 177254401Scy fin = fin; /* LINT */ 178254401Scy 179254401Scy np = nat->nat_ptr; 180254401Scy size = np->in_size; 181254401Scy 182254401Scy KMALLOC(ti, tftpinfo_t *); 183254401Scy if (ti == NULL) 184254401Scy return -1; 185254401Scy KMALLOCS(ipn, ipnat_t *, size); 186254401Scy if (ipn == NULL) { 187254401Scy KFREE(ti); 188254401Scy return -1; 189254401Scy } 190254401Scy 191254401Scy aps->aps_data = ti; 192254401Scy aps->aps_psiz = sizeof(*ti); 193254401Scy bzero((char *)ti, sizeof(*ti)); 194254401Scy bzero((char *)ipn, size); 195254401Scy ti->ti_rule = ipn; 196254401Scy 197254401Scy udp = (udphdr_t *)fin->fin_dp; 198254401Scy aps->aps_sport = udp->uh_sport; 199254401Scy aps->aps_dport = udp->uh_dport; 200254401Scy 201254401Scy ipn->in_size = size; 202254401Scy ipn->in_apr = NULL; 203254401Scy ipn->in_use = 1; 204254401Scy ipn->in_hits = 1; 205254401Scy ipn->in_ippip = 1; 206254401Scy ipn->in_pr[0] = IPPROTO_UDP; 207254401Scy ipn->in_pr[1] = IPPROTO_UDP; 208254401Scy ipn->in_ifps[0] = nat->nat_ifps[0]; 209254401Scy ipn->in_ifps[1] = nat->nat_ifps[1]; 210254401Scy ipn->in_v[0] = nat->nat_ptr->in_v[1]; 211254401Scy ipn->in_v[1] = nat->nat_ptr->in_v[0]; 212254401Scy ipn->in_flags = IPN_UDP|IPN_FIXEDDPORT|IPN_PROXYRULE; 213254401Scy 214254401Scy ipn->in_nsrcip6 = nat->nat_odst6; 215254401Scy ipn->in_osrcip6 = nat->nat_ndst6; 216254401Scy 217254401Scy if ((np->in_redir & NAT_REDIRECT) != 0) { 218254401Scy ipn->in_redir = NAT_MAP; 219254401Scy if (ipn->in_v[0] == 4) { 220254401Scy ipn->in_snip = ntohl(nat->nat_odstaddr); 221254401Scy ipn->in_dnip = ntohl(nat->nat_nsrcaddr); 222254401Scy } else { 223254401Scy#ifdef USE_INET6 224254401Scy ipn->in_snip6 = nat->nat_odst6; 225254401Scy ipn->in_dnip6 = nat->nat_nsrc6; 226254401Scy#endif 227254401Scy } 228254401Scy ipn->in_ndstip6 = nat->nat_nsrc6; 229254401Scy ipn->in_odstip6 = nat->nat_osrc6; 230254401Scy } else { 231254401Scy ipn->in_redir = NAT_REDIRECT; 232254401Scy if (ipn->in_v[0] == 4) { 233254401Scy ipn->in_snip = ntohl(nat->nat_odstaddr); 234254401Scy ipn->in_dnip = ntohl(nat->nat_osrcaddr); 235254401Scy } else { 236254401Scy#ifdef USE_INET6 237254401Scy ipn->in_snip6 = nat->nat_odst6; 238254401Scy ipn->in_dnip6 = nat->nat_osrc6; 239254401Scy#endif 240254401Scy } 241254401Scy ipn->in_ndstip6 = nat->nat_osrc6; 242254401Scy ipn->in_odstip6 = nat->nat_nsrc6; 243254401Scy } 244254401Scy ipn->in_odport = htons(fin->fin_sport); 245254401Scy ipn->in_ndport = htons(fin->fin_sport); 246254401Scy 247254401Scy IP6_SETONES(&ipn->in_osrcmsk6); 248254401Scy IP6_SETONES(&ipn->in_nsrcmsk6); 249254401Scy IP6_SETONES(&ipn->in_odstmsk6); 250254401Scy IP6_SETONES(&ipn->in_ndstmsk6); 251254401Scy MUTEX_INIT(&ipn->in_lock, "tftp proxy NAT rule"); 252254401Scy 253254401Scy ipn->in_namelen = np->in_namelen; 254254401Scy bcopy(np->in_names, ipn->in_ifnames, ipn->in_namelen); 255254401Scy ipn->in_ifnames[0] = np->in_ifnames[0]; 256254401Scy ipn->in_ifnames[1] = np->in_ifnames[1]; 257254401Scy 258254401Scy ti->ti_lastcmd = 0; 259254401Scy 260254401Scy return 0; 261254401Scy} 262254401Scy 263254401Scy 264254401Scyvoid 265254401Scyipf_p_tftp_del(softc, aps) 266254401Scy ipf_main_softc_t *softc; 267254401Scy ap_session_t *aps; 268254401Scy{ 269254401Scy tftpinfo_t *tftp; 270254401Scy 271254401Scy tftp = aps->aps_data; 272254401Scy if (tftp != NULL) { 273254401Scy tftp->ti_rule->in_flags |= IPN_DELETE; 274254401Scy ipf_nat_rule_deref(softc, &tftp->ti_rule); 275254401Scy } 276254401Scy} 277254401Scy 278254401Scy 279254401Scy/* 280254401Scy * Setup for a new TFTP proxy. 281254401Scy */ 282254401Scyint 283254401Scyipf_p_tftp_backchannel(fin, aps, nat) 284254401Scy fr_info_t *fin; 285254401Scy ap_session_t *aps; 286254401Scy nat_t *nat; 287254401Scy{ 288254401Scy ipf_main_softc_t *softc = fin->fin_main_soft; 289254401Scy#ifdef USE_MUTEXES 290254401Scy ipf_nat_softc_t *softn = softc->ipf_nat_soft; 291254401Scy#endif 292254401Scy#ifdef USE_INET6 293254401Scy i6addr_t swip6, sw2ip6; 294254401Scy ip6_t *ip6; 295254401Scy#endif 296254401Scy struct in_addr swip, sw2ip; 297254401Scy tftpinfo_t *ti; 298254401Scy udphdr_t udp; 299254401Scy fr_info_t fi; 300256253Sdim u_short slen = 0; /* silence gcc */ 301254401Scy nat_t *nat2; 302254401Scy int nflags; 303254401Scy ip_t *ip; 304254401Scy int dir; 305254401Scy 306254401Scy ti = aps->aps_data; 307254401Scy /* 308254401Scy * Add skeleton NAT entry for connection which will come back the 309254401Scy * other way. 310254401Scy */ 311254401Scy bcopy((char *)fin, (char *)&fi, sizeof(fi)); 312254401Scy fi.fin_flx |= FI_IGNORE; 313254401Scy fi.fin_data[1] = 0; 314254401Scy 315254401Scy bzero((char *)&udp, sizeof(udp)); 316254401Scy udp.uh_sport = 0; /* XXX - don't specify remote port */ 317254401Scy udp.uh_dport = ti->ti_rule->in_ndport; 318254401Scy udp.uh_ulen = htons(sizeof(udp)); 319254401Scy udp.uh_sum = 0; 320254401Scy 321254401Scy fi.fin_fr = &tftpfr; 322254401Scy fi.fin_dp = (char *)&udp; 323254401Scy fi.fin_sport = 0; 324254401Scy fi.fin_dport = ntohs(ti->ti_rule->in_ndport); 325254401Scy fi.fin_dlen = sizeof(udp); 326254401Scy fi.fin_plen = fi.fin_hlen + sizeof(udp); 327254401Scy fi.fin_flx &= FI_LOWTTL|FI_FRAG|FI_TCPUDP|FI_OPTIONS|FI_IGNORE; 328254401Scy nflags = NAT_SLAVE|IPN_UDP|SI_W_SPORT; 329254401Scy#ifdef USE_INET6 330254401Scy ip6 = (ip6_t *)fin->fin_ip; 331254401Scy#endif 332254401Scy ip = fin->fin_ip; 333254401Scy sw2ip.s_addr = 0; 334254401Scy swip.s_addr = 0; 335254401Scy 336254401Scy fi.fin_src6 = nat->nat_ndst6; 337254401Scy fi.fin_dst6 = nat->nat_nsrc6; 338254401Scy if (nat->nat_v[0] == 4) { 339254401Scy slen = ip->ip_len; 340254401Scy ip->ip_len = htons(fin->fin_hlen + sizeof(udp)); 341254401Scy swip = ip->ip_src; 342254401Scy sw2ip = ip->ip_dst; 343254401Scy ip->ip_src = nat->nat_ndstip; 344254401Scy ip->ip_dst = nat->nat_nsrcip; 345254401Scy } else { 346254401Scy#ifdef USE_INET6 347254401Scy slen = ip6->ip6_plen; 348254401Scy ip6->ip6_plen = htons(sizeof(udp)); 349254401Scy swip6.in6 = ip6->ip6_src; 350254401Scy sw2ip6.in6 = ip6->ip6_dst; 351254401Scy ip6->ip6_src = nat->nat_ndst6.in6; 352254401Scy ip6->ip6_dst = nat->nat_nsrc6.in6; 353254401Scy#endif 354254401Scy } 355254401Scy 356254401Scy if (nat->nat_dir == NAT_INBOUND) { 357254401Scy dir = NAT_OUTBOUND; 358254401Scy fi.fin_out = 1; 359254401Scy } else { 360254401Scy dir = NAT_INBOUND; 361254401Scy fi.fin_out = 0; 362254401Scy } 363254401Scy nflags |= NAT_NOTRULEPORT; 364254401Scy 365254401Scy MUTEX_ENTER(&softn->ipf_nat_new); 366255332Scy#ifdef USE_INET6 367255332Scy if (nat->nat_v[0] == 6) 368255332Scy nat2 = ipf_nat6_add(&fi, ti->ti_rule, NULL, nflags, dir); 369255332Scy else 370255332Scy#endif 371254401Scy nat2 = ipf_nat_add(&fi, ti->ti_rule, NULL, nflags, dir); 372254401Scy MUTEX_EXIT(&softn->ipf_nat_new); 373254401Scy if (nat2 != NULL) { 374254401Scy (void) ipf_nat_proto(&fi, nat2, IPN_UDP); 375254401Scy ipf_nat_update(&fi, nat2); 376254401Scy fi.fin_ifp = NULL; 377254401Scy if (ti->ti_rule->in_redir == NAT_MAP) { 378254401Scy fi.fin_src6 = nat->nat_ndst6; 379254401Scy fi.fin_dst6 = nat->nat_nsrc6; 380254401Scy if (nat->nat_v[0] == 4) { 381254401Scy ip->ip_src = nat->nat_ndstip; 382254401Scy ip->ip_dst = nat->nat_nsrcip; 383254401Scy } else { 384254401Scy#ifdef USE_INET6 385254401Scy ip6->ip6_src = nat->nat_ndst6.in6; 386254401Scy ip6->ip6_dst = nat->nat_nsrc6.in6; 387254401Scy#endif 388254401Scy } 389254401Scy } else { 390254401Scy fi.fin_src6 = nat->nat_odst6; 391254401Scy fi.fin_dst6 = nat->nat_osrc6; 392254401Scy if (fin->fin_v == 4) { 393254401Scy ip->ip_src = nat->nat_odstip; 394254401Scy ip->ip_dst = nat->nat_osrcip; 395254401Scy } else { 396254401Scy#ifdef USE_INET6 397254401Scy ip6->ip6_src = nat->nat_odst6.in6; 398254401Scy ip6->ip6_dst = nat->nat_osrc6.in6; 399254401Scy#endif 400254401Scy } 401254401Scy } 402254401Scy if (ipf_state_add(softc, &fi, NULL, SI_W_SPORT) != 0) { 403254401Scy ipf_nat_setpending(softc, nat2); 404254401Scy } 405254401Scy } 406254401Scy if (nat->nat_v[0] == 4) { 407254401Scy ip->ip_len = slen; 408254401Scy ip->ip_src = swip; 409254401Scy ip->ip_dst = sw2ip; 410254401Scy } else { 411254401Scy#ifdef USE_INET6 412254401Scy ip6->ip6_plen = slen; 413254401Scy ip6->ip6_src = swip6.in6; 414254401Scy ip6->ip6_dst = sw2ip6.in6; 415254401Scy#endif 416254401Scy } 417254401Scy return 0; 418254401Scy} 419254401Scy 420254401Scy 421254401Scyint 422254401Scyipf_p_tftp_client(softt, fin, aps, nat) 423254401Scy ipf_tftp_softc_t *softt; 424254401Scy fr_info_t *fin; 425254401Scy ap_session_t *aps; 426254401Scy nat_t *nat; 427254401Scy{ 428254401Scy u_char *msg, *s, *t; 429254401Scy tftpinfo_t *ti; 430254401Scy u_short opcode; 431254401Scy udphdr_t *udp; 432254401Scy int len; 433254401Scy 434254401Scy if (fin->fin_dlen < 4) 435254401Scy return 0; 436254401Scy 437254401Scy ti = aps->aps_data; 438254401Scy msg = fin->fin_dp; 439254401Scy msg += sizeof(udphdr_t); 440254401Scy opcode = (msg[0] << 8) | msg[1]; 441254401Scy DT3(tftp_cmd, fr_info_t *, fin, int, opcode, nat_t *, nat); 442254401Scy 443254401Scy switch (opcode) 444254401Scy { 445254401Scy case TFTP_CMD_WRITE : 446254401Scy if (softt->ipf_p_tftp_readonly != 0) 447254401Scy break; 448254401Scy /* FALLTHROUGH */ 449254401Scy case TFTP_CMD_READ : 450254401Scy len = fin->fin_dlen - sizeof(*udp) - 2; 451254401Scy if (len > sizeof(ti->ti_filename) - 1) 452254401Scy len = sizeof(ti->ti_filename) - 1; 453254401Scy s = msg + 2; 454254401Scy for (t = (u_char *)ti->ti_filename; (len > 0); len--, s++) { 455254401Scy *t++ = *s; 456254401Scy if (*s == '\0') 457254401Scy break; 458254401Scy } 459254401Scy ipf_p_tftp_backchannel(fin, aps, nat); 460254401Scy break; 461254401Scy default : 462254401Scy return -1; 463254401Scy } 464254401Scy 465254401Scy ti = aps->aps_data; 466254401Scy ti->ti_lastcmd = opcode; 467254401Scy return 0; 468254401Scy} 469254401Scy 470254401Scy 471254401Scyint 472254401Scyipf_p_tftp_server(softt, fin, aps, nat) 473254401Scy ipf_tftp_softc_t *softt; 474254401Scy fr_info_t *fin; 475254401Scy ap_session_t *aps; 476254401Scy nat_t *nat; 477254401Scy{ 478254401Scy tftpinfo_t *ti; 479254401Scy u_short opcode; 480254401Scy u_short arg; 481254401Scy u_char *msg; 482254401Scy 483254401Scy if (fin->fin_dlen < 4) 484254401Scy return 0; 485254401Scy 486254401Scy ti = aps->aps_data; 487254401Scy msg = fin->fin_dp; 488254401Scy msg += sizeof(udphdr_t); 489254401Scy arg = (msg[2] << 8) | msg[3]; 490254401Scy opcode = (msg[0] << 8) | msg[1]; 491254401Scy 492254401Scy switch (opcode) 493254401Scy { 494254401Scy case TFTP_CMD_ACK : 495254401Scy ti->ti_lastblk = arg; 496254401Scy break; 497254401Scy 498254401Scy case TFTP_CMD_ERROR : 499254401Scy ti->ti_lasterror = arg; 500254401Scy break; 501254401Scy 502254401Scy default : 503254401Scy return -1; 504254401Scy } 505254401Scy 506254401Scy ti->ti_lastcmd = opcode; 507254401Scy return 0; 508254401Scy} 509