This is notify.c in view mode; [Download] [Up]
/* @(#)src/notify.c 1.2 24 Oct 1990 05:23:50 */
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* notify.c:
* keep track of errors and handle notification of the appropriate
* users for errors.
*
* external functions: defer_delivery, fail_delivery, succeed_delivery,
* error_delivery, process_msg_log, notify
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <signal.h>
#include <time.h>
#include "defs.h"
#include "smail.h"
#include "dys.h"
#include "addr.h"
#include "hash.h"
#include "main.h"
#include "log.h"
#include "direct.h"
#include "transport.h"
#include "child.h"
#include "exitcodes.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
#endif
/* exported variables */
int send_to_postmaster; /* set TRUE to mail to postmaster */
int return_to_sender; /* set TRUE to mail log to sender */
/* functions local to this file */
static char *decode_x_line();
static void put_defer_error();
static void classify_addr();
static void notify_new_message();
static struct addr *remove_addr();
/* variables local to this file */
static char *log_banner = "\
|------------------------- Message log follows: -------------------------|\n";
static char *addr_error_banner = "\
|------------------------- Failed addresses follow: ---------------------|\n";
static char *text_banner = "\
|------------------------- Message text follows: ------------------------|\n";
/*
* fail_delivery - log that delivery to the set of addresses failed
*/
void
fail_delivery(addr)
struct addr *addr; /* list of failed addrs */
{
register struct addr *cur;
if (cur && exitvalue == EX_OK) {
exitvalue = EX_UNAVAILABLE;
}
for (cur = addr; cur; cur = cur->succ) {
write_log(LOG_SYS,
"%s ... failed: (ERR_%03ld) %s",
cur->in_addr,
cur->error->info & ERR_MASK,
cur->error->message);
/* X entries in msg log will be picked up by queue run scans */
if (cur->parent) {
write_log(LOG_MLOG,
"Xfail: <%s> parent: <%s> reason: (ERR_%03ld) %s",
cur->in_addr,
cur->parent->in_addr,
cur->error->info & ERR_MASK,
cur->error->message);
} else {
write_log(LOG_MLOG,
"Xfail: <%s> reason: (ERR_%03ld) %s",
cur->in_addr,
cur->error->info & ERR_MASK,
cur->error->message);
}
}
}
/*
* defer_delivery - log that delivery to the set of addresses is deferred
*
* don't write an error message, if it would be a repeat of the most
* recent deferal message written to the message log file for the
* address.
*/
void
defer_delivery(addr, defer_errors)
struct addr *addr; /* list of succeeded addrs */
struct defer_addr *defer_errors; /* previous deferals */
{
struct addr *cur;
struct defer_addr *link;
if (cur && exitvalue == EX_OK) {
exitvalue = EX_TEMPFAIL;
}
for (cur = addr; cur; cur = cur->succ) {
for (link = defer_errors; link; link = link->succ) {
if (EQ(link->address, cur->in_addr) ||
link->parent == NULL && cur->parent == NULL ||
link->parent != NULL && cur->parent != NULL &&
EQ(link->parent, cur->parent->in_addr))
{
break;
}
}
if (link) {
if (link->error == (cur->error->info & ERR_MASK) &&
EQ(link->message, cur->error->message))
{
break;
}
}
write_log((cur->error->info & ERR_CONFERR)? (LOG_SYS|LOG_TTY): LOG_SYS,
"%s ... deferred: (ERR_%03ld) %s",
cur->in_addr,
cur->error->info & ERR_MASK,
cur->error->message);
if (cur->parent) {
write_log(LOG_MLOG,
"Xdefer: <%s> parent: <%s> reason: (ERR_%03ld) %s",
cur->in_addr,
cur->parent->in_addr,
cur->error->info & ERR_MASK,
cur->error->message);
} else {
write_log(LOG_MLOG,
"Xdefer: <%s> reason: (ERR_%03ld) %s",
cur->in_addr,
cur->error->info & ERR_MASK,
cur->error->message);
}
}
}
/*
* succeed_delivery - log that delivery to the set of addresses succeeded
*/
void
succeed_delivery(addr)
struct addr *addr; /* list of succeeded addrs */
{
register struct addr *cur;
for (cur = addr; cur; cur = cur->succ) {
if (dont_deliver) {
write_log(LOG_SYS, "%s ... not delivered, debugging",
cur->in_addr);
} else {
write_log(LOG_SYS, "%s ... delivered", cur->in_addr);
}
/* X entries will be picked up by queue run scans */
if (cur->parent) {
write_log(LOG_MLOG, "Xsucceed: <%s> parent: <%s>",
cur->in_addr, cur->parent->in_addr);
} else {
write_log(LOG_MLOG, "Xsucceed: <%s>", cur->in_addr);
}
}
}
/*
* error_delivery - log that an error was sent for this list of addrs
*
* detect mail sent to owners and log the original address.
*/
void
error_delivery(addr)
struct addr *addr; /* list of error addrs */
{
register struct addr *cur;
for (cur = addr; cur; cur = cur->succ) {
register struct addr *log_addr;
if (cur->true_addr) {
log_addr = cur->true_addr;
write_log(LOG_SYS, "%s ... error sent to %s",
log_addr->in_addr, cur->in_addr);
} else {
if (cur->error->info & ERR_NSENDER) {
write_log(LOG_SYS, "%s ... error returned to %s",
cur->in_addr, sender);
}
if (cur->error->info & ERR_NPOSTMAST) {
write_log(LOG_SYS, "%s ... error sent to postmaster",
cur->in_addr);
}
}
/* X entries will be picked up by queue run scans */
if (cur->parent) {
write_log(LOG_MLOG, "Xsent_error: <%s> parent: <%s>",
cur->in_addr, cur->parent->in_addr);
} else {
write_log(LOG_MLOG, "Xsent_error: <%s>", cur->in_addr);
}
}
}
/*
* process_msg_log - process X lines in the per-message log
*
* Lines beginning with X in the per-message log contain information on what
* mail processing was completed in a previous run on the input spool file.
* This function takes, as input, the set of addresses which were produced
* by resolve_addr_list() for delivery and returns a list which does not
* contain addresses which have already been delivered.
*
* It also returns a pointer to information which should be passed to the
* notify() routine after all the transports have been called.
*/
struct addr *
process_msg_log(in, sent_errors, defer_errors)
struct addr *in; /* input resolved addresses */
struct identify_addr **sent_errors; /* return errors that have been sent */
struct defer_addr **defer_errors; /* last defer message for addrs */
{
register struct addr *ret; /* addr list to return */
char *s; /* line of text from scan_msg_log() */
char *m; /* defer message */
ret = in;
/* scan through all of the lines marked X in the message log file */
for (s = scan_msg_log(TRUE); s; s = scan_msg_log(FALSE)) {
char *address; /* the address to remove */
char *parent; /* parent of address to remove */
if (strncmp(s, "Xfail:", sizeof("Xfail:") - 1) == 0) {
/* get the failed address and the parent address */
(void) decode_x_line(s + sizeof("Xfail:") - 1 , &address, &parent);
/* remove any occurance from the input list */
if (address) {
ret = remove_addr(ret, address, parent);
}
} else if (strncmp(s, "Xsucceed:", sizeof("Xsucceed:") - 1) == 0) {
/* get the delivered address and the parent address */
(void) decode_x_line(s + sizeof("Xsucceed:") - 1,
&address, &parent);
/* remove any occurance from the input list */
if (address) {
ret = remove_addr(ret, address, parent);
}
} else if (strncmp(s, "Xdefer:", sizeof("Xdefer:") - 1) == 0) {
/* grab the message and error number of the defered address */
m = decode_x_line(s + sizeof("Xdefer:") - 1, &address, &parent);
/* put defer message into output defer_error list */
if (m) {
put_defer_error(defer_errors, m, address, parent);
}
} else if (strncmp(s, "Xsent_error:", sizeof("Xsent_error:")-1) == 0) {
register struct identify_addr *new_sent;
/* get the sent error address and the parent address */
(void) decode_x_line(s + sizeof("Xsent_error:") - 1,
&address, &parent);
if (address == NULL) {
continue;
}
/* give this address to notify */
new_sent = (struct identify_addr *)xmalloc(sizeof(*new_sent));
new_sent->address = COPY_STRING(address);
if (parent) {
new_sent->parent = COPY_STRING(parent);
} else {
new_sent->parent = NULL;
}
new_sent->succ = *sent_errors;
*sent_errors = new_sent;
}
}
return ret;
}
/*
* decode_x_line - decode an X-line from the per-message log file
*
* The input should be of the form:
*
* <address> parent: <parent_address> garbage...
* or <address> garbage...
*
* this routine returns the address and the parent_address. It also
* returns a pointer to the garbage.
*/
static char *
decode_x_line(line, address, parent)
char *line; /* input line */
char **address; /* return address here */
char **parent; /* return parent address here */
{
int level; /* count < and > nesting */
register char *p = line; /* point to parts of the line */
/* skip initial white-space */
while (isspace(*p)) p++;
/* ignore anything not of the proper form */
if (*p != '<') {
*address = NULL;
return NULL;
}
p++; /* skip the initial < */
/*
* extract the address as the text up until before the balancing
* > character, in <address>.
*/
*address = p; /* mark this spot as the address */
level = 1; /* start at nesting level 1 */
if (*p == '<') {
level++;
}
while (level > 0) {
p = address_token(p);
if (p == NULL) {
*address = NULL; /* bad input form */
return NULL;
}
if (*p == '<') {
level++;
} else if (*p == '>') {
--level;
}
}
*p++ = '\0'; /* the last > is the end of address */
/* scan for a possible parent address */
while (isspace(*p)) p++; /* skip up until parent: */
/* is there a parent address? */
if (strncmp(p, "parent:", sizeof("parent:") - 1) != 0) {
/* no */
*parent = NULL;
return p;
}
p += sizeof("parent:") - 1;
/* skip initial white-space before actual parent address */
while (isspace(*p)) p++;
/* ignore anything not of the proper form */
if (*p != '<') {
*parent = NULL;
return NULL;
}
p++; /* skip the initial < */
/*
* extract the parent as the text up until before the balancing
* > character, in <parent_addres>
*/
*parent = p;
level = 1;
if (*p == '<') {
level++;
}
while (level > 0){
p = address_token(p);
if (p == NULL) {
*address = NULL;
return;
}
if (*p == '<') {
level++;
} else if (*p == '>') {
--level;
}
}
*p++ = '\0';
while (isspace(*p)) p++;
return p;
}
/*
* put_defer_error - put a deferal message for an address in a list
*
* remove previous entries for this address from the list.
*/
static void
put_defer_error(list, message, address, parent)
struct defer_addr **list;
char *message;
char *address;
char *parent;
{
int error;
static char reason_pfx[] = "reason: (ERR_";
char *p;
struct defer_addr *link, *next_link, *new_list;
if (strncmp(message, reason_pfx, sizeof(reason_pfx) - 1) != 0) {
/* doesn't match the correct format */
return;
}
message += sizeof(reason_pfx) - 1;
p = message;
while (isdigit(*message)) message++;
if (*message != ')') {
/* doesn't match the correct format */
return;
}
*message = 0;
error = atoi(p);
*message++ = ')';
while (isspace(*message)) message++;
new_list = (struct defer_addr *)xmalloc(sizeof(*new_list));
new_list->succ = NULL;
new_list->error = error;
new_list->message = COPY_STRING(message);
new_list->address = COPY_STRING(address);
new_list->parent = parent? COPY_STRING(parent): NULL;
for (link = *list; link; link = next_link) {
next_link = link->succ;
if (EQ(link->address, address) ||
link->parent == NULL && parent == NULL ||
link->parent != NULL && parent != NULL &&
EQ(link->parent, parent))
{
continue;
}
link->succ = new_list;
new_list = link;
}
*list = new_list;
}
/*
* notify - notify all users who are to receive a note about errors
*
* also, note any configuration errors and set call_defer_message
* if any are found. If any addrs exist in the defer list, set
* some_deferred_addrs to prevent main from calling unlink_spool().
*/
void
notify(defer, fail, sent_errors)
struct addr *defer; /* list of deferred addresses */
struct addr *fail; /* list of failed addresses */
struct identify_addr *sent_errors; /* addrs already handled */
{
register struct addr *cur; /* current addr being processed */
struct addr *next; /* next addr to process */
struct addr *to_owner = NULL; /* addrs to send to addr owners */
struct addr *verify = NULL; /* list of owner addrs to verify */
struct addr *to_other = NULL; /* to sender and/or postmaster */
static char *mailargs[] = {
NULL,
#ifdef GLOTZNET
"-G", NULL,
#endif /* GLOTZNET */
"-bS",
NULL,
};
FILE *f; /* stdin to child process */
int pid; /* child process id */
DEBUG(DBG_NOTIFY_HI, "notify called\n");
/* set the exec name to the smail binary */
mailargs[0] = smail;
#ifdef GLOTZNET
mailargs[2] = glotzhost;
#endif /* GLOTZNET */
/* remove any address which have already been taken care of */
while (sent_errors) {
defer = remove_addr(defer, sent_errors->address, sent_errors->parent);
fail = remove_addr(fail, sent_errors->address, sent_errors->parent);
sent_errors = sent_errors->succ;
}
/*
* for deferred addresses, don't return anything, though note config
* errors and defer the message if any were found.
*/
for (cur = defer; cur; cur = next) {
next = cur->succ;
some_deferred_addrs = TRUE; /* main should not call unlink_spool */
if (cur->error->info & ERR_CONFERR) {
call_defer_message = TRUE; /* config error, defer whole message */
}
}
/*
* for failed addresses put them in two lists, one list going to
* the sender or the postmaster, the other list going to addr owners.
*/
for (cur = fail; cur; cur = next) {
next = cur->succ;
classify_addr(cur, &to_other, &verify);
}
/* verify all owners. If some don't verify, retry classify_addr() */
while (verify) {
struct addr *new_list = NULL; /* unverified addrs */
/*
* treat defer and fail lists as identical, since we cannot
* reasonably handle defer addresses here.
*/
verify_addr_list(verify, &to_owner, &new_list, &new_list);
/* take any new addrs and send through classify_addr */
verify = NULL;
for (cur = new_list; cur; cur = next) {
next = cur->succ;
classify_addr(cur, &to_other, &verify);
}
}
/* sort the list of verified addresses */
if (to_owner) {
to_owner = addr_sort(to_owner, OFFSET(addr, in_addr));
}
if (return_to_sender) {
/*
* if return_to_sender is on but the error_processing flag
* doesn't call for a return message, then turn the flag off.
*/
if (error_processing != MAIL_BACK && error_processing != WRITE_BACK) {
return_to_sender = FALSE;
}
/* turn it off, as well, for low priority messages */
if (islower(msg_grade) && msg_grade >= 'n') {
return_to_sender = FALSE;
}
}
/*
* lastly, if the sender was given as the special string <>,
* then an error message failed. Rather than risk an infinite
* loop here, defer the message rather than delivering a recursive
* error message.
*
* error message loops can still exist if SMTP is not used or if
* an intermediate site does not correctly handle the special
* sender string <>.
*/
if (error_sender) {
if (return_to_sender || send_to_postmaster || to_owner) {
call_defer_message = TRUE;
return;
}
}
/* if nobody needs to be notified, then nothing needs to be done */
if (! return_to_sender &&
! send_to_postmaster &&
to_owner == NULL)
{
return;
}
/*
* open a channel to a new smail process, operating in bsmtp mode,
* and send it messages for all of the recipients.
*/
pid = open_child(mailargs, (char **)NULL, &f, (FILE **)NULL, -1,
CHILD_MINENV|CHILD_RETRY, prog_euid, prog_egid);
if (pid < 0) {
DEBUG(DBG_NOTIFY_LO, "notify: open_child failed, cannot notify\n");
/* if we can't notify, let humans interfere later */
call_defer_message = TRUE;
return;
}
/* start up the session */
(void) fprintf(f, "HELO %s\n", primary_name);
/*
* if any owners are to receive a message, then send to each
* owner in turn
*/
cur = to_owner;
while (cur) {
char *last_owner;
/* initiate a new message */
notify_new_message(f, cur->in_addr, "sending to address owner");
send_log(f, FALSE, log_banner); /* don't include X lines from log */
(void) fputs(addr_error_banner, f);
/* write out all of the address errors */
do {
if (cur->true_addr->error) {
(void) fprintf(f, " %s ... failed: %s\n",
cur->true_addr->in_addr,
cur->true_addr->error->message);
}
last_owner = cur->in_addr;
cur = cur->succ;
} while (cur && EQIC(last_owner, cur->in_addr));
(void) fputs(text_banner, f);
write_header(f, (long)(PUT_RECEIVED|LOCAL_TPORT));
putc('\n', f);
write_body(f, (long)PUT_DOTS);
(void) fputs(".\n", f); /* finish the message */
}
/* if the postmaster is to receive mail, then send it to him (her?) */
if (send_to_postmaster) {
int wrote_addr_error_banner = FALSE; /* only write banner once */
notify_new_message(f, "postmaster", "sending to postmaster");
send_log(f, TRUE, log_banner); /* include X lines from log */
/* write messages in to_other destined to the postmaster */
for (cur = to_other; cur; cur = cur->succ) {
if (cur->error->info & ERR_NPOSTMAST) {
if (!wrote_addr_error_banner) {
(void) fputs(addr_error_banner, f);
wrote_addr_error_banner = TRUE;
}
(void) fprintf(f, " %s ... %s\n",
cur->in_addr, cur->error->message);
}
}
(void) fputs(text_banner, f);
write_header(f, (long)(PUT_RECEIVED|LOCAL_TPORT));
putc('\n', f);
write_body(f, (long)PUT_DOTS);
(void) fputs(".\n", f); /* finish the message */
}
/* if the sender is to receive mail, arrange for that */
if (return_to_sender) {
int wrote_addr_error_banner = FALSE; /* only write banner once */
notify_new_message(f, sender, "returning to sender");
send_log(f, FALSE, log_banner); /* don't include X lines from log */
/* write messages in to_other destined to the sender */
for (cur = to_other; cur; cur = cur->succ) {
if (cur->error->info & ERR_NSENDER) {
if (!wrote_addr_error_banner) {
(void) fputs(addr_error_banner, f);
wrote_addr_error_banner = TRUE;
}
(void) fprintf(f, " %s ... %s\n",
cur->in_addr, cur->error->message);
}
}
(void) fputs(text_banner, f);
/*
* a message grade between a and z causes body not to be returned.
* This is handy when notification of errors is desired, but
* when the overhead of sending the entire message back is not
* necessary.
*/
write_header(f, (long)(PUT_RECEIVED|LOCAL_TPORT));
putc('\n', f);
if (islower(msg_grade)) {
fputs("[low-priority message, body not included]\n", f);
} else {
write_body(f, (long)PUT_DOTS);
}
(void) fputs(".\n", f); /* finish the message */
}
(void) fputs("QUIT\n", f);
(void) close_child(f, (FILE *)NULL, pid);
/* note the addresses for which error messages have been sent */
error_delivery(to_owner);
error_delivery(to_other);
}
/*
* classify_addr - determine who is to be sent an error message
*
* If postmaster or the sender is to receive an error, link into the
* to_other list, setting send_to_postmaster and/or return_to_sender
* appropriately. If a config error is found, set call_defer_message.
*
* If we need to send to an owner, determine if an owner string is
* defined and link an addr structure for that owner into the to_owner
* list.
*/
static void
classify_addr(addr, to_other, to_owner)
register struct addr *addr; /* input address */
struct addr **to_other; /* list to sender or postmaster */
struct addr **to_owner; /* list to address owner */
{
int link_to_other = FALSE; /* TRUE to link into to_other list */
if (addr->error->info & (ERR_NSENDER|ERR_NPOSTMAST)) {
/* send to either the sender or the postmaster */
link_to_other = TRUE;
/* note which one */
if (addr->error->info & ERR_NSENDER) {
return_to_sender = TRUE;
}
if (addr->error->info & ERR_NPOSTMAST) {
send_to_postmaster = TRUE;
}
}
if (addr->error->info & (ERR_NSOWNER|ERR_NPOWNER)) {
/* send to an address owner, if one is defined and expandable */
register char *s = NULL; /* expanded owner string */
register struct addr *cur; /* addr for scanning parents */
char *work_addr; /* output from preparse_address() */
/*
* find the closest parent associated with a director that
* defines an owner string.
*/
for (cur = addr; cur; cur = cur->parent) {
char *error; /* temp for storing parse error */
/*
* must have a reasonable, expandable owner address
*/
if (cur->director &&
cur->director->owner &&
(s = expand_string(cur->director->owner,
(struct addr *)NULL,
cur->home,
cur->remainder)) &&
(work_addr = preparse_address(s, &error)))
{
break;
}
}
if (cur) {
/* owner found for the address, build entry in to_owner list */
struct addr *new = alloc_addr();
new->succ = *to_owner; /* link */
*to_owner = new;
new->in_addr = COPY_STRING(s); /* copy owner address */
/*
* if this addr has already been seen by classify_addr() use
* the true_addr assigned previously, otherwise this is the
* first time through, so assign the original addr
*/
if (addr->true_addr) {
new->true_addr = addr->true_addr;
} else {
new->true_addr = addr; /* keep track of true addr */
}
new->work_addr = work_addr; /* use the preparsed address */
/* disable use of smartuser driver */
new->flags = ADDR_SMARTUSER;
/*
* if verify_addr_list() fails to verify this list look higher
* up for a parent that has an associated owner.
*/
new->parent = cur->parent;
} else {
/* no owner for the address, send to sender and/or postmaster */
if (addr->true_addr) {
int perhaps_link = FALSE; /* true to see link, if not linked */
struct error *true_error = addr->true_addr->error;
if ((addr->error->info & ERR_NSOWNER) ||
(addr->error->info & ERR_NPOWNER))
{
perhaps_link = TRUE;
}
/* only link if this has not been linked to to_other before */
if (perhaps_link) {
if (! (true_error->info & (ERR_NSENDER|ERR_NPOSTMAST)) ) {
/* link the true address, not this address */
addr = addr->true_addr;
link_to_other = TRUE;
}
}
} else {
link_to_other = TRUE;
}
if (addr->error->info & ERR_NSOWNER) {
addr->error->info |= ERR_NSENDER;
return_to_sender = TRUE;
}
if (addr->error->info & ERR_NPOWNER) {
addr->error->info |= ERR_NPOSTMAST;
send_to_postmaster = TRUE;
}
}
}
/* if link_to_other was set anywhere above, link into to_other list */
if (link_to_other) {
addr->succ = *to_other;
*to_other = addr;
}
}
static void
notify_new_message(f, to, subject_to)
FILE *f;
char *to; /* recipient */
char *subject_to; /* message for inclusion in subject */
{
/* initiate a new message */
(void) fprintf(f, "MAIL FROM:<>\n");
/* state the recipient */
(void) fprintf(f, "RCPT TO:<%s>\n", to);
/* start the message text, with the complete header */
(void) fprintf(f, "DATA\n\From: <MAILER-DAEMON@%s>\n", primary_name);
if (to[0] == '@') {
/* watch out for route-addrs */
(void) fprintf(f, "To: <%s>\n", to);
} else {
(void) fprintf(f, "To: %s\n", to);
}
(void) fprintf(f, "Subject: mail failed, %s\nReference: <%s@%s>\n\n",
subject_to, message_id, primary_name);
}
/*
* remove_addr - remove any matched addresses from an input list
*
* given an address string and (perhaps) a parent address string and
* an input address list, remove any occurance of an address in the
* input list whose in_addr matches the specified address string and
* whose parent in_addr string matches the specified parent string.
* If parent is NULL then there must not be a parent address, otherwise
* there must be a matching parent address.
*/
static struct addr *
remove_addr(in, address, parent)
struct addr *in; /* input addr list */
char *address; /* address to match against */
char *parent; /* parent of address to match */
{
register struct addr *cur; /* current address to process */
struct addr *next; /* next address to process */
struct addr *out = NULL; /* output address list */
for (cur = in; cur; cur = next) {
next = cur->succ;
if (EQ(cur->in_addr, address)) {
/* the address does matches */
if (parent) {
/* a matching parent is required for a match */
if (cur->parent && EQ(parent, cur->parent->in_addr)) {
/* match, don't put it on the output queue */
DEBUG1(DBG_NOTIFY_LO, "%s ... already delivered\n",
cur->in_addr);
continue;
}
} else if (cur->parent == NULL) {
/* match, don't put it on the output queue */
DEBUG1(DBG_NOTIFY_LO, "%s ... already delivered\n",
cur->in_addr);
continue;
}
}
/* no match, put the address on the output queue */
cur->succ = out;
out = cur;
}
return out; /* return the new list */
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.