/*++
/* NAME
/*	rewrite_clnt 3
/* SUMMARY
/*	address rewrite service client
/* SYNOPSIS
/*	#include <vstring.h>
/*	#include <rewrite_clnt.h>
/*
/*	VSTRING	*rewrite_clnt(ruleset, address, result)
/*	const char *ruleset;
/*	const char *address;
/*
/*	VSTRING	*rewrite_clnt_internal(ruleset, address, result)
/*	const char *ruleset;
/*	const char *address;
/*	VSTRING	*result;
/* DESCRIPTION
/*	This module implements a mail address rewriting client.
/*
/*	rewrite_clnt() sends a rule set name and external-form address to the
/*	rewriting service and returns the resulting external-form address.
/*	In case of communication failure the program keeps trying until the
/*	mail system shuts down.
/*
/*	rewrite_clnt_internal() performs the same functionality but takes
/*	input in internal (unquoted) form, and produces output in internal
/*	(unquoted) form.
/* DIAGNOSTICS
/*	Warnings: communication failure. Fatal error: mail system is down.
/* SEE ALSO
/*	mail_proto(3h) low-level mail component glue.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

/* Utility library. */

#include <msg.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <events.h>
#include <iostuff.h>
#include <quote_822_local.h>

/* Global library. */

#include "mail_proto.h"
#include "mail_params.h"
#include "clnt_stream.h"
#include "rewrite_clnt.h"

/* Application-specific. */

 /*
  * XXX this is shared with the resolver client to save a file descriptor.
  */
CLNT_STREAM *rewrite_clnt_stream = 0;

static time_t last_expire;
static VSTRING *last_rule;
static VSTRING *last_addr;
static VSTRING *last_result;

/* rewrite_clnt_handshake - receive server protocol announcement */

static int rewrite_clnt_handshake(VSTREAM *stream)
{
    return (attr_scan(stream, ATTR_FLAG_STRICT,
		  RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TRIVIAL),
		      ATTR_TYPE_END));
}

/* rewrite_clnt - rewrite address to (transport, next hop, recipient) */

VSTRING *rewrite_clnt(const char *rule, const char *addr, VSTRING *result)
{
    VSTREAM *stream;
    int     server_flags;
    int     count = 0;

    /*
     * One-entry cache.
     */
    if (last_addr == 0) {
	last_rule = vstring_alloc(10);
	last_addr = vstring_alloc(100);
	last_result = vstring_alloc(100);
    }

    /*
     * Sanity check. An address must be in externalized form. The result must
     * not clobber the input, because we may have to retransmit the query.
     */
#define STR vstring_str

    if (*addr == 0)
	addr = "";
    if (addr == STR(result))
	msg_panic("rewrite_clnt: result clobbers input");

    /*
     * Peek at the cache.
     */
    if (time((time_t *) 0) < last_expire
	&& strcmp(addr, STR(last_addr)) == 0
	&& strcmp(rule, STR(last_rule)) == 0) {
	vstring_strcpy(result, STR(last_result));
	if (msg_verbose)
	    msg_info("rewrite_clnt: cached: %s: %s -> %s",
		     rule, addr, vstring_str(result));
	return (result);
    }

    /*
     * Keep trying until we get a complete response. The rewrite service is
     * CPU bound and making the client asynchronous would just complicate the
     * code.
     */
    if (rewrite_clnt_stream == 0)
	rewrite_clnt_stream = clnt_stream_create(MAIL_CLASS_PRIVATE,
						 var_rewrite_service,
						 var_ipc_idle_limit,
						 var_ipc_ttl_limit,
						 rewrite_clnt_handshake);

    for (;;) {
	stream = clnt_stream_access(rewrite_clnt_stream);
	errno = 0;
	count += 1;
	if (stream == 0
	    || attr_print(stream, ATTR_FLAG_NONE,
			  SEND_ATTR_STR(MAIL_ATTR_REQ, REWRITE_ADDR),
			  SEND_ATTR_STR(MAIL_ATTR_RULE, rule),
			  SEND_ATTR_STR(MAIL_ATTR_ADDR, addr),
			  ATTR_TYPE_END) != 0
	    || vstream_fflush(stream)
	    || attr_scan(stream, ATTR_FLAG_STRICT,
			 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &server_flags),
			 RECV_ATTR_STR(MAIL_ATTR_ADDR, result),
			 ATTR_TYPE_END) != 2) {
	    if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
		msg_warn("problem talking to service %s: %m",
			 var_rewrite_service);
	} else {
	    if (msg_verbose)
		msg_info("rewrite_clnt: %s: %s -> %s",
			 rule, addr, vstring_str(result));
	    /* Server-requested disconnect. */
	    if (server_flags != 0)
		clnt_stream_recover(rewrite_clnt_stream);
	    break;
	}
	sleep(1);				/* XXX make configurable */
	clnt_stream_recover(rewrite_clnt_stream);
    }

    /*
     * Update the cache.
     */
    vstring_strcpy(last_rule, rule);
    vstring_strcpy(last_addr, addr);
    vstring_strcpy(last_result, STR(result));
    last_expire = time((time_t *) 0) + 30;	/* XXX make configurable */

    return (result);
}

