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 *)); 32254401Scyint 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 */ 217254401Scyint 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 return 0; 231254401Scy} 232254401Scy 233254401Scy 234254401Scy/* 235254401Scy * Tries to match the base string (in our ACL) with the query from a packet. 236254401Scy */ 237254401Scyint 238254401Scyipf_p_dns_match_names(idns, query, qlen) 239254401Scy ipf_dns_filter_t *idns; 240254401Scy char *query; 241254401Scy int qlen; 242254401Scy{ 243254401Scy int blen; 244254401Scy char *base; 245254401Scy 246254401Scy blen = idns->idns_namelen; 247254401Scy base = idns->idns_name; 248254401Scy 249254401Scy if (blen > qlen) 250254401Scy return 1; 251254401Scy 252254401Scy if (blen == qlen) 253254401Scy return strncasecmp(base, query, qlen); 254254401Scy 255254401Scy /* 256254401Scy * If the base string string is shorter than the query, allow the 257254401Scy * tail of the base to match the same length tail of the query *if*: 258254401Scy * - the base string starts with a '*' (*cnn.com) 259254401Scy * - the base string represents a domain (.cnn.com) 260254401Scy * as otherwise it would not be possible to block just "cnn.com" 261254401Scy * without also impacting "foocnn.com", etc. 262254401Scy */ 263254401Scy if (*base == '*') { 264254401Scy base++; 265254401Scy blen--; 266254401Scy } else if (*base != '.') 267254401Scy return 1; 268254401Scy 269254401Scy return strncasecmp(base, query + qlen - blen, blen); 270254401Scy} 271254401Scy 272254401Scy 273254401Scyint 274254401Scyipf_p_dns_get_name(softd, start, len, buffer, buflen) 275254401Scy ipf_dns_softc_t *softd; 276254401Scy char *start; 277254401Scy int len; 278254401Scy char *buffer; 279254401Scy int buflen; 280254401Scy{ 281254401Scy char *s, *t, clen; 282254401Scy int slen, blen; 283254401Scy 284254401Scy s = start; 285254401Scy t = buffer; 286254401Scy slen = len; 287254401Scy blen = buflen - 1; /* Always make room for trailing \0 */ 288254401Scy 289254401Scy while (*s != '\0') { 290254401Scy clen = *s; 291254401Scy if ((clen & 0xc0) == 0xc0) { /* Doesn't do compression */ 292254401Scy softd->ipf_p_dns_compress++; 293254401Scy return 0; 294254401Scy } 295254401Scy if (clen > slen) { 296254401Scy softd->ipf_p_dns_toolong++; 297254401Scy return 0; /* Does the name run off the end? */ 298254401Scy } 299254401Scy if ((clen + 1) > blen) { 300254401Scy softd->ipf_p_dns_nospace++; 301254401Scy return 0; /* Enough room for name+.? */ 302254401Scy } 303254401Scy s++; 304254401Scy bcopy(s, t, clen); 305254401Scy t += clen; 306254401Scy s += clen; 307254401Scy *t++ = '.'; 308254401Scy slen -= clen; 309254401Scy blen -= (clen + 1); 310254401Scy } 311254401Scy 312254401Scy *(t - 1) = '\0'; 313254401Scy return s - start; 314254401Scy} 315254401Scy 316254401Scy 317254401Scyint 318254401Scyipf_p_dns_allow_query(softd, dnsi) 319254401Scy ipf_dns_softc_t *softd; 320254401Scy dnsinfo_t *dnsi; 321254401Scy{ 322254401Scy ipf_dns_filter_t *idns; 323254401Scy int len; 324254401Scy 325254401Scy len = strlen(dnsi->dnsi_buffer); 326254401Scy 327254401Scy for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next) 328254401Scy if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0) 329254401Scy return idns->idns_pass; 330254401Scy return 0; 331254401Scy} 332254401Scy 333254401Scy 334254401Scy/* ARGSUSED */ 335254401Scyint 336254401Scyipf_p_dns_inout(arg, fin, aps, nat) 337254401Scy void *arg; 338254401Scy fr_info_t *fin; 339254401Scy ap_session_t *aps; 340254401Scy nat_t *nat; 341254401Scy{ 342254401Scy ipf_dns_softc_t *softd = arg; 343254401Scy ipf_dns_hdr_t *dns; 344254401Scy dnsinfo_t *di; 345254401Scy char *data; 346254401Scy int dlen, q, rc = 0; 347254401Scy 348254401Scy if (fin->fin_dlen < sizeof(*dns)) 349254401Scy return APR_ERR(1); 350254401Scy 351254401Scy dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t)); 352254401Scy 353254401Scy q = dns->dns_qdcount; 354254401Scy 355254401Scy data = (char *)(dns + 1); 356254401Scy dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t); 357254401Scy 358254401Scy di = aps->aps_data; 359254401Scy 360254401Scy READ_ENTER(&softd->ipf_p_dns_rwlock); 361254401Scy MUTEX_ENTER(&di->dnsi_lock); 362254401Scy 363254401Scy for (; (dlen > 0) && (q > 0); q--) { 364254401Scy int len; 365254401Scy 366254401Scy len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer, 367254401Scy sizeof(di->dnsi_buffer)); 368254401Scy if (len == 0) { 369254401Scy rc = 1; 370254401Scy break; 371254401Scy } 372254401Scy rc = ipf_p_dns_allow_query(softd, di); 373254401Scy if (rc != 0) 374254401Scy break; 375254401Scy data += len; 376254401Scy dlen -= len; 377254401Scy } 378254401Scy MUTEX_EXIT(&di->dnsi_lock); 379254401Scy RWLOCK_EXIT(&softd->ipf_p_dns_rwlock); 380254401Scy 381254401Scy return APR_ERR(rc); 382254401Scy} 383254401Scy 384254401Scy 385254401Scy/* ARGSUSED */ 386254401Scyint 387254401Scyipf_p_dns_match(fin, aps, nat) 388254401Scy fr_info_t *fin; 389254401Scy ap_session_t *aps; 390254401Scy nat_t *nat; 391254401Scy{ 392254401Scy dnsinfo_t *di = aps->aps_data; 393254401Scy ipf_dns_hdr_t *dnh; 394254401Scy 395254401Scy if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG)) 396254401Scy return -1; 397254401Scy 398254401Scy dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t)); 399254401Scy if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id) 400254401Scy return -1; 401254401Scy return 0; 402254401Scy} 403