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