Mercurial > notdcc
comparison dcclib/ckwhite.c @ 0:c7f6b056b673
First import of vendor version
author | Peter Gervai <grin@grin.hu> |
---|---|
date | Tue, 10 Mar 2009 13:49:58 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c7f6b056b673 |
---|---|
1 /* Distributed Checksum Clearinghouse | |
2 * | |
3 * check checksums in the local whitelist | |
4 * | |
5 * Copyright (c) 2008 by Rhyolite Software, LLC | |
6 * | |
7 * This agreement is not applicable to any entity which sells anti-spam | |
8 * solutions to others or provides an anti-spam solution as part of a | |
9 * security solution sold to other entities, or to a private network | |
10 * which employs the DCC or uses data provided by operation of the DCC | |
11 * but does not provide corresponding data to other users. | |
12 * | |
13 * Permission to use, copy, modify, and distribute this software without | |
14 * changes for any purpose with or without fee is hereby granted, provided | |
15 * that the above copyright notice and this permission notice appear in all | |
16 * copies and any distributed versions or copies are either unchanged | |
17 * or not called anything similar to "DCC" or "Distributed Checksum | |
18 * Clearinghouse". | |
19 * | |
20 * Parties not eligible to receive a license under this agreement can | |
21 * obtain a commercial license to use DCC by contacting Rhyolite Software | |
22 * at sales@rhyolite.com. | |
23 * | |
24 * A commercial license would be for Distributed Checksum and Reputation | |
25 * Clearinghouse software. That software includes additional features. This | |
26 * free license for Distributed ChecksumClearinghouse Software does not in any | |
27 * way grant permision to use Distributed Checksum and Reputation Clearinghouse | |
28 * software | |
29 * | |
30 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL | |
31 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES | |
32 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC | |
33 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES | |
34 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, | |
35 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, | |
36 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS | |
37 * SOFTWARE. | |
38 * | |
39 * Rhyolite Software DCC 1.3.103-1.133 $Revision$ | |
40 */ | |
41 | |
42 #include "dcc_ck.h" | |
43 | |
44 | |
45 static const DCC_WHITE_MAGIC white_magic = WHITE_MAGIC_B_STR WHITE_MAGIC_V_STR; | |
46 | |
47 #define EMPTY_WHITE_SIZE (sizeof(DCC_WHITE_TBL) - sizeof(DCC_WHITE_ENTRY)) | |
48 #define MAX_WHITE_ENTRIES (DCC_WHITE_TBL_BINS*10) | |
49 #define ENTRIES2SIZE0(_l) (sizeof(DCC_WHITE_TBL) \ | |
50 - sizeof(DCC_WHITE_ENTRY) \ | |
51 + ((_l) * sizeof(DCC_WHITE_ENTRY))) | |
52 #ifdef DCC_WIN32 | |
53 /* Make the hash table files maximum size on WIN32. | |
54 * You cannot change the size of a WIN32 mapping object without | |
55 * getting all processes using it to release it so that it can be | |
56 * recreated. This may cause problems if the size of hash table | |
57 * header changes. | |
58 * Since the file does not change size, there is no need to remap it */ | |
59 #define ENTRIES2SIZE(_l) ENTRIES2SIZE0(MAX_WHITE_ENTRIES) | |
60 #else | |
61 #define ENTRIES2SIZE(_l) ENTRIES2SIZE0(_l) | |
62 #endif | |
63 | |
64 | |
65 | |
66 void | |
67 dcc_wf_init(DCC_WF *wf, | |
68 u_int wf_flags) /* DCC_WF_* */ | |
69 { | |
70 memset(wf, 0, sizeof(*wf)); | |
71 wf->ht_fd = -1; | |
72 wf->wf_flags = wf_flags & ~DCC_WF_NOFILE; | |
73 } | |
74 | |
75 | |
76 | |
77 /* this is needed only on systems without coherent mmap()/read()/write() */ | |
78 static void | |
79 sync_white(DCC_WF *wf) | |
80 { | |
81 if (wf->wtbl | |
82 && 0 > MSYNC(wf->wtbl, wf->wtbl_size, MS_SYNC)) | |
83 dcc_error_msg("msync(%s): %s", wf->ht_nm, ERROR_STR()); | |
84 } | |
85 | |
86 | |
87 | |
88 #define STILL_BROKE(wf,now) ((wf)->broken != 0 \ | |
89 && !DCC_IS_TIME(now, (wf)->broken, \ | |
90 DCC_WHITE_BROKEN_DELAY)) | |
91 static void | |
92 is_broken(DCC_WF *wf) | |
93 { | |
94 wf->broken = time(0) + DCC_WHITE_BROKEN_DELAY; | |
95 | |
96 /* This is racy, but the worst that can happen is that | |
97 * another process races to set the retry timer */ | |
98 if (!(wf->wf_flags & DCC_WF_RO) && wf->wtbl) { | |
99 wf->wtbl->hdr.broken = wf->broken; | |
100 sync_white(wf); | |
101 } | |
102 } | |
103 | |
104 | |
105 | |
106 static void | |
107 unmap_white_ht(DCC_WF *wf) | |
108 { | |
109 if (!wf->wtbl) | |
110 return; | |
111 | |
112 sync_white(wf); | |
113 #ifdef DCC_WIN32 | |
114 win32_unmap(&wf->ht_map, wf->wtbl, wf->ht_nm); | |
115 #else | |
116 if (0 > munmap((void *)wf->wtbl, wf->wtbl_size)) | |
117 dcc_error_msg("munmap(%s,%d): %s", | |
118 wf->ht_nm, wf->wtbl_size, ERROR_STR()); | |
119 #endif | |
120 wf->wtbl = 0; | |
121 } | |
122 | |
123 | |
124 | |
125 static u_char /* 1=done, 0=failed */ | |
126 map_white_ht(DCC_EMSG emsg, DCC_WF *wf, DCC_WHITE_INX entries) | |
127 { | |
128 size_t size; | |
129 #ifndef DCC_WIN32 | |
130 void *p; | |
131 #endif | |
132 | |
133 unmap_white_ht(wf); | |
134 | |
135 if (entries > MAX_WHITE_ENTRIES) { | |
136 dcc_pemsg(EX_IOERR, emsg, "%s should not contain %d entries", | |
137 wf->ht_nm, entries); | |
138 return 0; | |
139 } | |
140 | |
141 size = ENTRIES2SIZE(entries); | |
142 #ifdef DCC_WIN32 | |
143 if (!wf->wtbl) { | |
144 wf->wtbl = win32_map(emsg, &wf->ht_map, wf->ht_nm, wf->ht_fd, | |
145 size); | |
146 if (!wf->wtbl) | |
147 return 0; | |
148 } | |
149 #else | |
150 p = mmap(0, size, | |
151 (wf->wf_flags & DCC_WF_RO) | |
152 ? PROT_READ : (PROT_READ|PROT_WRITE), | |
153 MAP_SHARED, wf->ht_fd, 0); | |
154 if (p == MAP_FAILED) { | |
155 dcc_pemsg(EX_IOERR, emsg, "mmap(%s,%d): %s", | |
156 wf->ht_nm, (int)size, ERROR_STR()); | |
157 return 0; | |
158 } | |
159 wf->wtbl = p; | |
160 #endif | |
161 wf->wtbl_size = size; | |
162 wf->wtbl_entries = entries; | |
163 wf->wtbl_flags = wf->wtbl->hdr.flags; | |
164 | |
165 return 1; | |
166 } | |
167 | |
168 | |
169 | |
170 static u_char | |
171 close_white_ht(DCC_EMSG emsg, DCC_WF *wf) | |
172 { | |
173 u_char result = 1; | |
174 | |
175 if (wf->ht_fd < 0) | |
176 return result; | |
177 | |
178 wf->closed = 1; | |
179 | |
180 unmap_white_ht(wf); | |
181 | |
182 #ifdef DCC_WIN32 | |
183 /* unlock the file before closing it to keep Win95 happy */ | |
184 if (!dcc_unlock_fd(emsg, wf->ht_fd, DCC_LOCK_ALL_FILE, | |
185 "whitelist ", wf->ht_nm)) | |
186 result = 0; | |
187 #endif | |
188 if (0 > close(wf->ht_fd)) { | |
189 dcc_pemsg(EX_IOERR, emsg, "close(%s): %s", | |
190 wf->ht_nm, ERROR_STR()); | |
191 result = 0; | |
192 } | |
193 | |
194 wf->ht_fd = -1; | |
195 memset(&wf->ht_sb, 0, sizeof(wf->ht_sb)); | |
196 #ifndef DCC_WIN32 | |
197 wf->ht_sb.st_dev = -1; | |
198 wf->ht_sb.st_ino = -1; | |
199 #endif | |
200 return result; | |
201 } | |
202 | |
203 | |
204 | |
205 static int /* 0=ok -1=failed */ | |
206 unlink_white_ht(DCC_WF *wf, | |
207 time_t now) /* 0=our own new file */ | |
208 { | |
209 int result; | |
210 | |
211 /* mark it bad if it is a brand new hash table */ | |
212 if (!now && wf->wtbl && !(wf->wf_flags & DCC_WF_RO)) | |
213 wf->wtbl->hdr.ascii_mtime = 0; | |
214 | |
215 #ifdef DCC_WIN32 | |
216 /* racy but you cannot unlink an open file on WIN32 */ | |
217 close_white_ht(0, wf); | |
218 #endif | |
219 result = -1; | |
220 if (!(wf->wf_flags & DCC_WF_RO)) { | |
221 if (0 <= unlink(wf->ht_nm)) { | |
222 result = 0; | |
223 #ifndef DCC_WIN32 | |
224 } else if ((errno == EACCES || errno == EPERM) | |
225 && dcc_get_priv_home(wf->ht_nm)) { | |
226 if (0 <= unlink(wf->ht_nm)) | |
227 result = 0; | |
228 dcc_rel_priv(); | |
229 #endif | |
230 } | |
231 if (result < 0) | |
232 dcc_error_msg("unlink(%s): %s", | |
233 wf->ht_nm, ERROR_STR()); | |
234 } | |
235 | |
236 /* If we failed to unlink the old hash table, | |
237 * remember in core that things are broken but do not touch the file | |
238 * in case it is a link to /etc/passwd or something else dangerous */ | |
239 if (result < 0 && now) | |
240 wf->broken = now + DCC_WHITE_BROKEN_DELAY; | |
241 #ifndef DCC_WIN32 | |
242 close_white_ht(0, wf); | |
243 #endif | |
244 | |
245 return result; | |
246 } | |
247 | |
248 | |
249 | |
250 static u_char | |
251 new_ht_nm(DCC_EMSG emsg, DCC_WF *wf, u_char new) | |
252 { | |
253 if (wf->ascii_nm_len >= ISZ(wf->ht_nm) - LITZ(DCC_WHITE_SUFFIX)) { | |
254 dcc_pemsg(EX_NOINPUT, emsg, "bad whitelist file name \"%s\"", | |
255 wf->ascii_nm); | |
256 is_broken(wf); | |
257 return 0; | |
258 } | |
259 | |
260 memcpy(wf->ht_nm, wf->ascii_nm, wf->ascii_nm_len); | |
261 strcpy(&wf->ht_nm[wf->ascii_nm_len], | |
262 new ? DCC_WHITE_NEW_SUFFIX : DCC_WHITE_SUFFIX); | |
263 return 1; | |
264 } | |
265 | |
266 | |
267 | |
268 u_char | |
269 dcc_new_white_nm(DCC_EMSG emsg, DCC_WF *wf, | |
270 const char *new_white_nm) | |
271 { | |
272 DCC_PATH new_path; | |
273 const char *new; | |
274 u_int len; | |
275 int i; | |
276 | |
277 if (!strcmp(new_white_nm, wf->ascii_nm)) | |
278 return 1; | |
279 | |
280 close_white_ht(0, wf); | |
281 dcc_wf_init(wf, wf->wf_flags); | |
282 | |
283 if (!fnm2rel(new_path, new_white_nm, 0)) { | |
284 dcc_pemsg(EX_USAGE, emsg, "bad whitelist name \"%s\"", | |
285 new_white_nm); | |
286 return 0; | |
287 } | |
288 | |
289 len = strlen(new_path); | |
290 i = len - LITZ(DCC_WHITE_SUFFIX); | |
291 if (i > 0 && (!strcmp(&new_path[i], DCC_WHITE_SUFFIX) | |
292 || !strcmp(&new_path[i], DCC_WHITE_NEW_SUFFIX))) { | |
293 len = i; | |
294 new_path[len] = '\0'; | |
295 } | |
296 | |
297 if (len > ISZ(wf->ht_nm) - ISZ(DCC_WHITE_SUFFIX)) { | |
298 dcc_pemsg(EX_USAGE, emsg, "long whitelist name \"%s\"", | |
299 new_white_nm); | |
300 return 0; | |
301 } | |
302 | |
303 new = path2fnm(new_path); | |
304 len = strlen(new); | |
305 memcpy(wf->ascii_nm, new, len+1); | |
306 wf->ascii_nm_len = len; | |
307 return 1; | |
308 } | |
309 | |
310 | |
311 | |
312 /* open and shared-lock a hash table file */ | |
313 static int /* -1=fatal 0=rebuild, 1=ok */ | |
314 open_white_ht(DCC_EMSG emsg, DCC_WF *wf) | |
315 { | |
316 int size; | |
317 DCC_WHITE_INX entries; | |
318 | |
319 close_white_ht(0, wf); | |
320 | |
321 if (!new_ht_nm(emsg, wf, 0)) | |
322 return -1; | |
323 | |
324 wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm, | |
325 (wf->wf_flags & DCC_WF_RO) | |
326 ? O_RDONLY : O_RDWR, | |
327 DCC_LOCK_OPEN_SHARE, | |
328 DCC_LOCK_ALL_FILE, 0); | |
329 if (wf->ht_fd < 0) { | |
330 /* if this is a `wlist` command and neither -P nor -Q | |
331 * were specified, try to open the file read-only */ | |
332 if ((wf->wf_flags & DCC_WF_WLIST) | |
333 && !(wf->wf_flags & (DCC_WF_WLIST_RO | DCC_WF_WLIST_RW))) { | |
334 if (emsg) | |
335 emsg[0] = '\0'; | |
336 wf->wf_flags |= DCC_WF_RO; | |
337 wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm, | |
338 O_RDONLY, | |
339 DCC_LOCK_OPEN_SHARE, | |
340 DCC_LOCK_ALL_FILE, 0); | |
341 } | |
342 if (wf->ht_fd < 0) | |
343 return 0; | |
344 } | |
345 | |
346 if (fstat(wf->ht_fd, &wf->ht_sb) < 0) { | |
347 dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s", | |
348 wf->ht_nm, ERROR_STR()); | |
349 close_white_ht(0, wf); | |
350 is_broken(wf); | |
351 return -1; | |
352 } | |
353 | |
354 size = wf->ht_sb.st_size - EMPTY_WHITE_SIZE; | |
355 if (size < 0) { | |
356 dcc_pemsg(EX_NOINPUT, emsg, | |
357 "%s is too small to be a DCC whitelist hash table", | |
358 wf->ht_nm); | |
359 return unlink_white_ht(wf, time(0)); | |
360 | |
361 } else { | |
362 entries = size / sizeof(DCC_WHITE_ENTRY); | |
363 if (!map_white_ht(emsg, wf, entries)) | |
364 return unlink_white_ht(wf, time(0)); | |
365 } | |
366 | |
367 if (memcmp(&wf->wtbl->magic, &white_magic, sizeof(white_magic))) { | |
368 /* rebuild old format files */ | |
369 if (!memcmp(&wf->wtbl->magic, WHITE_MAGIC_B_STR, | |
370 LITZ(WHITE_MAGIC_B_STR))) { | |
371 if (dcc_clnt_debug) | |
372 dcc_trace_msg("%s is obsolete %s", | |
373 wf->ht_nm, wf->wtbl->magic); | |
374 } else { | |
375 dcc_pemsg(EX_NOINPUT, emsg, | |
376 "%s is not a DCC whitelist hash file", | |
377 wf->ht_nm); | |
378 } | |
379 return unlink_white_ht(wf, time(0)); | |
380 } | |
381 | |
382 if ((size % sizeof(DCC_WHITE_ENTRY)) != 0 | |
383 || entries > MAX_WHITE_ENTRIES | |
384 || entries < wf->wtbl->hdr.entries) { | |
385 dcc_pemsg(EX_NOINPUT, emsg, | |
386 "invalid size of whitelist %s="OFF_DPAT, | |
387 wf->ht_nm, wf->ht_sb.st_size); | |
388 return unlink_white_ht(wf, time(0)); | |
389 } | |
390 | |
391 if (wf->wtbl->hdr.ascii_mtime == 0) { | |
392 close_white_ht(0, wf); | |
393 return 0; /* broken hash table */ | |
394 } | |
395 | |
396 /* we know the hash table is usable | |
397 * but we might want to rebuild it */ | |
398 wf->need_reopen = 0; | |
399 wf->wtbl_flags = wf->wtbl->hdr.flags; | |
400 | |
401 /* wlist and dccproc work on both per-user and global whitelists, | |
402 * so do not change the nature of the file if it is already known */ | |
403 if (wf->wf_flags & DCC_WF_EITHER) { | |
404 if (wf->wtbl_flags & DCC_WHITE_FG_PER_USER) | |
405 wf->wf_flags |= DCC_WF_PER_USER; | |
406 else | |
407 wf->wf_flags &= ~DCC_WF_PER_USER; | |
408 } | |
409 | |
410 return 1; | |
411 } | |
412 | |
413 | |
414 | |
415 static void | |
416 create_white_ht_sub(DCC_EMSG emsg, DCC_WF *new_wf, const DCC_WF *wf, | |
417 u_char *busyp) | |
418 { | |
419 /* do not use O_EXCL because we want to wait for any other | |
420 * process to finish | |
421 * | |
422 * wait if and only if we do not have a usable file open */ | |
423 | |
424 new_wf->ht_fd = dcc_lock_open(emsg, new_wf->ht_nm, | |
425 O_RDWR | O_CREAT, | |
426 wf->ht_fd >= 0 ? DCC_LOCK_OPEN_NOWAIT : 0, | |
427 DCC_LOCK_ALL_FILE, busyp); | |
428 if (new_wf->ht_fd < 0) | |
429 return; | |
430 | |
431 /* a new hash table must be empty */ | |
432 if (0 > fstat(new_wf->ht_fd, &new_wf->ht_sb)) { | |
433 dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s", | |
434 new_wf->ht_nm, ERROR_STR()); | |
435 close_white_ht(emsg, new_wf); | |
436 return; | |
437 } | |
438 | |
439 if (new_wf->ht_sb.st_size != 0) { | |
440 dcc_pemsg(EX_IOERR, emsg, "%s has non-zero size %d", | |
441 new_wf->ht_nm, (int)new_wf->ht_sb.st_size); | |
442 close_white_ht(emsg, new_wf); | |
443 return; | |
444 } | |
445 } | |
446 | |
447 | |
448 | |
449 /* create and write-lock a new hash table file | |
450 * wait for lock if we don't have existing file open */ | |
451 static int /* 1=done, 0=file busy, -1=fatal */ | |
452 create_white_ht(DCC_EMSG emsg, | |
453 DCC_WF *tmp_wf, /* build with this */ | |
454 const DCC_WF *wf) /* from this */ | |
455 { | |
456 u_char busy = 0; | |
457 | |
458 tmp_wf->ascii_nm_len = wf->ascii_nm_len; | |
459 memcpy(tmp_wf->ascii_nm, wf->ascii_nm, wf->ascii_nm_len+1); | |
460 if (!new_ht_nm(emsg, tmp_wf, 1)) { | |
461 tmp_wf->ht_nm[0] = '\0'; | |
462 return -1; | |
463 } | |
464 | |
465 if (tmp_wf->wf_flags & DCC_WF_RO) { | |
466 dcc_pemsg(EX_IOERR, emsg, | |
467 "read only access; cannot create or update %s", | |
468 tmp_wf->ht_nm); | |
469 tmp_wf->ht_nm[0] = '\0'; | |
470 return -1; | |
471 } | |
472 | |
473 #ifndef DCC_WIN32 | |
474 /* We want to create a private hash table if the ASCII file | |
475 * is private, but a hash table owned by the DCC user if the | |
476 * ASCII file is public */ | |
477 if (0 > access(tmp_wf->ascii_nm, R_OK | W_OK) | |
478 && dcc_get_priv_home(tmp_wf->ht_nm)) { | |
479 /* first try to open a public hash table */ | |
480 create_white_ht_sub(emsg, tmp_wf, wf, &busy); | |
481 if (tmp_wf->ht_fd < 0 && !busy) { | |
482 if (emsg && dcc_clnt_debug > 2) | |
483 dcc_error_msg("%s", emsg); | |
484 unlink(tmp_wf->ht_nm); | |
485 create_white_ht_sub(emsg, tmp_wf, wf, &busy); | |
486 } | |
487 dcc_rel_priv(); | |
488 } | |
489 #endif | |
490 | |
491 if (tmp_wf->ht_fd < 0 && !busy) { | |
492 /* try to open or create a private hash table */ | |
493 create_white_ht_sub(emsg, tmp_wf, wf, &busy); | |
494 if (tmp_wf->ht_fd < 0 && !busy) { | |
495 if (emsg && dcc_clnt_debug > 2) | |
496 dcc_error_msg("%s", emsg); | |
497 unlink(tmp_wf->ht_nm); | |
498 create_white_ht_sub(emsg, tmp_wf, wf, &busy); | |
499 } | |
500 } | |
501 | |
502 #ifndef DCC_WIN32 | |
503 /* try one last time with privileges in case the ASCII file has | |
504 * mode 666 but the directory does not */ | |
505 if (tmp_wf->ht_fd < 0 && !busy) { | |
506 if (dcc_get_priv_home(tmp_wf->ht_nm)) { | |
507 if (emsg && dcc_clnt_debug > 2) | |
508 dcc_error_msg("%s", emsg); | |
509 unlink(tmp_wf->ht_nm); | |
510 create_white_ht_sub(emsg, tmp_wf, wf, &busy); | |
511 dcc_rel_priv(); | |
512 } | |
513 } | |
514 #endif | |
515 if (tmp_wf->ht_fd < 0) { | |
516 tmp_wf->ht_nm[0] = '\0'; | |
517 if (busy) | |
518 return 0; | |
519 return -1; | |
520 } | |
521 return 1; | |
522 } | |
523 | |
524 | |
525 | |
526 #define FIND_WHITE_BROKEN ((DCC_WHITE_ENTRY *)-1) | |
527 static DCC_WHITE_ENTRY * | |
528 find_white(DCC_EMSG emsg, DCC_WF *wf, DCC_CK_TYPES type, const DCC_SUM sum, | |
529 DCC_WHITE_INX *binp) | |
530 { | |
531 u_long accum; | |
532 DCC_WHITE_INX bin, inx; | |
533 DCC_WHITE_ENTRY *e; | |
534 int loop_cnt, i; | |
535 | |
536 if (!wf->wtbl || wf->wtbl->hdr.ascii_mtime == 0) | |
537 return FIND_WHITE_BROKEN; | |
538 | |
539 accum = type; | |
540 for (i = sizeof(DCC_SUM)-1; i >= 0; --i) | |
541 accum = (accum >> 28) + (accum << 4) + sum[i]; | |
542 bin = accum % DIM(wf->wtbl->bins); | |
543 if (binp) | |
544 *binp = bin; | |
545 inx = wf->wtbl->bins[bin]; | |
546 | |
547 for (loop_cnt = wf->wtbl->hdr.entries+1; | |
548 loop_cnt >= 0; | |
549 --loop_cnt) { | |
550 if (!inx) | |
551 return 0; | |
552 --inx; | |
553 /* if necessary, expand the mapped window into the file */ | |
554 if (inx >= wf->wtbl_entries) { | |
555 if (inx >= wf->wtbl->hdr.entries) { | |
556 dcc_pemsg(EX_DATAERR, emsg, | |
557 "bogus index %u in %s", | |
558 inx, wf->ht_nm); | |
559 if (!(wf->wf_flags & DCC_WF_RO)) | |
560 wf->wtbl->hdr.ascii_mtime = 0; | |
561 sync_white(wf); | |
562 return FIND_WHITE_BROKEN; | |
563 } | |
564 if (!map_white_ht(emsg, wf, wf->wtbl->hdr.entries)) | |
565 return 0; | |
566 } | |
567 e = &wf->wtbl->tbl[inx]; | |
568 if (e->type == type && !memcmp(e->sum, sum, sizeof(DCC_SUM))) | |
569 return e; | |
570 inx = e->fwd; | |
571 } | |
572 | |
573 dcc_pemsg(EX_DATAERR, emsg, "chain length %d in %s" | |
574 " starting at %d near %d for %s %s", | |
575 wf->wtbl->hdr.entries+1, | |
576 wf->ht_nm, | |
577 (DCC_WHITE_INX)(accum % DIM(wf->wtbl->bins)), inx, | |
578 dcc_type2str_err(type, 0, 0, 0), | |
579 dcc_ck2str_err(type, sum, 0)); | |
580 | |
581 if (!(wf->wf_flags & DCC_WF_RO)) | |
582 wf->wtbl->hdr.ascii_mtime = 0; | |
583 sync_white(wf); | |
584 return FIND_WHITE_BROKEN; | |
585 } | |
586 | |
587 | |
588 | |
589 static int /* 1=quit stat_white_nms() */ | |
590 stat_1white_nm(int *resultp, /* set=1 if (supposedly) no change */ | |
591 DCC_WF *wf, const char *nm, time_t ascii_mtime) | |
592 { | |
593 struct stat sb; | |
594 time_t now; | |
595 | |
596 if (stat(nm, &sb) < 0) { | |
597 if (errno != ENOENT | |
598 || !wf->wtbl | |
599 || (wf->wf_flags & DCC_WF_RO)) { | |
600 dcc_trace_msg("stat(%s): %s", nm, ERROR_STR()); | |
601 is_broken(wf); | |
602 *resultp = 0; | |
603 return 1; | |
604 } | |
605 | |
606 /* only complain if an ASCII file disappears temporarily */ | |
607 if (wf->broken == 0) { | |
608 dcc_trace_msg("%s disappeared: %s", nm, ERROR_STR()); | |
609 is_broken(wf); | |
610 *resultp = 1; | |
611 return 1; | |
612 } | |
613 now = time(0); | |
614 if (STILL_BROKE(wf, now)) { | |
615 if (dcc_clnt_debug > 1) | |
616 dcc_trace_msg("ignoring stat(%s): %s", | |
617 nm, ERROR_STR()); | |
618 *resultp = 1; | |
619 } else { | |
620 if (dcc_clnt_debug > 1) | |
621 dcc_trace_msg("pay attention to stat(%s): %s", | |
622 nm, ERROR_STR()); | |
623 *resultp = 0; | |
624 } | |
625 return 1; | |
626 } | |
627 | |
628 if (sb.st_mtime != ascii_mtime) { | |
629 *resultp = 0; | |
630 return 1; | |
631 } | |
632 | |
633 return 0; | |
634 } | |
635 | |
636 | |
637 | |
638 /* see if the ASCII files have changed */ | |
639 static int /* 1=same 0=changed or sick -1=broken */ | |
640 stat_white_nms(DCC_EMSG emsg, DCC_WF *wf) | |
641 { | |
642 struct stat ht_sb; | |
643 time_t now; | |
644 int i, result; | |
645 | |
646 if (!wf->wtbl) | |
647 return -1; | |
648 | |
649 now = time(0); | |
650 wf->next_stat_time = now + DCC_WHITE_STAT_DELAY; | |
651 | |
652 /* Notice if the hash file has been unlinked */ | |
653 if (stat(wf->ht_nm, &ht_sb) < 0) { | |
654 if (emsg && dcc_clnt_debug) | |
655 dcc_error_msg("stat(%s): %s", | |
656 wf->ht_nm, ERROR_STR()); | |
657 return -1; | |
658 } | |
659 #ifdef DCC_WIN32 | |
660 /* open files cannot be unlinked in WIN32, which lets us not | |
661 * worry about whether WIN32 files have device and i-numbers */ | |
662 #else | |
663 if (wf->ht_sb.st_dev != ht_sb.st_dev | |
664 || wf->ht_sb.st_ino != ht_sb.st_ino) { | |
665 if (emsg && dcc_clnt_debug > 2) | |
666 dcc_error_msg("%s disappeared", wf->ht_nm); | |
667 return -1; | |
668 } | |
669 #endif /* DCC_WIN32 */ | |
670 wf->ht_sb = ht_sb; | |
671 | |
672 /* delays on re-parsing and complaining in the file override */ | |
673 if (wf->reparse < wf->wtbl->hdr.reparse) | |
674 wf->reparse = wf->wtbl->hdr.reparse; | |
675 if (wf->broken < wf->wtbl->hdr.broken) | |
676 wf->broken = wf->wtbl->hdr.broken; | |
677 | |
678 /* seriously broken hash tables are unusable */ | |
679 if (wf->wtbl->hdr.ascii_mtime == 0) | |
680 return -1; | |
681 | |
682 /* pretend things are fine if they are recently badly broken */ | |
683 if (STILL_BROKE(wf, now)) | |
684 return 1; | |
685 | |
686 /* if the main ASCII file has disappeared, | |
687 * leave the hash file open and just complain, | |
688 * but only for a while */ | |
689 result = 1; | |
690 if (stat_1white_nm(&result, wf, wf->ascii_nm, | |
691 wf->wtbl->hdr.ascii_mtime)) | |
692 return result; | |
693 /* see if any of the included ASCII files are new */ | |
694 for (i = 0; i < DIM(wf->wtbl->hdr.white_incs); ++i) { | |
695 if (wf->wtbl->hdr.white_incs[i].nm[0] == '\0') | |
696 break; | |
697 /* stop at the first missing or changed included file */ | |
698 if (stat_1white_nm(&result, wf, | |
699 wf->wtbl->hdr.white_incs[i].nm, | |
700 wf->wtbl->hdr.white_incs[i].mtime)) | |
701 return result; | |
702 } | |
703 | |
704 /* force periodic reparsing of syntax errors to nag in system log */ | |
705 if (wf->reparse != 0 | |
706 && DCC_IS_TIME(now, wf->reparse, DCC_WHITE_REPARSE_DELAY)) | |
707 return 0; | |
708 | |
709 if ((wf->wtbl_flags & DCC_WHITE_FG_PER_USER) | |
710 && !(wf->wf_flags & DCC_WF_PER_USER)) { | |
711 dcc_error_msg("%s is a per-user whitelist" | |
712 " used as a global whitelist", | |
713 wf->ht_nm); | |
714 return 0; | |
715 } | |
716 if (!(wf->wtbl_flags & DCC_WHITE_FG_PER_USER) | |
717 && (wf->wf_flags & DCC_WF_PER_USER)) { | |
718 dcc_error_msg("%s is a global whitelist" | |
719 " used as a per-user whitelist", | |
720 wf->ht_nm); | |
721 return 0; | |
722 } | |
723 | |
724 /* Checksums of SMTP client IP addresses are compared against the | |
725 * checksums of the IP addresses of the hostnames in the ASCII file. | |
726 * Occassionaly check for changes in DNS A RR's for entries in | |
727 * the ASCII file, but only if there are host names or IP | |
728 * addresses in the file */ | |
729 if ((wf->wtbl->hdr.flags & DCC_WHITE_FG_HOSTNAMES) | |
730 && DCC_IS_TIME(now, wf->ht_sb.st_mtime+DCC_WHITECLNT_RESOLVE, | |
731 DCC_WHITECLNT_RESOLVE)) { | |
732 if (dcc_clnt_debug > 2) | |
733 dcc_trace_msg("time to rebuild %s", wf->ht_nm); | |
734 return 0; | |
735 } | |
736 | |
737 return 1; | |
738 } | |
739 | |
740 | |
741 | |
742 static u_char | |
743 write_white(DCC_EMSG emsg, DCC_WF *wf, const void *buf, int buf_len, off_t pos) | |
744 { | |
745 int i; | |
746 | |
747 if (wf->wtbl) { | |
748 #ifdef DCC_WIN32 | |
749 /* Windows disclaims coherence between ordinary writes | |
750 * and memory mapped writing. The hash tables are | |
751 * fixed size on Windows because of problems with WIN32 | |
752 * mapping objects, so we do not need to worry about | |
753 * extending the hash table file. */ | |
754 memcpy((char *)wf->wtbl+pos, buf, buf_len); | |
755 return 1; | |
756 #else | |
757 /* some UNIX systems have coherence trouble without msync() */ | |
758 if (0 > MSYNC(wf->wtbl, wf->wtbl_size, MS_SYNC)) { | |
759 dcc_pemsg(EX_IOERR, emsg, "msync(%s): %s", | |
760 wf->ht_nm, ERROR_STR()); | |
761 return 0; | |
762 } | |
763 #endif | |
764 } | |
765 | |
766 i = lseek(wf->ht_fd, pos, SEEK_SET); | |
767 if (i < 0) { | |
768 dcc_pemsg(EX_IOERR, emsg, "lseek(%s,"OFF_DPAT"): %s", | |
769 wf->ht_nm, pos, ERROR_STR()); | |
770 return 0; | |
771 } | |
772 i = write(wf->ht_fd, buf, buf_len); | |
773 if (i != buf_len) { | |
774 if (i < 0) | |
775 dcc_pemsg(EX_IOERR, emsg, "write(%s,%d): %s", | |
776 wf->ht_nm, buf_len, ERROR_STR()); | |
777 else | |
778 dcc_pemsg(EX_IOERR, emsg, "write(%s,%d): %d", | |
779 wf->ht_nm, buf_len, i); | |
780 return 0; | |
781 } | |
782 return 1; | |
783 } | |
784 | |
785 | |
786 | |
787 static int /* 1=ok, 0=bad entry, -1=fatal */ | |
788 add_white(DCC_EMSG emsg, DCC_WF *wf, | |
789 DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts) | |
790 { | |
791 DCC_WHITE_ENTRY *e, new; | |
792 DCC_WHITE_INX bin; | |
793 DCC_FNM_LNO_BUF fnm_buf; | |
794 off_t end; | |
795 | |
796 /* find the hash chain for the new entry */ | |
797 e = find_white(emsg, wf, type, sum, &bin); | |
798 if (e == FIND_WHITE_BROKEN) | |
799 return -1; | |
800 | |
801 /* ignore duplicates on this, the first pass */ | |
802 if (e) | |
803 return 1; | |
804 | |
805 memset(&new, 0, sizeof(new)); | |
806 new.type = type; | |
807 memcpy(new.sum, sum, sizeof(DCC_SUM)); | |
808 new.tgts = tgts; | |
809 new.fwd = wf->wtbl->bins[bin]; | |
810 new.lno = wf->lno; | |
811 new.fno = wf->fno; | |
812 | |
813 /* Use a new entry at the end of the file | |
814 * It will be beyond the memory mapped window into the file */ | |
815 if (wf->wtbl->hdr.entries >= MAX_WHITE_ENTRIES) { | |
816 dcc_pemsg(EX_DATAERR, emsg, "more than maximum %d entries%s", | |
817 wf->wtbl->hdr.entries, wf_fnm_lno(&fnm_buf, wf)); | |
818 return 0; | |
819 } | |
820 end = ENTRIES2SIZE(wf->wtbl->hdr.entries); | |
821 wf->wtbl->bins[bin] = ++wf->wtbl->hdr.entries; | |
822 return write_white(emsg, wf, &new, sizeof(new), end) ? 1 : -1; | |
823 } | |
824 | |
825 | |
826 | |
827 #define MAX_CIDR_BITS 7 | |
828 | |
829 static int /* 1=ok, 0=bad entry, -1=fatal */ | |
830 add_white_cidr(DCC_EMSG emsg, DCC_WF *wf, | |
831 int bits, | |
832 const struct in6_addr *addrp, const struct in6_addr *maskp, | |
833 DCC_TGTS tgts) | |
834 { | |
835 DCC_WHITE_CIDR_ENTRY *e; | |
836 struct in6_addr addr; | |
837 DCC_SUM sum; | |
838 DCC_FNM_LNO_BUF fnm_buf; | |
839 int result, i, j; | |
840 | |
841 /* use individual entries for MAX_CIDR_BITS and smaller blocks */ | |
842 if (128-bits <= MAX_CIDR_BITS) { | |
843 addr = *addrp; | |
844 result = 1; | |
845 for (i = 1 << (128-bits); i > 0; --i) { | |
846 dcc_ck_ipv6(sum, &addr); | |
847 j = add_white(emsg, wf, DCC_CK_IP, sum, tgts); | |
848 if (j <= 0) { | |
849 if (j < 0) | |
850 return j; | |
851 result = j; | |
852 } | |
853 addr.s6_addr32[3] = ntohl(addr.s6_addr32[3]); | |
854 ++addr.s6_addr32[3]; | |
855 addr.s6_addr32[3] = htonl(addr.s6_addr32[3]); | |
856 } | |
857 return result; | |
858 } | |
859 | |
860 i = wf->wtbl->hdr.cidr.len; | |
861 for (e = wf->wtbl->hdr.cidr.e; i > 0; ++e, --i) { | |
862 /* ignore collisions on this, the first pass */ | |
863 if (e->bits == bits | |
864 && !memcmp(addrp, &e->addr, sizeof(*addrp))) | |
865 return 1; | |
866 } | |
867 | |
868 if (wf->wtbl->hdr.cidr.len >= DIM(wf->wtbl->hdr.cidr.e)) { | |
869 dcc_pemsg(EX_DATAERR, emsg, "too many CIDR blocks%s", | |
870 wf_fnm_lno(&fnm_buf, wf)); | |
871 return 0; | |
872 } | |
873 | |
874 e->bits = bits; | |
875 e->addr = *addrp; | |
876 e->mask = *maskp; | |
877 e->tgts = tgts; | |
878 e->lno = wf->lno; | |
879 e->fno = wf->fno; | |
880 ++wf->wtbl->hdr.cidr.len; | |
881 | |
882 return 1; | |
883 } | |
884 | |
885 | |
886 | |
887 static void | |
888 dup_msg(DCC_EMSG emsg, DCC_WF *wf, int e_fno, int e_lno, | |
889 DCC_TGTS e_tgts, DCC_TGTS tgts) | |
890 { | |
891 char tgts_buf[30], e_tgts_buf[30]; | |
892 const char *fname1, *fname2; | |
893 | |
894 fname1 = wf_fnm(wf, wf->fno); | |
895 fname2 = wf_fnm(wf, e_fno); | |
896 dcc_pemsg(EX_DATAERR, emsg, | |
897 "\"%s\" in line %d%s%s conflicts with \"%s\" in line" | |
898 " %d of %s", | |
899 dcc_tgts2str(tgts_buf, sizeof(tgts_buf), tgts, 0), | |
900 wf->lno, | |
901 fname1 != fname2 ? " of " : "", | |
902 fname1 != fname2 ? fname1 : "", | |
903 dcc_tgts2str(e_tgts_buf, sizeof(e_tgts_buf), e_tgts, 0), | |
904 e_lno, | |
905 fname2); | |
906 } | |
907 | |
908 | |
909 | |
910 static DCC_TGTS | |
911 combine_white_tgts(DCC_TGTS new, DCC_TGTS old) | |
912 { | |
913 if (new < DCC_TGTS_TOO_MANY && old == DCC_TGTS_TOO_MANY) | |
914 return new; | |
915 | |
916 if (new == DCC_TGTS_OK || old == DCC_TGTS_OK) | |
917 return DCC_TGTS_OK; | |
918 if (new == DCC_TGTS_OK2 || old == DCC_TGTS_OK2) | |
919 return DCC_TGTS_OK2; | |
920 if (new == DCC_TGTS_OK_MX || old == DCC_TGTS_OK_MX) | |
921 return DCC_TGTS_OK_MX; | |
922 if (new == DCC_TGTS_OK_MXDCC || old == DCC_TGTS_OK_MXDCC) | |
923 return DCC_TGTS_OK_MXDCC; | |
924 if (new == DCC_TGTS_TOO_MANY || old == DCC_TGTS_TOO_MANY) | |
925 return DCC_TGTS_TOO_MANY; | |
926 if (new == DCC_TGTS_SUBMIT_CLIENT || old == DCC_TGTS_SUBMIT_CLIENT) | |
927 return DCC_TGTS_SUBMIT_CLIENT; | |
928 | |
929 return new; | |
930 } | |
931 | |
932 | |
933 | |
934 static int /* 1=ok, 0=bad entry, -1=fatal */ | |
935 ck_dup_white(DCC_EMSG emsg, DCC_WF *wf, | |
936 DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts, u_char complain) | |
937 { | |
938 DCC_WHITE_ENTRY *e; | |
939 DCC_FNM_LNO_BUF buf; | |
940 const char *from; | |
941 | |
942 /* find the hash table entry */ | |
943 e = find_white(emsg, wf, type, sum, 0); | |
944 if (e == FIND_WHITE_BROKEN) | |
945 return -1; | |
946 if (!e) { | |
947 /* We failed to find the hash table entry. Either the | |
948 * hash file is corrupt, the ASCII file is changing beneath | |
949 * our feet, or a host name that failed to resolve the first | |
950 * time we parsed the ASCII file is now resolving during | |
951 * this second parsing. */ | |
952 from = wf_fnm_lno(&buf, wf); | |
953 if (type == DCC_CK_IP) { | |
954 if (dcc_clnt_debug > 2) | |
955 dcc_trace_msg("%s entry (dis)appeared in %s%s", | |
956 dcc_type2str_err(type, 0, 0, 0), | |
957 wf->ht_nm, | |
958 *from == '\0' ? "in ?" : from); | |
959 return 0; | |
960 } | |
961 dcc_pemsg(EX_DATAERR, emsg, "%s entry (dis)appeared in %s%s", | |
962 dcc_type2str_err(type, 0, 0, 0), | |
963 wf->ht_nm, | |
964 *from == '\0' ? "in ?" : from); | |
965 return -1; | |
966 } | |
967 | |
968 /* ignore perfect duplicates */ | |
969 if (e->tgts == tgts) | |
970 return 1; | |
971 | |
972 if (complain) | |
973 dup_msg(emsg, wf, e->fno, e->lno, e->tgts, tgts); | |
974 | |
975 if (e->tgts != combine_white_tgts(tgts, e->tgts)) { | |
976 e->tgts = tgts; | |
977 e->lno = wf->lno; | |
978 e->fno = wf->fno; | |
979 } | |
980 return 0; | |
981 } | |
982 | |
983 | |
984 | |
985 static int /* 1=ok, 0=bad entry, -1=fatal */ | |
986 ck_dup_white_complain(DCC_EMSG emsg, DCC_WF *wf, | |
987 DCC_CK_TYPES type, DCC_SUM sum, DCC_TGTS tgts) | |
988 { | |
989 return ck_dup_white(emsg, wf, type, sum, tgts, 1); | |
990 } | |
991 | |
992 | |
993 | |
994 /* Without brute force checks that would take too long, it is impossible to | |
995 * check that a new CIDR block does not collide with individual entries that | |
996 * are already in the hash table. Without expanding a big CIDR block into | |
997 * IP addresses and looking for each in the hash table, how can you check | |
998 * whether it covers an entry for an individual address? One way is to | |
999 * parse the file twice and so check each individual IP address against | |
1000 * the CIDR blocks. */ | |
1001 static int /* 1=ok, 0=bad entry, -1=fatal */ | |
1002 ck_dup_white_cidr(DCC_EMSG emsg UATTRIB, DCC_WF *wf, | |
1003 int bits, | |
1004 const struct in6_addr *addrp, const struct in6_addr *maskp, | |
1005 DCC_TGTS tgts) | |
1006 { | |
1007 DCC_WHITE_CIDR_ENTRY *e; | |
1008 struct in6_addr addr; | |
1009 DCC_SUM sum; | |
1010 int result, i, j; | |
1011 | |
1012 result = 1; | |
1013 | |
1014 /* Check for collisions between CIDR blocks and either other | |
1015 * CIDR blocks or individual addresses. Individual addresses | |
1016 * are sent here as /128 blocks. */ | |
1017 i = wf->wtbl->hdr.cidr.len; | |
1018 for (e = wf->wtbl->hdr.cidr.e; i > 0; ++e, --i) { | |
1019 if (!DCC_IN_BLOCK(e->addr, *addrp, *maskp) | |
1020 && !DCC_IN_BLOCK(*addrp, e->addr, e->mask)) | |
1021 continue; | |
1022 | |
1023 /* ignore simple duplicates */ | |
1024 if (e->tgts == tgts) | |
1025 continue; | |
1026 | |
1027 dup_msg(emsg, wf, e->fno, e->lno, e->tgts, tgts); | |
1028 result = 0; | |
1029 | |
1030 /* fix direct collisions */ | |
1031 if (e->bits == bits | |
1032 && !memcmp(addrp, &e->addr, sizeof(*addrp)) | |
1033 && e->tgts != combine_white_tgts(tgts, e->tgts)) { | |
1034 e->tgts = tgts; | |
1035 e->lno = wf->lno; | |
1036 e->fno = wf->fno; | |
1037 } | |
1038 } | |
1039 | |
1040 /* check and fix collisions among individual entries */ | |
1041 if (128-bits <= MAX_CIDR_BITS) { | |
1042 addr = *addrp; | |
1043 for (i = 1 << (128-bits); i > 0; --i) { | |
1044 dcc_ck_ipv6(sum, &addr); | |
1045 j = ck_dup_white(emsg, wf, DCC_CK_IP, sum, tgts, | |
1046 result > 0); | |
1047 if (j < 0) | |
1048 return j; | |
1049 if (j == 0) | |
1050 result = j; | |
1051 addr.s6_addr32[3] = htonl(1+ntohl(addr.s6_addr32[3])); | |
1052 } | |
1053 } | |
1054 | |
1055 return result; | |
1056 } | |
1057 | |
1058 | |
1059 | |
1060 /* bail out on creating a whiteclnt hash table file */ | |
1061 static DCC_WHITE_RESULT | |
1062 make_white_hash_bail(DCC_WHITE_RESULT result, DCC_WF *wf, DCC_WF *tmp_wf) | |
1063 { | |
1064 if (tmp_wf->ht_nm[0] != '\0') { | |
1065 unlink_white_ht(tmp_wf, 0); | |
1066 tmp_wf->ht_nm[0] = '\0'; | |
1067 } | |
1068 | |
1069 /* we have a usable file open */ | |
1070 if (wf->wtbl) | |
1071 return DCC_WHITE_CONTINUE; | |
1072 return result; | |
1073 } | |
1074 | |
1075 | |
1076 | |
1077 /* (re)create the hash table file */ | |
1078 static DCC_WHITE_RESULT | |
1079 make_white_hash(DCC_EMSG emsg, DCC_WF *wf, DCC_WF *tmp_wf) | |
1080 { | |
1081 static const u_char zero = 0; | |
1082 int ascii_fd; | |
1083 struct stat ascii_sb; | |
1084 DCC_PATH path; | |
1085 DCC_WHITE_RESULT result; | |
1086 DCC_CK_TYPES type; | |
1087 int part_result, i; | |
1088 | |
1089 if (dcc_clnt_debug > 2) | |
1090 dcc_trace_msg("start parsing %s", wf->ascii_nm); | |
1091 | |
1092 /* Do not wait to create a new, locked hash table file if we have | |
1093 * a usable file. Assume some other process is re-parsing | |
1094 * the ASCII file. | |
1095 * If we should wait to create a new file, then we don't have a | |
1096 * usable hash table and so there is no reason to unlock the | |
1097 * DCC_WF structure. */ | |
1098 | |
1099 #ifdef DCC_DEBUG_CLNT_LOCK | |
1100 if (tmp_wf == &cmn_tmp_wf) | |
1101 assert_cwf_locked(); | |
1102 #endif | |
1103 | |
1104 dcc_wf_init(tmp_wf, wf->wf_flags); | |
1105 | |
1106 if (0 > stat(wf->ascii_nm, &ascii_sb)) { | |
1107 result = ((errno == ENOENT || errno == ENOTDIR) | |
1108 ? DCC_WHITE_NOFILE | |
1109 : DCC_WHITE_COMPLAIN); | |
1110 | |
1111 dcc_pemsg(EX_IOERR, emsg, "stat(%s): %s", | |
1112 wf->ascii_nm, ERROR_STR()); | |
1113 | |
1114 /* Delete the hash table if the ASCII file has disappeared. | |
1115 * stat_white_nms() delays forcing a re-build if the ASCII | |
1116 * file disappears only temporarily */ | |
1117 if (wf->ht_nm[0] != '\0') { | |
1118 close_white_ht(0, wf); | |
1119 i = unlink(wf->ht_nm); | |
1120 if (i < 0) { | |
1121 if (errno != ENOENT && errno != ENOTDIR) | |
1122 dcc_trace_msg("%s missing,:unlink(%s):" | |
1123 " %s", | |
1124 wf->ascii_nm, | |
1125 fnm2abs_err(path, | |
1126 wf->ht_nm), | |
1127 ERROR_STR()); | |
1128 } else if (dcc_clnt_debug > 1) { | |
1129 dcc_trace_msg("delete %s after %s missing", | |
1130 fnm2abs_err(path, wf->ht_nm), | |
1131 wf->ascii_nm); | |
1132 } | |
1133 } | |
1134 return make_white_hash_bail(result, wf, tmp_wf); | |
1135 } | |
1136 | |
1137 part_result = create_white_ht(emsg, tmp_wf, wf); | |
1138 if (part_result == 0) { | |
1139 /* The new hash table file is busy. | |
1140 * Do not complain if we have a usable open hash table file */ | |
1141 if (!dcc_clnt_debug && wf->wtbl) | |
1142 return DCC_WHITE_OK; | |
1143 /* at least ignore the need to reparse */ | |
1144 return DCC_WHITE_CONTINUE; | |
1145 } | |
1146 if (part_result < 0) | |
1147 return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf); | |
1148 | |
1149 /* clear the new hash table */ | |
1150 if (!write_white(emsg, tmp_wf, white_magic, sizeof(white_magic), 0) | |
1151 || !write_white(emsg, tmp_wf, &zero, 1, EMPTY_WHITE_SIZE-1) | |
1152 || !map_white_ht(emsg, tmp_wf, 0)) | |
1153 return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf); | |
1154 if (tmp_wf->wf_flags & DCC_WF_PER_USER) | |
1155 tmp_wf->wtbl->hdr.flags = DCC_WHITE_FG_PER_USER; | |
1156 tmp_wf->wtbl_flags = tmp_wf->wtbl->hdr.flags; | |
1157 for (type = 0; type <= DCC_CK_TYPE_LAST; ++type) | |
1158 tmp_wf->wtbl->hdr.tholds_rej[type] = DCC_THOLD_UNSET; | |
1159 | |
1160 ascii_fd = dcc_lock_open(emsg, tmp_wf->ascii_nm, O_RDONLY, | |
1161 DCC_LOCK_OPEN_NOWAIT | DCC_LOCK_OPEN_SHARE, | |
1162 DCC_LOCK_ALL_FILE, 0); | |
1163 if (ascii_fd == -1) | |
1164 return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf); | |
1165 | |
1166 tmp_wf->wtbl->hdr.ascii_mtime = ascii_sb.st_mtime; | |
1167 part_result = dcc_parse_whitefile(emsg, tmp_wf, ascii_fd, | |
1168 add_white, add_white_cidr); | |
1169 if (part_result > 0) { | |
1170 /* parse again to detect colliding definitions among | |
1171 * host names and CIDR blocks */ | |
1172 if (0 != lseek(ascii_fd, 0, SEEK_SET)) { | |
1173 dcc_pemsg(EX_IOERR, emsg, "lseek(%s, 0, SEEK_SET): %s", | |
1174 wf->ascii_nm, ERROR_STR()); | |
1175 part_result = -1; | |
1176 } else { | |
1177 part_result = dcc_parse_whitefile(emsg, tmp_wf, | |
1178 ascii_fd, | |
1179 ck_dup_white_complain, | |
1180 ck_dup_white_cidr); | |
1181 } | |
1182 } | |
1183 | |
1184 /* if the hash table is tolerable, compute a checksum of it | |
1185 * to detect differing per-user hash tables */ | |
1186 if (part_result >= 0 | |
1187 && map_white_ht(emsg, tmp_wf, tmp_wf->wtbl->hdr.entries)) { | |
1188 MD5_CTX ctx; | |
1189 | |
1190 MD5Init(&ctx); | |
1191 MD5Update(&ctx, (u_char *)&tmp_wf->wtbl->hdr.cidr, | |
1192 sizeof(tmp_wf->wtbl->hdr.cidr)); | |
1193 i = tmp_wf->wtbl->hdr.entries; | |
1194 while (--i >= 0) { | |
1195 MD5Update(&ctx, &tmp_wf->wtbl->tbl[i].type, | |
1196 sizeof(tmp_wf->wtbl->tbl[i].type)); | |
1197 MD5Update(&ctx, tmp_wf->wtbl->tbl[i].sum, | |
1198 sizeof(tmp_wf->wtbl->tbl[i].sum)); | |
1199 } | |
1200 MD5Final(tmp_wf->wtbl->hdr.ck_sum, &ctx); | |
1201 } | |
1202 #ifdef DCC_WIN32 | |
1203 /* unlock the file before closing it to keep Win95 happy */ | |
1204 dcc_unlock_fd(0, ascii_fd, DCC_LOCK_ALL_FILE, | |
1205 "whitelist ", tmp_wf->ascii_nm); | |
1206 #endif | |
1207 if (close(ascii_fd) < 0) | |
1208 dcc_trace_msg("close(%s): %s", wf->ascii_nm, ERROR_STR()); | |
1209 if (part_result < 0) | |
1210 return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf); | |
1211 result = (part_result > 0) ? DCC_WHITE_OK : DCC_WHITE_CONTINUE; | |
1212 | |
1213 /* ensure continued complaints about errors */ | |
1214 if (result == DCC_WHITE_CONTINUE) | |
1215 tmp_wf->wtbl->hdr.reparse = time(0) + DCC_WHITE_REPARSE_DELAY; | |
1216 sync_white(tmp_wf); | |
1217 | |
1218 #ifdef DCC_WIN32 | |
1219 /* WIN32 prohibits renaming open files | |
1220 * and there is little or no concurrency on WIN32 DCC clients | |
1221 * So lock the original file and copy to it. */ | |
1222 close_white_ht(0, wf); | |
1223 wf->ht_fd = dcc_lock_open(emsg, wf->ht_nm, O_RDWR | O_CREAT, | |
1224 0, DCC_LOCK_ALL_FILE, 0); | |
1225 if (wf->ht_fd < 0) | |
1226 return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf); | |
1227 if (!map_white_ht(emsg, wf, tmp_wf->wtbl_entries)) | |
1228 return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf); | |
1229 memcpy(wf->wtbl, tmp_wf->wtbl, tmp_wf->wtbl_size); | |
1230 close_white_ht(emsg, tmp_wf); | |
1231 unlink(tmp_wf->ht_nm); | |
1232 #else | |
1233 part_result = rename(tmp_wf->ht_nm, wf->ht_nm); | |
1234 if (0 > part_result | |
1235 && dcc_get_priv_home(wf->ht_nm)) { | |
1236 part_result = rename(tmp_wf->ht_nm, wf->ht_nm); | |
1237 dcc_rel_priv(); | |
1238 } | |
1239 if (0 > part_result) { | |
1240 dcc_pemsg(EX_IOERR, emsg, "rename(%s, %s): %s", | |
1241 tmp_wf->ht_nm, wf->ht_nm, ERROR_STR()); | |
1242 return make_white_hash_bail(DCC_WHITE_COMPLAIN, wf, tmp_wf); | |
1243 } | |
1244 #endif | |
1245 | |
1246 close_white_ht(0, tmp_wf); | |
1247 if (dcc_clnt_debug > 1) | |
1248 dcc_trace_msg("finished parsing %s", wf->ascii_nm); | |
1249 part_result = open_white_ht(emsg, wf); | |
1250 if (part_result < 0) | |
1251 result = DCC_WHITE_COMPLAIN; | |
1252 else if (part_result == 0 && result == DCC_WHITE_OK) | |
1253 result = DCC_WHITE_CONTINUE; | |
1254 | |
1255 wf->next_stat_time = time(0) + DCC_WHITE_STAT_DELAY; | |
1256 | |
1257 return result; | |
1258 } | |
1259 | |
1260 | |
1261 | |
1262 /* see that a local whitelist is ready | |
1263 * on failure the file is not locked | |
1264 * The caller must lock the DCC_WF if necessary */ | |
1265 DCC_WHITE_RESULT | |
1266 dcc_rdy_white(DCC_EMSG emsg, DCC_WF *wf, DCC_WF *tmp_wf) | |
1267 { | |
1268 time_t now; | |
1269 int i; | |
1270 | |
1271 if (wf->need_reopen) { | |
1272 /* The resolver thread has change the hash table in the file | |
1273 * and possibly renamed it. | |
1274 * We need to re-open the hash table */ | |
1275 wf->broken = 0; | |
1276 wf->reparse = 0; | |
1277 close_white_ht(0, wf); | |
1278 } | |
1279 | |
1280 now = time(0); | |
1281 if (emsg) | |
1282 *emsg = '\0'; | |
1283 | |
1284 if (wf->ht_fd >= 0) { | |
1285 /* The hash table is open. | |
1286 * If we have checked recently, assume everything is good. */ | |
1287 if (!DCC_IS_TIME(now, wf->next_stat_time, DCC_WHITE_STAT_DELAY)) | |
1288 return DCC_WHITE_OK; | |
1289 i = stat_white_nms(emsg, wf); | |
1290 if (i > 0) | |
1291 return DCC_WHITE_OK; | |
1292 if (i < 0) { | |
1293 /* Things are broken or not open, so try to open the | |
1294 * hash table. | |
1295 * We may be racing here. Be happy if another process | |
1296 * fixes the hash table while we stall trying to open | |
1297 * it locked. */ | |
1298 i = open_white_ht(emsg, wf); | |
1299 if (i < 0) | |
1300 return DCC_WHITE_COMPLAIN; | |
1301 if (i > 0) | |
1302 i = stat_white_nms(emsg, wf); | |
1303 } | |
1304 | |
1305 } else { | |
1306 if (wf->ascii_nm[0] == '\0') { | |
1307 dcc_pemsg(EX_NOINPUT, emsg, "no whitelist"); | |
1308 return DCC_WHITE_NOFILE; | |
1309 } | |
1310 | |
1311 if (STILL_BROKE(wf, now)) { | |
1312 /* only check for a missing file occassionally */ | |
1313 if (wf->wf_flags & DCC_WF_NOFILE) { | |
1314 dcc_pemsg(EX_NOINPUT, emsg, "%s does not exist", | |
1315 wf->ascii_nm); | |
1316 return DCC_WHITE_NOFILE; | |
1317 } | |
1318 | |
1319 /* If things are broken, and it has not been a while, | |
1320 * then assume things are still broken. */ | |
1321 dcc_pemsg(EX_DATAERR, emsg, | |
1322 "%s still broken", wf->ascii_nm); | |
1323 return (dcc_clnt_debug > 2 | |
1324 ? DCC_WHITE_COMPLAIN | |
1325 : DCC_WHITE_SILENT); | |
1326 } | |
1327 | |
1328 i = open_white_ht(emsg, wf); | |
1329 if (i < 0) | |
1330 return DCC_WHITE_COMPLAIN; | |
1331 if (i > 0) | |
1332 i = stat_white_nms(emsg, wf); | |
1333 } | |
1334 | |
1335 if (i > 0) | |
1336 return DCC_WHITE_OK; | |
1337 | |
1338 /* Try to let the resolver thread wait for the DNS chitchat | |
1339 * for host names that might now be in the ASCII file. | |
1340 * To avoid racing with the resolver thread to delete the main | |
1341 * hash files, fail if there is no hash table and this is not | |
1342 * the resolver thread. */ | |
1343 if (i == 0 | |
1344 && !(wf->wf_flags & DCC_WF_PER_USER) | |
1345 && dcc_clnt_wake_resolve()) { | |
1346 if (wf->wtbl) | |
1347 return DCC_WHITE_OK; | |
1348 return (dcc_clnt_debug > 2 | |
1349 ? DCC_WHITE_COMPLAIN : DCC_WHITE_SILENT); | |
1350 } | |
1351 | |
1352 if (STILL_BROKE(wf, now)) { | |
1353 dcc_pemsg(EX_DATAERR, emsg, "%s still broken", wf->ascii_nm); | |
1354 if (i == 0 && wf->wtbl) | |
1355 return (dcc_clnt_debug > 2 | |
1356 ? DCC_WHITE_CONTINUE : DCC_WHITE_SILENT); | |
1357 return (dcc_clnt_debug > 2 | |
1358 ? DCC_WHITE_COMPLAIN : DCC_WHITE_SILENT); | |
1359 } | |
1360 | |
1361 if (emsg && *emsg != '\0') { | |
1362 if (i < 0) { | |
1363 dcc_error_msg("%s", emsg); | |
1364 } else if (dcc_clnt_debug > 2) { | |
1365 dcc_trace_msg("%s", emsg); | |
1366 } | |
1367 *emsg = '\0'; | |
1368 } | |
1369 | |
1370 switch (make_white_hash(emsg, wf, tmp_wf)) { | |
1371 case DCC_WHITE_OK: | |
1372 wf->wf_flags &= ~DCC_WF_NOFILE; | |
1373 return DCC_WHITE_OK; /* all is good */ | |
1374 case DCC_WHITE_CONTINUE: /* minor syntax error or bad hostname */ | |
1375 wf->wf_flags &= ~DCC_WF_NOFILE; | |
1376 return DCC_WHITE_CONTINUE; | |
1377 case DCC_WHITE_SILENT: /* no new message */ | |
1378 wf->wf_flags &= ~DCC_WF_NOFILE; | |
1379 return DCC_WHITE_CONTINUE; | |
1380 case DCC_WHITE_NOFILE: | |
1381 wf->wf_flags |= DCC_WF_NOFILE; | |
1382 return DCC_WHITE_NOFILE; | |
1383 case DCC_WHITE_COMPLAIN: | |
1384 default: | |
1385 is_broken(wf); | |
1386 wf->wf_flags &= ~DCC_WF_NOFILE; | |
1387 return DCC_WHITE_COMPLAIN; | |
1388 } | |
1389 } | |
1390 | |
1391 | |
1392 | |
1393 static u_char | |
1394 lookup_white(DCC_EMSG emsg, | |
1395 DCC_TGTS *tgtsp, /* value if hit else DCC_TGTS_INVALID */ | |
1396 DCC_WF *wf, | |
1397 const DCC_GOT_CKS *cks, const DCC_GOT_SUM *g) | |
1398 { | |
1399 const DCC_WHITE_ENTRY *e; | |
1400 const DCC_WHITE_CIDR_ENTRY *cidrp; | |
1401 int bits; | |
1402 | |
1403 e = find_white(emsg, wf, g->type, g->sum, 0); | |
1404 if (e == FIND_WHITE_BROKEN) { | |
1405 *tgtsp = DCC_TGTS_OK; | |
1406 return 0; | |
1407 } | |
1408 | |
1409 if (!e) { | |
1410 if (g->type != DCC_CK_IP) { | |
1411 *tgtsp = DCC_TGTS_INVALID; | |
1412 return 1; | |
1413 } | |
1414 | |
1415 /* if we had no hit and it is an IP address, | |
1416 * check the CIDR blocks */ | |
1417 bits = 0; | |
1418 cidrp = &wf->wtbl->hdr.cidr.e[wf->wtbl->hdr.cidr.len]; | |
1419 while (cidrp != wf->wtbl->hdr.cidr.e) { | |
1420 --cidrp; | |
1421 /* look for the longest match */ | |
1422 if (cidrp->bits <= bits) | |
1423 continue; | |
1424 if (DCC_IN_BLOCK(cks->ip_addr, | |
1425 cidrp->addr, cidrp->mask)) { | |
1426 *tgtsp = cidrp->tgts; | |
1427 bits = cidrp->bits; | |
1428 } | |
1429 } | |
1430 if (bits == 0) | |
1431 *tgtsp = DCC_TGTS_INVALID; | |
1432 return 1; | |
1433 } | |
1434 | |
1435 *tgtsp = e->tgts; | |
1436 return 1; | |
1437 } | |
1438 | |
1439 | |
1440 | |
1441 /* check a local whitelist for a single checksum | |
1442 * on exit the file is locked except after an error */ | |
1443 DCC_WHITE_RESULT | |
1444 dcc_white_sum(DCC_EMSG emsg, | |
1445 DCC_WF *wf, /* in this whitelist */ | |
1446 DCC_CK_TYPES type, const DCC_SUM sum, /* look for this checksum */ | |
1447 DCC_TGTS *tgtsp, /* set only if we find the checksum */ | |
1448 DCC_WHITE_LISTING *listingp) | |
1449 { | |
1450 DCC_WHITE_ENTRY *e; | |
1451 DCC_WHITE_RESULT result; | |
1452 | |
1453 result = dcc_rdy_white(emsg, wf, &cmn_tmp_wf); | |
1454 switch (result) { | |
1455 case DCC_WHITE_OK: | |
1456 case DCC_WHITE_CONTINUE: | |
1457 break; | |
1458 case DCC_WHITE_SILENT: | |
1459 case DCC_WHITE_NOFILE: | |
1460 case DCC_WHITE_COMPLAIN: | |
1461 *listingp = DCC_WHITE_RESULT_FAILURE; | |
1462 return result; | |
1463 } | |
1464 | |
1465 e = find_white(emsg, wf, type, sum, 0); | |
1466 if (e == FIND_WHITE_BROKEN) { | |
1467 *listingp = DCC_WHITE_RESULT_FAILURE; | |
1468 return DCC_WHITE_COMPLAIN; | |
1469 } | |
1470 | |
1471 if (!e) { | |
1472 *listingp = DCC_WHITE_UNLISTED; | |
1473 } else if (e->tgts == DCC_TGTS_OK2 | |
1474 && type == DCC_CK_ENV_TO) { | |
1475 /* deprecated mechanism for turn DCC checks on and off for | |
1476 * individual targets */ | |
1477 *tgtsp = e->tgts; | |
1478 *listingp = DCC_WHITE_USE_DCC; | |
1479 } else if (e->tgts == DCC_TGTS_OK) { | |
1480 *tgtsp = e->tgts; | |
1481 *listingp = DCC_WHITE_LISTED; | |
1482 } else if (e->tgts == DCC_TGTS_TOO_MANY) { | |
1483 *tgtsp = e->tgts; | |
1484 *listingp = DCC_WHITE_BLACK; | |
1485 } else { | |
1486 *listingp = DCC_WHITE_UNLISTED; | |
1487 } | |
1488 | |
1489 return result; | |
1490 } | |
1491 | |
1492 | |
1493 | |
1494 /* see if an IP addess is that of one of our MX servers | |
1495 * the caller must lock cmn_wf if necessray */ | |
1496 u_char /* 0=problems */ | |
1497 dcc_white_mx(DCC_EMSG emsg, | |
1498 DCC_TGTS *tgtsp, /* !=0 if listed */ | |
1499 const DCC_GOT_CKS *cks) /* this IP address checksum */ | |
1500 { | |
1501 u_char result; | |
1502 | |
1503 result = 1; | |
1504 switch (dcc_rdy_white(emsg, &cmn_wf, &cmn_tmp_wf)) { | |
1505 case DCC_WHITE_OK: | |
1506 break; | |
1507 case DCC_WHITE_CONTINUE: | |
1508 result = 0; | |
1509 break; | |
1510 case DCC_WHITE_NOFILE: | |
1511 *tgtsp = 0; | |
1512 return 1; | |
1513 case DCC_WHITE_SILENT: | |
1514 case DCC_WHITE_COMPLAIN: | |
1515 *tgtsp = 0; | |
1516 return 0; | |
1517 } | |
1518 | |
1519 if (cks->sums[DCC_CK_IP].type != DCC_CK_IP) { | |
1520 *tgtsp = 0; | |
1521 return result; | |
1522 } | |
1523 | |
1524 if (!lookup_white(emsg, tgtsp, &cmn_wf, cks, &cks->sums[DCC_CK_IP])) | |
1525 return 0; | |
1526 | |
1527 return result; | |
1528 } | |
1529 | |
1530 | |
1531 | |
1532 /* See what a local whitelist file says about the checksums for a message. | |
1533 * The message is whitelisted if at least one checksum is in the local | |
1534 * whitelist or if there are two or more OK2 values. | |
1535 * Otherwise it is blacklisted if at least one checksum is. | |
1536 * The caller must lock the DCC_WF if necessary. */ | |
1537 DCC_WHITE_RESULT /* whether the lookup worked */ | |
1538 dcc_white_cks(DCC_EMSG emsg, DCC_WF *wf, | |
1539 DCC_GOT_CKS *cks, /* these checksums */ | |
1540 DCC_CKS_WTGTS wtgts, /* whitelist targets, each cks->sums */ | |
1541 DCC_WHITE_LISTING *listingp) /* the answer found */ | |
1542 { | |
1543 const DCC_GOT_SUM *g; | |
1544 int inx; | |
1545 DCC_TGTS tgts, prev_tgts; | |
1546 DCC_WHITE_RESULT result; | |
1547 | |
1548 result = dcc_rdy_white(emsg, wf, &cmn_tmp_wf); | |
1549 switch (result) { | |
1550 case DCC_WHITE_OK: | |
1551 case DCC_WHITE_CONTINUE: | |
1552 break; | |
1553 case DCC_WHITE_NOFILE: | |
1554 case DCC_WHITE_COMPLAIN: | |
1555 case DCC_WHITE_SILENT: | |
1556 *listingp = DCC_WHITE_RESULT_FAILURE; | |
1557 return result; | |
1558 } | |
1559 | |
1560 /* look for each checksum in the hash file */ | |
1561 *listingp = DCC_WHITE_UNLISTED; | |
1562 prev_tgts = DCC_TGTS_INVALID; | |
1563 for (g = &cks->sums[inx = DCC_CK_TYPE_FIRST]; | |
1564 g <= LAST(cks->sums); | |
1565 ++g, ++inx) { | |
1566 /* ignore checksums we don't have */ | |
1567 if (g->type == DCC_CK_INVALID) | |
1568 continue; | |
1569 | |
1570 if (!lookup_white(emsg, &tgts, wf, cks, g)) { | |
1571 *listingp = DCC_WHITE_RESULT_FAILURE; | |
1572 return DCC_WHITE_COMPLAIN; | |
1573 } | |
1574 if (tgts == DCC_TGTS_INVALID) { | |
1575 /* report any body checksums as spam for a spam trap */ | |
1576 if ((wf->wtbl_flags & DCC_WHITE_FG_TRAPS) | |
1577 && DCC_CK_IS_BODY(g->type)) | |
1578 tgts = DCC_TGTS_TOO_MANY; | |
1579 else | |
1580 continue; | |
1581 } | |
1582 | |
1583 if (wtgts) | |
1584 wtgts[inx] = tgts; | |
1585 | |
1586 if (tgts == DCC_TGTS_OK) { | |
1587 /* found the checksum in our whitelist, | |
1588 * so we have the answer */ | |
1589 *listingp = DCC_WHITE_LISTED; | |
1590 | |
1591 } else if (tgts == DCC_TGTS_OK2) { | |
1592 if (prev_tgts == DCC_TGTS_OK2) { | |
1593 /* two half-white checksums count the same | |
1594 * as a single pure white checksum | |
1595 * and gives the answer */ | |
1596 *listingp = DCC_WHITE_LISTED; | |
1597 continue; | |
1598 } | |
1599 prev_tgts = DCC_TGTS_OK2; | |
1600 | |
1601 } else if (tgts == DCC_TGTS_TOO_MANY) { | |
1602 if (*listingp == DCC_WHITE_UNLISTED) | |
1603 *listingp = DCC_WHITE_BLACK; | |
1604 } | |
1605 } | |
1606 | |
1607 return result; | |
1608 } |