#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <eps.h>

#define DEBUG
#define MAX_FAX_VARS 10
#define MAX_VAR_LEN 255
#define DEFAULT_IMAGE_SIZE 10240
#define TMPDIR "/var/tmp"
#define OUTDIR "/var/spool/asterisk/outgoing"

typedef struct __fvar_ {
  char name[MAX_VAR_LEN],
       data[MAX_VAR_LEN];
} fvar_t;

static fvar_t f_vars[MAX_FAX_VARS];
static int f_vars_num = 0;
static line_t *image = NULL;
static char p_number[30] = { 0 },
            p_file[255] = { 0 },
	    pc_file[255] = { 0 },
	    *c_template = NULL;

static int process_recipient(const char *);
static int user_is_number(const char *);
static int process_image(eps_t *);
static int send_fax(void);
static const char *send_fax_variable(const char *);
static int set_fax_variable(const char *, const char *);

int main(int argc, char *argv[])
{
  char *l = NULL;
  eps_t *eps = NULL;
  header_t *h = NULL;
  int fd = 0, ret = 0, df = 0, i = 0;

  if (argc > 1)
     c_template = argv[1];
  else
     c_template = "/usr/local/share/astfax/ast_fax.call";

  f_vars_num = 0;

  for (i = 0; i < MAX_FAX_VARS; i++)
      memset(&f_vars[i], 0, sizeof(fvar_t));

  /*
     Stream from stdin
  */

  fd = 0;
  
  eps = eps_begin(INTERFACE_STREAM, &fd);
  if (eps == NULL) {
#ifdef DEBUG
     fprintf(stderr, "ast_fax: eps_begin(INTERFACE_STREAM, 0) failed\n");
#endif
     return 1;
  }

  /*
     Run through headers
  */

  memset(p_number, 0, sizeof(p_number));

  for (h = eps_next_header(eps); h; h = eps_next_header(eps)) {
      if ((!(strcasecmp(h->name, "To"))) || (!(strcasecmp(h->name, "Cc")))) {
         ret = process_recipient(h->data);
         if (!ret) {
#ifdef DEBUG
            fprintf(stderr, "ast_fax: process_to(%s) failed\n", h->data);
#endif
            eps_end(eps);
            return 1;
         }
      }
  }

  if (!(*p_number)) {
#ifdef DEBUG
     fprintf(stderr, "ast_fax: no numbers found\n");
#endif
     eps_end(eps);
     return 1;
  }

  ret = set_fax_variable("PHONE", p_number);
  if (!ret) {
#ifdef DEBUG
     fprintf(stderr, "ast_fax: set_fax_variable(\"PHONE\", %s) failed\n", p_number);
#endif
     eps_end(eps);
     return 1;
  }

  /*
     Make sure we're multipart
  */

  if (!(eps->content_type & CON_MULTI)) {
#ifdef DEBUG
     fprintf(stderr, "ast_fax: message is not multipart\n");
#endif
     eps_end(eps);
     return 1;
  }

  /*
     Skip main body
  */

  for (l = eps_next_line(eps); l; l = eps_next_line(eps));

  /*
     Look for attachments
  */

  while(!(eps_is_eof(eps))) {
    df = 0;

    ret = mime_init_stream(eps);
    if (!ret)
       break;

    /*
       Make sure we've got a TIFF image
    */

    for (h = mime_next_header(eps); h; h = mime_next_header(eps)) {
        if (!(strcasecmp(h->name, "Content-type"))) {
           if ((h->atoms) && (h->atoms->next->data)) {
              if (!(strcasecmp(h->atoms->next->data, "image/tiff")))
                 df = 1;
           }
        }
    }

    if (df) {
       if (eps->m->encoding == ENC_BASE64) {
          ret = process_image(eps);
          if (!ret) {
#ifdef DEBUG
             fprintf(stderr, "ast_fax: process_image failed\n");
#endif
             eps_end(eps);
             return 1;
          }
       }
 
#ifdef DEBUG
       else {
          fprintf(stderr, "ast_fax: unsupported encoding scheme\n");
       }
#endif
    }

    /*
       Skip the attachment body
    */

    else {
       for (l = mime_next_line(eps); l; l = mime_next_line(eps));
    }
  }

  /*
     Done with EPS
  */

  eps_end(eps);

  if (!(*p_file)) {
#ifdef DEBUG
     fprintf(stderr, "ast_fax: no image could be processed\n");
#endif
     return 1;
  }

  /*
     Pass the fax to Asterisk
  */

  ret = send_fax();
  if (!ret) {
#ifdef DEBUG
     fprintf(stderr, "ast_fax: send_fax failed\n");
#endif
     return 1;
  }

  fprintf(stderr, "ast_fax: fax sent\n");
  fflush(stderr);
  
  return 0;
}

