1254401Scy/* 2254401Scy * Copyright (C) 2012 by Darren Reed. 3254401Scy * 4254401Scy * See the IPFILTER.LICENCE file for details on licencing. 5254401Scy * 6254401Scy * $Id: ip_dns_pxy.c,v 1.1.2.10 2012/07/22 08:04:23 darren_r Exp $ 7254401Scy */ 8254401Scy 9254401Scy#define IPF_DNS_PROXY 10254401Scy 11254401Scy/* 12254401Scy * map ... proxy port dns/udp 53 { block .cnn.com; } 13254401Scy */ 14254401Scytypedef struct ipf_dns_filter { 15254401Scy struct ipf_dns_filter *idns_next; 16254401Scy char *idns_name; 17254401Scy int idns_namelen; 18254401Scy int idns_pass; 19254401Scy} ipf_dns_filter_t; 20254401Scy 21254401Scy 22254401Scytypedef struct ipf_dns_softc_s { 23254401Scy ipf_dns_filter_t *ipf_p_dns_list; 24254401Scy ipfrwlock_t ipf_p_dns_rwlock; 25254401Scy u_long ipf_p_dns_compress; 26254401Scy u_long ipf_p_dns_toolong; 27254401Scy u_long ipf_p_dns_nospace; 28254401Scy} ipf_dns_softc_t; 29254401Scy 30254401Scyint ipf_p_dns_allow_query __P((ipf_dns_softc_t *, dnsinfo_t *)); 31254401Scyint ipf_p_dns_ctl __P((ipf_main_softc_t *, void *, ap_ctl_t *)); 32272996Scyvoid ipf_p_dns_del __P((ipf_main_softc_t *, ap_session_t *)); 33254401Scyint ipf_p_dns_get_name __P((ipf_dns_softc_t *, char *, int, char *, int)); 34254401Scyint ipf_p_dns_inout __P((void *, fr_info_t *, ap_session_t *, nat_t *)); 35254401Scyint ipf_p_dns_match __P((fr_info_t *, ap_session_t *, nat_t *)); 36254401Scyint ipf_p_dns_match_names __P((ipf_dns_filter_t *, char *, int)); 37254401Scyint ipf_p_dns_new __P((void *, fr_info_t *, ap_session_t *, nat_t *)); 38254401Scyvoid *ipf_p_dns_soft_create __P((ipf_main_softc_t *)); 39254401Scyvoid ipf_p_dns_soft_destroy __P((ipf_main_softc_t *, void *)); 40254401Scy 41254401Scytypedef struct { 42254401Scy u_char dns_id[2]; 43254401Scy u_short dns_ctlword; 44254401Scy u_short dns_qdcount; 45254401Scy u_short dns_ancount; 46254401Scy u_short dns_nscount; 47254401Scy u_short dns_arcount; 48254401Scy} ipf_dns_hdr_t; 49254401Scy 50254401Scy#define DNS_QR(x) ((ntohs(x) & 0x8000) >> 15) 51254401Scy#define DNS_OPCODE(x) ((ntohs(x) & 0x7800) >> 11) 52254401Scy#define DNS_AA(x) ((ntohs(x) & 0x0400) >> 10) 53254401Scy#define DNS_TC(x) ((ntohs(x) & 0x0200) >> 9) 54254401Scy#define DNS_RD(x) ((ntohs(x) & 0x0100) >> 8) 55254401Scy#define DNS_RA(x) ((ntohs(x) & 0x0080) >> 7) 56254401Scy#define DNS_Z(x) ((ntohs(x) & 0x0070) >> 4) 57254401Scy#define DNS_RCODE(x) ((ntohs(x) & 0x000f) >> 0) 58254401Scy 59254401Scy 60254401Scyvoid * 61254401Scyipf_p_dns_soft_create(softc) 62254401Scy ipf_main_softc_t *softc; 63254401Scy{ 64254401Scy ipf_dns_softc_t *softd; 65254401Scy 66254401Scy KMALLOC(softd, ipf_dns_softc_t *); 67254401Scy if (softd == NULL) 68254401Scy return NULL; 69254401Scy 70254401Scy bzero((char *)softd, sizeof(*softd)); 71254401Scy RWLOCK_INIT(&softd->ipf_p_dns_rwlock, "ipf dns rwlock"); 72254401Scy 73254401Scy return softd; 74254401Scy} 75254401Scy 76254401Scy 77254401Scyvoid 78254401Scyipf_p_dns_soft_destroy(softc, arg) 79254401Scy ipf_main_softc_t *softc; 80254401Scy void *arg; 81254401Scy{ 82254401Scy ipf_dns_softc_t *softd = arg; 83254401Scy ipf_dns_filter_t *idns; 84254401Scy 85254401Scy while ((idns = softd->ipf_p_dns_list) != NULL) { 86254401Scy KFREES(idns->idns_name, idns->idns_namelen); 87254401Scy idns->idns_name = NULL; 88254401Scy idns->idns_namelen = 0; 89254401Scy softd->ipf_p_dns_list = idns->idns_next; 90254401Scy KFREE(idns); 91254401Scy } 92254401Scy RW_DESTROY(&softd->ipf_p_dns_rwlock); 93254401Scy 94254401Scy KFREE(softd); 95254401Scy} 96254401Scy 97254401Scy 98254401Scyint 99254401Scyipf_p_dns_ctl(softc, arg, ctl) 100254401Scy ipf_main_softc_t *softc; 101254401Scy void *arg; 102254401Scy ap_ctl_t *ctl; 103254401Scy{ 104254401Scy ipf_dns_softc_t *softd = arg; 105254401Scy ipf_dns_filter_t *tmp, *idns, **idnsp; 106254401Scy int error = 0; 107254401Scy 108254401Scy /* 109254401Scy * To make locking easier. 110254401Scy */ 111254401Scy KMALLOC(tmp, ipf_dns_filter_t *); 112254401Scy 113254401Scy WRITE_ENTER(&softd->ipf_p_dns_rwlock); 114254401Scy for (idnsp = &softd->ipf_p_dns_list; (idns = *idnsp) != NULL; 115254401Scy idnsp = &idns->idns_next) { 116254401Scy if (idns->idns_namelen != ctl->apc_dsize) 117254401Scy continue; 118254401Scy if (!strncmp(ctl->apc_data, idns->idns_name, 119254401Scy idns->idns_namelen)) 120254401Scy break; 121254401Scy } 122254401Scy 123254401Scy switch (ctl->apc_cmd) 124254401Scy { 125254401Scy case APC_CMD_DEL : 126254401Scy if (idns == NULL) { 127254401Scy IPFERROR(80006); 128254401Scy error = ESRCH; 129254401Scy break; 130254401Scy } 131254401Scy *idnsp = idns->idns_next; 132254401Scy idns->idns_next = NULL; 133254401Scy KFREES(idns->idns_name, idns->idns_namelen); 134254401Scy idns->idns_name = NULL; 135254401Scy idns->idns_namelen = 0; 136254401Scy KFREE(idns); 137254401Scy break; 138254401Scy case APC_CMD_ADD : 139254401Scy if (idns != NULL) { 140254401Scy IPFERROR(80007); 141254401Scy error = EEXIST; 142254401Scy break; 143254401Scy } 144254401Scy if (tmp == NULL) { 145254401Scy IPFERROR(80008); 146254401Scy error = ENOMEM; 147254401Scy break; 148254401Scy } 149254401Scy idns = tmp; 150254401Scy tmp = NULL; 151254401Scy idns->idns_namelen = ctl->apc_dsize; 152254401Scy idns->idns_name = ctl->apc_data; 153254401Scy idns->idns_pass = ctl->apc_arg; 154254401Scy idns->idns_next = NULL; 155254401Scy *idnsp = idns; 156254401Scy ctl->apc_data = NULL; 157254401Scy ctl->apc_dsize = 0; 158254401Scy break; 159254401Scy default : 160254401Scy IPFERROR(80009); 161254401Scy error = EINVAL; 162254401Scy break; 163254401Scy } 164254401Scy RWLOCK_EXIT(&softd->ipf_p_dns_rwlock); 165254401Scy 166254401Scy if (tmp != NULL) { 167254401Scy KFREE(tmp); 168254401Scy tmp = NULL; 169254401Scy } 170254401Scy 171254401Scy return error; 172254401Scy} 173254401Scy 174254401Scy 175254401Scy/* ARGSUSED */ 176254401Scyint 177254401Scyipf_p_dns_new(arg, fin, aps, nat) 178254401Scy void *arg; 179254401Scy fr_info_t *fin; 180254401Scy ap_session_t *aps; 181254401Scy nat_t *nat; 182254401Scy{ 183254401Scy dnsinfo_t *di; 184254401Scy int dlen; 185254401Scy 186254401Scy if (fin->fin_v != 4) 187254401Scy return -1; 188254401Scy 189254401Scy dlen = fin->fin_dlen - sizeof(udphdr_t); 190254401Scy if (dlen < sizeof(ipf_dns_hdr_t)) { 191254401Scy /* 192254401Scy * No real DNS packet is smaller than that. 193254401Scy */ 194254401Scy return -1; 195254401Scy } 196254401Scy 197254401Scy aps->aps_psiz = sizeof(dnsinfo_t); 198254401Scy KMALLOCS(di, dnsinfo_t *, sizeof(dnsinfo_t)); 199254401Scy if (di == NULL) { 200254401Scy printf("ipf_dns_new:KMALLOCS(%d) failed\n", sizeof(*di)); 201254401Scy return -1; 202254401Scy } 203254401Scy 204254401Scy MUTEX_INIT(&di->dnsi_lock, "dns lock"); 205254401Scy 206254401Scy aps->aps_data = di; 207254401Scy 208254401Scy dlen = fin->fin_dlen - sizeof(udphdr_t); 209254401Scy COPYDATA(fin->fin_m, fin->fin_hlen + sizeof(udphdr_t), 210254401Scy MIN(dlen, sizeof(di->dnsi_buffer)), di->dnsi_buffer); 211254401Scy di->dnsi_id = (di->dnsi_buffer[0] << 8) | di->dnsi_buffer[1]; 212254401Scy return 0; 213254401Scy} 214254401Scy 215254401Scy 216254401Scy/* ARGSUSED */ 217272996Scyvoid 218254401Scyipf_p_dns_del(softc, aps) 219254401Scy ipf_main_softc_t *softc; 220254401Scy ap_session_t *aps; 221254401Scy{ 222254401Scy#ifdef USE_MUTEXES 223254401Scy dnsinfo_t *di = aps->aps_data; 224254401Scy 225254401Scy MUTEX_DESTROY(&di->dnsi_lock); 226254401Scy#endif 227254401Scy KFREES(aps->aps_data, aps->aps_psiz); 228254401Scy aps->aps_data = NULL; 229254401Scy aps->aps_psiz = 0; 230254401Scy} 231254401Scy 232254401Scy 233254401Scy/* 234254401Scy * Tries to match the base string (in our ACL) with the query from a packet. 235254401Scy */ 236254401Scyint 237254401Scyipf_p_dns_match_names(idns, query, qlen) 238254401Scy ipf_dns_filter_t *idns; 239254401Scy char *query; 240254401Scy int qlen; 241254401Scy{ 242254401Scy int blen; 243254401Scy char *base; 244254401Scy 245254401Scy blen = idns->idns_namelen; 246254401Scy base = idns->idns_name; 247254401Scy 248254401Scy if (blen > qlen) 249254401Scy return 1; 250254401Scy 251254401Scy if (blen == qlen) 252254401Scy return strncasecmp(base, query, qlen); 253254401Scy 254254401Scy /* 255254401Scy * If the base string string is shorter than the query, allow the 256254401Scy * tail of the base to match the same length tail of the query *if*: 257254401Scy * - the base string starts with a '*' (*cnn.com) 258254401Scy * - the base string represents a domain (.cnn.com) 259254401Scy * as otherwise it would not be possible to block just "cnn.com" 260254401Scy * without also impacting "foocnn.com", etc. 261254401Scy */ 262254401Scy if (*base == '*') { 263254401Scy base++; 264254401Scy blen--; 265254401Scy } else if (*base != '.') 266254401Scy return 1; 267254401Scy 268254401Scy return strncasecmp(base, query + qlen - blen, blen); 269254401Scy} 270254401Scy 271254401Scy 272254401Scyint 273254401Scyipf_p_dns_get_name(softd, start, len, buffer, buflen) 274254401Scy ipf_dns_softc_t *softd; 275254401Scy char *start; 276254401Scy int len; 277254401Scy char *buffer; 278254401Scy int buflen; 279254401Scy{ 280254401Scy char *s, *t, clen; 281254401Scy int slen, blen; 282254401Scy 283254401Scy s = start; 284254401Scy t = buffer; 285254401Scy slen = len; 286254401Scy blen = buflen - 1; /* Always make room for trailing \0 */ 287254401Scy 288254401Scy while (*s != '\0') { 289254401Scy clen = *s; 290254401Scy if ((clen & 0xc0) == 0xc0) { /* Doesn't do compression */ 291254401Scy softd->ipf_p_dns_compress++; 292254401Scy return 0; 293254401Scy } 294254401Scy if (clen > slen) { 295254401Scy softd->ipf_p_dns_toolong++; 296254401Scy return 0; /* Does the name run off the end? */ 297254401Scy } 298254401Scy if ((clen + 1) > blen) { 299254401Scy softd->ipf_p_dns_nospace++; 300254401Scy return 0; /* Enough room for name+.? */ 301254401Scy } 302254401Scy s++; 303254401Scy bcopy(s, t, clen); 304254401Scy t += clen; 305254401Scy s += clen; 306254401Scy *t++ = '.'; 307254401Scy slen -= clen; 308254401Scy blen -= (clen + 1); 309254401Scy } 310254401Scy 311254401Scy *(t - 1) = '\0'; 312254401Scy return s - start; 313254401Scy} 314254401Scy 315254401Scy 316254401Scyint 317254401Scyipf_p_dns_allow_query(softd, dnsi) 318254401Scy ipf_dns_softc_t *softd; 319254401Scy dnsinfo_t *dnsi; 320254401Scy{ 321254401Scy ipf_dns_filter_t *idns; 322254401Scy int len; 323254401Scy 324254401Scy len = strlen(dnsi->dnsi_buffer); 325254401Scy 326254401Scy for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next) 327254401Scy if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0) 328254401Scy return idns->idns_pass; 329254401Scy return 0; 330254401Scy} 331254401Scy 332254401Scy 333254401Scy/* ARGSUSED */ 334254401Scyint 335254401Scyipf_p_dns_inout(arg, fin, aps, nat) 336254401Scy void *arg; 337254401Scy fr_info_t *fin; 338254401Scy ap_session_t *aps; 339254401Scy nat_t *nat; 340254401Scy{ 341254401Scy ipf_dns_softc_t *softd = arg; 342254401Scy ipf_dns_hdr_t *dns; 343254401Scy dnsinfo_t *di; 344254401Scy char *data; 345254401Scy int dlen, q, rc = 0; 346254401Scy 347254401Scy if (fin->fin_dlen < sizeof(*dns)) 348254401Scy return APR_ERR(1); 349254401Scy 350254401Scy dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t)); 351254401Scy 352254401Scy q = dns->dns_qdcount; 353254401Scy 354254401Scy data = (char *)(dns + 1); 355254401Scy dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t); 356254401Scy 357254401Scy di = aps->aps_data; 358254401Scy 359254401Scy READ_ENTER(&softd->ipf_p_dns_rwlock); 360254401Scy MUTEX_ENTER(&di->dnsi_lock); 361254401Scy 362254401Scy for (; (dlen > 0) && (q > 0); q--) { 363254401Scy int len; 364254401Scy 365254401Scy len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer, 366254401Scy sizeof(di->dnsi_buffer)); 367254401Scy if (len == 0) { 368254401Scy rc = 1; 369254401Scy break; 370254401Scy } 371254401Scy rc = ipf_p_dns_allow_query(softd, di); 372254401Scy if (rc != 0) 373254401Scy break; 374254401Scy data += len; 375254401Scy dlen -= len; 376254401Scy } 377254401Scy MUTEX_EXIT(&di->dnsi_lock); 378254401Scy RWLOCK_EXIT(&softd->ipf_p_dns_rwlock); 379254401Scy 380254401Scy return APR_ERR(rc); 381254401Scy} 382254401Scy 383254401Scy 384254401Scy/* ARGSUSED */ 385254401Scyint 386254401Scyipf_p_dns_match(fin, aps, nat) 387254401Scy fr_info_t *fin; 388254401Scy ap_session_t *aps; 389254401Scy nat_t *nat; 390254401Scy{ 391254401Scy dnsinfo_t *di = aps->aps_data; 392254401Scy ipf_dns_hdr_t *dnh; 393254401Scy 394254401Scy if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG)) 395254401Scy return -1; 396254401Scy 397254401Scy dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t)); 398254401Scy if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id) 399254401Scy return -1; 400254401Scy return 0; 401254401Scy} 402