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