/*
   Determine if the To header contains
   a phone number.  Check if it's valid,
   and save it.
*/
static int process_recipient(const char *data)
{
  int len = 0;
  group_t *g = NULL;
  address_t *a = NULL;

  /*
     Parse addresses looking for a phone number
  */

  g = address_evaluate(data);
  if (g == NULL) {
#ifdef DEBUG
     fprintf(stderr, "process_recipient: address_evaluate(%s) failed\n", data);
#endif
     return 0;
  }

  if ((!(g->nmembers)) || (g->members == NULL)) {
#ifdef DEBUG
     fprintf(stderr, "process_recipient: address_evaluate(%s): no addresses found\n", data);
#endif
     address_kill(g);
     return 1;
  }

  /*
     Look for a phone number
  */

  for (a = g->members; a->next; a = a->next) {
      if (!(a->next->user))
         continue;

      if (user_is_number(a->next->user)) {
	 if (*p_number) {
#ifdef DEBUG
            fprintf(stderr, "process_recipient: found more than one number, ignoring %s\n", a->next->user);
#endif
            continue;
         }

         len = strlen(a->next->user);
         if (len >= (sizeof(p_number) - 1))
            len = (sizeof(p_number) - 1);

         memcpy(p_number, a->next->user, len);
      }
  }

  address_kill(g);
  return 1;
}

/*
   Validate username is entirely made up of numbers
*/
static int user_is_number(const char *user)
{
  int i = 0;
  const char *p = NULL;
  const char vn[] = "0123456789";

  for (p = user; *p; p++) {
      for (i = 0; vn[i]; i++) {
          if (*p == vn[i])
             break;
      }

      if (!(vn[i]))
         return 0;
  }

  return 1;
}

/*
   Decode a base64 encoded image
*/
static int process_image(eps_t *eps)
{
  base64_t b;
  char *l = NULL;
  int ret = 0, fd = 0, i = 0, num = 0;

  base64_init(&b);

  image = line_alloc();
  if (image == NULL) {
#ifdef DEBUG
     fprintf(stderr, "process_image: line_alloc failed\n");
#endif
     return 0;
  }

  ret = line_init(image, NULL, DEFAULT_IMAGE_SIZE);
  if (!ret) {
#ifdef DEBUG
     fprintf(stderr, "process_image: line_init(%d) failed\n", DEFAULT_IMAGE_SIZE);
#endif
     return 0;
  }

  for (l = mime_next_line(eps); l; l = mime_next_line(eps)) {
      ret = base64_decode(&b, image, l);
      if (!ret) {
#ifdef DEBUG
         fprintf(stderr, "process_image: base64_decode(%p, %p, ...)\n", &b, image);
#endif
         return 0;         
      }
  }

  /*
     Create a temporary file
  */

  fd = -1;
  num = rand();

  for (i = 0; i < 10; i++) {      
      memset(p_file, 0, sizeof(p_file));
      snprintf(p_file, sizeof(p_file), "%s/ast_fax-%d%c%lu%c%d%c%d", TMPDIR, (int)time(NULL), '.',
               image->size, '.', num, '.', i);

      fd = open(p_file, O_WRONLY|O_CREAT|O_EXCL, 0644);
#ifdef DEBUG
      if (fd == -1)
         fprintf(stderr, "process_image: open(%s, O_WRONLY|O_CREAT|O_EXCL, 0644) failed\n", p_file);
#endif

      else
         break;
  }

  if (fd == -1) {
#ifdef DEBUG
     fprintf(stderr, "process_image: couldnt create temporary storage\n");
#endif
     memset(p_file, 0, sizeof(p_file));
     line_kill(image);
     return 0;
  }

  ret = write(fd, image->data, image->bytes);
  if (ret != image->bytes) {
#ifdef DEBUG
     fprintf(stderr, "process_image: write(%d, ..., %lu) failed\n", fd, image->bytes);
#endif
     line_kill(image);
     return 0;
  }

  close(fd);
  line_kill(image);

  ret = set_fax_variable("FILE", p_file);
  if (!ret) {
#ifdef DEBUG
     fprintf(stderr, "process_image: set_fax_variable(\"FILE\", %s) failed\n",
	p_file);
#endif
     return 0;
  }

  return 1;
}

