/* IrDA dock program */
/* loosely based on the test code in openobex apps */
/* Accepts OBEX data on your IrDA connection, converts it to a
   MIME-format mail message, and drops it into a mailbox. */
/* currently takes no parameters, so if you want to change the default
   drop location, you'll need to change the mbox define below. */
/* Things that need work:
   - allow mbox to be specified on command line
     - remove nasty chdir hack
   - do some file locking on mbox
   - see if it's possible to dig the IR connection info out of the
     connection, so we can fake up Received headers.
*/

/* note, this links against openssl because I'm too damned lazy to
   reimplement base64 encoding. Here's the Makefile:
LDFLAGS=-lopenobex `pkg-config --libs openssl`
CFLAGS=-Wall `pkg-config --cflags openssl` -DDEBUG

all: irmail
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>

#include <openobex/obex.h>

/* for b64 encoding */
#include <openssl/bio.h>
#include <openssl/evp.h>

struct context
{
  uint8_t *name;
  uint8_t *desc;
  int expected;
  int len;
  uint8_t *data;
  uint8_t *type;
  int serverdone;
  int clientdone;
  int dumped;
};

struct context global_context = { 0, };

#ifdef DEBUG
#define dprintf(x...) fprintf( stderr, x )
#else
#define dprintf(x...)
#endif

#define mbox "Mail/irda"

/*
 * Dump the gc object out as a MIME mail message
 */
int dump_as_mime( struct context *gc ) {
  int binary = 0;
  int i;
  time_t now = time( NULL );
  char datestamp[41];
  FILE *file;

  if ( !gc->len ) {
    /* no point */
    return 1;
  }

  if ( gc->dumped ) {
    return 1;
  }

  /* very crude */
  chdir( getenv( "HOME" ));
  if (( file = fopen( mbox, "r+" )) == NULL ) {
    fprintf( stderr, "can't open %s: %s\n", mbox, strerror( errno ));
    return 1;
  }

  fprintf( stderr, "Dumping obex object\n" );

  gc->dumped = 1;

  i = strftime( datestamp, 40, "%a %b %d %H:%M:%S %Y", localtime( &now ));
  fprintf( file, "From IrDA %s\n", datestamp );
  fprintf( file, "From: IrDA subsytem <irda>\n" );
  i = strftime( datestamp, 40, "%a, %d %b %Y %H:%M:%S %z", localtime( &now ));
  if ( gc->name ) {
    fprintf( file, "Subject: %s\n", gc->name );
  }
  fprintf( file, "Date: %s\n", datestamp );
  fprintf( file, "MIME-Version: 1.0\n" );

  if ( gc->type ) {
    /* vm/sagem hack */
    if ( strcmp( gc->type, "image/jpg" ) == 0 ) {
      fprintf( file, "Content-Type: image/jpeg\n" );
    } else {
      fprintf( file, "Content-Type: %s\n", gc->type );
    }
    binary = strncmp( gc->type, "text", 4 );
    free( gc->type );
    gc->type = NULL;
  }

  if ( gc->name ) {
    /* should escape gc->name in case it's got dubious chars */
    fprintf( file, "Content-Disposition: inline; filename=\"%s\"\n", gc->name );
    free( gc->name );
    gc->name = NULL;
  }

  if ( gc->desc ) {
    fprintf( file, "Content-Description: %s\n", gc->desc );
    free( gc->desc );
    gc->desc = NULL;
  }

  if ( binary ) {
    BIO *bio, *b64;
    /* we need to calculate the content length */
    /* base64 bulks up your data by 4/3, then breaks it into 64 char
       lines with a LF */
    float bulk = ((float)gc->len) * 4.0 / 3.0;
    int lines;

    if ( bulk - (int)bulk )
      bulk = (int)bulk + 3;
    lines = bulk / 64.0 + 0.5;
    fprintf( file, "Content-Length: %d\n", (int)bulk + lines );
    fprintf( file, "Content-Transfer-Encoding: base64\n\n" );

    b64 = BIO_new( BIO_f_base64());
    bio = BIO_new_fp( file, BIO_NOCLOSE );
    bio = BIO_push( b64, bio );
    BIO_write( bio, gc->data, gc->len );
    BIO_flush( bio );
    BIO_free_all( bio );

  } else {
    fprintf( file, "Content-Length: %d\n\n", gc->len );

    for ( i = 0; i < gc->len; i++ ) {
      fprintf( file, "%c", gc->data[i] );
    }
  }
  /* make sure the mailbox doesn't get jammed up */
  fprintf( file, "\n" );
  fclose( file );

  return 0;
}

