/*++
/* NAME
/*	anvil_clnt 3
/* SUMMARY
/*	connection count and rate management client interface
/* SYNOPSIS
/*	#include <anvil_clnt.h>
/*
/*	ANVIL_CLNT *anvil_clnt_create(void)
/*
/*	void	anvil_clnt_free(anvil_clnt)
/*	ANVIL_CLNT *anvil_clnt;
/*
/*	int	anvil_clnt_connect(anvil_clnt, service, addr,
/*					count, rate)
/*	ANVIL_CLNT *anvil_clnt;
/*	const char *service;
/*	const char *addr;
/*	int	*count;
/*	int	*rate;
/*
/*	int	anvil_clnt_mail(anvil_clnt, service, addr, msgs)
/*	ANVIL_CLNT *anvil_clnt;
/*	const char *service;
/*	const char *addr;
/*	int	*msgs;
/*
/*	int	anvil_clnt_rcpt(anvil_clnt, service, addr, rcpts)
/*	ANVIL_CLNT *anvil_clnt;
/*	const char *service;
/*	const char *addr;
/*	int	*rcpts;
/*
/*	int	anvil_clnt_newtls(anvil_clnt, service, addr, newtls)
/*	ANVIL_CLNT *anvil_clnt;
/*	const char *service;
/*	const char *addr;
/*	int	*newtls;
/*
/*	int	anvil_clnt_newtls_stat(anvil_clnt, service, addr, newtls)
/*	ANVIL_CLNT *anvil_clnt;
/*	const char *service;
/*	const char *addr;
/*	int	*newtls;
/*
/*	int	anvil_clnt_auth(anvil_clnt, service, addr, auths)
/*	ANVIL_CLNT *anvil_clnt;
/*	const char *service;
/*	const char *addr;
/*	int	*auths;
/*
/*	int	anvil_clnt_disconnect(anvil_clnt, service, addr)
/*	ANVIL_CLNT *anvil_clnt;
/*	const char *service;
/*	const char *addr;
/*
/*	int	anvil_clnt_lookup(anvil_clnt, service, addr, count,
/*					rate, msgs, rcpts, ntls, auths)
/*	ANVIL_CLNT *anvil_clnt;
/*	const char *service;
/*	const char *addr;
/*	int	*count;
/*	int	*rate;
/*	int	*msgs;
/*	int	*rcpts;
/*	int	*ntls;
/*	int	*auths;
/* DESCRIPTION
/*	anvil_clnt_create() instantiates a local anvil service
/*	client endpoint.
/*
/*	anvil_clnt_connect() informs the anvil server that a
/*	remote client has connected, and returns the current
/*	connection count and connection rate for that remote client.
/*
/*	anvil_clnt_mail() registers a MAIL FROM event and
/*	returns the current MAIL FROM rate for the specified remote
/*	client.
/*
/*	anvil_clnt_rcpt() registers a RCPT TO event and
/*	returns the current RCPT TO rate for the specified remote
/*	client.
/*
/*	anvil_clnt_newtls() registers a remote client request
/*	to negotiate a new (uncached) TLS session and returns the
/*	current newtls request rate for the specified remote client.
/*
/*	anvil_clnt_newtls_stat() returns the current newtls request
/*	rate for the specified remote client.
/*
/*	anvil_clnt_auth() registers an AUTH event and returns the
/*	current AUTH event rate for the specified remote client.
/*
/*	anvil_clnt_disconnect() informs the anvil server that a remote
/*	client has disconnected.
/*
/*	anvil_clnt_lookup() returns the current count and rate
/*	information for the specified client.
/*
/*	anvil_clnt_free() destroys a local anvil service client
/*	endpoint.
/*
/*	Arguments:
/* .IP anvil_clnt
/*	Client rate control service handle.
/* .IP service
/*	The service that the remote client is connected to.
/* .IP addr
/*	Null terminated string that identifies the remote client.
/* .IP count
/*	Pointer to storage for the current number of connections from
/*	this remote client.
/* .IP rate
/*	Pointer to storage for the current connection rate for this
/*	remote client.
/* .IP msgs
/*	Pointer to storage for the current message rate for this
/*	remote client.
/* .IP rcpts
/*	Pointer to storage for the current recipient rate for this
/*	remote client.
/* .IP newtls
/*	Pointer to storage for the current "new TLS session" rate
/*	for this remote client.
/* .IP auths
/*	Pointer to storage for the current AUTH event rate for this
/*	remote client.
/* DIAGNOSTICS
/*	The update and status query routines return
/*	ANVIL_STAT_OK in case of success, ANVIL_STAT_FAIL otherwise
/*	(either the communication with the server is broken or the
/*	server experienced a problem).
/* SEE ALSO
/*	anvil(8), connection/rate limiting
/* 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>

/* Utility library. */