/* rewrite_clnt_internal - rewrite from/to internal form */

VSTRING *rewrite_clnt_internal(const char *ruleset, const char *addr, VSTRING *result)
{
    VSTRING *src = vstring_alloc(100);
    VSTRING *dst = vstring_alloc(100);

    /*
     * Convert the address from internal address form to external RFC822
     * form, then rewrite it. After rewriting, convert to internal form.
     */
    quote_822_local(src, addr);
    rewrite_clnt(ruleset, STR(src), dst);
    unquote_822_local(result, STR(dst));
    vstring_free(src);
    vstring_free(dst);
    return (result);
}

#ifdef TEST

#include <stdlib.h>
#include <string.h>
#include <msg_vstream.h>
#include <split_at.h>
#include <vstring_vstream.h>
#include <mail_conf.h>
#include <mail_params.h>

static NORETURN usage(char *myname)
{
    msg_fatal("usage: %s [-v] [rule address...]", myname);
}

static void rewrite(char *rule, char *addr, VSTRING *reply)
{
    rewrite_clnt(rule, addr, reply);
    vstream_printf("%-10s %s\n", "rule", rule);
    vstream_printf("%-10s %s\n", "address", addr);
    vstream_printf("%-10s %s\n\n", "result", STR(reply));
    vstream_fflush(VSTREAM_OUT);
}

int     main(int argc, char **argv)
{
    VSTRING *reply;
    int     ch;
    char   *rule;
    char   *addr;

    msg_vstream_init(argv[0], VSTREAM_ERR);

    mail_conf_read();
    msg_info("using config files in %s", var_config_dir);
    if (chdir(var_queue_dir) < 0)
	msg_fatal("chdir %s: %m", var_queue_dir);

    while ((ch = GETOPT(argc, argv, "v")) > 0) {
	switch (ch) {
	case 'v':
	    msg_verbose++;
	    break;
	default:
	    usage(argv[0]);
	}
    }
    reply = vstring_alloc(1);

    if (argc > optind) {
	for (;;) {
	    if ((rule = argv[optind++]) == 0)
		break;
	    if ((addr = argv[optind++]) == 0)
		usage(argv[0]);
	    rewrite(rule, addr, reply);
	}
    } else {
	VSTRING *buffer = vstring_alloc(1);

	while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
	    if ((addr = split_at(STR(buffer), ' ')) == 0
		|| *(rule = STR(buffer)) == 0)
		usage(argv[0]);
	    rewrite(rule, addr, reply);
	}
	vstring_free(buffer);
    }
    vstring_free(reply);
    exit(0);
}

#endif
