/*
 * Copyright (c) 2004, 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: qmgrctl.c,v 1.11 2005/11/28 06:08:33 ca Exp $")
#include "sm/common.h"
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/ctype.h"
#include "sm/fcntl.h"
#include "sm/mta.h"
#include "sm/rcb.h"
#include "sm/unixsock.h"
#include "sm/reccom.h"
#include "sm/util.h"
#include "sm/sysexits.h"

#define SM_RCBSIZE	1024
#define SM_MAXRCBSIZE	(64 * 1024)

static int Verbose = 0;

/*
**  USAGE -- show usage
**
**	Parameters:
**		prg -- program name
**
**	Returns:
**		does not return
*/

static void
usage(const char *prg)
{
	sm_io_fprintf(smioerr,
		"usage: %s [options] socket\n"
		"interact with qmgr to receive status information [default]\n"
		"or request some actions.\n"
		"socket must be the path name of the control_socket of qmgr\n"
		"options\n"
		"-d n        set QMGR debug level\n"
/*
		"-R rcpt-id  remove recipient from queue\n"
*/
		"-r          reload QMGR maps\n"
		"-s rcpt-id  schedule recipient for delivery as soon as possible\n"
/*
		"-T ta-id    remove entire transaction from queue\n"
*/
		"-V          increase verbosity\n"
		"-x c[.l]    set QMGR debug level for category c to l\n"
		, prg);
	exit(EX_USAGE);
}

/*
**  QPRTBUF -- print content of buffer (unprintable char as hex)
**
**	Parameters:
**		fp -- file for output
**		buf -- buffer to print
**		len -- len of buffer
**
**	Returns:
**		nothing
*/

static void
qprtbuf(sm_file_T *fp, const uchar *buf, size_t len)
{
	size_t i;
	int c;

	for (i = 0; i < len; i++)
	{
		c = (int) (buf[i] & 0xff);
		if (ISPRINT(c) || c == '\n' || c == '\r')
			sm_io_putc(fp, c);
		else
			sm_io_fprintf(fp, " %02x ", c);
	}
}

/*
**  QSENDRCB -- send an rcb
**
**	Parameters:
**		rcb -- RCB
**		fd -- fd
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qsendrcb(sm_rcb_P rcb, rcb_fd_T fd)
{
	int res;
	sm_ret_T ret;

	ret = sm_rcb_open_snd(rcb);
	do
	{
		res = sm_rcb_snd(fd, rcb);
	} while (res > 0);
	ret = sm_rcb_close_snd(rcb);
	if (res != 0)
	{
		sm_io_fprintf(smioerr,
			"write_fd failed=%d, len=%d, errno=%d\n",
			res, (int) sm_rcb_getlen(rcb), errno);
		ret = sm_error_perm(SM_EM_Q, EIO);
	}
	return ret;
}

/*
**  QRCVRCB -- rcv rcb
**
**	Parameters:
**		rcb -- RCB
**		fd -- fd
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qrcvrcb(sm_rcb_P rcb, rcb_fd_T fd)
{
	sm_ret_T ret;
	uint32_t rt, val, len;
	uchar buf[SM_MAXRCBSIZE];

	ret = sm_rcb_open_rcv(rcb);
	val = 0;
	len = 0;
	do
	{
		ret = sm_rcb_rcv(fd, rcb, 16);
	} while (ret > 0);
	if (ret < 0)
		return ret;
	ret = sm_rcb_close_rcv(rcb);

	ret = sm_rcb_open_dec(rcb);
	if (sm_is_err(ret))
		return ret;

	/* total length of record */
	ret = sm_rcb_getuint32(rcb, &len);
	if (sm_is_err(ret))
		goto error;
	if (len > sm_rcb_getlen(rcb))
	{
		ret = sm_error_perm(SM_EM_Q, SM_E_RCB2LONG);
		goto error;
	}

	/* protocol header: version */
	ret = sm_rcb_get3uint32(rcb, &len, &rt, &val);
	if (sm_is_err(ret))
		goto error;
	if (len != 4 || rt != RT_PROT_VER || val != PROT_VER_RT)
	{
		ret = sm_error_perm(SM_EM_Q, SM_E_PR_V_MISM);
		goto error;
	}

	do
	{
		ret = sm_rcb_get2uint32(rcb, &len, &rt);
		if (sm_is_err(ret))
			goto error;

		switch (rt)
		{
		  case 0:
			ret = sm_rcb_getuint32(rcb, &val);
			break;
		  case RT_Q2CTL_INFO:
			if (len < sizeof(buf) - 1)
			{
				sm_memzero(buf, sizeof(buf));
				ret = sm_rcb_getn(rcb, buf, len);
				if (sm_is_err(ret))
					break;
				qprtbuf(smioerr, buf, len);
				sm_io_putc(smioerr, '\n');
			}
			break;
		  default:
			goto error;
			break;
		}
	} while (!SM_RCB_ISEOB(rcb));
	ret = sm_rcb_close_dec(rcb);
	return ret;

  error:
	(void) sm_rcb_close_decn(rcb);
	return ret;
}