#include <mymalloc.h>
#include <msg.h>
#include <attr_clnt.h>
#include <stringops.h>

/* Global library. */

#include <mail_proto.h>
#include <mail_params.h>
#include <anvil_clnt.h>

/* Application specific. */

#define ANVIL_IDENT(service, addr) \
    printable(concatenate(service, ":", addr, (char *) 0), '?')

/* anvil_clnt_handshake - receive server protocol announcement */

static int anvil_clnt_handshake(VSTREAM *stream)
{
    return (attr_scan_plain(stream, ATTR_FLAG_STRICT,
		    RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_ANVIL),
			    ATTR_TYPE_END));
}

/* anvil_clnt_create - instantiate connection rate service client */

ANVIL_CLNT *anvil_clnt_create(void)
{
    ATTR_CLNT *anvil_clnt;

    /*
     * Use whatever IPC is preferred for internal use: UNIX-domain sockets or
     * Solaris streams.
     */
#ifndef VAR_ANVIL_SERVICE
    anvil_clnt = attr_clnt_create("local:" ANVIL_CLASS "/" ANVIL_SERVICE,
				  var_ipc_timeout, 0, 0);
#else
    anvil_clnt = attr_clnt_create(var_anvil_service, var_ipc_timeout, 0, 0);
#endif
    attr_clnt_control(anvil_clnt,
		      ATTR_CLNT_CTL_HANDSHAKE, anvil_clnt_handshake,
		      ATTR_CLNT_CTL_END);
    return ((ANVIL_CLNT *) anvil_clnt);
}

/* anvil_clnt_free - destroy connection rate service client */

void    anvil_clnt_free(ANVIL_CLNT *anvil_clnt)
{
    attr_clnt_free((ATTR_CLNT *) anvil_clnt);
}

/* anvil_clnt_lookup - status query */

int     anvil_clnt_lookup(ANVIL_CLNT *anvil_clnt, const char *service,
			          const char *addr, int *count, int *rate,
		             int *msgs, int *rcpts, int *newtls, int *auths)
{
    char   *ident = ANVIL_IDENT(service, addr);
    int     status;

    if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
			  ATTR_FLAG_NONE,	/* Query attributes. */
			  SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_LOOKUP),
			  SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
			  ATTR_TYPE_END,
			  ATTR_FLAG_MISSING,	/* Reply attributes. */
			  RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
			  RECV_ATTR_INT(ANVIL_ATTR_COUNT, count),
			  RECV_ATTR_INT(ANVIL_ATTR_RATE, rate),
			  RECV_ATTR_INT(ANVIL_ATTR_MAIL, msgs),
			  RECV_ATTR_INT(ANVIL_ATTR_RCPT, rcpts),
			  RECV_ATTR_INT(ANVIL_ATTR_NTLS, newtls),
			  RECV_ATTR_INT(ANVIL_ATTR_AUTH, auths),
			  ATTR_TYPE_END) != 7)
	status = ANVIL_STAT_FAIL;
    else if (status != ANVIL_STAT_OK)
	status = ANVIL_STAT_FAIL;
    myfree(ident);
    return (status);
}

/* anvil_clnt_connect - heads-up and status query */

int     anvil_clnt_connect(ANVIL_CLNT *anvil_clnt, const char *service,
			           const char *addr, int *count, int *rate)
{
    char   *ident = ANVIL_IDENT(service, addr);
    int     status;

    if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
			  ATTR_FLAG_NONE,	/* Query attributes. */
			  SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_CONN),
			  SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
			  ATTR_TYPE_END,
			  ATTR_FLAG_MISSING,	/* Reply attributes. */
			  RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
			  RECV_ATTR_INT(ANVIL_ATTR_COUNT, count),
			  RECV_ATTR_INT(ANVIL_ATTR_RATE, rate),
			  ATTR_TYPE_END) != 3)
	status = ANVIL_STAT_FAIL;
    else if (status != ANVIL_STAT_OK)
	status = ANVIL_STAT_FAIL;
    myfree(ident);
    return (status);
}

/* anvil_clnt_mail - heads-up and status query */

