1 /* $Id: actsync.c 10182 2017-10-16 04:17:01Z eagle $
2 *
3 * actsync - sync or merge two active files
4 *
5 * usage:
6 * actsync [-A][-b hostid][-d hostid][-g max][-i ignore_file][-I hostid][-k]
7 * [-l hostid][-m][-n name][-o fmt][-p %][-q hostid][-s size]
8 * [-t hostid][-T][-v verbose_lvl][-w sec][-z sec]
9 * [host1] host2
10 *
11 * -A use authentication to server
12 * -b hostid ignore *.bork.bork.bork groups from: (def: -b 0)
13 * 0 from neither host
14 * 1 from host1
15 * 2 from host2
16 * 12 from host1 and host2
17 * 21 from host1 and host2
18 * -d hostid ignore groups with all numeric components (def: -d 0)
19 * -g max ignore group >max levels (0=dont ignore) (def: -g 0)
20 * -i ignore_file file with list/types of groups to ignore (def: no file)
21 * -I hostid ignore_file applies only to hostid (def: -I 12)
22 * -k keep host1 groups with errors (def: remove)
23 * -l hostid flag =group problems as errors (def: -l 12)
24 * -m merge, keep group not on host2 (def: sync)
25 * -n name name given to ctlinnd newgroup commands (def: actsync)
26 * -o fmt type of output: (def: -o c)
27 * a output groups in active format
28 * a1 like 'a', but output ignored non-err host1 grps
29 * ak like 'a', keep host2 hi/low values on new groups
30 * aK like 'a', use host2 hi/low values always
31 * a1k like both 'a1' and 'ak'
32 * a1K like both 'a1' and 'aK'
33 * ak1 like both 'a1' and 'ak'
34 * aK1 like both 'a1' and 'aK'
35 * c output in ctlinnd change commands
36 * x no output, safely exec ctlinnd commands
37 * xi no output, safely exec commands interactively
38 * -p % min % host1 lines unchanged allowed (def: -p 96)
39 * -q hostid silence errors from a host (see -b) (def: -q 0)
40 * -s size ignore names longer than size (0=no lim) (def: -s 0)
41 * -t hostid ignore bad top level groups from:(see -b) (def: -t 2)
42 * -T no new hierarchies (def: allow)
43 * -v verbose_lvl verbosity level (def: -v 0)
44 * 0 no debug or status reports
45 * 1 summary if work done
46 * 2 summary & actions (if exec output) only if done
47 * 3 summary & actions (if exec output)
48 * 4 debug output plus all -v 3 messages
49 * -w sec wait sec seconds before ctlinnd timing out (def: -w 30)
50 * -z sec sleep sec seconds per exec if -o x (def: -z 4)
51 * host1 host to be changed (def: local server)
52 * host2 reference host used in merge
53 */
54 /*
55 * By: Landon Curt Noll chongo@toad.com (chongo was here /\../\)
56 *
57 * Copyright (c) Landon Curt Noll, 1996.
58 * All rights reserved.
59 *
60 * Permission to use and modify is hereby granted so long as this
61 * notice remains. Use at your own risk. No warranty is implied.
62 */
63
64 #include "config.h"
65 #include "clibrary.h"
66 #include <ctype.h>
67 #include <dirent.h>
68 #include <fcntl.h>
69 #include <errno.h>
70 #include <math.h>
71 #include <signal.h>
72 #include <sys/stat.h>
73 #include <sys/wait.h>
74
75 #include "inn/innconf.h"
76 #include "inn/messages.h"
77 #include "inn/hashtab.h"
78 #include "inn/qio.h"
79 #include "inn/libinn.h"
80 #include "inn/paths.h"
81
82 static const char usage[] = "\
83 Usage: actsync [-A][-b hostid][-d hostid][-i ignore_file][-I hostid][-k]\n\
84 [-l hostid][-m][-n name][-o fmt][-p min_%_unchg][-q hostid]\n\
85 [-s size][-t hostid][-T][-v verbose_lvl][-w sec][-z sec]\n\
86 [host1] host2\n\
87 \n\
88 -A use authentication to server\n\
89 -b hostid ignore *.bork.bork.bork groups from: (def: -b 0)\n\
90 0 from neither host\n\
91 1 from host1\n\
92 2 from host2\n\
93 12 from host1 and host2\n\
94 21 from host1 and host2\n\
95 -d hostid ignore grps with all numeric components (def: -d 0)\n\
96 -g max ignore group >max levels (0=don't) (def: -g 0)\n\
97 -i file file with groups to ignore (def: no file)\n\
98 -I hostid ignore_file applies only to hostid (def: -I 12)\n\
99 -k keep host1 groups with errors (def: remove)\n\
100 -l hostid flag =group problems as errors (def: -l 12)\n\
101 -m merge, keep group not on host2 (def: sync)\n\
102 -n name name given to ctlinnd newgroup cmds (def: actsync)\n\
103 -o fmt type of output: (def: -o c)\n\
104 a output groups in active format\n\
105 a1 like 'a', but output ignored non-err host1 grps\n\
106 ak like 'a', keep host2 hi/low values on new groups\n\
107 aK like 'a', use host2 hi/low values always\n\
108 a1k like both 'a1' and 'ak'\n\
109 a1K like both 'a1' and 'aK'\n\
110 ak1 like both 'a1' and 'ak'\n\
111 aK1 like both 'a1' and 'aK'\n\
112 c output in ctlinnd change commands\n\
113 x no output, safely exec ctlinnd commands\n\
114 xi no output, safely exec commands interactively\n\
115 -p % min % host1 lines unchanged allowed (def: -p 96)\n\
116 -q hostid silence errors from a host (see -b) (def: -q 0)\n\
117 -s size ignore names > than size (0=no lim) (def: -s 0)\n\
118 -t hostid ignore bad top level grps from: (see -b)(def: -t 2)\n\
119 -T no new hierarchies (def: allow)\n\
120 -v level verbosity level (def: -v 0)\n\
121 0 no debug or status reports\n\
122 1 summary if work done\n\
123 2 summary & actions (if exec output) only if done\n\
124 3 summary & actions (if exec output)\n\
125 4 debug output plus all -v 3 messages\n\
126 -w sec wait sec seconds before ctlinnd timing out (def: -w 30)\n\
127 -z sec sleep sec seconds per exec if -o x (def: -z 4)\n\
128 \n\
129 host1 host to be changed (def: local server)\n\
130 host2 reference host used in merge\n";
131
132
133 /*
134 * pat - internal ignore/check pattern
135 *
136 * A pattern, derived from an ignore file, will determine if a group
137 * will be checked if it is on both hosts or ignored altogether.
138 *
139 * The type related to the 4th field of an active file. Types may
140 * currently be one of [ymjnx=]. If '=' is one of the types, an
141 * optional equivalence pattern may be given in the 'epat' element.
142 *
143 * For example, to ignore "foo.bar.*", if it is junked or equated to
144 * a group of the form "alt.*.foo.bar.*":
145 *
146 * x.pat = "foo.bar.*";
147 * x.type = "j=";
148 * x.epat = "alt.*.foo.bar.*";
149 * x.ignore = 1;
150 *
151 * To further check "foo.bar.mod" if it is moderated:
152 *
153 * x.pat = "foo.bar.mod";
154 * x.type = "m";
155 * x.epat = NULL;
156 * x.ignore = 0;
157 *
158 * The 'i' value means ignore, 'c' value means 'compare'. The last pattern
159 * that matches a group determines the fate of the group. By default all
160 * groups are included.
161 */
162 struct pat {
163 char *pat; /* newsgroup pattern */
164 int type_match; /* 1 => match only if group type matches */
165 int y_type; /* 1 => match if a 'y' type group */
166 int m_type; /* 1 => match if a 'm' type group */
167 int n_type; /* 1 => match if a 'n' type group */
168 int j_type; /* 1 => match if a 'j' type group */
169 int x_type; /* 1 => match if a 'x' type group */
170 int eq_type; /* 1 => match if a 'eq' type group */
171 char *epat; /* =pattern to match, if non-NULL and = is in type */
172 int ignore; /* 0 => check matching group, 1 => ignore it */
173 };
174
175 /* internal representation of an active line */
176 struct grp {
177 int ignore; /* ignore reason, 0 => not ignore (see below) */
178 int hostid; /* HOSTID this group is from */
179 int linenum; /* >0 => active line number, <=0 => not a line */
180 int output; /* 1 => output to produce the merged active file */
181 int remove; /* 1 => remove this group */
182 char *name; /* newsgroup name */
183 char *hi; /* high article string */
184 char *low; /* low article string */
185 char *type; /* newsgroup type string */
186 const char *outhi; /* output high article string */
187 const char *outlow; /* output low article string */
188 char *outtype; /* output newsgroup type string */
189 };
190
191 /* structure used in the process of looking for =group type problems */
192 struct eqgrp {
193 int skip; /* 1 => skip this entry */
194 struct grp *g; /* =group that is being examined */
195 char *eq; /* current equivalence name */
196 };
197
198 /*
199 * These ignore reasons are listed in order severity; from mild to severe.
200 */
201 #define NOT_IGNORED 0x0000 /* newsgroup has not been ignored */
202 #define CHECK_IGNORE 0x0001 /* ignore file ignores this entry */
203 #define CHECK_TYPE 0x0002 /* group type is ignored */
204 #define CHECK_BORK 0x0004 /* group is a *.bork.bork.bork group */
205 #define CHECK_HIER 0x0008 /* -T && new group's hierarchy does not exist */
206 #define ERROR_LONGLOOP 0x0010 /* =name refers to long =grp chain or cycle */
207 #define ERROR_EQLOOP 0x0020 /* =name refers to itself in some way */
208 #define ERROR_NONEQ 0x0040 /* =name does not refer to a valid group */
209 #define ERROR_DUP 0x0080 /* newsgroup is a duplicate of another */
210 #define ERROR_EQNAME 0x0100 /* =name is a bad group name */
211 #define ERROR_BADTYPE 0x0200 /* newsgroup type is invalid */
212 #define ERROR_BADNAME 0x0400 /* newsgroup name is invalid */
213 #define ERROR_FORMAT 0x0800 /* entry line is malformed */
214
215 #define IS_IGNORE(ign) ((ign) & (CHECK_IGNORE|CHECK_TYPE|CHECK_BORK|CHECK_HIER))
216 #define IS_ERROR(ign) ((ign) & ~(CHECK_IGNORE|CHECK_TYPE|CHECK_BORK|CHECK_HIER))
217
218 #define NOHOST 0 /* neither host1 nor host2 */
219 #define HOSTID1 1 /* entry from the first host */
220 #define HOSTID2 2 /* entry from the second host */
221
222 #define CHUNK 5000 /* number of elements to alloc at a time */
223
224 #define TYPES "ymjnx=" /* group types (1st char of 4th active fld) */
225 #define TYPECNT (sizeof(TYPES)-1)
226
227 #define DEF_HI "0000000000" /* default hi string value for new groups */
228 #define DEF_LOW "0000000001" /* default low string value for new groups */
229
230 #define DEF_NAME "actsync" /* default name to use for ctlinnd newgroup */
231
232 #define MIN_UNCHG (double)96.0 /* min % of host1 lines unchanged allowed */
233
234 #define DEV_NULL "/dev/null" /* path to the bit bucket */
235 #define CTLINND_NAME "ctlinnd" /* basename of ctlinnd command */
236
237 #define READ_SIDE 0 /* read side of a pipe */
238 #define WRITE_SIDE 1 /* write side of a pipe */
239
240 #define EQ_LOOP 16 /* give up if =eq loop/chain is this long */
241 #define NOT_REACHED 127 /* exit value if unable to get active files */
242
243 #define NEWGRP_EMPTY 0 /* no new group dir was found */
244 #define NEWGRP_NOCHG 1 /* new group dir found but no hi/low change */
245 #define NEWGRP_CHG 2 /* new group dir found but no hi/low change */
246
247 /* -b macros */
248 #define BORK_CHECK(hostid) \
249 ((hostid == HOSTID1 && bork_host1_flag) || \
250 (hostid == HOSTID2 && bork_host2_flag))
251
252 /* -d macros */
253 #define NUM_CHECK(hostid) \
254 ((hostid == HOSTID1 && num_host1_flag) || \
255 (hostid == HOSTID2 && num_host2_flag))
256
257 /* -t macros */
258 #define TOP_CHECK(hostid) \
259 ((hostid == HOSTID1 && t_host1_flag) || \
260 (hostid == HOSTID2 && t_host2_flag))
261
262 /* -o output types */
263 #define OUTPUT_ACTIVE 1 /* output in active file format */
264 #define OUTPUT_CTLINND 2 /* output in ctlinnd change commands */
265 #define OUTPUT_EXEC 3 /* no output, safely exec commands */
266 #define OUTPUT_IEXEC 4 /* no output, exec commands interactively */
267
268 /* -q macros */
269 #define QUIET(hostid) \
270 ((hostid == HOSTID1 && quiet_host1) || (hostid == HOSTID2 && quiet_host2))
271
272 /* -v verbosity level */
273 #define VER_MIN 0 /* minimum -v level */
274 #define VER_NONE 0 /* no -v output */
275 #define VER_SUMM_IF_WORK 1 /* output summary if actions were performed */
276 #define VER_REPT_IF_WORK 2 /* output summary & actions only if performed */
277 #define VER_REPORT 3 /* output summary & actions performed */
278 #define VER_FULL 4 /* output all summary, actins and debug */
279 #define VER_MAX 4 /* maximum -v level */
280 #define D_IF_SUMM (v_flag >= VER_SUMM_IF_WORK) /* true => give summary always */
281 #define D_REPORT (v_flag >= VER_REPT_IF_WORK) /* true => give reports */
282 #define D_BUG (v_flag == VER_FULL) /* true => debug processing */
283 #define D_SUMMARY (v_flag >= VER_REPORT) /* true => give summary always */
284
285 /* flag and arg related defaults */
286 int bork_host1_flag = 0; /* 1 => -b 1 or -b 12 or -b 21 given */
287 int bork_host2_flag = 0; /* 1 => -b 2 or -b 12 or -b 21 given */
288 int num_host1_flag = 0; /* 1 => -d 1 or -d 12 or -d 21 given */
289 int num_host2_flag = 0; /* 1 => -d 2 or -d 12 or -d 21 given */
290 char *ign_file = NULL; /* default ignore file */
291 int ign_host1_flag = 1; /* 1 => -i ign_file applies to host1 */
292 int ign_host2_flag = 1; /* 1 => -i ign_file applies to host2 */
293 int g_flag = 0; /* ignore grps deeper than > g_flag, 0=>dont */
294 int k_flag = 0; /* 1 => -k given */
295 int l_host1_flag = HOSTID1; /* HOSTID1 => host1 =group error detection */
296 int l_host2_flag = HOSTID2; /* HOSTID2 => host2 =group error detection */
297 int m_flag = 0; /* 1 => merge active files, don't sync */
298 const char *new_name = DEF_NAME; /* ctlinnd newgroup name */
299 int o_flag = OUTPUT_CTLINND; /* default output type */
300 double p_flag = MIN_UNCHG; /* min % host1 lines allowed to be unchanged */
301 int host1_errs = 0; /* errors found in host1 active file */
302 int host2_errs = 0; /* errors found in host2 active file */
303 int quiet_host1 = 0; /* 1 => -q 1 or -q 12 or -q 21 given */
304 int quiet_host2 = 0; /* 1 => -q 2 or -q 12 or -q 21 given */
305 int s_flag = 0; /* max group size (length), 0 => do not check */
306 int t_host1_flag = 0; /* 1 => -t 1 or -t 12 or -t 21 given */
307 int t_host2_flag = 1; /* 1 => -t 2 or -t 12 or -t 21 given */
308 int no_new_hier = 0; /* 1 => -T; no new hierarchies */
309 int host2_hilow_newgrp = 0; /* 1 => use host2 hi/low on new groups */
310 int host2_hilow_all = 0; /* 1 => use host2 hi/low on all groups */
311 int host1_ign_print = 0; /* 1 => print host1 ignored groups too */
312 int v_flag = 0; /* default verbosity level */
313 int w_flag = 30; /* sleep w_flag sec before ctlinnd timing out */
314 int z_flag = 4; /* sleep z_flag sec per exec if -o x */
315 int A_flag = 0; /* 1 => authentication before LIST command */
316
317 /* forward declarations */
318 static void process_args(int argc, char *argv[], char **host1, char **host2);
319 static struct grp *get_active(char *host, int hostid, int *len, struct grp *,
320 int *errs);
321 static int bad_grpname(char *name, int num_chk);
322 static struct pat *get_ignore(char *filename, int *len);
323 static void ignore(struct grp *, int grplen, struct pat *, int iglen);
324 static int merge_cmp(const void *, const void *);
325 static void merge_grps(struct grp *, int grplen, char *host1, char *host2);
326 static int active_cmp(const void *, const void *);
327 static void output_grps(struct grp *, int grplen);
328 static void error_mark(struct grp *, int grplen, int hostid);
329 static int eq_merge_cmp(const void *, const void *);
330 static int mark_eq_probs(struct grp *, int grplen, int hostid, char *host1,
331 char *host2);
332 static int exec_cmd(int mode, const char *cmd, char *grp, char *type,
333 const char *who);
334 static int new_top_hier(char *name, struct hash *existing_hier);
335 static const void *string_key(const void *entry);
336 static bool string_equal(const void *key, const void *entry);
337
338 int
main(int argc,char * argv[])339 main(int argc, char *argv[])
340 {
341 struct grp *grp; /* struct grp array for host1 & host2 */
342 struct pat *ignor; /* ignore list from ignore file */
343 int grplen; /* length of host1/host2 group array */
344 int iglen; /* length of ignore list */
345 char *host1; /* host to change */
346 char *host2; /* comparison host */
347
348 /* First thing, set up our identity. */
349 message_program_name = "actsync";
350
351 /* Read in default info from inn.conf. */
352 if (!innconf_read(NULL))
353 exit(1);
354 process_args(argc, argv, &host1, &host2);
355
356 /* obtain the active files */
357 grp = get_active(host1, HOSTID1, &grplen, NULL, &host1_errs);
358 grp = get_active(host2, HOSTID2, &grplen, grp, &host2_errs);
359
360 /* ignore groups from both active files, if -i */
361 if (ign_file != NULL) {
362
363 /* read in the ignore file */
364 ignor = get_ignore(ign_file, &iglen);
365
366 /* ignore groups */
367 ignore(grp, grplen, ignor, iglen);
368 }
369
370 /* compare groups from both hosts */
371 merge_grps(grp, grplen, host1, host2);
372
373 /* mark for removal, error groups from host1 if -e */
374 if (! k_flag) {
375
376 /* mark error groups for removal */
377 error_mark(grp, grplen, HOSTID1);
378 }
379
380 /* output result of merge */
381 output_grps(grp, grplen);
382
383 /* all done */
384 exit(0);
385 }
386
387 /*
388 * process_args - process the command line arguments
389 *
390 * given:
391 * argc arg count
392 * argv the args
393 * host1 name of first host (may be 2nd if -R)
394 * host2 name of second host2 *may be 1st if -R)
395 */
396 static void
process_args(int argc,char * argv[],char ** host1,char ** host2)397 process_args(int argc, char *argv[], char **host1, char **host2)
398 {
399 char *def_serv = NULL; /* name of default server */
400 int i;
401
402 /* parse args */
403 while ((i = getopt(argc,argv,"Ab:d:g:i:I:kl:mn:o:p:q:s:t:Tv:w:z:")) != EOF) {
404 switch (i) {
405 case 'A':
406 A_flag = 1;
407 break;
408 case 'b': /* -b {0|1|2|12|21} */
409 switch (atoi(optarg)) {
410 case 0:
411 bork_host1_flag = 0;
412 bork_host2_flag = 0;
413 break;
414 case 1:
415 bork_host1_flag = 1;
416 break;
417 case 2:
418 bork_host2_flag = 1;
419 break;
420 case 12:
421 case 21:
422 bork_host1_flag = 1;
423 bork_host2_flag = 1;
424 break;
425 default:
426 warn("-b option must be 0, 1, 2, 12, or 21");
427 die("%s", usage);
428 }
429 break;
430 case 'd': /* -d {0|1|2|12|21} */
431 switch (atoi(optarg)) {
432 case 0:
433 num_host1_flag = 0;
434 num_host2_flag = 0;
435 break;
436 case 1:
437 num_host1_flag = 1;
438 break;
439 case 2:
440 num_host2_flag = 1;
441 break;
442 case 12:
443 case 21:
444 num_host1_flag = 1;
445 num_host2_flag = 1;
446 break;
447 default:
448 warn("-d option must be 0, 1, 2, 12, or 21");
449 die("%s", usage);
450 }
451 break;
452 case 'g': /* -g max */
453 g_flag = atoi(optarg);
454 break;
455 case 'i': /* -i ignore_file */
456 ign_file = optarg;
457 break;
458 case 'I': /* -I {0|1|2|12|21} */
459 switch (atoi(optarg)) {
460 case 0:
461 ign_host1_flag = 0;
462 ign_host2_flag = 0;
463 break;
464 case 1:
465 ign_host1_flag = 1;
466 ign_host2_flag = 0;
467 break;
468 case 2:
469 ign_host1_flag = 0;
470 ign_host2_flag = 1;
471 break;
472 case 12:
473 case 21:
474 ign_host1_flag = 1;
475 ign_host2_flag = 1;
476 break;
477 default:
478 warn("-I option must be 0, 1, 2, 12, or 21");
479 die("%s", usage);
480 }
481 break;
482 case 'k': /* -k */
483 k_flag = 1;
484 break;
485 case 'l': /* -l {0|1|2|12|21} */
486 switch (atoi(optarg)) {
487 case 0:
488 l_host1_flag = NOHOST;
489 l_host2_flag = NOHOST;
490 break;
491 case 1:
492 l_host1_flag = HOSTID1;
493 l_host2_flag = NOHOST;
494 break;
495 case 2:
496 l_host1_flag = NOHOST;
497 l_host2_flag = HOSTID2;
498 break;
499 case 12:
500 case 21:
501 l_host1_flag = HOSTID1;
502 l_host2_flag = HOSTID2;
503 break;
504 default:
505 warn("-l option must be 0, 1, 2, 12, or 21");
506 die("%s", usage);
507 }
508 break;
509 case 'm': /* -m */
510 m_flag = 1;
511 break;
512 case 'n': /* -n name */
513 new_name = optarg;
514 break;
515 case 'o': /* -o out_type */
516 switch (optarg[0]) {
517 case 'a':
518 o_flag = OUTPUT_ACTIVE;
519 switch (optarg[1]) {
520 case '1':
521 switch(optarg[2]) {
522 case 'K': /* -o a1K */
523 host1_ign_print = 1;
524 host2_hilow_all = 1;
525 host2_hilow_newgrp = 1;
526 break;
527 case 'k': /* -o a1k */
528 host1_ign_print = 1;
529 host2_hilow_newgrp = 1;
530 break;
531 default: /* -o a1 */
532 host1_ign_print = 1;
533 break;
534 }
535 break;
536 case 'K':
537 switch(optarg[2]) {
538 case '1': /* -o aK1 */
539 host1_ign_print = 1;
540 host2_hilow_all = 1;
541 host2_hilow_newgrp = 1;
542 break;
543 default: /* -o aK */
544 host2_hilow_all = 1;
545 host2_hilow_newgrp = 1;
546 break;
547 };
548 break;
549 case 'k':
550 switch(optarg[2]) {
551 case '1': /* -o ak1 */
552 host1_ign_print = 1;
553 host2_hilow_newgrp = 1;
554 break;
555 default: /* -o ak */
556 host2_hilow_newgrp = 1;
557 break;
558 };
559 break;
560 case '\0': /* -o a */
561 break;
562 default:
563 warn("-o type must be a, a1, ak, aK, ak1, aK1, a1k or a1K");
564 die("%s", usage);
565 }
566 break;
567 case 'c':
568 o_flag = OUTPUT_CTLINND;
569 break;
570 case 'x':
571 if (optarg[1] == 'i') {
572 o_flag = OUTPUT_IEXEC;
573 } else {
574 o_flag = OUTPUT_EXEC;
575 }
576 break;
577 default:
578 warn("-o type must be a, a1, ak, aK, ak1, aK1, a1k, a1K, c, x, or xi");
579 die("%s", usage);
580 }
581 break;
582 case 'p': /* -p %_min_host1_change */
583 /* parse % into [0,100] */
584 p_flag = atof(optarg);
585 if (p_flag > (double)100.0) {
586 p_flag = (double)100.0;
587 } else if (p_flag < (double)0.0) {
588 p_flag = (double)0.0;
589 }
590 break;
591 case 'q': /* -q {0|1|2|12|21} */
592 switch (atoi(optarg)) {
593 case 0:
594 quiet_host1 = 0;
595 quiet_host2 = 0;
596 break;
597 case 1:
598 quiet_host1 = 1;
599 break;
600 case 2:
601 quiet_host2 = 1;
602 break;
603 case 12:
604 case 21:
605 quiet_host1 = 1;
606 quiet_host2 = 1;
607 break;
608 default:
609 warn("-q option must be 0, 1, 2, 12, or 21");
610 die("%s", usage);
611 }
612 break;
613 case 's': /* -s size */
614 s_flag = atoi(optarg);
615 break;
616 case 't': /* -t {0|1|2|12|21} */
617 switch (atoi(optarg)) {
618 case 0:
619 t_host1_flag = NOHOST;
620 t_host2_flag = NOHOST;
621 break;
622 case 1:
623 t_host1_flag = HOSTID1;
624 t_host2_flag = NOHOST;
625 break;
626 case 2:
627 t_host1_flag = NOHOST;
628 t_host2_flag = HOSTID2;
629 break;
630 case 12:
631 case 21:
632 t_host1_flag = HOSTID1;
633 t_host2_flag = HOSTID2;
634 break;
635 default:
636 warn("-t option must be 0, 1, 2, 12, or 21");
637 die("%s", usage);
638 }
639 break;
640 case 'T': /* -T */
641 no_new_hier = 1;
642 break;
643 case 'v': /* -v verbose_lvl */
644 v_flag = atoi(optarg);
645 if (v_flag < VER_MIN || v_flag > VER_MAX) {
646 warn("-v level must be >= %d and <= %d", VER_MIN, VER_MAX);
647 die("%s", usage);
648 }
649 break;
650 case 'w': /* -w sec */
651 w_flag = atoi(optarg);
652 if (w_flag < 0) {
653 warn("-w option must be a positive integer");
654 die("%s", usage);
655 }
656 break;
657 case 'z': /* -z sec */
658 z_flag = atoi(optarg);
659 break;
660 default:
661 warn("unknown flag");
662 die("%s", usage);
663 }
664 }
665
666 /* process the remaining args */
667 argc -= optind;
668 argv += optind;
669 *host1 = NULL;
670 switch (argc) {
671 case 1:
672 /* assume host1 is the local server */
673 *host2 = argv[0];
674 break;
675 case 2:
676 *host1 = argv[0];
677 *host2 = argv[1];
678 break;
679 default:
680 warn("expected 1 or 2 host args, found %d", argc);
681 die("%s", usage);
682 }
683
684 /* determine default host name if needed */
685 if (*host1 == NULL || strcmp(*host1, "-") == 0) {
686 def_serv = innconf->server;
687 *host1 = def_serv;
688 }
689 if (*host2 == NULL || strcmp(*host2, "-") == 0) {
690 def_serv = innconf->server;
691 *host2 = def_serv;
692 }
693 if (*host1 == NULL || *host2 == NULL)
694 die("unable to determine default server name");
695 if (D_BUG && def_serv != NULL)
696 warn("STATUS: using default server: %s", def_serv);
697
698 /* processing done */
699 return;
700 }
701
702 /*
703 * get_active - get an active file from a host
704 *
705 * given:
706 * host host to contact or file to read
707 * hostid HOST_ID of host
708 * len pointer to length of grp return array
709 * grp existing host array to add, or NULL
710 * errs count of lines that were found to have some error
711 *
712 * returns;
713 * Pointer to an array of grp structures describing each active entry.
714 * Does not return on fatal error.
715 *
716 * If host starts with a '/' or '.', then it is assumed to be a local file.
717 * In that case, the local file is opened and read.
718 */
719 static struct grp *
get_active(char * host,int hostid,int * len,struct grp * grp,int * errs)720 get_active(char *host, int hostid, int *len, struct grp *grp, int *errs)
721 {
722 FILE *active; /* stream for fetched active data */
723 FILE *FromServer; /* stream from server */
724 FILE *ToServer; /* stream to server */
725 QIOSTATE *qp; /* QIO active state */
726 char buff[8192+1]; /* QIO buffer */
727 char *line; /* the line just read */
728 struct grp *ret; /* array of groups to return */
729 struct grp *cur; /* current grp entry being formed */
730 int max; /* max length of ret */
731 int cnt; /* number of entries read */
732 int ucnt; /* number of entries to be used */
733 int namelen; /* length of newsgroup name */
734 int is_file; /* 1 => host is actually a filename */
735 int num_check; /* true => check for all numeric components */
736 char *rhost;
737 int rport;
738 char *p;
739 int i;
740
741 /* firewall */
742 if (len == NULL)
743 die("internal error #1: len is NULL");
744 if (errs == NULL)
745 die("internal error #2: errs is NULL");
746 if (host == NULL)
747 die("internal error #10: host is NULL");
748 if (D_BUG)
749 warn("STATUS: obtaining active file from %s", host);
750
751 /* setup return array if needed */
752 if (grp == NULL) {
753 ret = xmalloc(CHUNK * sizeof(struct grp));
754 max = CHUNK;
755 *len = 0;
756
757 /* or prep to use the existing array */
758 } else {
759 ret = grp;
760 max = ((*len + CHUNK-1)/CHUNK)*CHUNK;
761 }
762
763 /* check for host being a filename */
764 if (host[0] == '/' || host[0] == '.') {
765
766 /* note that host is actually a file */
767 is_file = 1;
768
769 /* setup to read the local file quickly */
770 if ((qp = QIOopen(host)) == NULL)
771 sysdie("cannot open active file");
772
773 /* case: host is a hostname */
774 } else {
775
776 /* note that host is actually a hostname */
777 is_file = 0;
778
779 /* prepare remote host variables */
780 if ((p = strchr(host, ':')) != NULL) {
781 rport = atoi(p + 1);
782 *p = '\0';
783 rhost = xstrdup(host);
784 *p = ':';
785 } else {
786 rhost = xstrdup(host);
787 rport = NNTP_PORT;
788 }
789
790 /* open a connection to the server */
791 buff[0] = '\0';
792 if (NNTPconnect(rhost, rport, &FromServer, &ToServer, buff,
793 sizeof(buff)) < 0)
794 die("cannot connect to server: %s",
795 buff[0] ? buff : strerror(errno));
796
797 if (A_flag && NNTPsendpassword(rhost, FromServer, ToServer) < 0)
798 die("cannot authenticate to server");
799
800 free(rhost);
801
802 /* get the active data from the server */
803 active = CAlistopen(FromServer, ToServer, NULL);
804 if (active == NULL)
805 sysdie("cannot retrieve data");
806
807 /* setup to read the retrieved data quickly */
808 if ((qp = QIOfdopen((int)fileno(active))) == NULL)
809 sysdie("cannot read temp file");
810 }
811
812 /* scan server's output, processing appropriate lines */
813 num_check = NUM_CHECK(hostid);
814 for (cnt=0, ucnt=0; (line = QIOread(qp)) != NULL; ++(*len), ++cnt) {
815
816 /* expand return array if needed */
817 if (*len >= max) {
818 max += CHUNK;
819 ret = xrealloc(ret, sizeof(struct grp) * max);
820 }
821
822 /* setup the next return element */
823 cur = &ret[*len];
824 cur->ignore = NOT_IGNORED;
825 cur->hostid = hostid;
826 cur->linenum = cnt+1;
827 cur->output = 0;
828 cur->remove = 0;
829 cur->name = NULL;
830 cur->hi = NULL;
831 cur->low = NULL;
832 cur->type = NULL;
833 cur->outhi = NULL;
834 cur->outlow = NULL;
835 cur->outtype = NULL;
836
837 /* obtain a copy of the current line */
838 cur->name = xstrdup(line);
839
840 /* get the group name */
841 if ((p = strchr(cur->name, ' ')) == NULL) {
842 if (!QUIET(hostid))
843 warn("line %d from %s is malformed, skipping line", cnt + 1,
844 host);
845
846 /* don't form an entry for this group */
847 --(*len);
848 continue;
849 }
850 *p = '\0';
851 namelen = p - cur->name;
852
853 /* find the other 3 fields, ignore if not found */
854 cur->hi = p+1;
855 if ((p = strchr(p + 1, ' ')) == NULL) {
856 if (!QUIET(hostid))
857 warn("skipping malformed line %d (field 2) from %s", cnt + 1,
858 host);
859
860 /* don't form an entry for this group */
861 --(*len);
862 continue;
863 }
864 *p = '\0';
865 cur->low = p+1;
866 if ((p = strchr(p + 1, ' ')) == NULL) {
867 if (!QUIET(hostid))
868 warn("skipping malformed line %d (field 3) from %s", cnt + 1,
869 host);
870
871 /* don't form an entry for this group */
872 --(*len);
873 continue;
874 }
875 *p = '\0';
876 cur->type = p+1;
877 if ((p = strchr(p + 1, ' ')) != NULL) {
878 if (!QUIET(hostid))
879 warn("skipping line %d from %s, it has more than 4 fields",
880 cnt + 1, host);
881
882 /* don't form an entry for this group */
883 --(*len);
884 continue;
885 }
886
887 /* check for bad group name */
888 if (bad_grpname(cur->name, num_check)) {
889 if (!QUIET(hostid))
890 warn("line %d <%s> from %s has a bad newsgroup name",
891 cnt + 1, cur->name, host);
892 cur->ignore |= ERROR_BADNAME;
893 continue;
894 }
895
896 /* check for long name if requested */
897 if (s_flag > 0 && strlen(cur->name) > (size_t)s_flag) {
898 if (!QUIET(hostid))
899 warn("line %d <%s> from %s has a name that is too long",
900 cnt + 1, cur->name, host);
901 cur->ignore |= ERROR_BADNAME;
902 continue;
903 }
904
905 /* look for only a bad top level element if the proper -t was given */
906 if (TOP_CHECK(hostid)) {
907
908 /* look for a '.' in the name */
909 if (strcmp(cur->name, "junk") != 0 &&
910 strcmp(cur->name, "control") != 0 &&
911 strcmp(cur->name, "to") != 0 &&
912 strcmp(cur->name, "test") != 0 &&
913 strcmp(cur->name, "general") != 0 &&
914 strchr(cur->name, '.') == NULL) {
915 if (!QUIET(hostid))
916 warn("line %d <%s> from %s is an invalid top level name",
917 cnt + 1, cur->name, host);
918 cur->ignore |= ERROR_BADNAME;
919 continue;
920 }
921 }
922
923 /* look for *.bork.bork.bork groups if the proper -b was given */
924 if (BORK_CHECK(cur->hostid)) {
925 int elmlen; /* length of element */
926 char *q; /* beyond end of element */
927
928 /* scan the name backwards */
929 q = &(cur->name[namelen]);
930 for (p = &(cur->name[namelen-1]); p >= cur->name; --p) {
931 /* if '.', see if this is a bork element */
932 if (*p == '.') {
933 /* see if the bork element is short enough */
934 elmlen = q-p;
935 if (3*elmlen <= q-cur->name) {
936 /* look for a triple match */
937 if (strncmp(p,p-elmlen,elmlen) == 0 &&
938 strncmp(p,p-(elmlen*2),elmlen) == 0) {
939 /* found a *.bork.bork.bork group */
940 cur->ignore |= CHECK_BORK;
941 break;
942 }
943 }
944 /* note the end of a new element */
945 q = p;
946 }
947 }
948 }
949
950 /*
951 * check for bad chars in the hi water mark
952 */
953 for (p=cur->hi, i=0; *p && isdigit((unsigned char) *p); ++p, ++i) {
954 }
955 if (*p) {
956 if (!QUIET(hostid))
957 warn("line %d <%s> from %s has non-digits in hi water",
958 cnt + 1, cur->name, cur->hi);
959 cur->ignore |= ERROR_FORMAT;
960 continue;
961 }
962
963 /*
964 * check for excessive hi water length
965 */
966 if (i > ARTNUMPRINTSIZE) {
967 if (!QUIET(hostid))
968 warn("line %d <%s> from %s hi water len: %d < %d",
969 cnt + 1, cur->name, cur->hi, i, ARTNUMPRINTSIZE);
970 cur->ignore |= ERROR_FORMAT;
971 continue;
972 }
973
974 /*
975 * if the hi water length is too small, malloc and resize
976 */
977 if (i != ARTNUMPRINTSIZE) {
978 p = xmalloc(ARTNUMPRINTSIZE + 1);
979 memcpy(p, cur->hi, ((i > ARTNUMPRINTSIZE) ? ARTNUMPRINTSIZE : i)+1);
980 }
981
982 /*
983 * check for bad chars in the low water mark
984 */
985 for (p=cur->low, i=0; *p && isdigit((unsigned char) *p); ++p, ++i) {
986 }
987 if (*p) {
988 if (!QUIET(hostid))
989 warn("line %d <%s> from %s has non-digits in low water",
990 cnt + 1, cur->name, cur->low);
991 cur->ignore |= ERROR_FORMAT;
992 continue;
993 }
994
995 /*
996 * check for excessive low water length
997 */
998 if (i > ARTNUMPRINTSIZE) {
999 if (!QUIET(hostid))
1000 warn("line %d <%s> from %s low water len: %d < %d",
1001 cnt + 1, cur->name, cur->hi, i, ARTNUMPRINTSIZE);
1002 cur->ignore |= ERROR_FORMAT;
1003 continue;
1004 }
1005
1006 /*
1007 * if the low water length is too small, malloc and resize
1008 */
1009 if (i != ARTNUMPRINTSIZE) {
1010 p = xmalloc(ARTNUMPRINTSIZE + 1);
1011 memcpy(p, cur->low, ((i > ARTNUMPRINTSIZE) ? ARTNUMPRINTSIZE : i)+1);
1012 }
1013
1014 /* check for a bad group type */
1015 switch (cur->type[0]) {
1016 case NF_FLAG_OK:
1017 /* of COURSE: collabra has incompatible flags. but it */
1018 /* looks like they can be fixed easily enough. */
1019 if (cur->type[1] == 'g') {
1020 cur->type[1] = '\0';
1021 }
1022 /* fallthrough */
1023 case NF_FLAG_MODERATED:
1024 case NF_FLAG_JUNK:
1025 case NF_FLAG_NOLOCAL:
1026 case NF_FLAG_IGNORE:
1027 if (cur->type[1] != '\0') {
1028 if (!QUIET(hostid))
1029 warn("line %d <%s> from %s has a bad newsgroup type",
1030 cnt + 1, cur->name, host);
1031 cur->ignore |= ERROR_BADTYPE;
1032 }
1033 break;
1034 case NF_FLAG_ALIAS:
1035 if (cur->type[1] == '\0') {
1036 if (!QUIET(hostid))
1037 warn("line %d <%s> from %s has an empty =group name",
1038 cnt + 1, cur->name, host);
1039 cur->ignore |= ERROR_BADTYPE;
1040 }
1041 break;
1042 default:
1043 if (!QUIET(hostid))
1044 warn("line %d <%s> from %s has an unknown newsgroup type",
1045 cnt + 1, cur->name, host);
1046 cur->ignore |= ERROR_BADTYPE;
1047 break;
1048 }
1049 if (cur->ignore & ERROR_BADTYPE) {
1050 continue;
1051 }
1052
1053 /* if an = type, check for bad = name */
1054 if (cur->type[0] == NF_FLAG_ALIAS && bad_grpname(&(cur->type[1]), num_check)) {
1055 if (!QUIET(hostid))
1056 warn("line %d <%s> from %s is equivalenced to a bad name:"
1057 " <%s>", cnt+1, cur->name, host,
1058 (cur->type) ? cur->type : "NULL");
1059 cur->ignore |= ERROR_EQNAME;
1060 continue;
1061 }
1062
1063 /* if an = type, check for long = name if requested */
1064 if (cur->type[0] == NF_FLAG_ALIAS && s_flag > 0 &&
1065 strlen(&(cur->type[1])) > (size_t)s_flag) {
1066 if (!QUIET(hostid))
1067 warn("line %d <%s> from %s is equivalenced to a long name:"
1068 " <%s>", cnt+1, cur->name, host,
1069 (cur->type) ? cur->type : "NULL");
1070 cur->ignore |= ERROR_EQNAME;
1071 continue;
1072 }
1073
1074 /* count this entry which will be used */
1075 ++ucnt;
1076 }
1077 if (D_BUG)
1078 warn("STATUS: read %d groups, will merge %d groups from %s",
1079 cnt, ucnt, host);
1080
1081 /* count the errors */
1082 *errs = cnt - ucnt;
1083 if (D_BUG)
1084 warn("STATUS: found %d line errors from %s", *errs, host);
1085
1086 /* determine why we stopped */
1087 if (QIOerror(qp))
1088 sysdie("cannot read temp file for %s at line %d", host, cnt);
1089 else if (QIOtoolong(qp))
1090 sysdie("line %d from host %s is too long", cnt, host);
1091
1092 /* all done */
1093 if (is_file) {
1094 QIOclose(qp);
1095 } else {
1096 CAclose();
1097 fprintf(ToServer, "QUIT\r\n");
1098 fclose(ToServer);
1099 fgets(buff, sizeof buff, FromServer);
1100 fclose(FromServer);
1101 }
1102 return ret;
1103 }
1104
1105 /*
1106 * bad_grpname - test if the string is a valid group name
1107 *
1108 * Newsgroup names must consist of only alphanumeric chars and
1109 * characters from the following regular expression:
1110 *
1111 * [.+-_]
1112 *
1113 * One cannot have two '.'s in a row or end in a '.' character.
1114 *
1115 * If we are checking for all numeric components, (see num_chk) then
1116 * a component cannot be all numeric. I.e,. there must be a non-numeric
1117 * character in the name, there must be a non-numeric character between
1118 * the start and the first '.', there must be a non-numeric character
1119 * between two '.'s and there must be a non-numeric character between
1120 * the last '.' and the end.
1121 *
1122 * given:
1123 * name newsgroup name to check
1124 * num_chk true => all numeric newsgroups components are invalid
1125 * false => do not check for numeric newsgroups
1126 *
1127 * returns:
1128 * 0 group is ok
1129 * 1 group is bad
1130 */
1131 static int
bad_grpname(char * name,int num_chk)1132 bad_grpname(char *name, int num_chk)
1133 {
1134 char *p;
1135 int non_num; /* true => found a non-numeric, non-. character */
1136 int level; /* group levels (.'s) */
1137
1138 /* firewall */
1139 if (name == NULL) {
1140 return 1;
1141 }
1142
1143 /* set non_num as needed */
1144 if (isalpha((unsigned char) name[0])
1145 || name[0] == '+' || name[0] == '-' || name[0] == '_') {
1146 non_num = true;
1147 } else if (isdigit((unsigned char) name[0])) {
1148 non_num = false;
1149 } else {
1150 return 1;
1151 }
1152
1153 /* scan each char */
1154 level = 0;
1155 for (p=name+1; *p; ++p) {
1156 /* alpha chars are ok */
1157 if (isalpha((unsigned char) *p)) {
1158 non_num = true;
1159 continue;
1160 }
1161
1162 /* numeric chars are ok */
1163 if (isdigit((unsigned char) *p)) {
1164 continue;
1165 }
1166
1167 /* +, - and _ are ok */
1168 if (*p == '+' || *p == '-' || *p == '_') {
1169 non_num = true;
1170 continue;
1171 }
1172
1173 /* check for the '.' case */
1174 if (*p == '.') {
1175 /*
1176 * look for groups that are too deep, if requested by -g
1177 */
1178 if (g_flag > 0 && ++level > g_flag) {
1179 /* we are too deep */
1180 return 1;
1181 }
1182
1183 /*
1184 * A '.' is ok as long as the next character is alphanumeric
1185 * or '+', '-', '_'.
1186 * This implies that '.' cannot be before a previous '.' and
1187 * that it cannot be at the end.
1188 *
1189 * If we are checking for all numeric components, then
1190 * '.' is ok if we saw a non-numeric char before the
1191 * last '.', or before the beginning if no previous '.'
1192 * has been seen.
1193 */
1194 if ((!num_chk || non_num) &&
1195 (isalnum((unsigned char) *(p+1)) || *(p+1) == '+' || *(p+1) == '-' || *(p+1) == '_' )) {
1196 ++p; /* '.' is ok, and so is the next char */
1197 if (isdigit((unsigned char) *p)) { /* reset non_num as needed */
1198 non_num = false;
1199 } else {
1200 non_num = true;
1201 }
1202 continue;
1203 }
1204 }
1205
1206 /* this character must be invalid */
1207 return 1;
1208 }
1209 if (num_chk && !non_num) {
1210 /* last component is all numeric */
1211 return 1;
1212 }
1213
1214 /* the name must be ok */
1215 return 0;
1216 }
1217
1218 /*
1219 * get_ignore - get the ignore list from an ignore file
1220 *
1221 * given:
1222 * filename name of the ignore file to read
1223 * *len pointer to length of ignore return array
1224 *
1225 * returns:
1226 * returns a malloced ignore pattern array, changes len
1227 *
1228 * An ignore file is of the form:
1229 *
1230 * # this is a comment which is ignored
1231 * # comments begin at the first # character
1232 * # comments may follow text on the same line
1233 *
1234 * # blank lines are ignored too
1235 *
1236 * # lines are [ic] <spaces-tabs> pattern [<spaces-tabs> type] ...
1237 * i foo.* # ignore foo.* groups,
1238 * c foo.bar m # but check foo.bar if moderated
1239 * c foo.keep.* # and check foo.keep.*
1240 * i foo.keep.* j =alt.* # except when foo.keep.* is junked
1241 * # or equivalenced to an alt.* group
1242 *
1243 * The 'i' value means ignore, 'c' value means 'compare'. The last pattern
1244 * that matches a group determines the fate of the group. By default all
1245 * groups are included.
1246 *
1247 * NOTE: Only one '=name' is allowed per line.
1248 * "=" is considered to be equivalent to "=*".
1249 */
1250 static struct pat *
get_ignore(char * filename,int * len)1251 get_ignore(char *filename, int *len)
1252 {
1253 QIOSTATE *qp; /* QIO ignore file state */
1254 char *line; /* the line just read */
1255 struct pat *ret; /* array of ignore patterns to return */
1256 struct pat *cur; /* current pattern entry being formed */
1257 int max; /* max length (in elements) of ret */
1258 int linenum; /* current line number */
1259 char *p;
1260 int i;
1261
1262 /* firewall */
1263 if (filename == NULL)
1264 die("internal error #3: filename is NULL");
1265 if (len == NULL)
1266 die("internal error #4: len is NULL");
1267 if (D_BUG)
1268 warn("STATUS: reading ignore file %s", filename);
1269
1270 /* setup return array */
1271 ret = xmalloc(CHUNK * sizeof(struct grp));
1272 max = CHUNK;
1273
1274 /* setup to read the ignore file data quickly */
1275 if ((qp = QIOopen(filename)) == NULL)
1276 sysdie("cannot read ignore file %s", filename);
1277
1278 /* scan server's output, displaying appropriate lines */
1279 *len = 0;
1280 for (linenum = 1; (line = QIOread(qp)) != NULL; ++linenum) {
1281
1282 /* expand return array if needed */
1283 if (*len >= max) {
1284 max += CHUNK;
1285 ret = xrealloc(ret, sizeof(struct pat) * max);
1286 }
1287
1288 /* remove any trailing comments */
1289 p = strchr(line, '#');
1290 if (p != NULL) {
1291 *p = '\0';
1292 }
1293
1294 /* remove any trailing spaces and tabs */
1295 for (p = &line[strlen(line)-1];
1296 p >= line && (*p == ' ' || *p == '\t');
1297 --p) {
1298 *p = '\0';
1299 }
1300
1301 /* ignore line if the remainder of the line is empty */
1302 if (line[0] == '\0') {
1303 continue;
1304 }
1305
1306 /* ensure that the line starts with an i or c token */
1307 if ((line[0] != 'i' && line[0] != 'c') ||
1308 (line[1] != ' ' && line[1] != '\t'))
1309 die("first token is not i or c in line %d of %s", linenum,
1310 filename);
1311
1312 /* ensure that the second newsgroup pattern token follows */
1313 p = strtok(line+2, " \t");
1314 if (p == NULL)
1315 die("did not find 2nd field in line %d of %s", linenum,
1316 filename);
1317
1318 /* setup the next return element */
1319 cur = &ret[*len];
1320 cur->pat = NULL;
1321 cur->type_match = 0;
1322 cur->y_type = 0;
1323 cur->m_type = 0;
1324 cur->n_type = 0;
1325 cur->j_type = 0;
1326 cur->x_type = 0;
1327 cur->eq_type = 0;
1328 cur->epat = NULL;
1329 cur->ignore = (line[0] == 'i');
1330
1331 /* obtain a copy of the newsgroup pattern token */
1332 cur->pat = xstrdup(p);
1333
1334 /* process any other type tokens */
1335 for (p=strtok(NULL, " \t"), i=3;
1336 p != NULL;
1337 p=strtok(NULL, " \t"), ++i) {
1338
1339 /* ensure that this next token is a valid type */
1340 switch (p[0]) {
1341 case NF_FLAG_OK:
1342 case NF_FLAG_MODERATED:
1343 case NF_FLAG_JUNK:
1344 case NF_FLAG_NOLOCAL:
1345 case NF_FLAG_IGNORE:
1346 if (p[1] != '\0') {
1347 warn("field %d on line %d of %s not a valid type",
1348 i, linenum, filename);
1349 die("valid types are a char from [ymnjx=] or =name");
1350 }
1351 break;
1352 case NF_FLAG_ALIAS:
1353 break;
1354 default:
1355 warn("field %d on line %d of %s is not a valid type",
1356 i, linenum, filename);
1357 die("valid types are a char from [ymnjx=] or =name");
1358 }
1359
1360 /* note that we have a type specific pattern */
1361 cur->type_match = 1;
1362
1363 /* ensure that type is not a duplicate */
1364 if ((p[0] == NF_FLAG_OK && cur->y_type) ||
1365 (p[0] == NF_FLAG_MODERATED && cur->m_type) ||
1366 (p[0] == NF_FLAG_NOLOCAL && cur->n_type) ||
1367 (p[0] == NF_FLAG_JUNK && cur->j_type) ||
1368 (p[0] == NF_FLAG_IGNORE && cur->x_type) ||
1369 (p[0] == NF_FLAG_ALIAS && cur->eq_type)) {
1370 warn("only one %c type allowed per line", p[0]);
1371 die("field %d on line %d of %s is a duplicate type",
1372 i, linenum, filename);
1373 }
1374
1375 /* note what we have seen */
1376 switch (p[0]) {
1377 case NF_FLAG_OK:
1378 cur->y_type = 1;
1379 break;
1380 case NF_FLAG_MODERATED:
1381 cur->m_type = 1;
1382 break;
1383 case NF_FLAG_JUNK:
1384 cur->j_type = 1;
1385 break;
1386 case NF_FLAG_NOLOCAL:
1387 cur->n_type = 1;
1388 break;
1389 case NF_FLAG_IGNORE:
1390 cur->x_type = 1;
1391 break;
1392 case NF_FLAG_ALIAS:
1393 cur->eq_type = 1;
1394 if (p[0] == NF_FLAG_ALIAS && p[1] != '\0')
1395 cur->epat = xstrdup(p + 1);
1396 break;
1397 }
1398
1399 /* object if too many fields */
1400 if (i-3 > (int) TYPECNT)
1401 die("too many fields on line %d of %s", linenum, filename);
1402 }
1403
1404 /* count another pat element */
1405 ++(*len);
1406 }
1407
1408 /* return the pattern array */
1409 return ret;
1410 }
1411
1412 /*
1413 * ignore - ignore newsgroups given an ignore list
1414 *
1415 * given:
1416 * grp array of groups
1417 * grplen length of grp array in elements
1418 * igcl array of ignore
1419 * iglen length of igcl array in elements
1420 */
1421 static void
ignore(struct grp * grp,int grplen,struct pat * igcl,int iglen)1422 ignore(struct grp *grp, int grplen, struct pat *igcl, int iglen)
1423 {
1424 struct grp *gp; /* current group element being examined */
1425 struct pat *pp; /* current pattern element being examined */
1426 int g; /* current group index number */
1427 int p; /* current pattern index number */
1428 int ign; /* 1 => ignore this group, 0 => check it */
1429 int icnt; /* groups ignored */
1430 int ccnt; /* groups to be checked */
1431
1432 /* firewall */
1433 if (grp == NULL)
1434 die("internal error #5: grp is NULL");
1435 if (igcl == NULL)
1436 die("internal error $6: igcl is NULL");
1437 if (D_BUG)
1438 warn("STATUS: determining which groups to ignore");
1439
1440 /* if nothing to do, return quickly */
1441 if (grplen <= 0 || iglen <= 0) {
1442 return;
1443 }
1444
1445 /* examine each group */
1446 icnt = 0;
1447 ccnt = 0;
1448 for (g=0; g < grplen; ++g) {
1449
1450 /* check the group to examine */
1451 gp = &grp[g];
1452 if (gp->ignore) {
1453 /* already ignored no need to examine */
1454 continue;
1455 }
1456
1457 /* check group against all patterns */
1458 ign = 0;
1459 for (p=0, pp=igcl; p < iglen; ++p, ++pp) {
1460
1461 /* if pattern has a specific type, check it first */
1462 if (pp->type_match) {
1463
1464 /* specific type required, check for match */
1465 switch (gp->type[0]) {
1466 case NF_FLAG_OK:
1467 if (! pp->y_type) continue; /* pattern does not apply */
1468 break;
1469 case NF_FLAG_MODERATED:
1470 if (! pp->m_type) continue; /* pattern does not apply */
1471 break;
1472 case NF_FLAG_NOLOCAL:
1473 if (! pp->n_type) continue; /* pattern does not apply */
1474 break;
1475 case NF_FLAG_JUNK:
1476 if (! pp->j_type) continue; /* pattern does not apply */
1477 break;
1478 case NF_FLAG_IGNORE:
1479 if (! pp->x_type) continue; /* pattern does not apply */
1480 break;
1481 case NF_FLAG_ALIAS:
1482 if (! pp->eq_type) continue; /* pattern does not apply */
1483 if (pp->epat != NULL && !uwildmat(&gp->type[1], pp->epat)) {
1484 /* equiv pattern doesn't match, patt does not apply */
1485 continue;
1486 }
1487 break;
1488 }
1489 }
1490
1491 /* perform a match on group name */
1492 if (uwildmat(gp->name, pp->pat)) {
1493 /* this pattern fully matches, use the ignore value */
1494 ign = pp->ignore;
1495 }
1496 }
1497
1498 /* if this group is to be ignored, note it */
1499 if (ign) {
1500 switch (gp->hostid) {
1501 case HOSTID1:
1502 if (ign_host1_flag) {
1503 gp->ignore |= CHECK_IGNORE;
1504 ++icnt;
1505 }
1506 break;
1507 case HOSTID2:
1508 if (ign_host2_flag) {
1509 gp->ignore |= CHECK_IGNORE;
1510 ++icnt;
1511 }
1512 break;
1513 default:
1514 die("newsgroup %s bad hostid: %d", gp->name, gp->hostid);
1515 }
1516 } else {
1517 ++ccnt;
1518 }
1519 }
1520 if (D_BUG)
1521 warn("STATUS: examined %d groups: %d ignored, %d to be checked",
1522 grplen, icnt, ccnt);
1523 }
1524
1525 /*
1526 * merge_cmp - qsort compare function for later group merge
1527 *
1528 * given:
1529 * a group a to compare
1530 * b group b to compare
1531 *
1532 * returns:
1533 * >0 a > b
1534 * 0 a == b elements match (fatal error if a and b are different)
1535 * <0 a < b
1536 *
1537 * To speed up group comparison, we compare by the following items listed
1538 * in order of sorting:
1539 *
1540 * group name
1541 * hostid (host1 ahead of host2)
1542 * linenum (active file line number)
1543 */
1544 static int
merge_cmp(const void * arg_a,const void * arg_b)1545 merge_cmp(const void *arg_a, const void *arg_b)
1546 {
1547 const struct grp *a = arg_a; /* group a to compare */
1548 const struct grp *b = arg_b; /* group b to compare */
1549 int i;
1550
1551 /* firewall */
1552 if (a == b) {
1553 /* we guess this could happen */
1554 return(0);
1555 }
1556
1557 /* compare group names */
1558 i = strcmp(a->name, b->name);
1559 if (i != 0) {
1560 return i;
1561 }
1562
1563 /* compare hostid's */
1564 if (a->hostid != b->hostid) {
1565 if (a->hostid > b->hostid) {
1566 return 1;
1567 } else {
1568 return -1;
1569 }
1570 }
1571
1572 /* compare active line numbers */
1573 if (a->linenum != b->linenum) {
1574 if (a->linenum > b->linenum) {
1575 return 1;
1576 } else {
1577 return -1;
1578 }
1579 }
1580
1581 /* two different elements match, this should not happen! */
1582 die("two internal grp elements match!");
1583 /*NOTREACHED*/
1584 }
1585
1586 /*
1587 * merge_grps - compare groups from both hosts
1588 *
1589 * given:
1590 * grp array of groups
1591 * grplen length of grp array in elements
1592 * host1 name of host with HOSTID1
1593 * host2 name of host with HOSTID2
1594 *
1595 * This routine will select which groups to output from a merged active file.
1596 */
1597 static void
merge_grps(struct grp * grp,int grplen,char * host1,char * host2)1598 merge_grps(struct grp *grp, int grplen, char *host1, char *host2)
1599 {
1600 int cur; /* current group index being examined */
1601 int nxt; /* next group index being examined */
1602 int outcnt; /* groups to output */
1603 int rmcnt; /* groups to remove */
1604 int h1_probs; /* =type problem groups from host1 */
1605 int h2_probs; /* =type problem groups from host2 */
1606
1607 /* firewall */
1608 if (grp == NULL)
1609 die("internal error #7: grp is NULL");
1610
1611 /* sort groups for the merge */
1612 if (D_BUG)
1613 warn("STATUS: sorting groups");
1614 qsort((char *)grp, grplen, sizeof(grp[0]), merge_cmp);
1615
1616 /* mark =type problem groups from host2, if needed */
1617 h2_probs = mark_eq_probs(grp, grplen, l_host2_flag, host1, host2);
1618
1619 /*
1620 * We will walk thru the sorted group array, looking for pairs
1621 * among the groups that we have not already ignored.
1622 *
1623 * If a host has duplicate groups, then the duplicates will
1624 * be next to each other.
1625 *
1626 * If both hosts have the name group, they will be next to each other.
1627 */
1628 if (D_BUG)
1629 warn("STATUS: merging groups");
1630 outcnt = 0;
1631 rmcnt = 0;
1632 for (cur=0; cur < grplen; cur=nxt) {
1633
1634 /* determine the next group index */
1635 nxt = cur+1;
1636
1637 /* skip if this group is ignored */
1638 if (grp[cur].ignore) {
1639 continue;
1640 }
1641 /* assert: cur is not ignored */
1642
1643 /* check for duplicate groups from the same host */
1644 while (nxt < grplen) {
1645
1646 /* mark the later as a duplicate */
1647 if (grp[cur].hostid == grp[nxt].hostid &&
1648 strcmp(grp[cur].name, grp[nxt].name) == 0) {
1649 grp[nxt].ignore |= ERROR_DUP;
1650 if (!QUIET(grp[cur].hostid))
1651 warn("lines %d and %d from %s refer to the same group",
1652 grp[cur].linenum, grp[nxt].linenum,
1653 ((grp[cur].hostid == HOSTID1) ? host1 : host2));
1654 ++nxt;
1655 } else {
1656 break;
1657 }
1658 }
1659 /* assert: cur is not ignored */
1660 /* assert: cur & nxt are not the same group from the same host */
1661
1662 /* if nxt is ignored, look for the next non-ignored group */
1663 while (nxt < grplen && grp[nxt].ignore) {
1664 ++nxt;
1665 }
1666 /* assert: cur is not ignored */
1667 /* assert: nxt is not ignored or is beyond end */
1668 /* assert: cur & nxt are not the same group from the same host */
1669
1670 /* case: cur and nxt are the same group */
1671 if (nxt < grplen && strcmp(grp[cur].name, grp[nxt].name) == 0) {
1672
1673 /* assert: cur is HOSTID1 */
1674 if (grp[cur].hostid != HOSTID1)
1675 die("internal error #8: grp[%d].hostid: %d != %d",
1676 cur, grp[cur].hostid, HOSTID1);
1677
1678 /*
1679 * Both hosts have the same group. Make host1 group type
1680 * match host2. (it may already)
1681 */
1682 grp[cur].output = 1;
1683 grp[cur].outhi = (host2_hilow_all ? grp[nxt].hi : grp[cur].hi);
1684 grp[cur].outlow = (host2_hilow_all ? grp[nxt].low : grp[cur].low);
1685 grp[cur].outtype = grp[nxt].type;
1686 ++outcnt;
1687
1688 /* do not process nxt, skip to the one beyond */
1689 ++nxt;
1690
1691 /* case: cur and nxt are different groups */
1692 } else {
1693
1694 /*
1695 * if cur is host2, then host1 doesn't have it, so output it
1696 */
1697 if (grp[cur].hostid == HOSTID2) {
1698 grp[cur].output = 1;
1699 grp[cur].outhi = (host2_hilow_newgrp ? grp[cur].hi : DEF_HI);
1700 grp[cur].outlow = (host2_hilow_newgrp ? grp[cur].low : DEF_LOW);
1701 grp[cur].outtype = grp[cur].type;
1702 ++outcnt;
1703
1704 /*
1705 * If cur is host1, then host2 doesn't have it.
1706 * Mark for removal if -m was not given.
1707 */
1708 } else {
1709 grp[cur].output = 1;
1710 grp[cur].outhi = grp[cur].hi;
1711 grp[cur].outlow = grp[cur].low;
1712 grp[cur].outtype = grp[cur].type;
1713 if (! m_flag) {
1714 grp[cur].remove = 1;
1715 ++rmcnt;
1716 }
1717 }
1718
1719 /* if no more groups to examine, we are done */
1720 if (nxt >= grplen) {
1721 break;
1722 }
1723 }
1724 }
1725
1726 /* mark =type problem groups from host1, if needed */
1727 h1_probs = mark_eq_probs(grp, grplen, l_host1_flag, host1, host2);
1728
1729 /* all done */
1730 if (D_BUG) {
1731 warn("STATUS: sort-merge passed thru %d groups", outcnt);
1732 warn("STATUS: sort-merge marked %d groups for removal", rmcnt);
1733 warn("STATUS: marked %d =type error groups from host1", h1_probs);
1734 warn("STATUS: marked %d =type error groups from host2", h2_probs);
1735 }
1736 return;
1737 }
1738
1739 /*
1740 * active_cmp - qsort compare function for active file style output
1741 *
1742 * given:
1743 * a group a to compare
1744 * b group b to compare
1745 *
1746 * returns:
1747 * >0 a > b
1748 * 0 a == b elements match (fatal error if a and b are different)
1749 * <0 a < b
1750 *
1751 * This sort will sort groups so that the lines that will be output
1752 * are host1 lines followed by host2 lines. Thus, we will sort by
1753 * the following keys:
1754 *
1755 * hostid (host1 ahead of host2)
1756 * linenum (active file line number)
1757 */
1758 static int
active_cmp(const void * arg_a,const void * arg_b)1759 active_cmp(const void *arg_a, const void *arg_b)
1760 {
1761 const struct grp *a = arg_a; /* group a to compare */
1762 const struct grp *b = arg_b; /* group b to compare */
1763
1764 /* firewall */
1765 if (a == b) {
1766 /* we guess this could happen */
1767 return(0);
1768 }
1769
1770 /* compare hostid's */
1771 if (a->hostid != b->hostid) {
1772 if (a->hostid > b->hostid) {
1773 return 1;
1774 } else {
1775 return -1;
1776 }
1777 }
1778
1779 /* compare active line numbers */
1780 if (a->linenum != b->linenum) {
1781 if (a->linenum > b->linenum) {
1782 return 1;
1783 } else {
1784 return -1;
1785 }
1786 }
1787
1788 /* two different elements match, this should not happen! */
1789 die("two internal grp elements match!");
1790 /*NOTREACHED*/
1791 }
1792
1793 /*
1794 * output_grps - output the result of the merge
1795 *
1796 * given:
1797 * grp array of groups
1798 * grplen length of grp array in elements
1799 */
1800 static void
output_grps(struct grp * grp,int grplen)1801 output_grps(struct grp *grp, int grplen)
1802 {
1803 int add; /* number of groups added */
1804 int change; /* number of groups changed */
1805 int remove; /* number of groups removed */
1806 int work; /* adds + changes + removals */
1807 int same; /* the number of groups the same */
1808 int ignore; /* host1 newsgroups to ignore */
1809 int not_done; /* exec errors and execs not performed */
1810 int rm_cycle; /* 1 => removals only, 0 => adds & changes only */
1811 int sleep_msg; /* 1 => -o x sleep message was given */
1812 int top_ignore; /* number of groups ignored because of no top level */
1813 int restore; /* host1 groups restored due to -o a1 */
1814 double host1_same; /* % of host1 that is the same */
1815 struct hash *existing_hier; /* hash of existing hierarchies for -T */
1816 char *p, *q;
1817 int i;
1818
1819 /* firewall */
1820 if (grp == NULL)
1821 die("internal error #9: grp is NULL");
1822
1823 /*
1824 * If -a1 was given, mark for output any host1 newsgroup that was
1825 * simply ignored due to the -i ign_file.
1826 */
1827 if (host1_ign_print) {
1828 restore = 0;
1829 for (i=0; i < grplen; ++i) {
1830 if (grp[i].hostid == HOSTID1 &&
1831 (grp[i].ignore == CHECK_IGNORE ||
1832 grp[i].ignore == CHECK_TYPE ||
1833 grp[i].ignore == (CHECK_IGNORE|CHECK_TYPE))) {
1834 /* force group to output and not be ignored */
1835 grp[i].ignore = 0;
1836 grp[i].output = 1;
1837 grp[i].remove = 0;
1838 grp[i].outhi = grp[i].hi;
1839 grp[i].outlow = grp[i].low;
1840 grp[i].outtype = grp[i].type;
1841 ++restore;
1842 }
1843 }
1844 if (D_BUG)
1845 warn("STATUS: restored %d host1 groups", restore);
1846 }
1847
1848 /*
1849 * If -T, ignore new top level groups from host2
1850 */
1851 if (no_new_hier) {
1852 existing_hier = hash_create(32, hash_string, string_key,
1853 string_equal, free);
1854 for (i=0; i < grplen; ++i) {
1855 if (grp[i].hostid == HOSTID2)
1856 continue;
1857 p = xstrdup(grp[i].name);
1858 q = strchr(p, '.');
1859 if (q != NULL)
1860 *q = '\0';
1861 if (!hash_insert(existing_hier, p, p))
1862 free(p);
1863 }
1864 top_ignore = 0;
1865 for (i=0; i < grplen; ++i) {
1866 /* look at new newsgroups */
1867 if (grp[i].hostid == HOSTID2 &&
1868 grp[i].output != 0 &&
1869 new_top_hier(grp[i].name, existing_hier)) {
1870 /* no top level ignore this new group */
1871 grp[i].ignore |= CHECK_HIER;
1872 grp[i].output = 0;
1873 if (D_BUG)
1874 warn("ignore new newsgroup: %s, new hierarchy",
1875 grp[i].name);
1876 ++top_ignore;
1877 }
1878 }
1879 hash_free(existing_hier);
1880 if (D_SUMMARY)
1881 warn("STATUS: ignored %d new newsgroups due to new hierarchy",
1882 top_ignore);
1883 }
1884
1885 /* sort by active file order if active style output (-a) */
1886 if (o_flag == OUTPUT_ACTIVE) {
1887 if (D_BUG)
1888 warn("STATUS: sorting groups in output order");
1889 qsort((char *)grp, grplen, sizeof(grp[0]), active_cmp);
1890 }
1891
1892 /*
1893 * Determine the % of lines from host1 active file that remain unchanged
1894 * ignoring any low/high water mark changes.
1895 *
1896 * Determine the number of old groups that will remain the same
1897 * the number of new groups that will be added.
1898 */
1899 add = 0;
1900 change = 0;
1901 remove = 0;
1902 same = 0;
1903 ignore = 0;
1904 for (i=0; i < grplen; ++i) {
1905 /* skip non-output ... */
1906 if (grp[i].output == 0) {
1907 if (grp[i].hostid == HOSTID1) {
1908 ++ignore;
1909 }
1910 continue;
1911
1912 /* case: group needs removal */
1913 } else if (grp[i].remove) {
1914 ++remove;
1915
1916 /* case: group is from host2, so we need a newgroup */
1917 } else if (grp[i].hostid == HOSTID2) {
1918 ++add;
1919
1920 /* case: group is from host1, but the type changed */
1921 } else if (grp[i].type != grp[i].outtype &&
1922 strcmp(grp[i].type,grp[i].outtype) != 0) {
1923 ++change;
1924
1925 /* case: group did not change */
1926 } else {
1927 ++same;
1928 }
1929 }
1930 work = add+change+remove;
1931 if (same+work+host1_errs <= 0) {
1932 /* no lines, no work, no errors == nothing changed == 100% the same */
1933 host1_same = (double)100.0;
1934 } else {
1935 /* calculate % unchanged */
1936 host1_same = (double)100.0 *
1937 ((double)same / (double)(same+work+host1_errs));
1938 }
1939 if (D_BUG) {
1940 warn("STATUS: same=%d add=%d, change=%d, remove=%d",
1941 same, add, change, remove);
1942 warn("STATUS: ignore=%d, work=%d, err=%d",
1943 ignore, work, host1_errs);
1944 warn("STATUS: same+work+err=%d, host1_same=%.2f%%",
1945 same+work+host1_errs, host1_same);
1946 }
1947
1948 /*
1949 * Bail out if we too few lines in host1 active file (ignoring
1950 * low/high water mark changes) remaining unchanged.
1951 *
1952 * We define change as:
1953 *
1954 * line errors from host1 active file
1955 * newsgroups to be added to host1
1956 * newsgroups to be removed from host1
1957 * newsgroups to be change in host1
1958 */
1959 if (host1_same < p_flag) {
1960 warn("HALT: lines unchanged: %.2f%% < min change limit: %.2f%%",
1961 host1_same, p_flag);
1962 warn(" No output or commands executed. Determine if the degree");
1963 warn(" of changes is okay and re-execute with a lower -p value");
1964 die(" or with the problem fixed.");
1965 }
1966
1967 /*
1968 * look at all groups
1969 *
1970 * If we are not producing active file output, we must do removals
1971 * before we do any adds and changes.
1972 *
1973 * We recalculate the work stats in finer detail as well as noting how
1974 * many actions were successful.
1975 */
1976 add = 0;
1977 change = 0;
1978 remove = 0;
1979 same = 0;
1980 ignore = 0;
1981 work = 0;
1982 not_done = 0;
1983 sleep_msg = 0;
1984 rm_cycle = ((o_flag == OUTPUT_ACTIVE) ? 0 : 1);
1985 do {
1986 for (i=0; i < grplen; ++i) {
1987
1988 /* if -o Ax, output ignored non-error groups too */
1989
1990 /*
1991 * skip non-output ...
1992 *
1993 * but if '-a' and active output mode, then don't skip ignored,
1994 * non-error, non-removed groups from host1
1995 */
1996 if (grp[i].output == 0) {
1997 /* Ignored groups should only be counted once. */
1998 if (!rm_cycle && grp[i].hostid == HOSTID1) {
1999 ++ignore;
2000 }
2001 continue;
2002 }
2003
2004 /* case: output active lines */
2005 if (o_flag == OUTPUT_ACTIVE) {
2006
2007 /* case: group needs removal */
2008 if (grp[i].remove) {
2009 ++remove;
2010 ++work;
2011
2012 /* case: group will be kept */
2013 } else {
2014
2015 /* output in active file format */
2016 printf("%s %s %s %s\n",
2017 grp[i].name, grp[i].outhi, grp[i].outlow,
2018 grp[i].outtype);
2019
2020 /* if -v level is high enough, do group accounting */
2021 if (D_IF_SUMM) {
2022
2023 /* case: group is from host2, so we need a newgroup */
2024 if (grp[i].hostid == HOSTID2) {
2025 ++add;
2026 ++work;
2027
2028 /* case: group is from host1, but the type changed */
2029 } else if (grp[i].type != grp[i].outtype &&
2030 strcmp(grp[i].type,grp[i].outtype) != 0) {
2031 ++change;
2032 ++work;
2033
2034 /* case: group did not change */
2035 } else {
2036 ++same;
2037 }
2038 }
2039 }
2040
2041 /* case: output ctlinnd commands */
2042 } else if (o_flag == OUTPUT_CTLINND) {
2043
2044 /* case: group needs removal */
2045 if (grp[i].remove) {
2046
2047 /* output rmgroup */
2048 if (rm_cycle) {
2049 printf("%s rmgroup %s\n", CTLINND_NAME, grp[i].name);
2050 ++remove;
2051 ++work;
2052 }
2053
2054 /* case: group is from host2, so we need a newgroup */
2055 } else if (grp[i].hostid == HOSTID2) {
2056
2057 /* output newgroup */
2058 if (! rm_cycle) {
2059 printf("%s newgroup %s %s %s\n",
2060 CTLINND_NAME, grp[i].name, grp[i].outtype, new_name);
2061 ++add;
2062 ++work;
2063 }
2064
2065 /* case: group is from host1, but the type changed */
2066 } else if (grp[i].type != grp[i].outtype &&
2067 strcmp(grp[i].type,grp[i].outtype) != 0) {
2068
2069 /* output changegroup */
2070 if (! rm_cycle) {
2071 printf("%s changegroup %s %s\n",
2072 CTLINND_NAME, grp[i].name, grp[i].outtype);
2073 ++change;
2074 ++work;
2075 }
2076
2077 /* case: group did not change */
2078 } else {
2079 if (! rm_cycle) {
2080 ++same;
2081 }
2082 }
2083
2084 /* case: exec ctlinnd commands */
2085 } else if (o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) {
2086
2087 /* warn about sleeping if needed and first time */
2088 if (o_flag == OUTPUT_EXEC && z_flag > 0 && sleep_msg == 0) {
2089 if (D_SUMMARY)
2090 warn("will sleep %d seconds before each fork/exec",
2091 z_flag);
2092 sleep_msg = 1;
2093 }
2094
2095 /* case: group needs removal */
2096 if (grp[i].remove) {
2097
2098 /* exec rmgroup */
2099 if (rm_cycle) {
2100 if (D_REPORT && o_flag == OUTPUT_EXEC)
2101 warn("rmgroup %s", grp[i].name);
2102 if (! exec_cmd(o_flag, "rmgroup",
2103 grp[i].name, NULL, NULL)) {
2104 ++not_done;
2105 } else {
2106 ++remove;
2107 ++work;
2108 }
2109 }
2110
2111 /* case: group is from host2, so we need a newgroup */
2112 } else if (grp[i].hostid == HOSTID2) {
2113
2114 /* exec newgroup */
2115 if (!rm_cycle) {
2116 if (D_REPORT && o_flag == OUTPUT_EXEC)
2117 warn("newgroup %s %s %s",
2118 grp[i].name, grp[i].outtype, new_name);
2119 if (! exec_cmd(o_flag, "newgroup", grp[i].name,
2120 grp[i].outtype, new_name)) {
2121 ++not_done;
2122 } else {
2123 ++add;
2124 ++work;
2125 }
2126 }
2127
2128 /* case: group is from host1, but the type changed */
2129 } else if (grp[i].type != grp[i].outtype &&
2130 strcmp(grp[i].type,grp[i].outtype) != 0) {
2131
2132 /* exec changegroup */
2133 if (!rm_cycle) {
2134 if (D_REPORT && o_flag == OUTPUT_EXEC)
2135 warn("changegroup %s %s",
2136 grp[i].name, grp[i].outtype);
2137 if (! exec_cmd(o_flag, "changegroup", grp[i].name,
2138 grp[i].outtype, NULL)) {
2139 ++not_done;
2140 } else {
2141 ++change;
2142 ++work;
2143 }
2144 }
2145
2146 /* case: group did not change */
2147 } else {
2148 if (! rm_cycle) {
2149 ++same;
2150 }
2151 }
2152 }
2153 }
2154 } while (--rm_cycle >= 0);
2155
2156 /* final accounting, if -v */
2157 if (D_SUMMARY || (D_IF_SUMM && (work > 0 || not_done > 0))) {
2158 warn("STATUS: %d group(s)", add+remove+change+same);
2159 warn("STATUS: %d group(s)%s added", add,
2160 ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
2161 "" : " to be"));
2162 warn("STATUS: %d group(s)%s removed", remove,
2163 ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
2164 "" : " to be"));
2165 warn("STATUS: %d group(s)%s changed", change,
2166 ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
2167 "" : " to be"));
2168 warn("STATUS: %d group(s) %s the same", same,
2169 ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
2170 "remain" : "are"));
2171 warn("STATUS: %.2f%% of lines unchanged", host1_same);
2172 warn("STATUS: %d group(s) ignored", ignore);
2173 if (o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC)
2174 warn("STATUS: %d exec(s) not performed", not_done);
2175 }
2176 }
2177
2178 /*
2179 * error_mark - mark for removal, error groups from a given host
2180 *
2181 * given:
2182 * grp array of groups
2183 * grplen length of grp array in elements
2184 * hostid host to mark error groups for removal
2185 */
2186 static void
error_mark(struct grp * grp,int grplen,int hostid)2187 error_mark(struct grp *grp, int grplen, int hostid)
2188 {
2189 int i;
2190 int errcnt;
2191
2192 /* firewall */
2193 if (grp == NULL)
2194 die("internal error #11: grp is NULL");
2195
2196 /* loop thru groups, looking for error groups from a given host */
2197 errcnt = 0;
2198 for (i=0; i < grplen; ++i) {
2199
2200 /* skip if not from hostid */
2201 if (grp[i].hostid != hostid) {
2202 continue;
2203 }
2204
2205 /* mark for removal if an error group not already removed */
2206 if (IS_ERROR(grp[i].ignore)) {
2207
2208 /* mark for removal */
2209 if (grp[i].output != 1 || grp[i].remove != 1) {
2210 grp[i].output = 1;
2211 grp[i].remove = 1;
2212 }
2213 ++errcnt;
2214 }
2215 }
2216
2217 /* all done */
2218 if (D_SUMMARY || (D_IF_SUMM && errcnt > 0))
2219 warn("STATUS: marked %d error groups for removal", errcnt);
2220 return;
2221 }
2222
2223 /*
2224 * eq_merge_cmp - qsort compare function for =type group processing
2225 *
2226 * given:
2227 * a =group a to compare
2228 * b =group b to compare
2229 *
2230 * returns:
2231 * >0 a > b
2232 * 0 a == b elements match (fatal error if a and b are different)
2233 * <0 a < b
2234 *
2235 * To speed up group comparison, we compare by the following items listed
2236 * in order of sorting:
2237 *
2238 * skip (non-skipped groups after skipped ones)
2239 * group equiv name
2240 * group name
2241 * hostid (host1 ahead of host2)
2242 * linenum (active file line number)
2243 */
2244 static int
eq_merge_cmp(const void * arg_a,const void * arg_b)2245 eq_merge_cmp(const void *arg_a, const void *arg_b)
2246 {
2247 const struct eqgrp *a = arg_a; /* group a to compare */
2248 const struct eqgrp *b = arg_b; /* group b to compare */
2249 int i;
2250
2251 /* firewall */
2252 if (a == b) {
2253 /* we guess this could happen */
2254 return(0);
2255 }
2256
2257 /* compare skip values */
2258 if (a->skip != b->skip) {
2259 if (a->skip > b->skip) {
2260 /* a is skipped, b is not */
2261 return 1;
2262 } else {
2263 /* b is skipped, a is not */
2264 return -1;
2265 }
2266 }
2267
2268 /* compare the names the groups are equivalenced to */
2269 i = strcmp(a->eq, b->eq);
2270 if (i != 0) {
2271 return i;
2272 }
2273
2274 /* compare the group names themselves */
2275 i = strcmp(a->g->name, b->g->name);
2276 if (i != 0) {
2277 return i;
2278 }
2279
2280 /* compare hostid's */
2281 if (a->g->hostid != b->g->hostid) {
2282 if (a->g->hostid > b->g->hostid) {
2283 return 1;
2284 } else {
2285 return -1;
2286 }
2287 }
2288
2289 /* compare active line numbers */
2290 if (a->g->linenum != b->g->linenum) {
2291 if (a->g->linenum > b->g->linenum) {
2292 return 1;
2293 } else {
2294 return -1;
2295 }
2296 }
2297
2298 /* two different elements match, this should not happen! */
2299 die("two internal eqgrp elements match!");
2300 }
2301
2302 /*
2303 * mark_eq_probs - mark =type groups from a given host that have problems
2304 *
2305 * given:
2306 * grp sorted array of groups
2307 * grplen length of grp array in elements
2308 * hostid host to mark error groups for removal, or NOHOST
2309 * host1 name of host with HOSTID1
2310 * host2 name of host with HOSTID2
2311 *
2312 * This function assumes that the grp array has been sorted by name.
2313 */
2314 static int
mark_eq_probs(struct grp * grp,int grplen,int hostid,char * host1,char * host2)2315 mark_eq_probs(struct grp *grp, int grplen, int hostid, char *host1,
2316 char *host2)
2317 {
2318 struct eqgrp *eqgrp; /* =type pointer array */
2319 int eq_cnt; /* number of =type groups from host */
2320 int new_eq_cnt; /* number of =type groups remaining */
2321 int missing; /* =type groups equiv to missing groups */
2322 int cycled; /* =type groups equiv to themselves */
2323 int chained; /* =type groups in long chain or loop */
2324 int cmp; /* strcmp of two names */
2325 int step; /* equiv loop step */
2326 int i;
2327 int j;
2328
2329 /* firewall */
2330 if (grp == NULL)
2331 die("internal error #12: grp is NULL");
2332 if (hostid == NOHOST) {
2333 /* nothing to detect, nothing else to do */
2334 return 0;
2335 }
2336
2337 /* count the =type groups from hostid that are not in error */
2338 eq_cnt = 0;
2339 for (i=0; i < grplen; ++i) {
2340 if (grp[i].hostid == hostid &&
2341 ! IS_ERROR(grp[i].ignore) &&
2342 grp[i].type != NULL &&
2343 grp[i].type[0] == NF_FLAG_ALIAS) {
2344 ++eq_cnt;
2345 }
2346 }
2347 if (D_BUG && hostid != NOHOST)
2348 warn("STATUS: host%d has %d =type groups", hostid, eq_cnt);
2349
2350 /* if no groups, then there is nothing to do */
2351 if (eq_cnt == 0) {
2352 return 0;
2353 }
2354
2355 /* setup the =group record array */
2356 eqgrp = xmalloc(eq_cnt * sizeof(eqgrp[0]));
2357 for (i=0, j=0; i < grplen && j < eq_cnt; ++i) {
2358 if (grp[i].hostid == hostid &&
2359 ! IS_ERROR(grp[i].ignore) &&
2360 grp[i].type != NULL &&
2361 grp[i].type[0] == NF_FLAG_ALIAS) {
2362
2363 /* initialize record */
2364 eqgrp[j].skip = 0;
2365 eqgrp[j].g = &grp[i];
2366 eqgrp[j].eq = &(grp[i].type[1]);
2367 ++j;
2368 }
2369 }
2370
2371 /*
2372 * try to resolve =type groups in at least EQ_LOOP equiv links
2373 */
2374 new_eq_cnt = eq_cnt;
2375 missing = 0;
2376 cycled = 0;
2377 for (step=0; step < EQ_LOOP && new_eq_cnt >= 0; ++step) {
2378
2379 /* sort the =group record array */
2380 qsort((char *)eqgrp, eq_cnt, sizeof(eqgrp[0]), eq_merge_cmp);
2381
2382 /* look for the groups to which =type group point at */
2383 eq_cnt = new_eq_cnt;
2384 for (i=0, j=0; i < grplen && j < eq_cnt; ++i) {
2385
2386 /* we will skip any group in error or from the wrong host */
2387 if (grp[i].hostid != hostid || IS_ERROR(grp[i].ignore)) {
2388 continue;
2389 }
2390
2391 /* we will skip any skipped eqgrp's */
2392 if (eqgrp[j].skip) {
2393 /* try the same group against the next eqgrp */
2394 --i;
2395 ++j;
2396 continue;
2397 }
2398
2399 /* compare the =name of the eqgrp with the name of the grp */
2400 cmp = strcmp(grp[i].name, eqgrp[j].eq);
2401
2402 /* case: this group is pointed at by an eqgrp */
2403 if (cmp == 0) {
2404
2405 /* see if we have looped around to the original group name */
2406 if (strcmp(grp[i].name, eqgrp[j].g->name) == 0) {
2407
2408 /* note the detected loop */
2409 if (! QUIET(hostid))
2410 warn("%s from %s line %d =loops around to itself",
2411 eqgrp[j].g->name,
2412 ((eqgrp[j].g->hostid == HOSTID1) ? host1 : host2),
2413 eqgrp[j].g->linenum);
2414 eqgrp[j].g->ignore |= ERROR_EQLOOP;
2415
2416 /* the =group is bad, so we don't need to bother with it */
2417 eqgrp[j].skip = 1;
2418 --new_eq_cnt;
2419 ++cycled;
2420 --i;
2421 ++j;
2422 continue;
2423 }
2424
2425 /* if =group refers to a valid group, we are done with it */
2426 if (grp[i].type != NULL && grp[i].type[0] != NF_FLAG_ALIAS) {
2427 eqgrp[j].skip = 1;
2428 --new_eq_cnt;
2429 /* otherwise note the equiv name */
2430 } else {
2431 eqgrp[j].eq = &(grp[i].type[1]);
2432 }
2433 --i;
2434 ++j;
2435
2436 /* case: we missed the =name */
2437 } else if (cmp > 0) {
2438
2439 /* mark the eqgrp in error */
2440 eqgrp[j].g->ignore |= ERROR_NONEQ;
2441 if (! QUIET(hostid))
2442 warn("%s from %s line %d not equiv to a valid group",
2443 eqgrp[j].g->name,
2444 ((eqgrp[j].g->hostid == HOSTID1) ? host1 : host2),
2445 eqgrp[j].g->linenum);
2446
2447 /* =group is bad, so we don't need to bother with it anymore */
2448 eqgrp[j].skip = 1;
2449 --new_eq_cnt;
2450 ++missing;
2451 ++j;
2452 }
2453 }
2454
2455 /* any remaining non-skipped eqgrps are bad */
2456 while (j < eq_cnt) {
2457
2458 /* mark the eqgrp in error */
2459 eqgrp[j].g->ignore |= ERROR_NONEQ;
2460 if (! QUIET(hostid))
2461 warn("%s from %s line %d isn't equiv to a valid group",
2462 eqgrp[j].g->name,
2463 ((hostid == HOSTID1) ? host1 : host2),
2464 eqgrp[j].g->linenum);
2465
2466 /* the =group is bad, so we don't need to bother with it anymore */
2467 eqgrp[j].skip = 1;
2468 --new_eq_cnt;
2469 ++missing;
2470 ++j;
2471 }
2472 }
2473
2474 /* note groups that are in a long chain or loop */
2475 chained = new_eq_cnt;
2476 qsort((char *)eqgrp, eq_cnt, sizeof(eqgrp[0]), eq_merge_cmp);
2477 for (j=0; j < new_eq_cnt; ++j) {
2478
2479 /* skip if already skipped */
2480 if (eqgrp[j].skip == 1) {
2481 continue;
2482 }
2483
2484 /* mark as a long loop group */
2485 eqgrp[j].g->ignore |= ERROR_LONGLOOP;
2486 if (! QUIET(hostid))
2487 warn("%s from %s line %d in a long equiv chain or loop > %d",
2488 eqgrp[j].g->name,
2489 ((hostid == HOSTID1) ? host1 : host2),
2490 eqgrp[j].g->linenum, EQ_LOOP);
2491 }
2492
2493 /* all done */
2494 if (D_BUG) {
2495 warn("%d =type groups from %s are not equiv to a valid group",
2496 missing, ((hostid == HOSTID1) ? host1 : host2));
2497 warn("%d =type groups from %s are equiv to themselves",
2498 cycled, ((hostid == HOSTID1) ? host1 : host2));
2499 warn("%d =type groups from %s are in a long chain or loop > %d",
2500 chained, ((hostid == HOSTID1) ? host1 : host2), EQ_LOOP);
2501 }
2502 free(eqgrp);
2503 return missing+cycled+chained;
2504 }
2505
2506 /*
2507 * exec_cmd - exec a ctlinnd command in forked process
2508 *
2509 * given:
2510 * mode OUTPUT_EXEC or OUTPUT_IEXEC (interactive mode)
2511 * cmd "changegroup", "newgroup", "rmgroup"
2512 * grp name of group
2513 * type type of group or NULL
2514 * who newgroup creator or NULL
2515 *
2516 * returns:
2517 * 1 exec was performed
2518 * 0 exec was not performed
2519 */
2520 static int
exec_cmd(int mode,const char * cmd,char * grp,char * type,const char * who)2521 exec_cmd(int mode, const char *cmd, char *grp, char *type, const char *who)
2522 {
2523 FILE *ch_stream = NULL; /* stream from a child process */
2524 char buf[BUFSIZ+1]; /* interactive buffer */
2525 int pid; /* pid of child process */
2526 int io[2]; /* pair of pipe descriptors */
2527 int status; /* wait status */
2528 int exitval; /* exit status of the child */
2529 char *w_string = NULL; /* will contain "-t "+w_flag */
2530 char *p;
2531
2532 /* firewall */
2533 if (cmd == NULL || grp == NULL)
2534 die("internal error #13, cmd or grp is NULL");
2535
2536 /* if interactive, ask the question */
2537 if (mode == OUTPUT_IEXEC) {
2538
2539 /* ask the question */
2540 fflush(stdout);
2541 fflush(stderr);
2542 if (type == NULL) {
2543 printf("%s %s [yn]? ", cmd, grp);
2544 } else if (who == NULL) {
2545 printf("%s %s %s [yn]? ", cmd, grp, type);
2546 } else {
2547 printf("%s %s %s %s [yn]? ", cmd, grp, type, who);
2548 }
2549 fflush(stdout);
2550 buf[0] = '\0';
2551 buf[BUFSIZ] = '\0';
2552 p = fgets(buf, BUFSIZ, stdin);
2553 if (p == NULL) {
2554 /* EOF/ERROR on interactive input, silently stop processing */
2555 exit(43);
2556 }
2557
2558 /* If non-empty line doesn't start with 'y' or 'Y', skip command. */
2559 if (buf[0] != 'y' && buf[0] != 'Y' && buf[0] != '\n') {
2560 /* indicate nothing was done */
2561 return 0;
2562 }
2563 }
2564
2565 /* build a pipe for output from child interactive mode */
2566 if (mode == OUTPUT_IEXEC) {
2567 if (pipe(io) < 0)
2568 sysdie("pipe create failed");
2569
2570 /* setup a fake pipe to /dev/null for non-interactive mode */
2571 } else {
2572 io[READ_SIDE] = open(DEV_NULL, 0);
2573 if (io[READ_SIDE] < 0)
2574 sysdie("unable to open %s for reading", DEV_NULL);
2575 io[WRITE_SIDE] = open(DEV_NULL, 1);
2576 if (io[WRITE_SIDE] < 0)
2577 sysdie("unable to open %s for writing", DEV_NULL);
2578 }
2579
2580 /* pause if in non-interactive mode so as to not busy-out the server */
2581 if (mode == OUTPUT_EXEC && z_flag > 0) {
2582 if (D_BUG) {
2583 warn("sleeping %d seconds before fork/exec", z_flag);
2584 /* be sure they know what we are stalling */
2585 fflush(stderr);
2586 }
2587 sleep(z_flag);
2588 }
2589
2590 /* fork the child process */
2591 fflush(stdout);
2592 fflush(stderr);
2593 pid = fork();
2594 if (pid == -1)
2595 sysdie("fork failed");
2596
2597 /* case: child process */
2598 if (pid == 0) {
2599
2600 /*
2601 * prep file descriptors
2602 */
2603 fclose(stdin);
2604 close(io[READ_SIDE]);
2605 if (dup2(io[WRITE_SIDE], 1) < 0)
2606 sysdie("child: dup of write I/O pipe to stdout failed");
2607 if (dup2(io[WRITE_SIDE], 2) < 0)
2608 sysdie("child: dup of write I/O pipe to stderr failed");
2609
2610 /* exec the ctlinnd command */
2611 p = concatpath(innconf->pathbin, INN_PATH_CTLINND);
2612 xasprintf(&w_string, "-t %d", w_flag);
2613
2614 if (type == NULL) {
2615 execl(p,
2616 CTLINND_NAME, w_string, cmd, grp, (char *) 0);
2617 } else if (who == NULL) {
2618 execl(p,
2619 CTLINND_NAME, w_string, cmd, grp, type, (char *) 0);
2620 } else {
2621 execl(p,
2622 CTLINND_NAME, w_string, cmd, grp, type, who, (char *) 0);
2623 }
2624 /* child exec failed */
2625 sysdie("child process exec failed");
2626
2627 /* case: parent process */
2628 } else {
2629
2630 /* prep file descriptors */
2631 if (mode != OUTPUT_IEXEC) {
2632 close(io[READ_SIDE]);
2633 }
2634 close(io[WRITE_SIDE]);
2635
2636 /* print a line from the child, if interactive */
2637 if (mode == OUTPUT_IEXEC) {
2638
2639 /* read what the child says */
2640 buf[0] = '\0';
2641 buf[BUFSIZ] = '\0';
2642 ch_stream = fdopen(io[READ_SIDE], "r");
2643 if (ch_stream == NULL)
2644 sysdie("fdopen of pipe failed");
2645 p = fgets(buf, BUFSIZ, ch_stream);
2646
2647 /* print what the child said, if anything */
2648 if (p != NULL) {
2649 if (buf[strlen(buf)-1] == '\n')
2650 buf[strlen(buf)-1] = '\0';
2651 warn(" %s", buf);
2652 }
2653 }
2654
2655 /* look for abnormal child termination/status */
2656 errno = 0;
2657 while (wait(&status) < 0) {
2658 if (errno == EINTR) {
2659 /* just an interrupt, try to wait again */
2660 errno = 0;
2661 } else {
2662 sysdie("wait returned -1");
2663 }
2664 }
2665 if (mode == OUTPUT_IEXEC) {
2666 /* close the pipe now that we are done with reading it */
2667 fclose(ch_stream);
2668 }
2669 if (WIFSTOPPED(status)) {
2670 warn(" %s %s %s%s%s%s%s stopped",
2671 CTLINND_NAME, cmd, grp,
2672 (type ? "" : " "), (type ? type : ""),
2673 (who ? "" : " "), (who ? who : ""));
2674 /* assume no work was done */
2675 return 0;
2676 }
2677 if (WIFSIGNALED(status)) {
2678 warn(" %s %s %s%s%s%s%s killed by signal %d",
2679 CTLINND_NAME, cmd, grp,
2680 (type ? "" : " "), (type ? type : ""),
2681 (who ? "" : " "), (who ? who : ""), WTERMSIG(status));
2682 /* assume no work was done */
2683 return 0;
2684 }
2685 if (!WIFEXITED(status)) {
2686 warn(" %s %s %s%s%s%s%s returned unknown wait status: 0x%x",
2687 CTLINND_NAME, cmd, grp,
2688 (type ? "" : " "), (type ? type : ""),
2689 (who ? "" : " "), (who ? who : ""), status);
2690 /* assume no work was done */
2691 return 0;
2692 }
2693 exitval = WEXITSTATUS(status);
2694 if (exitval != 0) {
2695 warn(" %s %s %s%s%s%s%s exited with status: %d",
2696 CTLINND_NAME, cmd, grp,
2697 (type ? "" : " "), (type ? type : ""),
2698 (who ? "" : " "), (who ? who : ""), exitval);
2699 /* assume no work was done */
2700 return 0;
2701 }
2702 }
2703
2704 /* all done */
2705 return 1;
2706 }
2707
2708 /*
2709 * new_top_hier - determine if the newsgroup represents a new hierarchy
2710 *
2711 * Determine of the newsgroup name is a new hierarchy.
2712 *
2713 * given:
2714 * name name of newsgroup to check
2715 * existing_hier hash table of existing hierarchies
2716 *
2717 * returns:
2718 * false hierarchy already exists
2719 * true hierarchy does not exist, name represents a new hierarchy
2720 */
2721 static int
new_top_hier(char * name,struct hash * existing_hier)2722 new_top_hier(char *name, struct hash *existing_hier)
2723 {
2724 int result; /* return result */
2725 char *dot;
2726
2727 /*
2728 * temp change name to just the top level
2729 */
2730 dot = strchr(name, '.');
2731 if (dot != NULL) {
2732 *dot = '\0';
2733 }
2734
2735 /*
2736 * determine if we can find this top level hierarchy directory
2737 */
2738 result = (hash_lookup(existing_hier, name) == NULL);
2739
2740 /* restore name */
2741 if (dot != NULL) {
2742 *dot = '.';
2743 }
2744
2745 /*
2746 * return the result
2747 */
2748 return result;
2749 }
2750
2751 /*
2752 * string_key - identity function, for use with hashtab library
2753 *
2754 * Returns its only argument.
2755 *
2756 * given:
2757 * entry void* pointer representing a string
2758 *
2759 * returns:
2760 * the same void* pointer
2761 */
2762 static const void *
string_key(const void * entry)2763 string_key(const void *entry)
2764 {
2765 return entry;
2766 }
2767
2768 /*
2769 * string_equal - string comparison function, for use with hashtab library
2770 *
2771 * Compares two strings.
2772 *
2773 * given:
2774 * key void* pointer representing a hash table key
2775 * entry void* pointer representing a hash table entry
2776 *
2777 * returns:
2778 * 0 arguments are not equal
2779 * 1 arguments are equal
2780 */
2781 static bool
string_equal(const void * key,const void * entry)2782 string_equal(const void *key, const void *entry)
2783 {
2784 const char *p, *q;
2785
2786 p = key;
2787 q = entry;
2788 return !strcmp(p, q);
2789 }
2790