1 /*
2  * $LynxId: HTRules.c,v 1.44 2013/05/03 09:42:04 tom Exp $
3  *
4  *	Configuration manager for Hypertext Daemon		HTRules.c
5  *	==========================================
6  *
7  *
8  * History:
9  *	 3 Jun 91	Written TBL
10  *	10 Aug 91	Authorisation added after Daniel Martin (pass, fail)
11  *			Rule order in file changed
12  *			Comments allowed with # on 1st char of rule line
13  *	17 Jun 92	Bug fix: pass and fail failed if didn't contain '*' TBL
14  *	 1 Sep 93	Bug fix: no memory check - Nathan Torkington
15  *			BYTE_ADDRESSING removed - Arthur Secret
16  *	11 Sep 93  MD	Changed %i into %d in debug printf.
17  *			VMS does not recognize %i.
18  *			Bug Fix: in case of PASS, only one parameter to printf.
19  *	19 Sep 93  AL	Added Access Authorization stuff.
20  *	 1 Nov 93  AL	Added htbin.
21  *	25 May 99  KW	Added redirect for lynx.
22  *
23  */
24 
25 #include <HTUtils.h>
26 
27 /* (c) CERN WorldWideWeb project 1990,91. See Copyright.html for details */
28 #include <HTRules.h>
29 
30 #include <HTFile.h>
31 #include <LYLeaks.h>
32 #include <HTAAProt.h>
33 
34 #define LINE_LENGTH 256
35 
36 typedef struct _rule {
37     struct _rule *next;
38     HTRuleOp op;
39     char *pattern;
40     char *equiv;
41     char *condition_op;		/* as strings - may be inefficient, */
42     char *condition;		/* but this is not for a server - kw */
43 } rule;
44 
45 #ifndef NO_RULES
46 
47 #include <HTTP.h>		/* for redirecting_url, indirectly HTPermitRedir - kw */
48 #include <LYGlobalDefs.h>	/* for LYUserSpecifiedURL - kw */
49 #include <LYStrings.h>		/* for LYscanFloat */
50 #include <LYUtils.h>		/* for LYFixCursesOn - kw */
51 #include <HTAlert.h>
52 
53 /*	Global variables
54  *	----------------
55  */
56 char *HTBinDir = NULL;		/* Physical /htbin directory path.      */
57 
58 				/* In future this should not be global. */
59 char *HTSearchScript = NULL;	/* Search script name.          */
60 
61 /*	Module-wide variables
62  *	---------------------
63  */
64 
65 static rule *rules = 0;		/* Pointer to first on list */
66 
67 #ifndef PUT_ON_HEAD
68 static rule *rule_tail = 0;	/* Pointer to last on list */
69 #endif
70 
71 /*	Add rule to the list					HTAddRule()
72  *	--------------------
73  *
74  *  On entry,
75  *	pattern		points to 0-terminated string containing a single "*"
76  *	equiv		points to the equivalent string with * for the
77  *			place where the text matched by * goes.
78  *  On exit,
79  *	returns		0 if success, -1 if error.
80  */
81 
HTAddRule(HTRuleOp op,const char * pattern,const char * equiv,const char * cond_op,const char * cond)82 int HTAddRule(HTRuleOp op, const char *pattern,
83 	      const char *equiv,
84 	      const char *cond_op,
85 	      const char *cond)
86 {				/* BYTE_ADDRESSING removed and memory check - AS - 1 Sep 93 */
87     rule *temp;
88     char *pPattern = NULL;
89 
90     temp = typecalloc(rule);
91     if (temp == NULL)
92 	outofmem(__FILE__, "HTAddRule");
93 
94     assert(temp != NULL);
95 
96     if (equiv) {		/* Two operands */
97 	char *pEquiv = NULL;
98 
99 	StrAllocCopy(pEquiv, equiv);
100 	temp->equiv = pEquiv;
101     } else {
102 	temp->equiv = 0;
103     }
104     if (cond_op) {
105 	StrAllocCopy(temp->condition_op, cond_op);
106 	StrAllocCopy(temp->condition, cond);
107     }
108     StrAllocCopy(pPattern, pattern);
109     temp->pattern = pPattern;
110     temp->op = op;
111 
112     if (equiv) {
113 	CTRACE((tfp, "Rule: For `%s' op %d `%s'", pattern, (int) op, equiv));
114     } else {
115 	CTRACE((tfp, "Rule: For `%s' op %d", pattern, (int) op));
116     }
117     if (cond_op) {
118 	CTRACE((tfp, "\t%s %s\n", cond_op, NONNULL(cond)));
119     } else {
120 	CTRACE((tfp, "\n"));
121     }
122 
123     if (!rules) {
124 #ifdef LY_FIND_LEAKS
125 	atexit(HTClearRules);
126 #endif
127     }
128 #ifdef PUT_ON_HEAD
129     temp->next = rules;
130     rules = temp;
131 #else
132     temp->next = 0;
133     if (rule_tail)
134 	rule_tail->next = temp;
135     else
136 	rules = temp;
137     rule_tail = temp;
138 #endif
139 
140     return 0;
141 }
142 
143 /*	Clear all rules						HTClearRules()
144  *	---------------
145  *
146  * On exit,
147  *	There are no rules
148  *
149  * See also
150  *	HTAddRule()
151  */
HTClearRules(void)152 void HTClearRules(void)
153 {
154     while (rules) {
155 	rule *temp = rules;
156 
157 	rules = temp->next;
158 	FREE(temp->pattern);
159 	FREE(temp->equiv);
160 	FREE(temp->condition_op);
161 	FREE(temp->condition);
162 	FREE(temp);
163     }
164 #ifndef PUT_ON_HEAD
165     rule_tail = 0;
166 #endif
167 }
168 
rule_cond_ok(rule * r)169 static BOOL rule_cond_ok(rule * r)
170 {
171     BOOL result;
172 
173     if (!r->condition_op)
174 	return YES;
175     if (strcmp(r->condition_op, "if") && strcmp(r->condition_op, "unless")) {
176 	CTRACE((tfp, "....... rule ignored, unrecognized `%s'!\n",
177 		r->condition_op));
178 	return NO;
179     }
180     if (!strcmp(r->condition, "redirected"))
181 	result = (BOOL) (redirection_attempts > 0);
182     else if (!strcmp(r->condition, "userspec"))
183 	result = LYUserSpecifiedURL;
184     else {
185 	CTRACE((tfp, "....... rule ignored, unrecognized `%s %s'!\n",
186 		r->condition_op, NONNULL(r->condition)));
187 	return NO;
188     }
189     if (!strcmp(r->condition_op, "if"))
190 	return result;
191     else
192 	return (BOOL) (!result);
193 
194 }
195 
196 /*	Translate by rules					HTTranslate()
197  *	------------------
198  *
199  *	The most recently defined rules are applied first.
200  *
201  * On entry,
202  *	required	points to a string whose equivalent value is needed
203  * On exit,
204  *	returns		the address of the equivalent string allocated from
205  *			the heap which the CALLER MUST FREE. If no translation
206  *			occurred, then it is a copy of the original.
207  * NEW FEATURES:
208  *			When a "protect" or "defprot" rule is matched,
209  *			a call to HTAA_setCurrentProtection() or
210  *			HTAA_setDefaultProtection() is made to notify
211  *			the Access Authorization module that the file is
212  *			protected, and so it knows how to handle it.
213  *								-- AL
214  */
HTTranslate(const char * required)215 char *HTTranslate(const char *required)
216 {
217     rule *r;
218     char *current = NULL;
219     char *msgtmp = NULL;
220     const char *pMsg;
221     int proxy_none_flag = 0;
222     int permitredir_flag = 0;
223 
224     StrAllocCopy(current, required);
225 
226     HTAA_clearProtections();	/* Reset from previous call -- AL */
227 
228     for (r = rules; r; r = r->next) {
229 	char *p = r->pattern;
230 	int m = 0;		/* Number of characters matched against wildcard */
231 	const char *q = current;
232 
233 	for (; *p && *q; p++, q++) {	/* Find first mismatch */
234 	    if (*p != *q)
235 		break;
236 	}
237 
238 	if (*p == '*') {	/* Match up to wildcard */
239 	    m = (int) strlen(q) - (int) strlen(p + 1);	/* Amount to match to wildcard */
240 	    if (m < 0)
241 		continue;	/* tail is too short to match */
242 	    if (0 != strcmp(q + m, p + 1))
243 		continue;	/* Tail mismatch */
244 	} else
245 	    /* Not wildcard */ if (*p != *q)
246 	    continue;		/* plain mismatch: go to next rule */
247 
248 	if (!rule_cond_ok(r))	/* check condition, next rule if false - kw */
249 	    continue;
250 
251 	switch (r->op) {	/* Perform operation */
252 
253 	case HT_DefProt:
254 	case HT_Protect:
255 #ifdef ACCESS_AUTH
256 	    {
257 		char *local_copy = NULL;
258 		char *p2;
259 		char *eff_ids = NULL;
260 		char *prot_file = NULL;
261 
262 		CTRACE((tfp, "HTRule: `%s' matched %s %s: `%s'\n",
263 			current,
264 			(r->op == HT_Protect ? "Protect" : "DefProt"),
265 			"rule, setup",
266 			(r->equiv ? r->equiv :
267 			 (r->op == HT_Protect ? "DEFAULT" : "NULL!!"))));
268 
269 		if (r->equiv) {
270 		    StrAllocCopy(local_copy, r->equiv);
271 		    p2 = local_copy;
272 		    prot_file = HTNextField(&p2);
273 		    eff_ids = HTNextField(&p2);
274 		}
275 
276 		if (r->op == HT_Protect)
277 		    HTAA_setCurrentProtection(current, prot_file, eff_ids);
278 		else
279 		    HTAA_setDefaultProtection(current, prot_file, eff_ids);
280 
281 		FREE(local_copy);
282 
283 		/* continue translating rules */
284 	    }
285 #endif /* ACCESS_AUTH */
286 	    break;
287 
288 	case HT_UserMsg:	/* Produce message immediately */
289 	    LYFixCursesOn("show rule message:");
290 	    HTUserMsg2((r->equiv ? r->equiv : "Rule: %s"), current);
291 	    break;
292 	case HT_InfoMsg:	/* Produce messages immediately */
293 	case HT_Progress:
294 	case HT_Alert:
295 	    LYFixCursesOn("show rule message:");	/* and fall through */
296 	case HT_AlwaysAlert:
297 	    pMsg = r->equiv ? r->equiv :
298 		(r->op == HT_AlwaysAlert) ? "%s" : "Rule: %s";
299 	    if (strchr(pMsg, '%')) {
300 		HTSprintf0(&msgtmp, pMsg, current);
301 		pMsg = msgtmp;
302 	    }
303 	    switch (r->op) {	/* Actually produce message */
304 	    case HT_InfoMsg:
305 		HTInfoMsg(pMsg);
306 		break;
307 	    case HT_Progress:
308 		HTProgress(pMsg);
309 		break;
310 	    case HT_Alert:
311 		HTAlert(pMsg);
312 		break;
313 	    case HT_AlwaysAlert:
314 		HTAlwaysAlert("Rule alert:", pMsg);
315 		break;
316 	    default:
317 		break;
318 	    }
319 	    FREE(msgtmp);
320 	    break;
321 
322 	case HT_PermitRedir:	/* Set special flag */
323 	    permitredir_flag = 1;
324 	    CTRACE((tfp, "HTRule: Mark for redirection permitted\n"));
325 	    break;
326 
327 	case HT_Pass:		/* Authorised */
328 	    if (!r->equiv) {
329 		if (proxy_none_flag) {
330 		    char *temp = NULL;
331 
332 		    StrAllocCopy(temp, "NoProxy=");
333 		    StrAllocCat(temp, current);
334 		    FREE(current);
335 		    current = temp;
336 		}
337 		CTRACE((tfp, "HTRule: Pass `%s'\n", current));
338 		return current;
339 	    }
340 	    /* Else fall through ...to map and pass */
341 
342 	case HT_Map:
343 	case HT_Redirect:
344 	case HT_RedirectPerm:
345 	    if (*p == *q) {	/* End of both strings, no wildcard */
346 		CTRACE((tfp, "For `%s' using `%s'\n", current, r->equiv));
347 		StrAllocCopy(current, r->equiv);	/* use entire translation */
348 	    } else {
349 		char *ins = strchr(r->equiv, '*');	/* Insertion point */
350 
351 		if (ins) {	/* Consistent rule!!! */
352 		    char *temp = NULL;
353 
354 		    HTSprintf0(&temp, "%.*s%.*s%s",
355 			       (int) (ins - r->equiv),
356 			       r->equiv,
357 			       m,
358 			       q,
359 			       ins + 1);
360 		    CTRACE((tfp, "For `%s' using `%s'\n",
361 			    current, temp));
362 		    FREE(current);
363 		    current = temp;	/* Use this */
364 
365 		} else {	/* No insertion point */
366 		    char *temp = NULL;
367 
368 		    StrAllocCopy(temp, r->equiv);
369 		    CTRACE((tfp, "For `%s' using `%s'\n",
370 			    current, temp));
371 		    FREE(current);
372 		    current = temp;	/* Use this */
373 		}		/* If no insertion point exists */
374 	    }
375 	    if (r->op == HT_Pass) {
376 		if (proxy_none_flag) {
377 		    char *temp = NULL;
378 
379 		    StrAllocCopy(temp, "NoProxy=");
380 		    StrAllocCat(temp, current);
381 		    FREE(current);
382 		    current = temp;
383 		}
384 		CTRACE((tfp, "HTRule: ...and pass `%s'\n",
385 			current));
386 		return current;
387 	    } else if (r->op == HT_Redirect) {
388 		CTRACE((tfp, "HTRule: ...and redirect to `%s'\n",
389 			current));
390 		redirecting_url = current;
391 		HTPermitRedir = (BOOL) (permitredir_flag == 1);
392 		return (char *) 0;
393 	    } else if (r->op == HT_RedirectPerm) {
394 		CTRACE((tfp, "HTRule: ...and redirect like 301 to `%s'\n",
395 			current));
396 		redirecting_url = current;
397 		permanent_redirection = TRUE;
398 		HTPermitRedir = (BOOL) (permitredir_flag == 1);
399 		return (char *) 0;
400 	    }
401 	    break;
402 
403 	case HT_UseProxy:
404 	    if (r->equiv && 0 == strcasecomp(r->equiv, "none")) {
405 		CTRACE((tfp, "For `%s' will not use proxy\n", current));
406 		proxy_none_flag = 1;
407 	    } else if (proxy_none_flag) {
408 		CTRACE((tfp, "For `%s' proxy server ignored: %s\n",
409 			current,
410 			NONNULL(r->equiv)));
411 	    } else {
412 		char *temp = NULL;
413 
414 		StrAllocCopy(temp, "Proxied=");
415 		StrAllocCat(temp, r->equiv);
416 		StrAllocCat(temp, current);
417 		CTRACE((tfp, "HTRule: proxy server found: %s\n",
418 			NONNULL(r->equiv)));
419 		FREE(current);
420 		return temp;
421 	    }
422 	    break;
423 
424 	case HT_Invalid:
425 	case HT_Fail:		/* Unauthorised */
426 	    CTRACE((tfp, "HTRule: *** FAIL `%s'\n", current));
427 	    FREE(current);
428 	    return (char *) 0;
429 	}			/* if tail matches ... switch operation */
430 
431     }				/* loop over rules */
432 
433     if (proxy_none_flag) {
434 	char *temp = NULL;
435 
436 	StrAllocCopy(temp, "NoProxy=");
437 	StrAllocCat(temp, current);
438 	FREE(current);
439 	return temp;
440     }
441 
442     return current;
443 }
444 
445 /*	Load one line of configuration
446  *	------------------------------
447  *
448  *	Call this, for example, to load a X resource with config info.
449  *
450  * returns	0 OK, < 0 syntax error.
451  */
HTSetConfiguration(char * config)452 int HTSetConfiguration(char *config)
453 {
454     HTRuleOp op;
455     char *line = NULL;
456     char *pointer = NULL;
457     char *word1;
458     const char *word2;
459     const char *word3;
460     const char *cond_op = NULL;
461     const char *cond = NULL;
462     float quality, secs, secs_per_byte;
463     long maxbytes;
464     int status;
465 
466     StrAllocCopy(line, config);
467     if (line != NULL) {
468 	char *p = line;
469 
470 	/* Chop off comments */
471 	while ((p = strchr(p, '#'))) {
472 	    if (p == line || isspace(UCH(*(p - 1)))) {
473 		*p = 0;
474 		break;
475 	    } else {
476 		p++;
477 	    }
478 	}
479     }
480     pointer = line;
481     word1 = HTNextField(&pointer);
482     if (!word1) {
483 	FREE(line);
484 	return 0;
485     };				/* Comment only or blank */
486 
487     word2 = HTNextField(&pointer);
488 
489     if (0 == strcasecomp(word1, "defprot") ||
490 	0 == strcasecomp(word1, "protect"))
491 	word3 = pointer;	/* The rest of the line to be parsed by AA module */
492     else
493 	word3 = HTNextField(&pointer);	/* Just the next word */
494 
495     if (!word2) {
496 	fprintf(stderr, "HTRule: %s %s\n", RULE_NEEDS_DATA, line);
497 	FREE(line);
498 	return -2;		/*syntax error */
499     }
500 
501     if (0 == strcasecomp(word1, "suffix")) {
502 	char *encoding = HTNextField(&pointer);
503 
504 	status = 0;
505 	if (pointer)
506 	    status = LYscanFloat(pointer, &quality);
507 
508 	HTSetSuffix(word2, word3,
509 		    encoding ? encoding : "binary",
510 		    status >= 1 ? quality : (float) 1.0);
511 
512     } else if (0 == strcasecomp(word1, "presentation")) {
513 	status = 0;
514 	if (pointer) {
515 	    const char *temp = pointer;
516 
517 	    if (LYscanFloat2(&temp, &quality)) {
518 		status = 1;
519 		if (LYscanFloat2(&temp, &secs)) {
520 		    status = 2;
521 		    if (LYscanFloat2(&temp, &secs_per_byte)) {
522 			status = 3;
523 			if (sscanf(temp, "%ld", &maxbytes)) {
524 			    status = 4;
525 			}
526 		    }
527 		}
528 	    }
529 	}
530 
531 	HTSetPresentation(word2, word3, NULL,
532 			  status >= 1 ? quality : 1.0,
533 			  status >= 2 ? secs : 0.0,
534 			  status >= 3 ? secs_per_byte : 0.0,
535 			  status >= 4 ? maxbytes : 0,
536 			  mediaCFG);
537 
538     } else if (0 == strncasecomp(word1, "htbin", 5) ||
539 	       0 == strncasecomp(word1, "bindir", 6)) {
540 	StrAllocCopy(HTBinDir, word2);	/* Physical /htbin location */
541 
542     } else if (0 == strncasecomp(word1, "search", 6)) {
543 	StrAllocCopy(HTSearchScript, word2);	/* Search script name */
544 
545     } else {
546 	op = 0 == strcasecomp(word1, "map") ? HT_Map
547 	    : 0 == strcasecomp(word1, "pass") ? HT_Pass
548 	    : 0 == strcasecomp(word1, "fail") ? HT_Fail
549 	    : 0 == strcasecomp(word1, "redirect") ? HT_Redirect
550 	    : 0 == strncasecomp(word1, "redirectperm", 12) ? HT_RedirectPerm
551 	    : 0 == strcasecomp(word1, "redirecttemp") ? HT_Redirect
552 	    : 0 == strcasecomp(word1, "permitredirection") ? HT_PermitRedir
553 	    : 0 == strcasecomp(word1, "useproxy") ? HT_UseProxy
554 	    : 0 == strcasecomp(word1, "alert") ? HT_Alert
555 	    : 0 == strcasecomp(word1, "alwaysalert") ? HT_AlwaysAlert
556 	    : 0 == strcasecomp(word1, "progress") ? HT_Progress
557 	    : 0 == strcasecomp(word1, "usermsg") ? HT_UserMsg
558 	    : 0 == strcasecomp(word1, "infomsg") ? HT_InfoMsg
559 	    : 0 == strcasecomp(word1, "defprot") ? HT_DefProt
560 	    : 0 == strcasecomp(word1, "protect") ? HT_Protect
561 	    : HT_Invalid;
562 	if (op == HT_Invalid) {
563 	    fprintf(stderr, "HTRule: %s '%s'\n", RULE_INCORRECT, config);
564 	} else {
565 	    switch (op) {
566 	    case HT_Fail:	/* never a or other 2nd parameter */
567 	    case HT_PermitRedir:
568 		cond_op = word3;
569 		if (cond_op && *cond_op) {
570 		    word3 = NULL;
571 		    cond = HTNextField(&pointer);
572 		}
573 		break;
574 
575 	    case HT_Pass:	/* possibly a URL2 */
576 		if (word3 && (!strcasecomp(word3, "if") ||
577 			      !strcasecomp(word3, "unless"))) {
578 		    cond_op = word3;
579 		    word3 = NULL;
580 		    cond = HTNextField(&pointer);
581 		    break;
582 		}
583 		/* else fall through */
584 	    case HT_Map:	/* always a URL2 (or other 2nd parameter) */
585 	    case HT_Redirect:
586 	    case HT_RedirectPerm:
587 	    case HT_UseProxy:
588 		cond_op = HTNextField(&pointer);
589 		/* check for extra status word in "Redirect" */
590 		if (op == HT_Redirect && 0 == strcasecomp(word1, "redirect") &&
591 		    cond_op &&
592 		    strcasecomp(cond_op, "if") &&
593 		    strcasecomp(cond_op, "unless")) {
594 		    if (0 == strcmp(word2, "301") ||
595 			0 == strcasecomp(word2, "permanent")) {
596 			op = HT_RedirectPerm;
597 		    } else if (!(0 == strcmp(word2, "302") ||
598 				 0 == strcmp(word2, "303") ||
599 				 0 == strcasecomp(word2, "temp") ||
600 				 0 == strcasecomp(word2, "seeother"))) {
601 			CTRACE((tfp, "Rule: Ignoring `%s' in Redirect\n", word2));
602 		    }
603 		    word2 = word3;
604 		    word3 = cond_op;	/* cond_op isn't condition op after all */
605 		    cond_op = HTNextField(&pointer);
606 		}
607 		if (cond_op && *cond_op)
608 		    cond = HTNextField(&pointer);
609 		break;
610 
611 	    case HT_Progress:
612 	    case HT_InfoMsg:
613 	    case HT_UserMsg:
614 	    case HT_Alert:
615 	    case HT_AlwaysAlert:
616 		cond_op = HTNextField(&pointer);
617 		if (cond_op && *cond_op)
618 		    cond = HTNextField(&pointer);
619 		if (word3) {	/* Fix string with too may %s - kw */
620 		    const char *cp = word3;
621 		    char *cp1, *cp2;
622 
623 		    while ((cp1 = strchr(cp, '%'))) {
624 			if (cp1[1] == '\0') {
625 			    *cp1 = '\0';
626 			    break;
627 			} else if (cp1[1] == '%') {
628 			    cp = cp1 + 2;
629 			    continue;
630 			} else
631 			    while ((cp2 = strchr(cp1 + 2, '%'))) {
632 				if (cp2[1] == '\0') {
633 				    *cp2 = '\0';
634 				    break;
635 				} else if (cp2[1] == '%') {
636 				    cp1 = cp2;
637 				} else {
638 				    *cp2 = '?';		/* replace bad % */
639 				    cp1 = cp2;
640 				}
641 			    }
642 			break;
643 		    }
644 		}
645 		break;
646 
647 	    default:
648 		break;
649 	    }
650 	    if (cond_op && cond && *cond && !strcasecomp(cond_op, "unless")) {
651 		cond_op = "unless";
652 	    } else if (cond_op && cond && *cond &&
653 		       !strcasecomp(cond_op, "if")) {
654 		cond_op = "if";
655 	    } else if (cond_op || cond) {
656 		fprintf(stderr, "HTRule: %s '%s'\n", RULE_INCORRECT, config);
657 		FREE(line);	/* syntax error, condition is a mess - kw */
658 		return -2;	/* NB unrecognized cond passes here - kw */
659 	    }
660 	    if (cond && !strncasecomp(cond, "redirected", (int) strlen(cond))) {
661 		cond = "redirected";	/* recognized, canonical case - kw */
662 	    } else if (cond && strlen(cond) >= 8 &&
663 		       !strncasecomp(cond, "userspecified", (int) strlen(cond))) {
664 		cond = "userspec";	/* also allow abbreviation - kw */
665 	    }
666 	    HTAddRule(op, word2, word3, cond_op, cond);
667 	}
668     }
669     FREE(line);
670     return 0;
671 }
672 
673 /*	Load the rules from a file				HTLoadRules()
674  *	--------------------------
675  *
676  * On entry,
677  *	Rules can be in any state
678  * On exit,
679  *	Any existing rules will have been kept.
680  *	Any new rules will have been loaded.
681  *	Returns		0 if no error, 0 if error!
682  *
683  * Bugs:
684  *	The strings may not contain spaces.
685  */
686 
HTLoadRules(const char * filename)687 int HTLoadRules(const char *filename)
688 {
689     FILE *fp = fopen(filename, TXT_R);
690     char line[LINE_LENGTH + 1];
691 
692     if (!fp) {
693 	CTRACE((tfp, "HTRules: Can't open rules file %s\n", filename));
694 	return -1;		/* File open error */
695     }
696     for (;;) {
697 	if (!fgets(line, LINE_LENGTH + 1, fp))
698 	    break;		/* EOF or error */
699 	(void) HTSetConfiguration(line);
700     }
701     fclose(fp);
702     return 0;			/* No error or syntax errors ignored */
703 }
704 
705 #endif /* NO_RULES */
706