/*
**  QQUERY -- send and receive rcb
**
**	Parameters:
**		prg -- name of program
**		sockname -- name of socket
**		cmd -- command to send
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qquery(const char *prg, const char *sockname, int cmd, uint32_t v, uint32_t v2, rcpt_id_P rcpt_id, sessta_id_P ta_ss_id)
{
	int fd;
	bool reply;
	sm_rcb_P rcb;
	sm_ret_T ret;

	rcb = NULL;
	reply = false;
	ret = unix_client_connect(sockname, &fd);
	if (sm_is_err(ret))
	{
		sm_io_fprintf(smioerr, "%s: cannot connect to %s, error=%#X\n"
			, prg, sockname, ret);
		return ret;
	}
	if (!is_valid_fd(fd))
		return sm_error_perm(SM_EM_Q, EINVAL);

	rcb = sm_rcb_new(NULL, SM_RCBSIZE, SM_MAXRCBSIZE);
	if (rcb == NULL)
	{
		ret = sm_error_temp(SM_EM_Q, ENOMEM);
		goto done;
	}

	switch (cmd)
	{
	  case 0:
		ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1,
			SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
			SM_RCBV_INT, RT_CTL2Q_INFO, 0,
			SM_RCBV_END);
		reply = true;
		break;

	  case RT_CTL2Q_DBG:
		ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1,
			SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
			SM_RCBV_INT, cmd, v,
			SM_RCBV_END);
		break;
	  case RT_CTL2Q_DBG_C:
		ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1,
			SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
			SM_RCBV_INT2, cmd, v, v2,
			SM_RCBV_END);
		break;

	  case RT_CTL2Q_S_RCPT:
	  case RT_CTL2Q_D_RCPT:
		ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1,
			SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
			SM_RCBV_BUF, cmd, rcpt_id, SMTP_RCPTID_SIZE,
			SM_RCBV_END);
		break;

	  case RT_CTL2Q_D_TA:
		ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1,
			SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
			SM_RCBV_BUF, cmd, ta_ss_id, SMTP_STID_SIZE,
			SM_RCBV_END);
		break;

#if 0
	  default:
		ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1,
			SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
			SM_RCBV_STR, rt, str,
			SM_RCBV_END);
#endif
	}

	ret = qsendrcb(rcb, fd);
	if (sm_is_err(ret))
		goto done;
	if (reply)
	{
		ret = qrcvrcb(rcb, fd);
		if (sm_is_err(ret) && Verbose > 3)
			sm_io_fprintf(smioerr, "clt: rcvrcb=%#x\n", ret);
	}

  done:
	if (rcb != NULL)
		sm_rcb_free(rcb);
	if (is_valid_fd(fd))
		close(fd);
	return ret;
}

int
main(int argc, char *argv[])
{
	int c, cmd;
	uint32_t v, v2;
	ulong first, last, level;
	sm_ret_T ret;
	char *sockname, *prg, *cptr;
	rcpt_id_T rcpt_id;
	sessta_id_T ta_ss_id;

	prg = argv[0];
	if (getuid() == 0 || geteuid() == 0)
	{
		sm_io_fprintf(smioerr, SM_DONTRUNASROOT "\n", prg);
		exit(EX_USAGE);
	}
	opterr = 0;
	Verbose = 0;
	cmd = 0;
	sockname = NULL;
	rcpt_id[0] = '\0';
	ta_ss_id[0] = '\0';
	v = v2 = 0;
	while ((c = getopt(argc, argv, "d:R:rs:T:Vx:")) != -1)
	{
		switch (c)
		{
		  case 'd':
			v = (uint) strtoul(optarg, NULL, 0);
			break;
		  case 'R':
			cmd = RT_CTL2Q_D_RCPT;
			if (strlen(optarg) != SMTP_RCPTID_LEN - 1)
				usage(prg);
			if (strlcpy(rcpt_id, optarg, sizeof(rcpt_id))
			    >= sizeof(rcpt_id))
				usage(prg);
			break;
		  case 'r':
			cmd = RT_CTL2Q_R_MAP;
			break;
		  case 's':
			cmd = RT_CTL2Q_S_RCPT;
			if (strlen(optarg) != SMTP_RCPTID_LEN - 1)
			{
				sm_io_fprintf(smioerr,
					"%s: expected_len=%d, got=%d\n", prg
					, SMTP_RCPTID_LEN - 1, strlen(optarg));
				usage(prg);
			}
			if (strlcpy(rcpt_id, optarg, sizeof(rcpt_id))
			    >= sizeof(rcpt_id))
			{
				sm_io_fprintf(smioerr,
					"%s: expected_len=%d, copied=%d\n", prg
					, sizeof(rcpt_id), strlen(optarg));
				usage(prg);
			}
			break;
		  case 'T':
			cmd = RT_CTL2Q_D_TA;
			if (strlen(optarg) != SMTP_STID_SIZE - 1)
				usage(prg);
			if (strlcpy(ta_ss_id, optarg, sizeof(ta_ss_id))
			    >= sizeof(ta_ss_id))
				usage(prg);
			break;
		  case 'V':
			++Verbose;
			break;
		  case 'x':
			cptr = optarg;
			ret = sm_parse_ct_lvl(&cptr, &first, &last, &level);
			if (sm_is_err(ret) || first != last)
				usage(prg);
			v = first;
			v2 = last;
			break;
		  default:
			usage(prg);
		}
	}
	argc -= optind;
	argv += optind;
	if (argc <= 0)
		usage(prg);
	sockname = argv[0];
	qquery(prg, sockname, cmd, v, v2, rcpt_id, ta_ss_id);
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1