/*
 *  intermediate_channel.c
 *
 *    A channel program which re-enqueues queued messages after first
 *    transforming their content with the "rot13" transformation.
 *
 *  Copyright (c) 2003, Sun Microsystems, Inc.  All Rights Reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include "mtasdk.h"

typedef struct {
     size_t  maxlen;
     char   *buf;
} rot13_buf_t;

static mta_dq_process_message_t process_message;
static mta_dq_process_done_t process_done;
static char rot13(char c);
static const char *rot13str(rot13_buf_t **dst, const char *src, size_t srclen);

main()
{
     int ires;

     /*
      *  Initialize the MTA SDK
      */
     if ((ires = mtaInit(0)))
     {
	  mtaLog("mtaInit() returned %d; %s\n", ires, mtaStrError(ires, 0));
	  return(1);
     }

     /*
      *  Start the dequeue loop
      */
     ires = mtaDequeueStart(NULL, process_message, process_done, 0);

     /*
      *  Check the return status
      */
     if (!ires)
	  /*
	   *  Success
	   */
	  exit(0);

     /*
      *  Produce an error message
      */
     mtaLog("mtaDequeueStart() returned %d; %s", ires, mtaStrError(ires, 0));

     /*
      *  And exit with an error
      */
     exit(1);
}


/*
 *  process_done -- Clean up the private context my_ctx_2 used by
 *                  process_message.
 */
static void process_done(void *my_ctx_2, void *my_ctx_1)
{
     rot13_buf_t *rbuf = (rot13_buf_t *)my_ctx_2;

     if (!my_ctx_2)
	  return;

     if (rbuf->buf)
	  free(rbuf->buf);
     free(rbuf);
}


/*
 *  process_message -- Process a single message by re-enqueuing but with
 *                     its message body converted to the rot13 character
 *                     set.  The private my_ctx_1 context is not used.
 *                     The private my_ctx_2 context is used for a
 *                     rot13 translation context.
 */
static int process_message(void **my_ctx_2, void *my_ctx_1, mta_dq_t *dq,
			   const char *env_from, size_t env_from_len)
{
     size_t len;
     const char *line, *to;
     int in_header;
     mta_nq_t *nq;

     /*
      *  Start a message enqueue
      */
     nq = NULL;
     if (mtaEnqueueStart(&nq, env_from, env_from_len, MTA_DQ_CONTEXT, dq, 0))
	  goto defer;

     /*
      *  Process the envelope recipient list
      */
     while (!mtaDequeueRecipientNext(dq, &to, &len, 0))
	  if (mtaEnqueueTo(nq, to, len, MTA_DQ_CONTEXT, dq, MTA_ENV_TO, 0) ||
	      mtaDequeueRecipientDisposition(dq, to, len,
					     MTA_DISP_DELIVERED, 0))
	       goto defer;

     /*
      *  If mta_errno == MTA_EOF, then we exited the loop normally;
      *  otherwise, there's been an error of some sort
      */
     if (mta_errno != MTA_EOF)
	  goto defer;

     /*
      *  First, get the message's header and write it
      *  unchanged to the new message being enqueued.
      */
     in_header = 1;
     while(in_header && !mtaDequeueLineNext(dq, &line, &len))
     {
	  if (mtaEnqueueWriteLine(nq, line, len, 0))
	       goto defer;
	  if (!len)
	       in_header = 0;
     }

     /*
      *  Determine why we exited the while loop
      */
     if (in_header)
     {
	  /*
	   *  We exited before seeing the body of the message
	   */
	  if (mta_errno == MTA_EOF)
	       /*
		*  Message read completely: it must have no body
		*/
	       goto done;
	  else
	       /*
		*  Error condition of some sort
		*/
	       goto defer;
     }

     /*
      *  Now rot13 the body of the message
      */
     while(!mtaDequeueLineNext(dq, &line, &len))
	  if (mtaEnqueueWriteLine(nq,
			  rot13str((rot13_buf_t **)my_ctx_2, line, len),
			  len, 0))
	       goto defer;

     /*
      *  If mta_errno == MTA_EOF, then we exited the loop normally;
      *  otherwise, there's been an error of some sort
      */
     if (mta_errno != MTA_EOF)
	  goto defer;

     /*
      *  All done, enqueue the new message
      */
done:
     if (!mtaEnqueueFinish(nq, 0) && !mtaDequeueMessageFinish(dq, 0))
	  return(0);

     /*
      *  Fall through to defer the message
      */
     nq = NULL;

     /*
      *  A processing error of some sort has occurred: defer the
      *  message for a later delivery attempt
      */
defer:
     mtaDequeueMessageFinish(dq, MTA_ABORT, 0);
     if (nq)
	  mtaEnqueueFinish(nq, MTA_ABORT, 0);
     return(MTA_NO);
}


/*
 *  rot13 -- an implmentation of the rotate-by-13 translation
 */
static char rot13(char c)
{
     if ('A' <= c && c <= 'Z') return (((c - 'A' + 13) % 26) + 'A');
     else if ('a' <= c && c <= 'z') return (((c - 'a' + 13) % 26) + 'a');
     else return (c);
}


/*
 *  rot13str -- Perform a rot13 translation on a string of text
 */
static const char *rot13str(rot13_buf_t **dst, const char *src, size_t srclen)
{
     size_t i;
     char *ptr;
     rot13_buf_t *rbuf = *dst;

     /*
      *  First call?  If so, then allocate a rot13_buf_t structure
      */
     if (!rbuf)
     {
	  rbuf = calloc(1, sizeof(rot13_buf_t));
	  if (!rbuf)
	       return(NULL);
	  *dst = rbuf;
     }

     /*
      *  Need a larger buffer?  If so, then increase the length of rbuf->buf
      */
     if (rbuf->maxlen < srclen || !rbuf->buf)
     {
	  size_t l;
	  char *tmp;

	  /* Round size up to the nearest 2k */
	  l = 2048 * (int)((srclen + 2047) / 2048);
	  tmp = (char *)malloc(l);
	  if (!tmp)
	       return(NULL);
	  if (rbuf->buf)
	       free(rbuf->buf);
	  rbuf->buf    = tmp;
	  rbuf->maxlen = l;
     }

     /*
      *  Now rot13 our input
      */
     ptr = rbuf->buf;
     for (i = 0; i < srclen; i++)
	  *ptr++ = rot13(*src++);

     /*
      *  All done
      */
     return(rbuf->buf);
}
