|
|
1.1 root 1: /*
2: * Copyright (c) 1983 Eric P. Allman
3: * Copyright (c) 1988 Regents of the University of California.
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms are permitted provided
7: * that: (1) source distributions retain this entire copyright notice and
8: * comment, and (2) distributions including binaries display the following
9: * acknowledgement: ``This product includes software developed by the
10: * University of California, Berkeley and its contributors'' in the
11: * documentation or other materials provided with the distribution and in
12: * all advertising materials mentioning features or use of this software.
13: * Neither the name of the University nor the names of its contributors may
14: * be used to endorse or promote products derived from this software without
15: * specific prior written permission.
16: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
17: * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
18: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19: */
20:
21: #ifndef lint
22: static char sccsid[] = "@(#)readcf.c 5.21 (Berkeley) 6/1/90";
23: #endif /* not lint */
24:
25: # include "sendmail.h"
26:
27: /*
28: ** READCF -- read control file.
29: **
30: ** This routine reads the control file and builds the internal
31: ** form.
32: **
33: ** The file is formatted as a sequence of lines, each taken
34: ** atomically. The first character of each line describes how
35: ** the line is to be interpreted. The lines are:
36: ** Dxval Define macro x to have value val.
37: ** Cxword Put word into class x.
38: ** Fxfile [fmt] Read file for lines to put into
39: ** class x. Use scanf string 'fmt'
40: ** or "%s" if not present. Fmt should
41: ** only produce one string-valued result.
42: ** Hname: value Define header with field-name 'name'
43: ** and value as specified; this will be
44: ** macro expanded immediately before
45: ** use.
46: ** Sn Use rewriting set n.
47: ** Rlhs rhs Rewrite addresses that match lhs to
48: ** be rhs.
49: ** Mn arg=val... Define mailer. n is the internal name.
50: ** Args specify mailer parameters.
51: ** Oxvalue Set option x to value.
52: ** Pname=value Set precedence name to value.
53: **
54: ** Parameters:
55: ** cfname -- control file name.
56: **
57: ** Returns:
58: ** none.
59: **
60: ** Side Effects:
61: ** Builds several internal tables.
62: */
63:
64: readcf(cfname)
65: char *cfname;
66: {
67: FILE *cf;
68: int ruleset = 0;
69: char *q;
70: char **pv;
71: struct rewrite *rwp = NULL;
72: char buf[MAXLINE];
73: register char *p;
74: extern char **prescan();
75: extern char **copyplist();
76: char exbuf[MAXLINE];
77: char pvpbuf[PSBUFSIZE];
78: extern char *fgetfolded();
79: extern char *munchstring();
80:
81: cf = fopen(cfname, "r");
82: if (cf == NULL)
83: {
84: syserr("cannot open %s", cfname);
85: exit(EX_OSFILE);
86: }
87:
88: FileName = cfname;
89: LineNumber = 0;
90: while (fgetfolded(buf, sizeof buf, cf) != NULL)
91: {
92: /* map $ into \001 (ASCII SOH) for macro expansion */
93: for (p = buf; *p != '\0'; p++)
94: {
95: if (*p != '$')
96: continue;
97:
98: if (p[1] == '$')
99: {
100: /* actual dollar sign.... */
101: (void) strcpy(p, p + 1);
102: continue;
103: }
104:
105: /* convert to macro expansion character */
106: *p = '\001';
107: }
108:
109: /* interpret this line */
110: switch (buf[0])
111: {
112: case '\0':
113: case '#': /* comment */
114: break;
115:
116: case 'R': /* rewriting rule */
117: for (p = &buf[1]; *p != '\0' && *p != '\t'; p++)
118: continue;
119:
120: if (*p == '\0')
121: {
122: syserr("invalid rewrite line \"%s\"", buf);
123: break;
124: }
125:
126: /* allocate space for the rule header */
127: if (rwp == NULL)
128: {
129: RewriteRules[ruleset] = rwp =
130: (struct rewrite *) xalloc(sizeof *rwp);
131: }
132: else
133: {
134: rwp->r_next = (struct rewrite *) xalloc(sizeof *rwp);
135: rwp = rwp->r_next;
136: }
137: rwp->r_next = NULL;
138:
139: /* expand and save the LHS */
140: *p = '\0';
141: expand(&buf[1], exbuf, &exbuf[sizeof exbuf], CurEnv);
142: rwp->r_lhs = prescan(exbuf, '\t', pvpbuf);
143: if (rwp->r_lhs != NULL)
144: rwp->r_lhs = copyplist(rwp->r_lhs, TRUE);
145:
146: /* expand and save the RHS */
147: while (*++p == '\t')
148: continue;
149: q = p;
150: while (*p != '\0' && *p != '\t')
151: p++;
152: *p = '\0';
153: expand(q, exbuf, &exbuf[sizeof exbuf], CurEnv);
154: rwp->r_rhs = prescan(exbuf, '\t', pvpbuf);
155: if (rwp->r_rhs != NULL)
156: rwp->r_rhs = copyplist(rwp->r_rhs, TRUE);
157: break;
158:
159: case 'S': /* select rewriting set */
160: ruleset = atoi(&buf[1]);
161: if (ruleset >= MAXRWSETS || ruleset < 0)
162: {
163: syserr("bad ruleset %d (%d max)", ruleset, MAXRWSETS);
164: ruleset = 0;
165: }
166: rwp = NULL;
167: break;
168:
169: case 'D': /* macro definition */
170: define(buf[1], newstr(munchstring(&buf[2])), CurEnv);
171: break;
172:
173: case 'H': /* required header line */
174: (void) chompheader(&buf[1], TRUE);
175: break;
176:
177: case 'C': /* word class */
178: case 'F': /* word class from file */
179: /* read list of words from argument or file */
180: if (buf[0] == 'F')
181: {
182: /* read from file */
183: for (p = &buf[2]; *p != '\0' && !isspace(*p); p++)
184: continue;
185: if (*p == '\0')
186: p = "%s";
187: else
188: {
189: *p = '\0';
190: while (isspace(*++p))
191: continue;
192: }
193: fileclass(buf[1], &buf[2], p);
194: break;
195: }
196:
197: /* scan the list of words and set class for all */
198: for (p = &buf[2]; *p != '\0'; )
199: {
200: register char *wd;
201: char delim;
202:
203: while (*p != '\0' && isspace(*p))
204: p++;
205: wd = p;
206: while (*p != '\0' && !isspace(*p))
207: p++;
208: delim = *p;
209: *p = '\0';
210: if (wd[0] != '\0')
211: setclass(buf[1], wd);
212: *p = delim;
213: }
214: break;
215:
216: case 'M': /* define mailer */
217: makemailer(&buf[1]);
218: break;
219:
220: case 'O': /* set option */
221: setoption(buf[1], &buf[2], TRUE, FALSE);
222: break;
223:
224: case 'P': /* set precedence */
225: if (NumPriorities >= MAXPRIORITIES)
226: {
227: toomany('P', MAXPRIORITIES);
228: break;
229: }
230: for (p = &buf[1]; *p != '\0' && *p != '=' && *p != '\t'; p++)
231: continue;
232: if (*p == '\0')
233: goto badline;
234: *p = '\0';
235: Priorities[NumPriorities].pri_name = newstr(&buf[1]);
236: Priorities[NumPriorities].pri_val = atoi(++p);
237: NumPriorities++;
238: break;
239:
240: case 'T': /* trusted user(s) */
241: p = &buf[1];
242: while (*p != '\0')
243: {
244: while (isspace(*p))
245: p++;
246: q = p;
247: while (*p != '\0' && !isspace(*p))
248: p++;
249: if (*p != '\0')
250: *p++ = '\0';
251: if (*q == '\0')
252: continue;
253: for (pv = TrustedUsers; *pv != NULL; pv++)
254: continue;
255: if (pv >= &TrustedUsers[MAXTRUST])
256: {
257: toomany('T', MAXTRUST);
258: break;
259: }
260: *pv = newstr(q);
261: }
262: break;
263:
264: default:
265: badline:
266: syserr("unknown control line \"%s\"", buf);
267: }
268: }
269: FileName = NULL;
270: }
271: /*
272: ** TOOMANY -- signal too many of some option
273: **
274: ** Parameters:
275: ** id -- the id of the error line
276: ** maxcnt -- the maximum possible values
277: **
278: ** Returns:
279: ** none.
280: **
281: ** Side Effects:
282: ** gives a syserr.
283: */
284:
285: toomany(id, maxcnt)
286: char id;
287: int maxcnt;
288: {
289: syserr("too many %c lines, %d max", id, maxcnt);
290: }
291: /*
292: ** FILECLASS -- read members of a class from a file
293: **
294: ** Parameters:
295: ** class -- class to define.
296: ** filename -- name of file to read.
297: ** fmt -- scanf string to use for match.
298: **
299: ** Returns:
300: ** none
301: **
302: ** Side Effects:
303: **
304: ** puts all lines in filename that match a scanf into
305: ** the named class.
306: */
307:
308: fileclass(class, filename, fmt)
309: int class;
310: char *filename;
311: char *fmt;
312: {
313: FILE *f;
314: char buf[MAXLINE];
315:
316: f = fopen(filename, "r");
317: if (f == NULL)
318: {
319: syserr("cannot open %s", filename);
320: return;
321: }
322:
323: while (fgets(buf, sizeof buf, f) != NULL)
324: {
325: register STAB *s;
326: register char *p;
327: # ifdef SCANF
328: char wordbuf[MAXNAME+1];
329:
330: if (sscanf(buf, fmt, wordbuf) != 1)
331: continue;
332: p = wordbuf;
333: # else SCANF
334: p = buf;
335: # endif SCANF
336:
337: /*
338: ** Break up the match into words.
339: */
340:
341: while (*p != '\0')
342: {
343: register char *q;
344:
345: /* strip leading spaces */
346: while (isspace(*p))
347: p++;
348: if (*p == '\0')
349: break;
350:
351: /* find the end of the word */
352: q = p;
353: while (*p != '\0' && !isspace(*p))
354: p++;
355: if (*p != '\0')
356: *p++ = '\0';
357:
358: /* enter the word in the symbol table */
359: s = stab(q, ST_CLASS, ST_ENTER);
360: setbitn(class, s->s_class);
361: }
362: }
363:
364: (void) fclose(f);
365: }
366: /*
367: ** MAKEMAILER -- define a new mailer.
368: **
369: ** Parameters:
370: ** line -- description of mailer. This is in labeled
371: ** fields. The fields are:
372: ** P -- the path to the mailer
373: ** F -- the flags associated with the mailer
374: ** A -- the argv for this mailer
375: ** S -- the sender rewriting set
376: ** R -- the recipient rewriting set
377: ** E -- the eol string
378: ** The first word is the canonical name of the mailer.
379: **
380: ** Returns:
381: ** none.
382: **
383: ** Side Effects:
384: ** enters the mailer into the mailer table.
385: */
386:
387: makemailer(line)
388: char *line;
389: {
390: register char *p;
391: register struct mailer *m;
392: register STAB *s;
393: int i;
394: char fcode;
395: extern int NextMailer;
396: extern char **makeargv();
397: extern char *munchstring();
398: extern char *DelimChar;
399: extern long atol();
400:
401: /* allocate a mailer and set up defaults */
402: m = (struct mailer *) xalloc(sizeof *m);
403: bzero((char *) m, sizeof *m);
404: m->m_mno = NextMailer;
405: m->m_eol = "\n";
406:
407: /* collect the mailer name */
408: for (p = line; *p != '\0' && *p != ',' && !isspace(*p); p++)
409: continue;
410: if (*p != '\0')
411: *p++ = '\0';
412: m->m_name = newstr(line);
413:
414: /* now scan through and assign info from the fields */
415: while (*p != '\0')
416: {
417: while (*p != '\0' && (*p == ',' || isspace(*p)))
418: p++;
419:
420: /* p now points to field code */
421: fcode = *p;
422: while (*p != '\0' && *p != '=' && *p != ',')
423: p++;
424: if (*p++ != '=')
425: {
426: syserr("`=' expected");
427: return;
428: }
429: while (isspace(*p))
430: p++;
431:
432: /* p now points to the field body */
433: p = munchstring(p);
434:
435: /* install the field into the mailer struct */
436: switch (fcode)
437: {
438: case 'P': /* pathname */
439: m->m_mailer = newstr(p);
440: break;
441:
442: case 'F': /* flags */
443: for (; *p != '\0'; p++)
444: setbitn(*p, m->m_flags);
445: break;
446:
447: case 'S': /* sender rewriting ruleset */
448: case 'R': /* recipient rewriting ruleset */
449: i = atoi(p);
450: if (i < 0 || i >= MAXRWSETS)
451: {
452: syserr("invalid rewrite set, %d max", MAXRWSETS);
453: return;
454: }
455: if (fcode == 'S')
456: m->m_s_rwset = i;
457: else
458: m->m_r_rwset = i;
459: break;
460:
461: case 'E': /* end of line string */
462: m->m_eol = newstr(p);
463: break;
464:
465: case 'A': /* argument vector */
466: m->m_argv = makeargv(p);
467: break;
468:
469: case 'M': /* maximum message size */
470: m->m_maxsize = atol(p);
471: break;
472: }
473:
474: p = DelimChar;
475: }
476:
477: /* now store the mailer away */
478: if (NextMailer >= MAXMAILERS)
479: {
480: syserr("too many mailers defined (%d max)", MAXMAILERS);
481: return;
482: }
483: Mailer[NextMailer++] = m;
484: s = stab(m->m_name, ST_MAILER, ST_ENTER);
485: s->s_mailer = m;
486: }
487: /*
488: ** MUNCHSTRING -- translate a string into internal form.
489: **
490: ** Parameters:
491: ** p -- the string to munch.
492: **
493: ** Returns:
494: ** the munched string.
495: **
496: ** Side Effects:
497: ** Sets "DelimChar" to point to the string that caused us
498: ** to stop.
499: */
500:
501: char *
502: munchstring(p)
503: register char *p;
504: {
505: register char *q;
506: bool backslash = FALSE;
507: bool quotemode = FALSE;
508: static char buf[MAXLINE];
509: extern char *DelimChar;
510:
511: for (q = buf; *p != '\0'; p++)
512: {
513: if (backslash)
514: {
515: /* everything is roughly literal */
516: backslash = FALSE;
517: switch (*p)
518: {
519: case 'r': /* carriage return */
520: *q++ = '\r';
521: continue;
522:
523: case 'n': /* newline */
524: *q++ = '\n';
525: continue;
526:
527: case 'f': /* form feed */
528: *q++ = '\f';
529: continue;
530:
531: case 'b': /* backspace */
532: *q++ = '\b';
533: continue;
534: }
535: *q++ = *p;
536: }
537: else
538: {
539: if (*p == '\\')
540: backslash = TRUE;
541: else if (*p == '"')
542: quotemode = !quotemode;
543: else if (quotemode || *p != ',')
544: *q++ = *p;
545: else
546: break;
547: }
548: }
549:
550: DelimChar = p;
551: *q++ = '\0';
552: return (buf);
553: }
554: /*
555: ** MAKEARGV -- break up a string into words
556: **
557: ** Parameters:
558: ** p -- the string to break up.
559: **
560: ** Returns:
561: ** a char **argv (dynamically allocated)
562: **
563: ** Side Effects:
564: ** munges p.
565: */
566:
567: char **
568: makeargv(p)
569: register char *p;
570: {
571: char *q;
572: int i;
573: char **avp;
574: char *argv[MAXPV + 1];
575:
576: /* take apart the words */
577: i = 0;
578: while (*p != '\0' && i < MAXPV)
579: {
580: q = p;
581: while (*p != '\0' && !isspace(*p))
582: p++;
583: while (isspace(*p))
584: *p++ = '\0';
585: argv[i++] = newstr(q);
586: }
587: argv[i++] = NULL;
588:
589: /* now make a copy of the argv */
590: avp = (char **) xalloc(sizeof *avp * i);
591: bcopy((char *) argv, (char *) avp, sizeof *avp * i);
592:
593: return (avp);
594: }
595: /*
596: ** PRINTRULES -- print rewrite rules (for debugging)
597: **
598: ** Parameters:
599: ** none.
600: **
601: ** Returns:
602: ** none.
603: **
604: ** Side Effects:
605: ** prints rewrite rules.
606: */
607:
608: printrules()
609: {
610: register struct rewrite *rwp;
611: register int ruleset;
612:
613: for (ruleset = 0; ruleset < 10; ruleset++)
614: {
615: if (RewriteRules[ruleset] == NULL)
616: continue;
617: printf("\n----Rule Set %d:", ruleset);
618:
619: for (rwp = RewriteRules[ruleset]; rwp != NULL; rwp = rwp->r_next)
620: {
621: printf("\nLHS:");
622: printav(rwp->r_lhs);
623: printf("RHS:");
624: printav(rwp->r_rhs);
625: }
626: }
627: }
628:
629: /*
630: ** SETOPTION -- set global processing option
631: **
632: ** Parameters:
633: ** opt -- option name.
634: ** val -- option value (as a text string).
635: ** safe -- set if this came from a configuration file.
636: ** Some options (if set from the command line) will
637: ** reset the user id to avoid security problems.
638: ** sticky -- if set, don't let other setoptions override
639: ** this value.
640: **
641: ** Returns:
642: ** none.
643: **
644: ** Side Effects:
645: ** Sets options as implied by the arguments.
646: */
647:
648: static BITMAP StickyOpt; /* set if option is stuck */
649: extern char *NetName; /* name of home (local) network */
650:
651: setoption(opt, val, safe, sticky)
652: char opt;
653: char *val;
654: bool safe;
655: bool sticky;
656: {
657: extern bool atobool();
658: extern time_t convtime();
659: extern int QueueLA;
660: extern int RefuseLA;
661: extern bool trusteduser();
662: extern char *username();
663:
664: if (tTd(37, 1))
665: printf("setoption %c=%s", opt, val);
666:
667: /*
668: ** See if this option is preset for us.
669: */
670:
671: if (bitnset(opt, StickyOpt))
672: {
673: if (tTd(37, 1))
674: printf(" (ignored)\n");
675: return;
676: }
677:
678: /*
679: ** Check to see if this option can be specified by this user.
680: */
681:
682: if (!safe && getuid() == 0)
683: safe = TRUE;
684: if (!safe && index("deiLmorsv", opt) == NULL)
685: {
686: if (opt != 'M' || (val[0] != 'r' && val[0] != 's'))
687: {
688: if (tTd(37, 1))
689: printf(" (unsafe)");
690: if (getuid() != geteuid())
691: {
692: printf("(Resetting uid)\n");
693: (void) setgid(getgid());
694: (void) setuid(getuid());
695: }
696: }
697: }
698: else if (tTd(37, 1))
699: printf("\n");
700:
701: switch (opt)
702: {
703: case 'A': /* set default alias file */
704: if (val[0] == '\0')
705: AliasFile = "aliases";
706: else
707: AliasFile = newstr(val);
708: break;
709:
710: case 'a': /* look N minutes for "@:@" in alias file */
711: if (val[0] == '\0')
712: SafeAlias = 5;
713: else
714: SafeAlias = atoi(val);
715: break;
716:
717: case 'B': /* substitution for blank character */
718: SpaceSub = val[0];
719: if (SpaceSub == '\0')
720: SpaceSub = ' ';
721: break;
722:
723: case 'c': /* don't connect to "expensive" mailers */
724: NoConnect = atobool(val);
725: break;
726:
727: case 'C': /* checkpoint after N connections */
728: CheckPointLimit = atoi(val);
729: break;
730:
731: case 'd': /* delivery mode */
732: switch (*val)
733: {
734: case '\0':
735: SendMode = SM_DELIVER;
736: break;
737:
738: case SM_QUEUE: /* queue only */
739: #ifndef QUEUE
740: syserr("need QUEUE to set -odqueue");
741: #endif QUEUE
742: /* fall through..... */
743:
744: case SM_DELIVER: /* do everything */
745: case SM_FORK: /* fork after verification */
746: SendMode = *val;
747: break;
748:
749: default:
750: syserr("Unknown delivery mode %c", *val);
751: exit(EX_USAGE);
752: }
753: break;
754:
755: case 'D': /* rebuild alias database as needed */
756: AutoRebuild = atobool(val);
757: break;
758:
759: case 'e': /* set error processing mode */
760: switch (*val)
761: {
762: case EM_QUIET: /* be silent about it */
763: case EM_MAIL: /* mail back */
764: case EM_BERKNET: /* do berknet error processing */
765: case EM_WRITE: /* write back (or mail) */
766: HoldErrs = TRUE;
767: /* fall through... */
768:
769: case EM_PRINT: /* print errors normally (default) */
770: ErrorMode = *val;
771: break;
772: }
773: break;
774:
775: case 'F': /* file mode */
776: FileMode = atooct(val) & 0777;
777: break;
778:
779: case 'f': /* save Unix-style From lines on front */
780: SaveFrom = atobool(val);
781: break;
782:
783: case 'g': /* default gid */
784: DefGid = atoi(val);
785: break;
786:
787: case 'H': /* help file */
788: if (val[0] == '\0')
789: HelpFile = "sendmail.hf";
790: else
791: HelpFile = newstr(val);
792: break;
793:
794: case 'I': /* use internet domain name server */
795: UseNameServer = atobool(val);
796: break;
797:
798: case 'i': /* ignore dot lines in message */
799: IgnrDot = atobool(val);
800: break;
801:
802: case 'L': /* log level */
803: LogLevel = atoi(val);
804: break;
805:
806: case 'M': /* define macro */
807: define(val[0], newstr(&val[1]), CurEnv);
808: sticky = FALSE;
809: break;
810:
811: case 'm': /* send to me too */
812: MeToo = atobool(val);
813: break;
814:
815: case 'n': /* validate RHS in newaliases */
816: CheckAliases = atobool(val);
817: break;
818:
819: # ifdef DAEMON
820: case 'N': /* home (local?) network name */
821: NetName = newstr(val);
822: break;
823: # endif DAEMON
824:
825: case 'o': /* assume old style headers */
826: if (atobool(val))
827: CurEnv->e_flags |= EF_OLDSTYLE;
828: else
829: CurEnv->e_flags &= ~EF_OLDSTYLE;
830: break;
831:
832: case 'P': /* postmaster copy address for returned mail */
833: PostMasterCopy = newstr(val);
834: break;
835:
836: case 'q': /* slope of queue only function */
837: QueueFactor = atoi(val);
838: break;
839:
840: case 'Q': /* queue directory */
841: if (val[0] == '\0')
842: QueueDir = "mqueue";
843: else
844: QueueDir = newstr(val);
845: break;
846:
847: case 'r': /* read timeout */
848: ReadTimeout = convtime(val);
849: break;
850:
851: case 'S': /* status file */
852: if (val[0] == '\0')
853: StatFile = "sendmail.st";
854: else
855: StatFile = newstr(val);
856: break;
857:
858: case 's': /* be super safe, even if expensive */
859: SuperSafe = atobool(val);
860: break;
861:
862: case 'T': /* queue timeout */
863: TimeOut = convtime(val);
864: /*FALLTHROUGH*/
865:
866: case 't': /* time zone name */
867: break;
868:
869: case 'u': /* set default uid */
870: DefUid = atoi(val);
871: setdefuser();
872: break;
873:
874: case 'v': /* run in verbose mode */
875: Verbose = atobool(val);
876: break;
877:
878: case 'x': /* load avg at which to auto-queue msgs */
879: QueueLA = atoi(val);
880: break;
881:
882: case 'X': /* load avg at which to auto-reject connections */
883: RefuseLA = atoi(val);
884: break;
885:
886: case 'y': /* work recipient factor */
887: WkRecipFact = atoi(val);
888: break;
889:
890: case 'Y': /* fork jobs during queue runs */
891: ForkQueueRuns = atobool(val);
892: break;
893:
894: case 'z': /* work message class factor */
895: WkClassFact = atoi(val);
896: break;
897:
898: case 'Z': /* work time factor */
899: WkTimeFact = atoi(val);
900: break;
901:
902: default:
903: break;
904: }
905: if (sticky)
906: setbitn(opt, StickyOpt);
907: return;
908: }
909: /*
910: ** SETCLASS -- set a word into a class
911: **
912: ** Parameters:
913: ** class -- the class to put the word in.
914: ** word -- the word to enter
915: **
916: ** Returns:
917: ** none.
918: **
919: ** Side Effects:
920: ** puts the word into the symbol table.
921: */
922:
923: setclass(class, word)
924: int class;
925: char *word;
926: {
927: register STAB *s;
928:
929: s = stab(word, ST_CLASS, ST_ENTER);
930: setbitn(class, s->s_class);
931: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.