/*
   Hand over the Fax information to Asterisk
*/
static int send_fax(void)
{
  int ret = 0;
  const char *data = NULL;
  FILE *stream = NULL, *out = NULL;
  char b[255] = { 0 }, *h = NULL, *t = NULL, ob[255] = { 0 }, *p = NULL;

  /*
     Generate our call file from template
  */

  stream = fopen(c_template, "r");
  if (stream == NULL) {
#ifdef DEBUG
     fprintf(stderr, "send_fax: fopen(%s,\"r\") failed\n", c_template);
#endif
     return 0;
  }

  memset(pc_file, 0, sizeof(pc_file));
  snprintf(pc_file, sizeof(pc_file), "%s.call", p_file);

  out = fopen(pc_file, "w+");
  if (out == NULL) {
#ifdef DEBUG
     fprintf(stderr, "send_fax: fopen(%s, \"w+\") failed\n", pc_file);
#endif
     fclose(stream);
     return 0;
  }

  while(!(feof(stream))) {
    memset(b, 0, sizeof(b));
    fgets(b, sizeof(b), stream);

    if ((*b == '#') || (*b == ';') || (*b == '\0'))
       continue;

    p = ob;
    memset(ob, 0, sizeof(ob));

    /*
       Generate call file from our very simple template
    */

    for (h = b, t = NULL; ((*h) && (p < (ob + (sizeof(ob) - 1)))); h++) {
        if ((*h == '$') && (*(h + 1) == '[') && (t == NULL)) {
           t = (h + 2);
           h++;
        }

        else if ((*h == ']') && (t != NULL)) {
           *h = '\0';

           data = send_fax_variable(t);
           if (data == NULL) {
#ifdef DEBUG
              fprintf(stderr, "send_fax: template contains unknown variable '%s'\n", t);
#endif
              fclose(stream);
              fclose(out);
              return 0;
           }

           for (t = (char *)data; ((*t) && (p < (ob + (sizeof(ob) - 1)))); t++)
               *p++ = *t;
               
           t = NULL;
        }

        else if (t == NULL)
           *p++ = *h;
    }

    /*
       Syntax error occured
    */

    if (t) {
#ifdef DEBUG
       fprintf(stderr, "send_fax: template contains unterminated variable '%s'\n", t);
#endif
       fclose(stream);
       fclose(out);
       return 0;
    }

    /*
       Write template-generated line to call file
    */

    ret = fwrite(ob, (p - ob), 1, out);
    if (ret != 1) {
#ifdef DEBUG
       fprintf(stderr, "send_fax: fwrite(...) failed; wrote %d/%d bytes\n",
		ret, (p - ob));
#endif
       fclose(stream);
       fclose(out);
       return 0;
    }
  }

  fclose(stream);
  fclose(out);

  /*
     Call file has been generated; hand it over to Asterisk
  */

  for (p = (pc_file + strlen(pc_file)); ((*p != '/') && (p > pc_file)); p--)

  if (p == pc_file) {
#ifdef DEBUG
     fprintf(stderr, "send_fax: unexpected syntax for call file path\n");
#endif
     return 0;
  }

  memset(b, 0, sizeof(b));
  snprintf(b, sizeof(b), "%s/%s", OUTDIR, p);

  ret = rename(pc_file, b);
  if (ret == -1) {
#ifdef DEBUG
     fprintf(stderr, "send_fax: rename(%s,%s) failed; errno=%d\n",
	pc_file, b, errno);
#endif
     return 0;
  }

  return 1;
}

/*
   Returns a set variable
*/
static const char *send_fax_variable(const char *name)
{
  int i = 0;

  if (f_vars_num == 0)
     return NULL;

  for (i = 0; i < f_vars_num; i++) {
      if (!(strcasecmp(f_vars[i].name, name)))
         return f_vars[i].data;
  }

  return NULL;
}

/*
   Sets a variable
*/
static int set_fax_variable(const char *name, const char *data)
{
  int len = 0;

  if ((name == NULL) || (data == NULL) || (!(*name)) || (!(*data)))
     return 0;

  if (f_vars_num >= MAX_FAX_VARS)
     return 0;

  len = strlen(name);
  if (len >= MAX_VAR_LEN)
     len = (MAX_VAR_LEN - 1);

  memcpy(f_vars[f_vars_num].name, name, len);

  len = strlen(data);
  if (len >= MAX_VAR_LEN)
     len = (MAX_VAR_LEN - 1);

  memcpy(f_vars[f_vars_num].data, data, len);

  f_vars_num++;
  
  return 1;
}



syntax highlighted by Code2HTML, v. 0.9.1