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