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