int     anvil_clnt_mail(ANVIL_CLNT *anvil_clnt, const char *service,
			        const char *addr, int *msgs)
{
    char   *ident = ANVIL_IDENT(service, addr);
    int     status;

    if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
			  ATTR_FLAG_NONE,	/* Query attributes. */
			  SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_MAIL),
			  SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
			  ATTR_TYPE_END,
			  ATTR_FLAG_MISSING,	/* Reply attributes. */
			  RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
			  RECV_ATTR_INT(ANVIL_ATTR_RATE, msgs),
			  ATTR_TYPE_END) != 2)
	status = ANVIL_STAT_FAIL;
    else if (status != ANVIL_STAT_OK)
	status = ANVIL_STAT_FAIL;
    myfree(ident);
    return (status);
}

/* anvil_clnt_rcpt - heads-up and status query */

int     anvil_clnt_rcpt(ANVIL_CLNT *anvil_clnt, const char *service,
			        const char *addr, int *rcpts)
{
    char   *ident = ANVIL_IDENT(service, addr);
    int     status;

    if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
			  ATTR_FLAG_NONE,	/* Query attributes. */
			  SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_RCPT),
			  SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
			  ATTR_TYPE_END,
			  ATTR_FLAG_MISSING,	/* Reply attributes. */
			  RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
			  RECV_ATTR_INT(ANVIL_ATTR_RATE, rcpts),
			  ATTR_TYPE_END) != 2)
	status = ANVIL_STAT_FAIL;
    else if (status != ANVIL_STAT_OK)
	status = ANVIL_STAT_FAIL;
    myfree(ident);
    return (status);
}

/* anvil_clnt_newtls - heads-up and status query */

int     anvil_clnt_newtls(ANVIL_CLNT *anvil_clnt, const char *service,
			          const char *addr, int *newtls)
{
    char   *ident = ANVIL_IDENT(service, addr);
    int     status;

    if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
			  ATTR_FLAG_NONE,	/* Query attributes. */
			  SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_NTLS),
			  SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
			  ATTR_TYPE_END,
			  ATTR_FLAG_MISSING,	/* Reply attributes. */
			  RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
			  RECV_ATTR_INT(ANVIL_ATTR_RATE, newtls),
			  ATTR_TYPE_END) != 2)
	status = ANVIL_STAT_FAIL;
    else if (status != ANVIL_STAT_OK)
	status = ANVIL_STAT_FAIL;
    myfree(ident);
    return (status);
}

/* anvil_clnt_newtls_stat - status query */

int     anvil_clnt_newtls_stat(ANVIL_CLNT *anvil_clnt, const char *service,
			               const char *addr, int *newtls)
{
    char   *ident = ANVIL_IDENT(service, addr);
    int     status;

    if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
			  ATTR_FLAG_NONE,	/* Query attributes. */
			  SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_NTLS_STAT),
			  SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
			  ATTR_TYPE_END,
			  ATTR_FLAG_MISSING,	/* Reply attributes. */
			  RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
			  RECV_ATTR_INT(ANVIL_ATTR_RATE, newtls),
			  ATTR_TYPE_END) != 2)
	status = ANVIL_STAT_FAIL;
    else if (status != ANVIL_STAT_OK)
	status = ANVIL_STAT_FAIL;
    myfree(ident);
    return (status);
}

/* anvil_clnt_auth - heads-up and status query */

int     anvil_clnt_auth(ANVIL_CLNT *anvil_clnt, const char *service,
			        const char *addr, int *auths)
{
    char   *ident = ANVIL_IDENT(service, addr);
    int     status;

    if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
			  ATTR_FLAG_NONE,	/* Query attributes. */
			  SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_AUTH),
			  SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
			  ATTR_TYPE_END,
			  ATTR_FLAG_MISSING,	/* Reply attributes. */
			  RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
			  RECV_ATTR_INT(ANVIL_ATTR_RATE, auths),
			  ATTR_TYPE_END) != 2)
	status = ANVIL_STAT_FAIL;
    else if (status != ANVIL_STAT_OK)
	status = ANVIL_STAT_FAIL;
    myfree(ident);
    return (status);
}

/* anvil_clnt_disconnect - heads-up only */

