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