|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T p
Length: 47802 (0xbaba) Types: TextFile Names: »phquery.c«
└─⟦9ae75bfbd⟧ Bits:30007242 EUUGD3: Starter Kit └─⟦bfebc70e2⟧ »EurOpenD3/mail/sendmail-5.65b+IDA-1.4.3.tar.Z« └─⟦f9e35cd84⟧ └─⟦this⟧ »sendmail/uiuc/phquery.c«
/* * Copyright (c) 1989 Paul Pomes * Copyright (c) 1989 University of Illinois Board of Trustees * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of Illinois, Urbana. In addition, redistribution * and use must conform to the terms listed in the CopyLeft text below. * * The name of the University may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #ifndef lint static char rcsid[] = "@(#)$Header: /usr/local/src/mail/sendmail/uiuc/RCS/phquery.c,v 1.21 1991/03/05 19:08:39 paul Exp $"; #endif /* lint */ #include <stdio.h> #include <assert.h> #include <sys/types.h> #include <sys/param.h> #if defined(pyr) || defined(is68k) || defined(NeXT) || defined(__convex__) \ || defined(BSD4_4) || defined(ibm032) # include <sys/time.h> # include <sys/vnode.h> # define IREAD VREAD # define IWRITE VWRITE #else /* ! pyr && ! is68k */ # if defined(sun) || defined(convex) || defined(apollo) # include <sys/stat.h> # define IREAD S_IREAD # define IWRITE S_IWRITE # else /* ! sun && ! convex */ # include <sys/inode.h> # endif /* sun || convex */ #endif /* pyr || is68k */ #include <netdb.h> #include <ctype.h> #include <sys/socket.h> #include <sys/syslog.h> #include <sys/errno.h> #include <sys/wait.h> #include <netinet/in.h> #include <sysexits.h> #include <strings.h> #include "phquery.h" #define VERSION "3.7" /* Domain to append to ph aliases when creating Reply-To: fields */ #ifndef DOMAIN # define DOMAIN "uiuc.edu" #endif /* DOMAIN */ /* Designated server port */ #define QISERVICE "ns" /* Mail transport agent of choice */ #if defined(BSD4_4) #define SENDMAIL "/usr/sbin/sendmail" #else /* !BSD4_4 */ #define SENDMAIL "/usr/lib/sendmail" #endif /* BSD4_4 */ /* How to print/log error messages */ #define DANGER_WILL_ROBINSON(KateBush) \ { if (Debug) \ perror (KateBush); \ if (Log) \ syslog (LOG_ERR, strcat (KateBush, ": %m")); \ finis (); } /* ** PHQUERY -- Resolve fuzzy addresses to specific a user@FQDN ** ** FQDN := Fully Qualified Domain Name ** Phquery is invoked as a mailer (not a final mailer!) by sendmail ** to resolve addresses of the form user@DOMAINMASTER where DOMAINMASTER ** is a m4 define used in building an IDA sendmail.cf file. At UIUC ** this would be user@uiuc.edu . The user token is interpreted first ** as a QI alias, then as a full name if that fails. QI is the CSnet ** Query Interpreter. At UIUC it contains the entire campus phone ** directory plus the unit directory. A user entry has about as many ** fields as ls has option letters. The most important are alias, name, ** email, phone, department, and curriculum. In the simplest case, ** matching an alias (guaranteed unique) returns the email address. ** ** Since life is seldom simple, the alternate cases/actions are summarized ** ** a) alias match, email found ** write a X-PH-To: header with the email address found, copy the ** rest of the message, and re-invoke sendmail ** OR ** write a X-PH: VX.Y@<host> and re-invoke sendmail. This is ** useful for sites that don't wish to expand alias lists in the ** header block. ** b) alias match, no email field: ** return public fields of ph entry and suggest phone usage ** c) alias match, bogus email field: ** sendmail catches this one. The user will see the X-PH-To: ** header. Not the best so far..... ** d) alias fail: ** try name field ** e) single name match, email present: ** deliver as in a) ** f) single name match, no email field: ** handle as in b) ** g) single name match, bogus email field: ** handle as in c) ** h) multiple (<5) name matches: ** return alias, name, email, and dept fields of matches ** i) multiple (>5) name matches: ** return "too ambiguous" message ** ** Phquery is also used to create return addresses of the form ** ph-alias@DOMAINMASTER. This is implemented by adding the fields ** ** Resent-From: postmaster@<host> ** Reply-To: ph-alias@DOMAINMASTER ** Comment: Reply-To: added by phquery (Vx.y) ** ** N.B., RFC-822, Section 4.4.1 requires that the From / Resent-From ** fields be a single, authenticated machine address. */ /* some handy defines */ #define CHNULL ('\0') #define CPNULL ((char *) NULL) #define FILE_NULL ((FILE *) NULL) #define NADD_NULL ((struct NewAddress *) NULL) #define QIR_NULL ((struct QI_response *) NULL) /* some handy compare operators */ #define nequal(s1,s2,n) (strncasecmp (s1, s2, n) == 0) #define equal(s1,s2) (strcasecmp (s1, s2) == 0) /* large string size */ #define MAXSTR 250 /* Bit flags to control printing of informative messages in ErrorReturn() */ #define NO_MATCH_MSG 0x1 #define MULTI_MSG 0x2 #define ABSENT_MSG 0x4 #define TOO_MANY_MSG 0x8 #define PHONE_MSG 0x10 /* Messages for ErrorReturn(). How simple, yet stupid, do we have to be? */ char *NoMatchMsg[] = { " The message, \"No matches to nameserver query,\" is generated whenever", " the ph nameserver fails to locate either a ph alias or name field that", " matches the supplied name. The usual causes are typographical errors or", " the use of nicknames. Recommended action is to use the ph program to", " determine the correct ph alias for the individuals addressed. If ph is", " not available, try sending to the most explicit form of the name, e.g.,", " if mike-fox fails, try michael-j-fox.", " ", CPNULL }; char *MultiMsg[] = { " The message, \"Multiple matches found for nameserver query,\" is generated", " whenever the ph nameserver finds multiple matches for the supplied name.", " The steering philosophy is that mail should be delivered only to the", " addressed individual. Since the supplied information is insufficient", " to locate a specific individual, your message is being returned.", " To help you locate the correct individual, selected fields from the", " possible matches are included below. The alias field is the only one", " guaranteed unique within a given ph community.", " ", CPNULL }; char *TooManyMsg[] = { " The message, \"Too many matches found to nameserver query,\" is generated", " whenever the supplied name or alias matched over twenty ph nameserver", " entries. In this case no information will be returned about possible", " matches. Recommended action is to supply more specific names, e.g.,", " john-b-smith instead of john-smith, or use the per-person unique ph alias.", " You may have thought that you had used a ph alias and not a name. This is", " an artifact of the address resolution process. If the address fails as an", " alias, it is retried first as a callsign and then as a name. While aliases", " are guaranteed unique, names can match multiple individuals depending on", " how common the name is.", " ", CPNULL }; char *AbsentMsg[] = { " The message, \"E-mail field not present in nameserver entry,\" is generated", " whenever the ph nameserver matched the supplied name or alias with an", " entry that lacked an email address field. In this case no delivery can", " be made. Recommended action is to contact the individual by alternate", " means via the information included below. If the individual already has", " an email address, s/he should edit their ph entry to include it.", " ", CPNULL }; char *PhoneMsg[] = { " A note regarding phone numbers: the UIUC area code is 217. There are three", " exchanges used by UIUC: 333, 332, and 244. UIUC phone numbers are often", " abbreviated by omitting the first two digits of the exchange. Thus the", " example phone number 3-6262 can be reached by dialing 1 217 333 6262.", " ", CPNULL }; FILE *ToQI = FILE_NULL; /* write to the QI */ FILE *FromQI = FILE_NULL; /* read from the QI */ extern int errno; /* Set to carbon-copy postmaster on error returns */ int PostmasterCC = 0; /* Set if the reply-to: field on outgoing mail is to inserted */ int ReplyTo = 0; /* Hostname of this machine */ char HostNameBuf[100]; /* How program was invoked (argv[0]) for error messages */ char *MyName; /* Exit status for finis() reporting to calling process */ int ExitStat = EX_TEMPFAIL; /* Temporary message file */ char TmpFile[] = "/tmp/PhMailXXXXXXX"; /* Temporary file for creating error messages */ char ErrorFile[] = "/tmp/PhErrMailXXXXXXX"; /* Temporary file for rewriting messages */ char NewFile[] = "/tmp/PhNewMailXXXXXXX"; /* * The types of nameserver queries to make. * N.B., Query() assumes that "name" is the last token in this list. * Also be sure to duplicate any extra keywords added to TryList to the * query fprintf near the top of Query(). */ char *TryList[] = { "alias", "callsign", "name", CPNULL }; /* * How to report events: Debug set for stderr messages, Log for syslog. * Setting Debug disables vfork/execve in ReMail. */ int Debug = 0; int Log = 1; /* From address supplied by caller */ char *From = CPNULL; char *usage[] = { "usage: %s [-d] [-p] [-s] [-l] [-R] [-i] [-x service] [-f FromAddress] address1 [address2]", CPNULL }; #ifdef __STDC__ # include <unistd.h> # include <stdlib.h> void ErrorReturn(NADD *, FILE *, char *[]); void FindFrom(FILE *); void ReMail(NADD *, FILE *, char *[]); char * CodeString(int); FILE * OpenTemp(const char *); QIR * PickField (QIR *, int); void Query(NADD *); int SendQuery(NADD *, const char *, const char *); void RevQuery(NADD *); QIR * ReadQI(FILE *); int FieldValue(const char *); void GarbageCollect(QIR *); char * Malloc(unsigned int); void PrintMsg(FILE *, char *[]); char * Realloc(char *, unsigned int); void PrtUsage(int); void finis(); #else /* !__STDC__ */ # define const void ErrorReturn(); void FindFrom(); void ReMail(); char * CodeString(); FILE * OpenTemp(); QIR * PickField (); void Query(); int SendQuery(); void RevQuery(); QIR * ReadQI(); int FieldValue(); void GarbageCollect(); char * Malloc(); void PrintMsg(); char * Realloc(); void PrtUsage(); void finis(); #endif /* !__STDC__ */ void ContactQI(); char *CopyLeft[] = { " Written by Paul Pomes, University of Illinois, Computing Services Office", " Copyright (C) 1989 by Paul Pomes and the University of Illinois Board", " of Trustees", " ", " This program is distributed in the hope that it will be useful, but without", " any warranty. No author or distributor accepts responsibility to anyone", " for the consequences of using it, no matter how awful, or for whether it", " serves any particular purpose or works at all, unless s/he says so in", " writing.", " ", " Everyone is granted permission to copy, modify and redistribute this", " program under the following conditions:", " ", " Permission is granted to anyone to make or distribute copies of program", " source code, either as received or modified, in any medium, provided", " that all copyright notices, permission and nonwarranty notices are", " preserved, and that the distributor grants the recipient permission for", " further redistribution as permitted by this document, and gives him and", " points out to him an exact copy of this document to inform him of his", " rights.", " ", " Permission is granted to distribute this program in compiled or", " executable form under the same conditions applying for source code,", " provided that either", " ", " A. it is accompanied by the corresponding machine-readable source code,", " or", " B. it is accompanied by a written offer, with no time limit, to give", " anyone a machine-readable copy of the corresponding source code in", " return for reimbursement of the cost of distribution. This written", " offer must permit verbatim duplication by anyone.", " C. it is distributed by someone who received only the executable form,", " and is accompanied by a copy of the written offer of source code", " which he received along with it.", " ", " In other words, you are welcome to use, share and improve this program.", " You are forbidden to forbid anyone else to use, share and improve what", " you give them. Help stamp out software-hoarding!", " ", "UUCP: {att,iuvax,uunet}!uiucuxc!paul ICBM: 40 06 47 N / 88 13 35 W", "Internet, BITNET: paul@uxc.cso.uiuc.edu Phone: 217 333 6262", "US Mail: UofIllinois, CSO, 1304 W Springfield Ave, Urbana, IL 61801-2910", CPNULL }; main(argc, argv, envp) int argc; char *argv[], *envp[]; { extern int optind; /* from getopt () */ extern char *optarg; /* from getopt () */ int option; /* option "letter" */ int i; /* good ol' i */ char *Service = CPNULL; /* ph alias from -x */ FILE *Msg; /* stream pointer for temp file */ NADD *New, *NewP; /* translated addresses */ char Buf[MAXSTR]; extern char HostNameBuf[]; MyName = ((MyName = rindex (*argv, '/')) == CPNULL) ? *argv : (MyName + 1); while ((option = getopt (argc, argv, "f:r:x:pRsdli")) != EOF) { switch (option) { case 'f': From = optarg; break; case 'x': Service = optarg; break; case 'R': /* Re-write outgoing address with Reply-To: field */ ReplyTo++; break; case 's': /* Designated humor section for humor-less CSO types */ if (Debug) { fprintf (stderr, "Checking Figure 1 ......"); (void) fflush (stderr); sleep (2); fprintf (stderr, "done.\n"); } break; case 'r': From = optarg; break; case 'p': PostmasterCC++; break; case 'l': Log++; break; case 'd': Debug++; if (Debug == 1) PrtUsage (1); Log = 0; break; case 'i': PrtUsage (1); finis (); break; default: PrtUsage (0); finis (); break; } } argc -= optind; /* skip options */ argv += optind; /* Fire up logging, or not, as the flags may be */ if (Log) #ifdef LOG_MAIL # ifndef SYSLOG # define SYSLOG LOG_MAIL # endif openlog(MyName, LOG_PID, SYSLOG); #else openlog(MyName, LOG_PID); #endif if (Log) syslog (LOG_DEBUG, "From %s", From); /* fetch our host name, some use will be found for it.... */ if (gethostname (HostNameBuf, 100-1) != 0) DANGER_WILL_ROBINSON("gethostname") /* Open the temp file, copy the message into it */ if ((Msg = OpenTemp (TmpFile)) == FILE_NULL) finis (); while ((i = fread (Buf, sizeof (char), MAXSTR, stdin)) != 0) if (fwrite (Buf, sizeof (char), i, Msg) != i) DANGER_WILL_ROBINSON("Msg copy") (void) fflush (Msg); /* * Remaining arguments are addresses. If From == CHNULL, * then submission was done locally and return address has * to be on the From: line. */ if (From == CPNULL || (From != CPNULL && From == CHNULL)) FindFrom (Msg); if (ReplyTo) { /* * Check with QI to see if this person has a email entry. * If so add the Resent-From, Reply-To, and Comment fields. * Then invoke ReMail with xyzzy appended to the From address * so that sendmail won't send it back to us. If a * Reply-To: field is already present, handle as though no * email field was found. */ /* * Allocate NewAddress structs for from address, to addresses, * plus 1 for terminal null. */ New = (NADD *) Malloc ((unsigned) ((argc+2) * sizeof (NADD))); (New + argc + 1)->original = CPNULL; NewP = New; RevQuery (NewP); assert (NewP->new != CPNULL); /* If a single alias was found, append the domain */ if (abs (NewP->code) == LR_OK) { NewP->new = Realloc (NewP->new, (unsigned) (strlen (NewP->new) + strlen (DOMAIN) + 2)); (void) strcat (NewP->new, "@"); (void) strcat (NewP->new, DOMAIN); } /* Add To: addresses to NewP array */ NewP++; while (argc > 0) { NewP->original = *argv; NewP->new = CPNULL; NewP++; argv++; argc--; } /* ReMail will add the new headers and call sendmail */ ReMail (New, Msg, envp); /* We done good. */ ExitStat = EX_OK; finis (); } /* * If not a ReplyTo ... * Allocate NewAddress structs for addresses (or just one if this * is a service forward. */ i = (Service == CPNULL) ? argc : 1; New = (NADD *) Malloc ((unsigned) ((i+1) * sizeof (NADD))); (New + i)->original = CPNULL; NewP = New; if (Service != CPNULL) { NewP->original = Service; NewP->new = CPNULL; Query (NewP); assert (NewP->new != CPNULL); if (Debug) printf ("code %d, %s --> %s\n", NewP->code, NewP->original, NewP->new); if (Log) syslog (LOG_INFO, "%s --> %s", NewP->original, NewP->new); } else /* Loop on addresses in argv building up translation table */ while (argc > 0) { NewP->original = *argv; NewP->new = CPNULL; Query (NewP); assert (NewP->new != CPNULL); if (Debug) printf ("code %d, %s --> %s\n", NewP->code, NewP->original, NewP->new); if (Log) syslog (LOG_INFO, "%s --> %s", NewP->original, NewP->new); NewP++; argv++; argc--; } /* * Now re-invoke sendmail with the translated addresses. * Make one pass for collecting error returns into one message. */ for (NewP = New; NewP->original != CPNULL; NewP++) if (abs (NewP->code) != LR_OK) { ErrorReturn (NewP, Msg, envp); break; } /* Any good addresses? */ for (NewP = New; NewP->original != CPNULL; NewP++) if (abs (NewP->code) == LR_OK) { ReMail (NewP, Msg, envp); break; } /* exit */ ExitStat = EX_OK; finis (); } \f /* ** ContactQI -- Connect to the QI server ** ** Examine the ToQI and FromQI file descriptors. If NULL, open ** socket connections to the QI server. Exits on any error. ** ** Parameters: ** none ** ** Returns: ** None ** ** Side Effects: ** Changes ToQI and FromQI if an open is done. */ void ContactQI () { int sock; /* our socket */ struct sockaddr_in QI; /* the address of the nameserver */ struct servent *Ns; /* nameserver service entry */ struct hostent *Host; /* host entry for nameserver */ char *QiHost = QI_HOST; /* Initial Qi server */ extern FILE *ToQI, *FromQI; /* read/write streams to QI */ /* Already opened... */ if (ToQI != FILE_NULL && FromQI != FILE_NULL) { if (Debug) printf("ToQI/FromQI already opened\n"); return; } if (Debug) printf("opening ToQI/FromQI\n"); /* Locate the proper port */ if (Ns = getservbyname (QISERVICE, "tcp")) { QI.sin_port = Ns->s_port; } else { if (Debug) fprintf (stderr, "server \"%s\" unknown - using 105", QISERVICE); if (Log) syslog (LOG_ERR, "server \"%s\" unknown - using 105", QISERVICE); QI.sin_port = 105; } QI.sin_family = AF_INET; again: /* Get a socket for the QI connection */ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { if (Log) syslog (LOG_ERR, "ContactQI: socket(): %m"); if (Debug) fprintf (stderr, "ContactQI: can't create socket"); finis(); } /* Locate the proper host */ if (Host = gethostbyname (QiHost)) { bcopy (Host->h_addr, (char *) &QI.sin_addr.s_addr, 4); } else { if (Log) syslog (LOG_ERR, "ContactQI: gethostbyname(%s): %m", QiHost); if (Debug) { fprintf (stderr, "gethostbyname(%s):", QiHost); perror (""); } finis(); } /* Connect to the nameserver */ if (connect (sock, (struct sockaddr *) &QI, sizeof (QI)) < 0) { if (Log) syslog (LOG_INFO, "ContactQI: connect(%s): %m", QiHost); if (Debug) { fprintf (stderr, "ContactQI: connect(%s):", QiHost); perror (""); } (void) close(sock); #ifdef QI_ALT if (!equal (QiHost, QI_ALT)) { QiHost = QI_ALT; goto again; } #endif /* QI_ALT */ finis (); } /* Connection ok, change to canonical form */ ToQI = fdopen (sock, "w"); FromQI = fdopen (sock, "r"); return; } \f /* ** ErrorReturn -- Create and send informative mail messages ** ** The envelope from address should be set to null as per RFC-821 ** in regard to notification messages (Section 3.6). ** ** Parameters: ** Addr -- pointer to NewAddress structure with addresses ** and messages ** Omsg -- stream pointer to original message ** envp -- environment pointer for vfork/execve ** ** Returns: ** Nothing ** ** Side Effects: ** None */ char *ap[] = { "-sendmail", "-f", "MAILER-DAEMON", "-t", 0}; void ErrorReturn (Addr, Omsg, envp) NADD *Addr; FILE *Omsg; char *envp[]; { int i; /* Good ol' i */ char Buf[MAXSTR]; /* Temp for copying msg test */ FILE *Emsg; /* For creating the error msg */ int pid; /* For vfork() */ int flags = 0; /* Controls printing of msgs */ int SubCode; /* Printing control */ int ByteLimit = 15000; /* Limit returned msg size */ NADD *AddrP; /* Loop variable */ QIR *QIp; /* Another loop variable */ extern char *ap[]; /* Open the error file */ if ((Emsg = OpenTemp (ErrorFile)) == FILE_NULL) finis (); /* Insert the headers */ if (fprintf (Emsg, "To: %s\n", From) < 0) finis (); if (PostmasterCC) fprintf (Emsg, "Cc: Postmaster\n"); fprintf (Emsg, "Subject: Returned mail - nameserver error report\n\n"); fprintf (Emsg, " --------Message not delivered to the following:\n\n"); for (AddrP = Addr; AddrP->original != CPNULL; AddrP++) if (abs (AddrP->code) != LR_OK) fprintf (Emsg, " %15s %s\n", AddrP->original, AddrP->new); fprintf (Emsg, "\n --------Error Detail (phquery V%s):\n\n", VERSION); /* Loop again to insert messages */ for (AddrP = Addr; AddrP->original != CPNULL; AddrP++) if (abs (AddrP->code) == LR_NOMATCH) { if (! (flags & NO_MATCH_MSG)) { PrintMsg (Emsg, NoMatchMsg); flags |= NO_MATCH_MSG; break; } } for (AddrP = Addr; AddrP->original != CPNULL; AddrP++) if (abs (AddrP->code) == LR_ABSENT) { if (! (flags & ABSENT_MSG)) { PrintMsg (Emsg, AbsentMsg); flags |= ABSENT_MSG; if (! (flags & PHONE_MSG)) { PrintMsg (Emsg, PhoneMsg); flags |= PHONE_MSG; } } for (QIp = AddrP->QIalt; QIp->code < 0; QIp++) if (abs (QIp->code) == LR_OK) fprintf (Emsg, " %s: %s\n", Fields[QIp->field].value, QIp->message); (void) putc ('\n', Emsg); } for (AddrP = Addr; AddrP->original != CPNULL; AddrP++) if (abs (AddrP->code) == LR_TOOMANY) { if (! (flags & TOO_MANY_MSG)) { PrintMsg (Emsg, TooManyMsg); flags |= TOO_MANY_MSG; break; } } for (AddrP = Addr; AddrP->original != CPNULL; AddrP++) if (abs (AddrP->code) == LR_AMBIGUOUS) { if (! (flags & MULTI_MSG)) { PrintMsg (Emsg, MultiMsg); flags |= MULTI_MSG; if (! (flags & PHONE_MSG)) { PrintMsg (Emsg, PhoneMsg); flags |= PHONE_MSG; } } for (QIp = AddrP->QIalt, SubCode = QIp->subcode; QIp->code < 0; QIp++) { if (QIp->subcode != SubCode) { SubCode = QIp->subcode; (void) putc ('\n', Emsg); } if (abs (QIp->code) == LR_OK) fprintf (Emsg, " %s: %s\n", Fields[QIp->field].value, QIp->message); } (void) putc ('\n', Emsg); } fprintf (Emsg, "\n --------Unsent Message below:\n\n"); rewind (Omsg); while ((i = fread (Buf, sizeof (char), MAXSTR, Omsg)) != 0 && ByteLimit > 0) { if (fwrite (Buf, sizeof (char), i, Emsg) != i) DANGER_WILL_ROBINSON("ErrorReturn: Emsg copy") else ByteLimit -= i; } fprintf (Emsg, "\n --------End of Unsent Message\n"); (void) fflush (Emsg); (void) fclose (Emsg); if (freopen (ErrorFile, "r", stdin) == FILE_NULL) DANGER_WILL_ROBINSON("ErrorReturn: ErrorFile freopen") /* Zap file so it disappears automagically */ if (! Debug) (void) unlink (ErrorFile); /* * vfork, then execve sendmail for delivery */ pid = 0; if (! Debug && (pid = vfork ()) == -1) DANGER_WILL_ROBINSON("ErrorReturn: fork") if (pid) { (void) wait(0); return; } else if (! Debug) execve (SENDMAIL, ap, envp); } \f /* ** FindFrom -- Find From: address in message headers ** ** Parameters: ** MsgFile -- stream pointer to message ** ** Returns: ** Nothing ** ** Side Effects: ** Global From pointer is adjusted to point at either a ** malloc'ed area containing the address, or to the ** constant string "Postmaster" if none is found. */ void FindFrom (MsgFile) FILE *MsgFile; { char *p1, *p2; extern char *From; char Buf[MAXSTR]; rewind (MsgFile); while (fgets (Buf, MAXSTR, MsgFile) != CPNULL && *Buf != '\n') { if (strncasecmp (Buf, "From:", 5)) continue; else { if ((p1 = index (Buf, '<')) != CPNULL) { p1++; if ((p2 = index (Buf, '>')) != CPNULL) { From = Malloc ((unsigned) ((p2-p1)+1)); (void) strncpy (From, p1, (p2-p1)); } else { if (Debug) fprintf (stderr, "Unbalanced <> in From: address\n"); if (Log) syslog (LOG_ERR, "Unbalanced <> in From: address\n"); From = "Postmaster"; } } else { /* * Punt to postmaster. If there's too * many, I'll fix this someday. */ if (Debug) fprintf (stderr, "No <> in From: address\n"); if (Log) syslog (LOG_ERR, "No <> in From: address\n"); From = "Postmaster"; } break; } } if (From == CPNULL) { if (Debug) fprintf (stderr, "No From: address in message\n"); if (Log) syslog (LOG_ERR, "No From: address in message\n"); From = "Postmaster"; } } \f /* ** ReMail -- Forward message to recipients after adding phquery headers ** ** Parameters: ** Addr -- pointer to NewAddress structure with addresses ** and messages ** Omsg -- stream pointer to original message ** envp -- environment pointer for vfork/execve ** ** Returns: ** Nothing ** ** Side Effects: ** None */ void ReMail (Addr, Omsg, envp) NADD *Addr; FILE *Omsg; char *envp[]; { int napi = 0; int i; char Buf[MAXSTR]; NADD *AddrP; FILE *Nmsg; int pid = 0; char *nap[50], nFrom[100]; extern char *From, HostNameBuf[]; extern int ReplyTo; /* Open the rewrite file */ if ((Nmsg = OpenTemp (NewFile)) == FILE_NULL) finis (); /* Fill out the first portion of the sendmail argument vector */ nap[napi++] = "-sendmail"; nap[napi++] = "-f"; if (ReplyTo == 0) nap[napi++] = From; for (AddrP = Addr; AddrP->original != CPNULL; AddrP++) if (abs (AddrP->code) == LR_OK) nap[napi++] = AddrP->new; else { /* * Tack on .xyzzy to the From address so sendmail will know * it's been here. */ (void) strcpy (nFrom, From); (void) strcat (nFrom, ".xyzzy"); nap[napi++] = nFrom; } /* Read and copy the header block, adding X-PH-To: or X-PH: header */ rewind (Omsg); while (fgets (Buf, MAXSTR, Omsg) != CPNULL && *Buf != '\n') { if ((nequal (Buf, "To:", 3) || nequal (Buf, "Cc:", 3) || nequal (Buf, "From:", 5)) && pid == 0) { int LineLength; if (ReplyTo == 0) { /* Write the PH header and add to argv */ #ifdef EXPAND_TO if (fprintf (Nmsg, "X-PH(%s)-To:", VERSION) < 0) finis (); LineLength = 8; for (AddrP = Addr; AddrP->original != CPNULL; AddrP++) if (abs (AddrP->code) == LR_OK) { if ((LineLength + strlen (AddrP->new)) > 75) { fprintf (Nmsg, "\n\t"); LineLength = 8; } fprintf (Nmsg, " %s", AddrP->new); } (void) putc ('\n', Nmsg); #else /* ! EXPAND_TO */ fprintf (Nmsg, "X-PH: V%s@%s\n", VERSION, HostNameBuf); #endif /* EXPAND_TO */ pid++; } else if (ReplyTo == 1) { /* Add the Reply-To: fields */ AddrP = Addr; if (fprintf (Nmsg, "Comment: Reply-To: added by phquery (V%s)\n", VERSION) < 0) finis (); fprintf (Nmsg, "Resent-From: postmaster@%s\n", HostNameBuf); fprintf (Nmsg, "Reply-To: %s\n", AddrP->new); AddrP++; for (; AddrP->original != CPNULL; AddrP++) nap[napi++] = AddrP->original; pid++; } } fputs (Buf, Nmsg); } (void) fputs (Buf, Nmsg); nap[napi] = CPNULL; if (Debug) { printf ("Final send vector:"); for (i = 0; nap[i] != CPNULL; i++) printf (" %s", nap[i]); (void) putchar ('\n'); } /* Copy the remainder of the message */ while ((i = fread (Buf, sizeof (char), MAXSTR, Omsg)) != 0) if (fwrite (Buf, sizeof (char), i, Nmsg) != i) DANGER_WILL_ROBINSON("ReMail: nmsg copy") /* Re-arrange the stream pointers and invoke sendmail */ (void) fflush (Nmsg); (void) fclose (Nmsg); if (freopen (NewFile, "r", stdin) == FILE_NULL) DANGER_WILL_ROBINSON("ReMail: NewFile freopen") /* Zap file so it disappears automagically */ if (! Debug) (void) unlink (NewFile); /* * vfork, then execve sendmail for delivery */ pid = 0; if (! Debug && (pid = vfork ()) == -1) DANGER_WILL_ROBINSON("ReMail: fork") if (pid) { (void) wait(0); return; } else if (! Debug) execve (SENDMAIL, nap, envp); } \f /* ** CodeString -- Return text string corresponding to supplied reply code ** ** Parameters: ** code -- reply value ** ** Returns: ** char pointer to text string or NULL pointer if no matching ** key is located. ** ** Side Effects: ** None */ char * CodeString (code) int code; { struct ReplyCodes *Cpnt; extern struct ReplyCodes Codes[]; for (Cpnt = Codes; Cpnt->key != -1; Cpnt++) if (Cpnt->key == abs (code)) return (Cpnt->value); return (CPNULL); } \f /* ** OpenTemp -- Create and open a temporary file ** ** For the supplied file name, create, open, and chmod the file ** ** Parameters: ** Name -- pathname of file to create in mkstemp format ** ** Returns: ** Stream descriptor of resulting file, or NULL if error ** ** Side Effects: ** mkstemp modifies calling argument */ FILE * OpenTemp (Name) const char *Name; { int fd; FILE *Stream; if ((fd = mkstemp (Name)) == -1) DANGER_WILL_ROBINSON("OpenTemp: mkstemp") /* Protect it */ if (fchmod (fd, IREAD|IWRITE) == -1) DANGER_WILL_ROBINSON("OpenTemp: fchmod") /* Make fd a stream */ if ((Stream = fdopen (fd, "r+")) == FILE_NULL) DANGER_WILL_ROBINSON("OpenTemp: fdopen") return (Stream); } \f /* ** PickField -- Find the QI_response with the named field ** ** Cycle through a chain of QI_response's looking for one with the ** named field. Return a pointer to that one or NULL if not present. ** Assumes that the last QI_response.code > 0. ** ** Parameters: ** qp -- QI_response chain pointer ** field -- QI field to search for ** ** Returns: ** pointer to located QI_response or NULL if not found ** ** Side Effects: ** None */ QIR * PickField (qp, field) QIR *qp; int field; { do { if (qp->field == field) return (qp); } while ((qp++)->code < 0); return (QIR_NULL); } \f /* ** Query -- Create queries to send to the CSnet central server ** ** Using the alias, call-sign, and full name fields, as known by the ** CSnet central name server Query Interpreter, Query creates variants ** of the supplied name (New->original) if a straight alias lookup fails. ** For each variant, SendQuery() is called until either one succeeds or ** all variants are exhausted. ** ** Parameters: ** New -- pointer to NewAddress struct ** ** Returns: ** None ** ** Side Effects: ** Modifies contents under New pointer. */ void Query(New) NADD *New; { char scratch[MAXSTR]; /* copy of FullName w.o. punct */ char *sp; /* work ptrs for scratch */ #ifdef WILDNAMES char *sp2; /* work ptrs for scratch */ #endif /* WILDNAMES */ char **Lpnt = TryList; /* Loop pointer for TryList */ int NoMore = -1; /* set if all name variants done */ /* * Try the query as an alias lookup first, then as a full name lookup. */ do { /* * Convert punctuation/separators in scratch to space * characters one at a time if testing for name. If * WILDNAMES is #define'd, a wildcard char '*' will be * appended after each single character name, e.g. p-pomes * is tried as p* pomes. This has risks as follows: assume * Duncan Lawrie sets his alias to "lawrie". A query for * d-lawrie will fail as a alias lookup but succeed as a * name lookup when written as "d* lawrie". This works until * Joe Student sets his alias to "d-lawrie". Whoops. * Still in a non-hostile environment, this function may be * more useful than dangerous. */ if (equal (*Lpnt, "name")) { /* Try as is first time for hyphenated names */ if (NoMore == -1) { (void) strcpy (scratch, New->original); if (SendQuery (New, *Lpnt, scratch)) return; NoMore = 0; } else { char stemp[MAXSTR], *st = stemp; for (sp = scratch; *sp != CHNULL; ) { /* copy until non-space punct char */ if (!ispunct (*sp) || *sp == ' ' || *sp == '*') { #ifdef WILDNAMES sp2 = sp; #endif /* WILDNAMES */ *st++ = *sp++; if (*sp == CHNULL) NoMore++; continue; } #ifdef WILDNAMES /* if one non-punct char, append * */ if ((sp - sp2) == 1) *st++ = '*'; #endif /* WILDNAMES */ *st++ = ' '; sp++; break; } while (*sp != CHNULL) *st++ = *sp++; *st = CHNULL; (void) strcpy (scratch, stemp); if (SendQuery (New, *Lpnt, scratch)) return; if (NoMore > 0) Lpnt++; continue; } } /* * Convert punctuation/separators in scratch to hyphen * characters if testing for alias. */ else if (equal (*Lpnt, "alias")) { (void) strcpy (scratch, New->original); for (sp = scratch; *sp != CHNULL; sp++) if (ispunct(*sp)) *sp = '-'; if (SendQuery (New, *Lpnt, scratch)) return; Lpnt++; } else { (void) strcpy (scratch, New->original); if (SendQuery (New, *Lpnt, scratch)) return; Lpnt++; } } while (*Lpnt != CPNULL); } \f /* ** SendQuery -- Send queries to the local CSnet central name server ** ** Takes a field type (alias, call-sign, full name, etc), as known by ** the CSnet central name server Query Interpreter, and looks up the ** corresponding email address "usercode@host". Cases where the ** alias/name aren't found, are ambiguous, or lack an email address ** return a message instead of the address. Additional information is ** returned as an array of QIR records pointed to by New->QIalt. ** ** Parameters: ** New -- pointer to NewAddress struct ** Field -- type of field (name, alias, etc) for Value ** Value -- name to lookup ** ** Returns: ** 1 if a match(es) is found including too many ** 0 otherwise ** ** Side Effects: ** Will call ContactQI() if the connection is closed. ** Modifies contents under New pointer. */ SendQuery(New, Field, Value) NADD *New; const char *Field, *Value; { QIR *EmailQ, *QIp; /* For handling ReadQI() responses */ int i; /* good ol' i */ /* Open the ToQI and FromQI descriptors if necessary */ ContactQI(); /* Make a query out of the arguments */ fprintf (ToQI, "query %s=%s return name alias callsign phone department curriculum email\n", Field, Value); if (Debug) printf ("querying for %s \"%s\"\n", Field, Value); if (Log) syslog (LOG_DEBUG, "querying for %s \"%s\"\n", Field, Value); (void) fflush (ToQI); /* * Grab the responses and let the fun begin. * The possibilities are: * * 102:There were N matches to your query * -200:1: alias: Paul-Pomes * -200:1: name: pomes paul b * -200:1: callsign: See Figure 1 * -508:1: curriculum: Not present in entry. * -200:1: department: Computing Services Office * -200:1: email: paul@uxc.cso.uiuc.edu * 200:Ok. * * 501:No matches to your query. * * 502:Too many matches to request. */ EmailQ = ReadQI (FromQI); /* * If we read a preliminary response (99<x<200), garbage * collect and read some more. */ i = abs (EmailQ->code); if (i > 99 && i < 200) { GarbageCollect (EmailQ); EmailQ = ReadQI (FromQI); } /* * If we read a temporary error, be a nice program and defer. */ else if (i > 399 && i < 500) finis (); /* * No matches at all? Too many? Note that single line errors * will have code > 0. */ if (EmailQ->code > 0) { New->new = CodeString (EmailQ->code); New->code = EmailQ->code; New->QIalt = QIR_NULL; GarbageCollect (EmailQ); if (New->code == LR_TOOMANY) return (1); return (0); } /* anything else must be multi-line */ assert (EmailQ->code < 0); /* Are there multiple responses (subcode > 1)? */ for (QIp = EmailQ; QIp->code < 0; QIp++) if (QIp->subcode > 1) { New->code = LR_AMBIGUOUS; New->new = CodeString (LR_AMBIGUOUS); New->QIalt = EmailQ; return (1); } /* If one person, handle as single match alias */ QIp = PickField (EmailQ, EMAIL); assert (QIp->field == EMAIL); New->code = abs (QIp->code); New->QIalt = EmailQ; switch (abs (QIp->code)) { case LR_ABSENT: New->new = CodeString (QIp->code); return (1); case LR_OK: New->new = QIp->message; return (1); default: if (Debug) fprintf (stderr, "unexpected code %d\n", QIp->code); if (Log) syslog (LOG_ERR, "Query: %s: unexpected code %d", Field, QIp->code); finis (); } GarbageCollect (EmailQ); return (0); } \f /* ** RevQuery -- Reverse query, email to ph alias ** ** Takes a email address as known by the CSnet central name server ** Query Interpreter, and looks up the corresponding alias. Cases ** where the email address matches multiple aliases return the ** original address. In addition the global variable ReplyTo is ** set to -1. ** ** Parameters: ** New -- pointer to NewAddress struct ** ** Returns: ** None ** ** Side Effects: ** Will call ContactQI() if the connection is closed. ** Modifies contents under New pointer. ** ReplyTo set to -1 if QI returns multiple aliases or ** no match. */ void RevQuery(New) NADD *New; { int i; QIR *AliasQ, *QIp; extern int ReplyTo; extern char *From, HostNameBuf[]; extern FILE *ToQI, *FromQI; /* Open the ToQI and FromQI descriptors if necessary */ ContactQI(); /* * We have to have a from address here. If it doesn't have * a fully qualified form, convert it to name@domain by * appending our Fully Qualified Domain Name. FQDN, the * litany of the new Internet Age. */ assert (From != CPNULL); if (index (From, '@') == CPNULL) { char *nFrom; /* * We can't Realloc(From) since it may point to * an area on the stack. */ nFrom = Malloc ((unsigned)(strlen (From) + 1)); (void) strcpy (nFrom, From); From = Realloc (nFrom, (unsigned)(strlen(nFrom) + strlen(HostNameBuf) + 5)); (void) strcat (From, "@"); (void) strcat (From, HostNameBuf); } New->original = From; /* Send the query * I'd check for a -1 here, but am unsure how network errors really * are manifested. */ fprintf (ToQI, "query email=%s return alias \n", From); if (Debug) printf ("querying alias corresponding to \"%s\"\n", From); if (Log) syslog (LOG_DEBUG, "querying alias for \"%s\"\n", From); (void) fflush (ToQI); /* * Grab the responses and let the fun begin. * The possibilities are: * * 102:There was N matches to your query. * * -200:1: alias: rrv * 200:Ok. * * -200:1: alias: Paul-Pomes * -200:2: alias: PostMaster * 200:Ok. * * 501:No matches to your query. * * 502:Too many matches to request. * * For anything other than the first case, set ReplyTo to -1 and * set New->new = New->original . */ AliasQ = ReadQI (FromQI); /* * If we read a preliminary response (99<x<200), garbage * collect and read some more. */ i = abs (AliasQ->code); if (i > 99 && i < 200) { GarbageCollect (AliasQ); AliasQ = ReadQI (FromQI); } /* Handle the 501, 502 codes */ if (AliasQ->code > 0) { ReplyTo = -1; New->new = New->original; GarbageCollect (AliasQ); return; } /* Are there multiple responses (subcode > 1)? */ for (QIp = AliasQ; QIp->code < 0; QIp++) if (QIp->subcode > 1) { ReplyTo = -1; New->new = New->original; GarbageCollect (AliasQ); return; } QIp = AliasQ; assert (abs (QIp->code) == LR_OK && QIp->field == ALIAS); New->code = abs (QIp->code); New->new = QIp->message; return; } \f /* ** ReadQI -- Read and store response from QI server ** ** A QI response has one of the following structures: ** ** <-><code>:<subcode><ws><field name>:<string> ** 5XX:Error message ** 200:Ok. ** ** The leading '-' marks a continuation line. The last line of a ** response will not have the '-'. ** ** <code> is the response code. Response codes are listed in phquery.h ** and closely follow the conventions of SMTP (RFC-821): ** ** 1XX - status ** 2XX - information ** 3XX - additional information or action needed ** 4XX - temporary errors ** 5XX - permanent errors ** 6XX - phquery specific codes ** ** <subcode> links multiple fields (e.g., email and pager) to a single ** individual. If a name query results in a multiple match, subcode ** increments by 1 for each person but has the same value for all response ** lines for that individual. ** ** <ws> is sufficient white space to right adjust <field name>: to the ** same position on each line. ** ** <field name> is one of the field type in phquery.h (e.g., department, ** mailcode, etc). ** ** <string> is either the value for <field name>, if <code> == 200 (LR_OK), ** or an error message it <code> is anything else. ** ** Parameters: ** InFile - stream pointer for input ** ** Returns: ** A pointer to a malloc()'ed block of QI_response structs that ** is terminated with QI_response.code > 0. ** ** Side Effects: ** Creates a block of data that must be later free()'d. ** Advances FromQI. */ QIR * ReadQI (InFile) FILE *InFile; { int i, code; int loopcnt = 1; char *tp; unsigned size = sizeof (QIR); char fstring[MAXSTR]; /* field string */ char message[MAXSTR]; /* field value */ char Temp[MAXSTR]; register QIR *Base, *RepChain; Base = RepChain = (QIR *) Malloc (size); RepChain->field = -1; Base->message = CPNULL; do { *fstring = *message = CHNULL; if (fgets (Temp, MAXSTR-1, InFile) == CPNULL) { if (Debug) fprintf (stderr, "premature EOF\n"); if (Log) syslog (LOG_ERR, "ReadQI: premature EOF"); finis (); } if (Debug > 1) printf ("ReadQI read =%s=\n", Temp); code = atoi (Temp); /* Positive response codes are formatted "<code>:<message>" */ if (code > 0) { RepChain->subcode = NONE_OF_ABOVE; if (sscanf (Temp, "%d:%[^\n]", &RepChain->code, message) != 2 || *message == CHNULL) { if (Debug) fprintf (stderr, "ReadQI: short #1 sscanf\n"); if (Log) syslog (LOG_ERR, "ReadQI: short #1 sscanf read: %m"); finis (); } } /* Otherwise they are the 4 field type */ else if (( i = sscanf (Temp, "%d:%d:%[^:]: %[^\n]", &RepChain->code, &RepChain->subcode, fstring, message)) != 4 || *fstring == CHNULL || *message == CHNULL) { if (Debug) fprintf (stderr, "ReadQI: short #2 sscanf, expected 4 got %d\n", i); if (Log) syslog (LOG_ERR, "ReadQI: short #2 sscanf, expected 4 got %d", i); /* * The short sscanf() read may be due to a embedded * newline. If so, continue for a bit to fill out the * code field before reading another line. */ if (!(i == 3 && *message == CHNULL)) finis (); } /* * Some fields go over multiple response lines. In that case * the field is all blanks. Copy the response field from the * previous response if not already set. */ if (RepChain->field == -1) { for (tp = fstring; tp <= fstring + (MAXSTR-1) && *tp == ' '; tp++) ; if (RepChain->code < 0 && *tp == CHNULL) RepChain->field = (RepChain - 1)->field; else RepChain->field = FieldValue (tp); } /* Now get a new line if message was empty. */ if (*message == CHNULL) continue; RepChain->message = Malloc ((unsigned) (strlen (message) + 1)); (void) strcpy (RepChain->message, message); if (RepChain->code > 0) break; size += sizeof (QIR); Base = (QIR *) Realloc ((char *) Base, size); RepChain = Base + loopcnt; RepChain->field = -1; } while (loopcnt++); if (Debug) for (RepChain = Base; RepChain->code < 0; RepChain++) printf ("code %d, subcode %d, field %s, message: %s\n", RepChain->code, RepChain->subcode, Fields[RepChain->field].value, RepChain->message); return (Base); } \f /* ** FieldValue -- Locate argument in Fields[] and return integer value ** ** Parameters: ** field -- character string to locate in Fields[] ** ** Returns: ** integer value of field or NONE_OF_ABOVE (-1) if not found. ** ** Side Effects: ** none */ FieldValue (field) const char *field; { struct QI_fields *QIp = Fields; /* Guard against stupid mistakes (so they show up somewhere else?) */ if (field == CPNULL || *field == CHNULL) return (NONE_OF_ABOVE); /* Replace this with a binary search if profiling peaks here. XXX */ do { if (equal (field, QIp->value)) break; } while ((++QIp)->key != NONE_OF_ABOVE); return (QIp->key); } \f /* ** GarbageCollect -- Free space allocated within QI_response array ** ** Parameters: ** QIp -- pointer to array of QI_response ** ** Returns: ** None ** ** Side Effects: ** none */ void GarbageCollect (QIp) QIR *QIp; { QIR *QIsave = QIp; assert (QIp != QIR_NULL); do { if (QIp->message != CPNULL) free (QIp->message); QIp->message = CPNULL; } while ((QIp++)->code < 0); free ((char *) QIsave); } \f /* ** Malloc -- malloc with error checking ** ** Parameters: ** size -- number of bytes to get ** ** Returns: ** (char *) of first char of block, or ** finis() if any error ** ** Side Effects: ** none */ char * Malloc (size) unsigned size; /* Bytes to get */ { char *cp; /* Pointer to memory */ if ((cp = (char *) malloc (size)) == CPNULL) { if (Debug) { fprintf (stderr, "malloc of %u bytes failed:", size); perror(""); } if (Log) syslog (LOG_ERR, "malloc of %u bytes failed: %m", size); finis (); } return (cp); } \f /* ** PrintMsg -- Print a message on the named stream ** ** Parameters: ** OutFile -- stream to print message to ** Msg - array of char pointers that make up message, ** null terminated ** ** Returns: ** None ** ** Side Effects: ** none */ void PrintMsg (OutFile, Msg) FILE *OutFile; char *Msg[]; { while (*Msg != CPNULL) { if (fprintf (OutFile, "%s\n", *Msg) < 0) finis (); Msg++; } } \f /* ** Realloc -- realloc with error checking ** ** Parameters: ** ptr -- pointer to existing data ** size -- number of bytes to get ** ** Returns: ** (char *) of first char of block, or ** finis() if any error ** ** Side Effects: ** none */ char * Realloc (ptr, size) char *ptr; unsigned size; { char *cp; /* pointer to memory */ if ((cp = (char *) realloc (ptr, size)) == CPNULL) { if (Debug) { fprintf (stderr, "realloc of %u bytes failed:", size); perror(""); } if (Log) syslog (LOG_ERR, "realloc of %u bytes failed: %m", size); finis (); } return (cp); } \f /* ** PrtUsage -- Print how to use message ** ** Print usage messages (char *usage[]) to stderr and exit nonzero. ** Each message is followed by a newline. ** ** Parameters: ** FullText -- prints the copyright statement if set ** ** Returns: ** none ** ** Side Effects: ** none */ void PrtUsage (FullText) int FullText; { int which = 0; /* current line */ while (usage[which] != CPNULL) { fprintf (stderr, usage[which++], MyName); (void) putc ('\n', stderr); } (void) fflush (stdout); if (FullText) PrintMsg (stdout, CopyLeft); } \f /* ** finis -- Clean up and exit. ** ** Parameters: ** none ** ** Returns: ** never ** ** Side Effects: ** exits sendmail */ void finis() { extern FILE *ToQI, *FromQI; /* clean up temp files */ if (ToQI != FILE_NULL) (void) fclose (ToQI); if (FromQI != FILE_NULL) (void) fclose (FromQI); ToQI = FromQI = FILE_NULL; if (! Debug) { (void) unlink (TmpFile); (void) unlink (ErrorFile); (void) unlink (NewFile); } /* and exit */ exit (ExitStat); }