/*
 * network.c -- common routines for POP3 and IMAP protocols
 *
 * Copyright (C) 2003 Hugo Villeneuve <hugo@hugovil.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* Define filename_M */
#define NETWORK_M 1

#if HAVE_CONFIG_H
#  include "config.h"
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#include "common.h"
#include "wmnotify.h"
#if HAVE_SSL
#  include "ssl.h"
#endif
#include "network.h"


#define SEND_FLAGS 0
#define RECV_FLAGS 0


/* Common buffers for IMAP4 and POP3. */
char tx_buffer[WMNOTIFY_BUFSIZE + 1];
char rx_buffer[WMNOTIFY_BUFSIZE + 1];


int
SocketOpen( char *server_name, int port )
{
  int status;
  int sock_fd = -1;
  struct hostent *hostinfo;
  struct sockaddr_in serv_addr;
  
  hostinfo = gethostbyname(server_name);
  if( hostinfo == NULL ) {
    herror( PACKAGE );
    ErrorLocation( __FILE__, __LINE__ );
    goto error;
  }

  /* Open socket for Stream (TCP) */
  sock_fd = socket( PF_INET, SOCK_STREAM, 0 );
  if( sock_fd < 0 ) {
    perror( PACKAGE );
    ErrorLocation( __FILE__, __LINE__ );
    goto error;
  }
  
  /*---Initialize server address/port struct---*/
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(port);
  serv_addr.sin_addr = *((struct in_addr *) hostinfo->h_addr );
  memset( &( serv_addr.sin_zero ), '\0', 8 ); /* Clear the rest of the structure. */

  if( wmnotify_infos.debug ) {
    printf( "  Server IP   = %s\n", inet_ntoa( serv_addr.sin_addr ) );
    printf( "  Server port = %d\n", ntohs(serv_addr.sin_port) );
  }
  
  /* Establishing connection. */
  status = connect( sock_fd, (struct sockaddr *) &(serv_addr), sizeof(serv_addr) );
  if( status < 0 ) {
    perror( PACKAGE );
    ErrorLocation( __FILE__, __LINE__ );
    goto error;
  }
  
 end:
  return sock_fd;

 error:
  if( sock_fd >= 0 ) {
    status = close( sock_fd );
    if( status < 0 ) {
      perror( PACKAGE );
      ErrorLocation( __FILE__, __LINE__ );
    }
  }
  
  sock_fd = -1;
  goto end;
}


int
ConnectionEstablish( char *server_name, int port )
{
  int len;
  char rx_buffer[1024]; /* Temporary... */

  wmnotify_infos.sock_fd = SocketOpen( wmnotify_infos.server_name, wmnotify_infos.port );
  if( wmnotify_infos.sock_fd < 0 ) {
    goto error;
  }

#if HAVE_SSL
  if( wmnotify_infos.use_ssl == true ) {
    int status;
    status = InitSSL( wmnotify_infos.sock_fd );
    if( status != EXIT_SUCCESS ) {
      goto error;
    }
  }
#endif  

  /* Testing connection. */
  len = WmnotifyGetResponse( rx_buffer, 1024 );
  if( len < 0 ) {
    goto error;
  }

  if( wmnotify_infos.debug ) {
    rx_buffer[len] = 0;
    printf(" Connect response:\n%s\n", rx_buffer );
  }
  
  return EXIT_SUCCESS;

 error:
  return EXIT_FAILURE;
}


int
ConnectionTerminate( void )
{
#if HAVE_SSL
  if( wmnotify_infos.use_ssl == true ) {
    SSL_free( ssl_infos.ssl ); /* release connection state */
  }
#endif

  close( wmnotify_infos.sock_fd ); /* close socket */

#if HAVE_SSL
  if( wmnotify_infos.use_ssl == true ) {
    SSL_CTX_free( ssl_infos.ctx ); /* release context */
  }
#endif
  
  return EXIT_SUCCESS;
}


int
WmnotifySendData( char *buffer, int size )
{
  int len;

#if HAVE_SSL
  if( wmnotify_infos.use_ssl == true ) {
    len = SSL_write( ssl_infos.ssl, buffer, size ); /* Encrypt & send message */
    if( len <= 0 ) {
      SSL_get_error( ssl_infos.ssl, len );
      len = -1;
    }
    
    return len;
  }
#endif /* HAVE_SSL */

  /* if errno = EINTR, it means the operation was interrupted by a signal before any data was
   * sent. We must retry the operation in this case. */
  do {
    len = send( wmnotify_infos.sock_fd, buffer, size, SEND_FLAGS );
  }
  while( ( len < 0 ) && ( errno == EINTR ) );
  
  if( len < 0 ) {
    perror( PACKAGE );
    ErrorLocation( __FILE__, __LINE__ );
  }
  
  return len;
}


int
WmnotifyGetResponse( char *buffer, int max_size )
{
  int len;

#if HAVE_SSL
  if( wmnotify_infos.use_ssl == true ) {
    len = SSL_read( ssl_infos.ssl, buffer, max_size ); /* Get reply & decrypt. */
    switch( SSL_get_error( ssl_infos.ssl, len ) ) {
    case SSL_ERROR_NONE:
      /* Success. */
      break;
    case SSL_ERROR_ZERO_RETURN:
      fprintf( stderr, "%s: SSL_read() connection closed.\n", PACKAGE );
      break;
    case SSL_ERROR_SYSCALL:
      fprintf( stderr, "%s: SSL_read() I/O error.\n", PACKAGE );
      goto ssl_error;
    case SSL_ERROR_SSL:
      fprintf( stderr, "%s: SSL_read() protocol error.\n", PACKAGE );
      goto ssl_error;
    default:
      fprintf( stderr, "%s: SSL_read() error.\n", PACKAGE );
      goto ssl_error;
    }

    return len;

  ssl_error:
    return -1;
  }
#endif /* HAVE_SSL */

  /* if errno = EINTR, it means the operation was interrupted by a signal before any data was
   * read. We must retry the operation in this case. */
  do {
    len = recv( wmnotify_infos.sock_fd, buffer, max_size, RECV_FLAGS );
  }
  while( ( len < 0 ) && ( errno == EINTR ) );
  
  if( len < 0 ) {
    perror( PACKAGE );
    ErrorLocation( __FILE__, __LINE__ );
  }
  
  return len;
}