rsalz@bbn.com (Rich Salz) (02/10/88)
Submitted-by: "Arnold D. Robbins" <arnold@EMORYU1.ARPA> Posting-number: Volume 13, Issue 30 Archive-name: cfc [ CFC is a program that turns sendmail.cf files into almost directly- readable EASE language files. I wrote the Makefile and just slipped it into the shar. --r$ ] Rich, I think the only BSD-ism is the use of index. Arnold Robbins The (wounded and bleeding) slayer of Sendmail dragons #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # Makefile # cfc.1 # cfc.c export PATH; PATH=/bin:$PATH echo shar: extracting "'Makefile'" '(some number of characters)' if test -f 'Makefile' then echo shar: will not over-write existing file "'Makefile'" else cat << \SHAR_EOF > 'Makefile' all: cfc cfc.1 install: all @echo install according to local conventions. cfc: cfc.c $(CC) $(CFLAGS) -o cfc cfc.c SHAR_EOF fi # end of overwriting check echo shar: extracting "'cfc.1'" '(3162 characters)' if test -f 'cfc.1' then echo shar: will not over-write existing file "'cfc.1'" else cat << \SHAR_EOF > 'cfc.1' ... ... $Header: cfc.1,v 1.3 88/01/21 16:23:21 arnold Locked $ ... ... $Log: cfc.1,v $ ... Revision 1.3 88/01/21 16:23:21 arnold ... Some typo fixes. ... ... Revision 1.2 87/04/08 10:21:47 arnold ... Small bug fixes, compatibility option added, also warnings for ... unrecognized flags and options. ADR. ... ... Revision 1.1 87/02/16 15:25:32 arnold ... Initial revision ... ... .TH CFC 8 local .SH NAME cfc \- Sendmail cf file compiler .SH SYNOPSIS .B cfc [ .B \-c ] < .I sendmail.cf-file > .I ease-source-file .SH DESCRIPTION .I Cfc is a filter that reads a raw .IR sendmail (8) configuration file on its standard input, and produces almost useable .IR ease (1) source on its standard output. .P It is designed as a conversion tool, to translate an existing .B sendmail.cf file into .I ease with the idea that all future work will be done in .IR ease . .P .I Cfc passes all comments through to the output, and converts all predefined .I sendmail macros, options, option values, and mailer flags into the names used by .IR ease . .P Once it is through, the user need only spend some time in a good screen editor doing the following: .RS .P Changing the RULESET_n names into more descriptive names, and adding ruleset bindings. .P Applying quoting to necessary string literals, principally in the definitions of headers. The .I ease documentation on header formats should be consulted. .P Adding new field names. .I Cfc just uses very generic field names, everywhere, when names like ``user,'' ``host,'' and ``relay'' might be more descriptive. .P Miscellanious formatting. .I Cfc introduces tabs on its own, as well as often passing through tabs from the .I sendmail input. It will also print a header for each different type of line, e.g. if the input had seven .B O (option) lines, there will be seven option blocks. These are usually succesive, and can therefore be merged. Finally, the block close on rulesets often comes after the comments that precede the next ruleset or mailer specification. .RE .P In short, .I cfc does over 90% of the tedious work of translating a .B sendmail.cf into .I ease format. The rest of the work can be done in a day or less. Suprisingly, the combination of .I cfc and .I ease can find bugs in a current .B sendmail.cf file! .P .I Cfc takes one option, .BR \-c , which indicates it should run in 4.2BSD compatibility mode. In this case, options and mailer flags which are new in the 4.3BSD version of .I sendmail will not be recognized. .\" .SH FILES .SH SEE ALSO .I "Sendmail Installation and Operation Guide" by Eric Allman (SMM:7 in the 4.3 BSD UNIX System Manager's Manual), .I "Ease: A Configuration Language for Sendmail" by James S. Schoner, .IR sendmail (8), .IR ease (1). .SH DIAGNOSTICS ``\c .IR Routine : malformed input line .IR line : fatal error'' for input it doesn't understand. .I Routine is the name of the routine in .I cfc which choked, and .I line is the line number in the input. .SH BUGS Only recognizes continuation lines (lines that begin with a \s-1TAB\s+1) for header (H) and mailer (M) definitions. .SH AUTHOR .nf Arnold Robbins Emory University Computing Center arnold@emory.edu .fi SHAR_EOF fi # end of overwriting check echo shar: extracting "'cfc.c'" '(20180 characters)' if test -f 'cfc.c' then echo shar: will not over-write existing file "'cfc.c'" else cat << \SHAR_EOF > 'cfc.c' #ifndef lint static char RCSid[] = "$Header: cfc.c,v 1.5 88/01/21 16:18:13 root Locked $"; #endif /* * $Log: cfc.c,v $ * Revision 1.5 88/01/21 16:18:13 root * Eliminated Rutgers-ism, linted, smartened Mailer Argv handling. ADR. * * Revision 1.4 88/01/21 15:57:52 root * Added the 'y' factor; missed it last time. ADR. * * Revision 1.3 87/04/08 10:23:02 root * Small bug fixes, compatibility option added, also warnings for * unrecognized flags and options. ADR. * * Revision 1.2 87/02/18 15:26:39 root * Fix to recognize multidigit ruleset numbers in $> (calls) in RHS. ADR. * * Revision 1.1 87/02/16 15:25:00 arnold * Initial revision * * Revision 1.1 87/02/16 15:25:00 arnold * Initial revision * */ /* * cfc.c * * Sendmail cf file compiler. * Reads a raw sendmail.cf file and produces ease source. * * There are very few comments in this source. You will need both the * "Sendmail Installation and Operation Guide" and the paper on Ease * to really understand this. * * Arnold Robbins * Emory University Computing Center * 2/87 */ #include <stdio.h> #include <ctype.h> char buffer[BUFSIZ]; int line = 0; int inruleset = 0; extern char *macro (); /* convert sendmail to ease macro names */ extern char *mflags (); /* convert sendmail to ease mailer flag names */ extern char *optionname (); /* convert sendmail to ease option names */ extern char *delivoption (); /* delivery options */ extern char *handle_option (); /* handling options */ extern char *ngets (); /* buffered gets () routine */ extern void ungets (); /* put a buffer back for getting */ #define endruleset() if (inruleset) { inruleset = 0; printf ("\t}\n"); } int compat = 0; /* complain about new 4.3 options & flags */ main (argc, argv) int argc; char **argv; { if (argc > 1) { if (strcmp (argv[1], "-c") == 0) compat = 1; else { fprintf (stderr, "usage: %s [ -c ]\n", argv[0]); fprintf (stderr, "illegal argument '%s' ignored\n", argv[1]); } } printf ("/******************************************************/\n"); printf ("/* This ease file generated by cfc from a sendmail.cf */\n"); printf ("/* file. It must be edited by hand before being fed */\n"); printf ("/* to ease! */\n"); printf ("/******************************************************/\n"); printf ("\n\nbind\n\t/* RULESET BINDINGS GO HERE (cfc) */\n\n"); /* * For perfection, everything but the comment and rule cases * should do an endruleset (), but practically speaking, it is * usually only the mailer new ruleset definitions that end a * previous ruleset. Occasionally a macro, too. */ while (ngets (buffer) != NULL) { line++; switch (buffer[0]) { case '#': comment (); continue; /* skip code to end ruleset */ case 'S': endruleset (); ruleset (); continue; /* skip code to end ruleset */ case 'R': rule (); continue; /* skip code to end ruleset */ case 'D': endruleset (); def (); break; case 'C': class (); break; case 'F': fileclass (); break; case 'M': endruleset (); mailer (); break; case 'H': header (); break; case 'O': option (); break; case 'T': trusted (); break; case 'P': precedence (); break; default: other (); continue; /* skip code to end ruleset */ } endruleset (); } endruleset (); /* just in case */ exit (0); /*NOTREACHED*/ } /* comment --- produce a comment */ comment () { static char format[] = "/* %s */\n"; register int i = strlen (buffer) - 1; /* try to be semi-intelligent about comments */ if (buffer[1] == '\0') printf ("\n"); else if (isspace (buffer[1]) && buffer[i] != '#') { for (i = 1; isspace (buffer[i]); i++) ; printf (format, buffer + i); } else printf (format, buffer); } /* ruleset --- name a ruleset */ ruleset () { static int first = 1; register char *cp = buffer + 1; if (first) { first = 0; printf ("\n/* These are sample field definitons (cfc) */\n"); printf ("\nfield\n\tzero_or_more : match (0*);\n"); printf ("\tone_or_more : match (1*);\n"); printf ("\texactly_one : match (1);\n"); printf ("\tany_in_? : match (1) in ?;\n"); printf ("\tany_not_in_? : match (0) in ?;\n\n"); } printf ("ruleset\n\tRULESET_"); while (*cp && ! isspace (*cp)) { putchar (*cp); cp++; } printf (" {"); if (*cp) printf ("\t/* %s */", cp); putchar ('\n'); inruleset++; } /* rule --- print out a rule */ rule () { register char *cp = buffer + 1; register char *cp2; register int com = 0; /* first, split it up into LHS, RHS, COMMENT */ while (*cp != '\t') cp++; *cp = '\0'; cp++; while (*cp == '\t') cp++; cp2 = cp; while (*cp && *cp != '\t') cp++; if (*cp == '\t' && cp[1]) { *cp = '\0'; com++; cp++; while (*cp == '\t') cp++; } /* now print */ lhs (buffer + 1); /* left hand side */ if (com) printf ("\t/* %s */", cp); putchar ('\n'); rhs (cp2); /* right hand side */ } /* lhs --- left hand side of a production */ lhs (text) char *text; { register char *cp = text; register int conditional = 0; register int quoting = 0; printf ("\tif ("); for (; *cp; cp++) { switch (*cp) { case '$': if (quoting) { quoting = 0; putchar ('"'); } switch (*++cp) { case '*': printf (" zero_or_more "); break; case '+': printf (" one_or_more "); break; case '-': printf (" exactly_one "); break; case '=': printf (" any_in_%c ", *++cp); break; case '~': printf (" any_not_in_%c ", *++cp); break; case '?': printf (" ifset (%s, ", macro (*++cp)); conditional++; break; case '|': printf (", "); break; case '.': putchar (')'); conditional--; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': printf ("$%c", *cp); break; default: if (quoting) printf ("${%s}", macro (*cp)); else printf ("$%s", macro (*cp)); break; } break; default: if (ispunct (*cp)) { if (quoting) /* end a literal */ { quoting = 0; putchar ('"'); } /* else do nothing */ } else { if (! quoting) /* start a literal */ { quoting = 1; putchar ('"'); } /* else do nothing */ } putchar (*cp); /* print the character */ break; } } if (quoting) putchar ('"'); if (conditional) die ("lhs"); printf (")"); } /* rhs --- right hand side of a production */ rhs (text) char *text; { register char *cp = text; char *index (); register int open = 0; register int conditional = 0; register int quoting = 0; printf ("\t\t"); if (*cp == '$' && index ("#@:", cp[1]) != NULL) ; /* not the default */ else { printf ("retry ("); open++; } for (; *cp; cp++) { switch (*cp) { case '$': if (quoting) { quoting = 0; putchar ('"'); } switch (*++cp) { case '>': printf ("RULESET_"); for (cp++; *cp && isdigit (*cp); cp++) putchar (*cp); cp--; printf (" ("); open++; break; case '[': printf ("canon ("); open++; break; case ']': putchar (')'); open--; break; case '?': printf ("ifset (%s, ", macro (*++cp)); conditional++; break; case '|': putchar (','); break; case '.': putchar (')'); conditional--; break; case '#': printf ("resolve (mailer ("); if (strncmp (cp+1, "local$", 6) == 0 || strncmp (cp+1, "error$", 6) == 0) goto skiphost; loop1: for (cp++; *cp != '$'; cp++) putchar (*cp); cp++; if (*cp != '@') { printf ("$%c", *cp); goto loop1; } printf ("),\n\t\t\t\thost ("); skiphost: loop2: for (cp++; *cp != '$'; cp++) putchar (*cp); cp++; if (*cp != ':') { printf ("$%c", *cp); goto loop2; } printf ("),\n\t\t\t\tuser ("); for (cp++; *cp; cp++) putchar (*cp); printf ("))"); goto out; /* string is exhausted */ /* break; */ case '@': printf ("return ("); open++; break; case ':': printf ("next ("); open++; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': printf ("$%c", *cp); break; default: if (quoting) printf ("${%s}", macro (*cp)); else printf ("$%s", macro (*cp)); break; } break; default: if (ispunct (*cp)) { if (quoting) /* end a literal */ { quoting = 0; putchar ('"'); } /* else do nothing */ } else { if (! quoting) /* start a literal */ { quoting = 1; putchar ('"'); } /* else do nothing */ } putchar (*cp); /* print the character */ break; } } out: if (quoting) putchar ('"'); while (open--) putchar (')'); printf (";\n"); if (conditional) die ("rhs"); } /* def --- define a macro */ def () { register char *mac = buffer + 1, *value = buffer + 2; register int conditional = 0; printf ("macro\n\t%s = \"", macro (*mac)); while (*value) { switch (*value) { case '$': switch (*++value) { case '?': printf ("ifset (%s, ", macro (*++value)); conditional++; break; case '|': putchar (','); break; case '.': putchar (')'); conditional--; break; default: printf ("${%s}", macro (*value)); break; } break; default: putchar (*value); break; } value++; } printf ("\";\n"); if (conditional) die ("def"); } /* class --- define a class list */ class () { register char *name = buffer + 1, *value = buffer + 2; printf ("class\n\t%c = { ", *name); while (*value && isspace (*value)) value++; while (*value) { if (isspace (*value)) { printf (", "); while (isspace (*value)) value++; value--; /* cancel loop */ } else putchar (*value); value++; } printf (" };\n"); } /* fileclass --- define a class that is to be read from a file */ fileclass () { register char *name = buffer + 1, *value = buffer + 2; printf ("class\n\t%c = readclass (\"", *name); for (; *value && !isspace (*value); value++) putchar (*value); putchar ('"'); while (*value && isspace (*value)) value++; if (*value) printf (", \"%s\"", value); printf (");\n"); } /* mailer --- convert a mailer specification */ mailer () { register char *cp = buffer + 1; printf ("mailer\n\t"); for (; *cp != ','; cp++) putchar (*cp); cp++; printf (" {\n"); /* just did mailer name */ #define skipname() cp++; while (*cp != '=') cp++; cp++ #define value() for (; *cp && *cp != ','; cp++) putchar (*cp); cp++ loop: while (*cp && isspace (*cp)) cp++; printf ("\t\t"); switch (*cp) { case 'A': skipname (); printf ("Argv = \""); for (; *cp && *cp != ','; cp++) { if (*cp == '$') /* XXX: assume no conditionals */ printf ("${%s}", macro (*++cp)); else if (*cp == '"') printf ("\\\""); else putchar (*cp); } cp++; /* do manually what value does */ putchar ('"'); break; case 'E': skipname (); printf ("Eol = \""); value (); putchar ('"'); break; case 'F': skipname (); printf ("Flags = { "); for (; *cp && *cp != ','; cp++) { printf ("%s", mflags (*cp)); if (cp[1] && cp[1] != ',') printf (", "); } cp++; /* do manually what value does */ printf (" }"); break; case 'M': skipname (); printf ("Maxsize = \""); value (); putchar ('"'); break; case 'P': skipname (); printf ("Path = \""); value (); putchar ('"'); break; case 'R': skipname (); printf ("Recipient = RULESET_"); value (); break; case 'S': skipname (); printf ("Sender = RULESET_"); value (); break; case '\0': goto done; } if (cp[-1] && cp[-1] == ',') { printf (",\n"); goto loop; } else putchar ('\n'); done: /* handle continuation lines */ if (ngets (buffer) != NULL) { line++; if (buffer[0] == '\t') { cp = buffer; goto loop; } else ungets (buffer); } else ungets ((char *) NULL); printf ("\t};\n"); #undef value #undef skipname } /* header --- define sendmail headers */ header () { register char *cp = buffer + 1; register int flags = 0; register int conditional = 0; printf ("header\n\t"); if (*cp == '?') /* header for mailers with these flags */ { flags++; printf ("for ("); for (cp++; *cp != '?'; cp++) { printf ("%s", mflags (*cp)); if (cp[1] != '?') putchar (','); } printf (") {\n\t\t"); cp++; /* skip final '?' */ } printf ("define (\""); for (; *cp && ! isspace (*cp); cp++) putchar (*cp); printf ("\", \""); body: while (*cp) { switch (*cp) { case '$': switch (*++cp) { case '?': printf ("ifset (%s, ", macro (*++cp)); conditional++; break; case '|': putchar (','); break; case '.': putchar (')'); conditional--; break; default: printf ("${%s}", macro (*cp)); break; } break; default: putchar (*cp); break; } cp++; } /* handle continuation lines */ if (ngets (buffer) != NULL) { line++; if (buffer[0] == '\t') { printf ("\\\n"); cp = buffer + 1; goto body; } else ungets (buffer); } else ungets ((char *) NULL); printf ("\");\n"); if (flags) printf ("\t};\n"); if (conditional) die ("header"); } /* option --- translate a sendmail option to an ease option */ option () { register char *name = buffer + 1, *value = buffer + 2; printf ("options\n\t"); if (*name == 'd') /* delivery */ printf ("o_delivery = %s;\n", delivoption (*value)); else if (*name == 'e') /* handling */ printf ("o_handling = %s;\n", handle_option (*value)); else printf ("%s = \"%s\";\n", optionname (*name), value); } /* trusted --- define the list of trusted users */ trusted () { register char *cp = buffer + 1; while (*cp) { if (isspace (*cp)) *cp = ','; cp++; } printf ("trusted\n\t{ %s };\n", buffer+1); } /* precedence --- define the precedence of a message class */ precedence () { register char *cp = buffer + 1; printf ("precedence\n\t"); for (; *cp && *cp != '='; cp++) putchar (*cp); printf (" = %s;\n", ++cp); } /* other --- not a sendmail control line */ other () { printf ("%s\n", buffer); } die (routine) char *routine; { fprintf (stderr, "%s: malformed input line %d: fatal error\n", routine, line); exit (1); } /* macro --- return name for sendmail predefined macro */ char *macro (c) char c; { static char buf[2] = { '\0', '\0' }; switch (c) { case 'a': /* The origination date in Arpanet format */ return ("m_odate"); case 'b': /* The current date in Arpanet format */ return ("m_adate"); case 'c': /* The hop count */ return ("m_hops"); case 'd': /* The date in UNIX (ctime) format */ return ("m_udate"); case 'e': /* The SMTP entry message */ return ("m_smtp"); case 'f': /* The sender (from) address */ return ("m_saddr"); case 'g': /* The sender address relative to the recipient */ return ("m_sreladdr"); case 'h': /* The recipient host */ return ("m_rhost"); case 'i': /* The queue id */ return ("m_qid"); case 'j': /* The official domain name for this site */ return ("m_oname"); case 'l': /* The format of the UNIX from line */ return ("m_ufrom"); case 'n': /* The name of the daemon (for error messages) */ return ("m_daemon"); case 'o': /* The set of "operators" in addresses */ return ("m_addrops"); case 'p': /* Sendmail's pid */ return ("m_pid"); case 'q': /* The default format of sender address */ return ("m_defaddr"); case 'r': /* Protocol used */ return ("m_protocol"); case 's': /* Sender's host name */ return ("m_shostname"); case 't': /* A numeric representation of the current time */ return ("m_ctime"); case 'u': /* The recipient user */ return ("m_ruser"); case 'v': /* The version number of sendmail */ return ("m_version"); case 'w': /* The hostname of this site */ return ("m_sitename"); case 'x': /* The full name of the sender */ return ("m_sname"); case 'y': /* The id of the sender's tty */ return ("m_stty"); case 'z': /* The home directory of the recipient */ return ("m_rhdir"); default: buf[0] = c; return (buf); } } #define docompat(val) if (compat) goto warn; else return (val) /* mflags --- convert sendmail mailer flags to ease names */ char *mflags (c) char c; { static char buf[2] = { '\0', '\0' }; switch (c) { case 'f': return ("f_ffrom"); case 'r': return ("f_rfrom"); case 'S': return ("f_noreset"); case 'n': return ("f_noufrom"); case 'l': return ("f_locm"); case 's': return ("f_strip"); case 'm': return ("f_mult"); case 'F': return ("f_from"); case 'D': return ("f_date"); case 'M': return ("f_mesg"); case 'x': return ("f_full"); case 'P': return ("f_return"); case 'u': return ("f_upperu"); case 'h': return ("f_upperh"); case 'A': return ("f_arpa"); case 'U': return ("f_ufrom"); case 'e': return ("f_expensive"); case 'X': return ("f_dot"); case 'L': return ("f_llimit"); case 'p': return ("f_retsmtp"); case 'I': return ("f_smtp"); case 'C': return ("f_addrw"); case 'E': docompat ("f_escape"); default: warn: fprintf (stderr, "warning: non standard mailer flag '%c' on line %d\n", c, line); buf[0] = c; return buf; } } /* optionname --- convert sendmail options to ease names */ char *optionname (c) char c; { static char buf[2] = { '\0', '\0' }; switch (c) { case 'A': return ("o_alias"); case 'a': return ("o_ewait"); case 'B': return ("o_bsub"); case 'c': return ("o_qwait"); case 'd': return ("o_delivery"); case 'D': return ("o_rebuild"); case 'e': return ("o_handling"); case 'F': return ("o_tmode"); case 'f': return ("o_usave"); case 'g': return ("o_gid"); case 'H': return ("o_fsmtp"); case 'i': return ("o_skipd"); case 'L': return ("o_slog"); case 'm': return ("o_rsend"); case 'N': return ("o_dnet"); case 'o': return ("o_hformat"); case 'Q': return ("o_qdir"); case 'q': docompat ("o_qfactor"); case 'r': return ("o_tread"); case 'S': return ("o_flog"); case 's': return ("o_safe"); case 'T': return ("o_qtimeout"); case 't': return ("o_timezone"); case 'u': return ("o_dmuid"); case 'v': return ("o_verbose"); case 'W': return ("o_wizpass"); case 'x': return ("o_loadq"); case 'X': return ("o_loadnc"); case 'Y': docompat ("o_newproc"); case 'y': docompat ("o_recipfactor"); case 'Z': docompat ("o_prifactor"); case 'z': docompat ("o_waitfactor"); default: warn: fprintf (stderr, "warning: non standard option '%c' on line %d\n", c, line); buf[0] = c; return buf; } } /* delivoption --- convert sendmail delivery option value to ease name */ char *delivoption (c) char c; { static char buf[2] = { '\0', '\0' }; switch (c) { case 'i': return ("d_interactive"); case 'b': return ("d_background"); case 'q': return ("d_queue"); default: fprintf (stderr, "warning: non standard delivery option '%c' on line %d\n", c, line); buf[0] = c; return buf; } } /* handle_option --- convert sendmail handling option value to ease name */ char *handle_option (c) char c; { static char buf[2] = { '\0', '\0' }; switch (c) { case 'p': return ("h_print"); case 'q': return ("h_exit"); case 'm': return ("h_mail"); case 'w': return ("h_write"); case 'e': return ("h_mailz"); default: fprintf (stderr, "warning: non standard handling option '%c' on line %d\n", c, line); buf[0] = c; return buf; } } /* * "buffered" i/o routines. These are necessary since * mail headers may have continuation lines, and we can't see if * a continuation line is there without getting it. If it isn't, * just put it back. */ int saved = 0; char *saveb = NULL; /* ngets --- get a line of input from either saved buffer or stdin */ char *ngets (bp) char *bp; { if (! saved) return (gets (bp)); saved = 0; bp = saveb; saveb = NULL; return (bp); } /* ungets --- put a buffer back on the input, so to speak */ void ungets (bp) char *bp; { saved = 1; saveb = bp; line--; } SHAR_EOF fi # end of overwriting check # End of shell archive exit 0 -- For comp.sources.unix stuff, mail to sources@uunet.uu.net.