ip_dns_pxy.c revision 272996
1/* 2 * Copyright (C) 2012 by Darren Reed. 3 * 4 * See the IPFILTER.LICENCE file for details on licencing. 5 * 6 * $Id: ip_dns_pxy.c,v 1.1.2.10 2012/07/22 08:04:23 darren_r Exp $ 7 */ 8 9#define IPF_DNS_PROXY 10 11/* 12 * map ... proxy port dns/udp 53 { block .cnn.com; } 13 */ 14typedef struct ipf_dns_filter { 15 struct ipf_dns_filter *idns_next; 16 char *idns_name; 17 int idns_namelen; 18 int idns_pass; 19} ipf_dns_filter_t; 20 21 22typedef struct ipf_dns_softc_s { 23 ipf_dns_filter_t *ipf_p_dns_list; 24 ipfrwlock_t ipf_p_dns_rwlock; 25 u_long ipf_p_dns_compress; 26 u_long ipf_p_dns_toolong; 27 u_long ipf_p_dns_nospace; 28} ipf_dns_softc_t; 29 30int ipf_p_dns_allow_query __P((ipf_dns_softc_t *, dnsinfo_t *)); 31int ipf_p_dns_ctl __P((ipf_main_softc_t *, void *, ap_ctl_t *)); 32void ipf_p_dns_del __P((ipf_main_softc_t *, ap_session_t *)); 33int ipf_p_dns_get_name __P((ipf_dns_softc_t *, char *, int, char *, int)); 34int ipf_p_dns_inout __P((void *, fr_info_t *, ap_session_t *, nat_t *)); 35int ipf_p_dns_match __P((fr_info_t *, ap_session_t *, nat_t *)); 36int ipf_p_dns_match_names __P((ipf_dns_filter_t *, char *, int)); 37int ipf_p_dns_new __P((void *, fr_info_t *, ap_session_t *, nat_t *)); 38void *ipf_p_dns_soft_create __P((ipf_main_softc_t *)); 39void ipf_p_dns_soft_destroy __P((ipf_main_softc_t *, void *)); 40 41typedef struct { 42 u_char dns_id[2]; 43 u_short dns_ctlword; 44 u_short dns_qdcount; 45 u_short dns_ancount; 46 u_short dns_nscount; 47 u_short dns_arcount; 48} ipf_dns_hdr_t; 49 50#define DNS_QR(x) ((ntohs(x) & 0x8000) >> 15) 51#define DNS_OPCODE(x) ((ntohs(x) & 0x7800) >> 11) 52#define DNS_AA(x) ((ntohs(x) & 0x0400) >> 10) 53#define DNS_TC(x) ((ntohs(x) & 0x0200) >> 9) 54#define DNS_RD(x) ((ntohs(x) & 0x0100) >> 8) 55#define DNS_RA(x) ((ntohs(x) & 0x0080) >> 7) 56#define DNS_Z(x) ((ntohs(x) & 0x0070) >> 4) 57#define DNS_RCODE(x) ((ntohs(x) & 0x000f) >> 0) 58 59 60void * 61ipf_p_dns_soft_create(softc) 62 ipf_main_softc_t *softc; 63{ 64 ipf_dns_softc_t *softd; 65 66 KMALLOC(softd, ipf_dns_softc_t *); 67 if (softd == NULL) 68 return NULL; 69 70 bzero((char *)softd, sizeof(*softd)); 71 RWLOCK_INIT(&softd->ipf_p_dns_rwlock, "ipf dns rwlock"); 72 73 return softd; 74} 75 76 77void 78ipf_p_dns_soft_destroy(softc, arg) 79 ipf_main_softc_t *softc; 80 void *arg; 81{ 82 ipf_dns_softc_t *softd = arg; 83 ipf_dns_filter_t *idns; 84 85 while ((idns = softd->ipf_p_dns_list) != NULL) { 86 KFREES(idns->idns_name, idns->idns_namelen); 87 idns->idns_name = NULL; 88 idns->idns_namelen = 0; 89 softd->ipf_p_dns_list = idns->idns_next; 90 KFREE(idns); 91 } 92 RW_DESTROY(&softd->ipf_p_dns_rwlock); 93 94 KFREE(softd); 95} 96 97 98int 99ipf_p_dns_ctl(softc, arg, ctl) 100 ipf_main_softc_t *softc; 101 void *arg; 102 ap_ctl_t *ctl; 103{ 104 ipf_dns_softc_t *softd = arg; 105 ipf_dns_filter_t *tmp, *idns, **idnsp; 106 int error = 0; 107 108 /* 109 * To make locking easier. 110 */ 111 KMALLOC(tmp, ipf_dns_filter_t *); 112 113 WRITE_ENTER(&softd->ipf_p_dns_rwlock); 114 for (idnsp = &softd->ipf_p_dns_list; (idns = *idnsp) != NULL; 115 idnsp = &idns->idns_next) { 116 if (idns->idns_namelen != ctl->apc_dsize) 117 continue; 118 if (!strncmp(ctl->apc_data, idns->idns_name, 119 idns->idns_namelen)) 120 break; 121 } 122 123 switch (ctl->apc_cmd) 124 { 125 case APC_CMD_DEL : 126 if (idns == NULL) { 127 IPFERROR(80006); 128 error = ESRCH; 129 break; 130 } 131 *idnsp = idns->idns_next; 132 idns->idns_next = NULL; 133 KFREES(idns->idns_name, idns->idns_namelen); 134 idns->idns_name = NULL; 135 idns->idns_namelen = 0; 136 KFREE(idns); 137 break; 138 case APC_CMD_ADD : 139 if (idns != NULL) { 140 IPFERROR(80007); 141 error = EEXIST; 142 break; 143 } 144 if (tmp == NULL) { 145 IPFERROR(80008); 146 error = ENOMEM; 147 break; 148 } 149 idns = tmp; 150 tmp = NULL; 151 idns->idns_namelen = ctl->apc_dsize; 152 idns->idns_name = ctl->apc_data; 153 idns->idns_pass = ctl->apc_arg; 154 idns->idns_next = NULL; 155 *idnsp = idns; 156 ctl->apc_data = NULL; 157 ctl->apc_dsize = 0; 158 break; 159 default : 160 IPFERROR(80009); 161 error = EINVAL; 162 break; 163 } 164 RWLOCK_EXIT(&softd->ipf_p_dns_rwlock); 165 166 if (tmp != NULL) { 167 KFREE(tmp); 168 tmp = NULL; 169 } 170 171 return error; 172} 173 174 175/* ARGSUSED */ 176int 177ipf_p_dns_new(arg, fin, aps, nat) 178 void *arg; 179 fr_info_t *fin; 180 ap_session_t *aps; 181 nat_t *nat; 182{ 183 dnsinfo_t *di; 184 int dlen; 185 186 if (fin->fin_v != 4) 187 return -1; 188 189 dlen = fin->fin_dlen - sizeof(udphdr_t); 190 if (dlen < sizeof(ipf_dns_hdr_t)) { 191 /* 192 * No real DNS packet is smaller than that. 193 */ 194 return -1; 195 } 196 197 aps->aps_psiz = sizeof(dnsinfo_t); 198 KMALLOCS(di, dnsinfo_t *, sizeof(dnsinfo_t)); 199 if (di == NULL) { 200 printf("ipf_dns_new:KMALLOCS(%d) failed\n", sizeof(*di)); 201 return -1; 202 } 203 204 MUTEX_INIT(&di->dnsi_lock, "dns lock"); 205 206 aps->aps_data = di; 207 208 dlen = fin->fin_dlen - sizeof(udphdr_t); 209 COPYDATA(fin->fin_m, fin->fin_hlen + sizeof(udphdr_t), 210 MIN(dlen, sizeof(di->dnsi_buffer)), di->dnsi_buffer); 211 di->dnsi_id = (di->dnsi_buffer[0] << 8) | di->dnsi_buffer[1]; 212 return 0; 213} 214 215 216/* ARGSUSED */ 217void 218ipf_p_dns_del(softc, aps) 219 ipf_main_softc_t *softc; 220 ap_session_t *aps; 221{ 222#ifdef USE_MUTEXES 223 dnsinfo_t *di = aps->aps_data; 224 225 MUTEX_DESTROY(&di->dnsi_lock); 226#endif 227 KFREES(aps->aps_data, aps->aps_psiz); 228 aps->aps_data = NULL; 229 aps->aps_psiz = 0; 230} 231 232 233/* 234 * Tries to match the base string (in our ACL) with the query from a packet. 235 */ 236int 237ipf_p_dns_match_names(idns, query, qlen) 238 ipf_dns_filter_t *idns; 239 char *query; 240 int qlen; 241{ 242 int blen; 243 char *base; 244 245 blen = idns->idns_namelen; 246 base = idns->idns_name; 247 248 if (blen > qlen) 249 return 1; 250 251 if (blen == qlen) 252 return strncasecmp(base, query, qlen); 253 254 /* 255 * If the base string string is shorter than the query, allow the 256 * tail of the base to match the same length tail of the query *if*: 257 * - the base string starts with a '*' (*cnn.com) 258 * - the base string represents a domain (.cnn.com) 259 * as otherwise it would not be possible to block just "cnn.com" 260 * without also impacting "foocnn.com", etc. 261 */ 262 if (*base == '*') { 263 base++; 264 blen--; 265 } else if (*base != '.') 266 return 1; 267 268 return strncasecmp(base, query + qlen - blen, blen); 269} 270 271 272int 273ipf_p_dns_get_name(softd, start, len, buffer, buflen) 274 ipf_dns_softc_t *softd; 275 char *start; 276 int len; 277 char *buffer; 278 int buflen; 279{ 280 char *s, *t, clen; 281 int slen, blen; 282 283 s = start; 284 t = buffer; 285 slen = len; 286 blen = buflen - 1; /* Always make room for trailing \0 */ 287 288 while (*s != '\0') { 289 clen = *s; 290 if ((clen & 0xc0) == 0xc0) { /* Doesn't do compression */ 291 softd->ipf_p_dns_compress++; 292 return 0; 293 } 294 if (clen > slen) { 295 softd->ipf_p_dns_toolong++; 296 return 0; /* Does the name run off the end? */ 297 } 298 if ((clen + 1) > blen) { 299 softd->ipf_p_dns_nospace++; 300 return 0; /* Enough room for name+.? */ 301 } 302 s++; 303 bcopy(s, t, clen); 304 t += clen; 305 s += clen; 306 *t++ = '.'; 307 slen -= clen; 308 blen -= (clen + 1); 309 } 310 311 *(t - 1) = '\0'; 312 return s - start; 313} 314 315 316int 317ipf_p_dns_allow_query(softd, dnsi) 318 ipf_dns_softc_t *softd; 319 dnsinfo_t *dnsi; 320{ 321 ipf_dns_filter_t *idns; 322 int len; 323 324 len = strlen(dnsi->dnsi_buffer); 325 326 for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next) 327 if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0) 328 return idns->idns_pass; 329 return 0; 330} 331 332 333/* ARGSUSED */ 334int 335ipf_p_dns_inout(arg, fin, aps, nat) 336 void *arg; 337 fr_info_t *fin; 338 ap_session_t *aps; 339 nat_t *nat; 340{ 341 ipf_dns_softc_t *softd = arg; 342 ipf_dns_hdr_t *dns; 343 dnsinfo_t *di; 344 char *data; 345 int dlen, q, rc = 0; 346 347 if (fin->fin_dlen < sizeof(*dns)) 348 return APR_ERR(1); 349 350 dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t)); 351 352 q = dns->dns_qdcount; 353 354 data = (char *)(dns + 1); 355 dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t); 356 357 di = aps->aps_data; 358 359 READ_ENTER(&softd->ipf_p_dns_rwlock); 360 MUTEX_ENTER(&di->dnsi_lock); 361 362 for (; (dlen > 0) && (q > 0); q--) { 363 int len; 364 365 len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer, 366 sizeof(di->dnsi_buffer)); 367 if (len == 0) { 368 rc = 1; 369 break; 370 } 371 rc = ipf_p_dns_allow_query(softd, di); 372 if (rc != 0) 373 break; 374 data += len; 375 dlen -= len; 376 } 377 MUTEX_EXIT(&di->dnsi_lock); 378 RWLOCK_EXIT(&softd->ipf_p_dns_rwlock); 379 380 return APR_ERR(rc); 381} 382 383 384/* ARGSUSED */ 385int 386ipf_p_dns_match(fin, aps, nat) 387 fr_info_t *fin; 388 ap_session_t *aps; 389 nat_t *nat; 390{ 391 dnsinfo_t *di = aps->aps_data; 392 ipf_dns_hdr_t *dnh; 393 394 if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG)) 395 return -1; 396 397 dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t)); 398 if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id) 399 return -1; 400 return 0; 401} 402