void obex_event( obex_t *handle, obex_object_t *object, int mode, int event,
                 int obex_cmd, int obex_rsp )
{
  char obex_cmd_s[20];
  struct context *gc = OBEX_GetUserData( handle );
  obex_headerdata_t hv;
  uint8_t hi;
  int hlen;
#if 0
  int rc;
  uint8_t *nhd;
#endif

  switch( obex_cmd ) {
  case OBEX_CMD_CONNECT:
    strcpy( obex_cmd_s, "connect" );
    break;

  case OBEX_CMD_DISCONNECT:
    strcpy( obex_cmd_s, "disconnect" );
    break;

  case OBEX_CMD_PUT:
    strcpy( obex_cmd_s, "put" );
    break;

  default:
    sprintf( obex_cmd_s, "unknown (%02x)", obex_cmd );
    break;
  }

#if 0
  /* no idea what this stuff is */
  rc = OBEX_ObjectGetNonHdrData( object, &nhd );
  if ( rc ) {
    int i;
    dprintf( "found non-header data\n" );
    for ( i = 0; i < rc; i++ ) {
      dprintf( "%02x ", nhd[i] );
    }
    dprintf( "\n" );
  }
#endif

  /* this is an attempt at providing a real indication of progress,
     but it's mostly useless, since we can't get any info on the data
     block until it's complete */
  OBEX_ObjectReParseHeaders( handle, object );

  while ( OBEX_ObjectGetNextHeader( handle, object, &hi, &hv, &hlen )) {
    switch( hi ) {
    case OBEX_HDR_NAME:
      break;
    case OBEX_HDR_BODY:
      break;
    case OBEX_HDR_LENGTH:
      gc->expected = hv.bq4;
      break;
    case OBEX_HDR_TYPE:
      break;
    case OBEX_HDR_DESCRIPTION:
      break;
    default:
      dprintf( "unknown header %d available\n", hi );
    }
  }

  switch( event ) {
  case OBEX_EV_PROGRESS:
    /* check obex for some way of getting the current data length */
    dprintf( "." );
    break;

  case OBEX_EV_ABORT:
    dprintf( "abort!\n" );
    break;

  case OBEX_EV_LINKERR:
    dprintf( "link error\n" );
    break;

  case OBEX_EV_REQHINT:
    dprintf( "%s request hint\n", obex_cmd_s );
    /* in theory, can switch this based on cmd */
    OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS);
    break;

  case OBEX_EV_REQ:
    dprintf( "%s request\n", obex_cmd_s );
    switch( obex_cmd ) {
    case OBEX_CMD_CONNECT:
      OBEX_ObjectSetRsp( object, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS );
      break;

    case OBEX_CMD_DISCONNECT:
      OBEX_ObjectSetRsp( object, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS );
      break;

    case OBEX_CMD_PUT:
      OBEX_ObjectSetRsp( object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS );
      break;

    default:
      dprintf( "rejecting command\n" );
      OBEX_ObjectSetRsp( object, OBEX_RSP_NOT_IMPLEMENTED,
                         OBEX_RSP_NOT_IMPLEMENTED );
    }
    break;

  case OBEX_EV_REQDONE:
    dprintf( "%s request done\n", obex_cmd_s );

    /* for disconnect request, close our handle */
    if ( !strcmp( obex_cmd_s, "disconnect" )) {
      OBEX_TransportDisconnect( handle );
      gc->clientdone = 1;
    }

    if ( !strcmp( obex_cmd_s, "connect" )) {
      gc->clientdone = 0;
    }

    if ( !strcmp( obex_cmd_s, "put" )) {
      OBEX_ObjectReParseHeaders( handle, object );

      while( OBEX_ObjectGetNextHeader( handle, object, &hi, &hv, &hlen )) {
        switch( hi ) {
        case OBEX_HDR_NAME:
          gc->name = malloc( hlen );
          if ( gc->name != NULL ) {
            OBEX_UnicodeToChar( gc->name, hv.bs, hlen );
          } else {
            fprintf( stderr, "can't allocate memory for name\n" );
          }
          break;

        case OBEX_HDR_BODY:
          if (( gc->data = malloc( hlen )) != NULL ) {
            memcpy( gc->data, hv.bs, hlen );
            gc->len = hlen;
          } else {
            gc->data = NULL;
          }
          break;

        case OBEX_HDR_LENGTH:
          dprintf( "Found length %u\n", hv.bq4 );
          gc->len = hv.bq4;
          break;

        case OBEX_HDR_TYPE:
          if (( gc->type = malloc( hlen + 1 )) != NULL ) {
            memcpy( gc->type, hv.bs, hlen );
            gc->type[hlen] = '\0';
          } else {
            gc->type = NULL;
          }
          break;

        case OBEX_HDR_DESCRIPTION:
          gc->desc = malloc( hlen );
          if ( gc->desc != NULL ) {
            OBEX_UnicodeToChar( gc->desc, hv.bs, hlen );
          } else {
            fprintf( stderr, "can't allocate memory for description\n" );
          }
          break;

        default:
          fprintf( stderr, "found header type %02x\n", hi );
          break;
        }
      }
      gc->clientdone = 1;
    }

    break;

  default:
    dprintf( "unknown event %02x\n", event );
    break;
  }

  if ( gc->clientdone ) {
    dump_as_mime( gc );
  }

#if 0
    printf( "handle: %p object: %p mode: %02x event: %02x cmd: %s rsp: %02x\n",
            handle, object, mode, event, obex_cmd_s, obex_rsp );
#endif
}

obex_t *obex_startup( void )
{
  obex_t *handle;

  if (( handle = OBEX_Init( OBEX_TRANS_IRDA, obex_event, 0 )) == NULL ) {
    perror( "OBEX_Init failed" );
    exit( 1 );
  }

  OBEX_SetUserData( handle, &global_context );

  return handle;
}

int main( int argc, char *argv[] )
{
  obex_t *handle;

  handle = obex_startup();

  dprintf( "Entering server loop\n" );
  while( 1 ) {
    struct context *gt;

    /* set up server */
    if ( IrOBEX_ServerRegister( handle, "OBEX" ) < 0 ) {
      perror( "Server register error\n" );
      exit( 1 );
    }

    gt = OBEX_GetUserData( handle );

    gt->serverdone = 0;

    while( !gt->serverdone ) {
      int i;
      if (( i = OBEX_HandleInput( handle, 0 )) < 0 ) {
        /* some clients appear to just drop the connection instead of
           disconnecting gracefully */
        if ( errno == ECONNRESET ) {
          OBEX_TransportDisconnect( handle );
        } else if ( errno ) { /* errno = 0 means we disconnected
                                 ourselves */
          perror( "Error doing OBEX_HandleInput" );
        }
        break;
      }
#if 0
      if ( i )
        dprintf( "Obex Input handled (%d)\n", i );
#endif
    }
  }

  return 0;
}