int     anvil_clnt_disconnect(ANVIL_CLNT *anvil_clnt, const char *service,
			              const char *addr)
{
    char   *ident = ANVIL_IDENT(service, addr);
    int     status;

    if (attr_clnt_request((ATTR_CLNT *) anvil_clnt,
			  ATTR_FLAG_NONE,	/* Query attributes. */
			  SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_DISC),
			  SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident),
			  ATTR_TYPE_END,
			  ATTR_FLAG_MISSING,	/* Reply attributes. */
			  RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status),
			  ATTR_TYPE_END) != 1)
	status = ANVIL_STAT_FAIL;
    else if (status != ANVIL_STAT_OK)
	status = ANVIL_STAT_FAIL;
    myfree(ident);
    return (status);
}

#ifdef TEST

 /*
  * Stand-alone client for testing.
  */
#include <unistd.h>
#include <string.h>
#include <msg_vstream.h>
#include <mail_conf.h>
#include <mail_params.h>
#include <vstring_vstream.h>

static void usage(void)
{
    vstream_printf("usage: "
		   ANVIL_REQ_CONN " service addr | "
		   ANVIL_REQ_DISC " service addr | "
		   ANVIL_REQ_MAIL " service addr | "
		   ANVIL_REQ_RCPT " service addr | "
		   ANVIL_REQ_NTLS " service addr | "
		   ANVIL_REQ_NTLS_STAT " service addr | "
		   ANVIL_REQ_AUTH " service addr | "
		   ANVIL_REQ_LOOKUP " service addr\n");
}

int     main(int unused_argc, char **argv)
{
    VSTRING *inbuf = vstring_alloc(1);
    char   *bufp;
    char   *cmd;
    ssize_t cmd_len;
    char   *service;
    char   *addr;
    int     count;
    int     rate;
    int     msgs;
    int     rcpts;
    int     newtls;
    int     auths;
    ANVIL_CLNT *anvil;

    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);

    msg_verbose++;

    anvil = anvil_clnt_create();

    while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) {
	bufp = vstring_str(inbuf);
	if ((cmd = mystrtok(&bufp, " ")) == 0 || *bufp == 0
	    || (service = mystrtok(&bufp, " ")) == 0 || *service == 0
	    || (addr = mystrtok(&bufp, " ")) == 0 || *addr == 0
	    || mystrtok(&bufp, " ") != 0) {
	    vstream_printf("bad command syntax\n");
	    usage();
	    vstream_fflush(VSTREAM_OUT);
	    continue;
	}
	cmd_len = strlen(cmd);
	if (strncmp(cmd, ANVIL_REQ_CONN, cmd_len) == 0) {
	    if (anvil_clnt_connect(anvil, service, addr, &count, &rate) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("count=%d, rate=%d\n", count, rate);
	} else if (strncmp(cmd, ANVIL_REQ_MAIL, cmd_len) == 0) {
	    if (anvil_clnt_mail(anvil, service, addr, &msgs) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", msgs);
	} else if (strncmp(cmd, ANVIL_REQ_RCPT, cmd_len) == 0) {
	    if (anvil_clnt_rcpt(anvil, service, addr, &rcpts) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", rcpts);
	} else if (strncmp(cmd, ANVIL_REQ_NTLS, cmd_len) == 0) {
	    if (anvil_clnt_newtls(anvil, service, addr, &newtls) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", newtls);
	} else if (strncmp(cmd, ANVIL_REQ_AUTH, cmd_len) == 0) {
	    if (anvil_clnt_auth(anvil, service, addr, &auths) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", auths);
	} else if (strncmp(cmd, ANVIL_REQ_NTLS_STAT, cmd_len) == 0) {
	    if (anvil_clnt_newtls_stat(anvil, service, addr, &newtls) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("rate=%d\n", newtls);
	} else if (strncmp(cmd, ANVIL_REQ_DISC, cmd_len) == 0) {
	    if (anvil_clnt_disconnect(anvil, service, addr) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("OK\n");
	} else if (strncmp(cmd, ANVIL_REQ_LOOKUP, cmd_len) == 0) {
	    if (anvil_clnt_lookup(anvil, service, addr, &count, &rate, &msgs,
				  &rcpts, &newtls, &auths) != ANVIL_STAT_OK)
		msg_warn("error!");
	    else
		vstream_printf("count=%d, rate=%d msgs=%d rcpts=%d newtls=%d "
			     "auths=%d\n", count, rate, msgs, rcpts, newtls,
			       auths);
	} else {
	    vstream_printf("bad command: \"%s\"\n", cmd);
	    usage();
	}
	vstream_fflush(VSTREAM_OUT);
    }
    vstring_free(inbuf);
    anvil_clnt_free(anvil);
    return (0);
}

#endif
