/*
 * Copyright (C) Tildeslash Ltd. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3.
 *
 * 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 Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU Affero General Public License in all respects
 * for all of the code used other than OpenSSL.  
 */

#include "config.h"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_POLL_H
#include <poll.h>
#endif

#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef NEED_SOCKLEN_T_DEFINED
#define _BSD_SOCKLEN_T_
#endif

#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif 

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif 

#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif

#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif

#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif

#ifdef HAVE_NETINET_IP_ICMP_H
#include <netinet/ip_icmp.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_STDDEF_H
#include <stddef.h>
#else
#define offsetof(st, m) ((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifndef __dietlibc__
#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif
#endif

#include <arpa/inet.h>

#include "monit.h"
#include "net.h"
#include "ssl.h"

/**
 *  General purpose Network and Socket methods.
 *
 *  @file
 */


/* ------------------------------------------------------------- Definitions */


#define DATALEN 64


/* ----------------------------------------------------------------- Private */


/*
 * Do a non blocking connect, timeout if not connected within timeout seconds
 */
static int do_connect(int s, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
  int error = 0;
  struct pollfd fds[1];

  switch (connect(s, addr, addrlen)) {
    case 0:
      return 0;
    default:
      if (errno != EINPROGRESS)
        return -1;
      break;
  }
  fds[0].fd = s;
  fds[0].events = POLLIN|POLLOUT;
  if (poll(fds, 1, timeout * 1000) == 0) {
    errno = ETIMEDOUT;
    return -1;
  }
  if (fds[0].events & POLLIN || fds[0].events & POLLOUT) {
    socklen_t len = sizeof(error);
    if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
      return -1; // Solaris pending error
  } else {
    return -1;
  }
  if (error) {
    errno = error;
    return -1;
  }
  return 0;
}


/*
 * Compute Internet Checksum for "count" bytes beginning at location "addr".
 * Based on RFC1071.
 */
static unsigned short checksum_ip(unsigned char *_addr, int count) {

  register long sum= 0;
  unsigned short *addr= (unsigned short *)_addr;

  while(count > 1) {
    sum += *addr++;
    count -= 2;
  }

  /* Add left-over byte, if any */
  if(count > 0)
    sum += *(unsigned char *)addr;

  /* Fold 32-bit sum to 16 bits */
  while(sum >> 16)
    sum= (sum & 0xffff) + (sum >> 16);

  return ~sum;

}


/*
 * Check if data is available, if not, wait timeout milliseconds for data
 * to be present.
  */
static int can_read_ms(int socket, int ms) {
  int r = 0;
  struct pollfd fds[1];

  fds[0].fd = socket;
  fds[0].events = POLLIN;
  do {
    r = poll(fds, 1, ms);
  } while (r == -1 && errno == EINTR);
  return (r > 0);
}


/* ------------------------------------------------------------------ Public */


/**
 * Check if the hostname resolves
 * @param hostname The host to check
 * @return TRUE if hostname resolves, otherwise FALSE
 */
int check_host(const char *hostname) {

  struct addrinfo hints;
  struct addrinfo *res;

  ASSERT(hostname);

  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = PF_INET; /* we support just IPv4 currently */

  if(getaddrinfo(hostname, NULL, &hints, &res) != 0)
    return FALSE;

  freeaddrinfo(res);

  return TRUE;

}


/**
 * Verify that the socket is ready for i|o
 * @param socket A socket
 * @return TRUE if the socket is ready, otherwise FALSE.
 */
int check_socket(int socket) {

  return (can_read(socket, 0) || can_write(socket, 0));

}


/**
 * Verify that the udp server is up. The given socket must be a 
 * connected udp socket if we should be able to test the udp server. 
 * The test is conducted by sending a datagram to the server and
 * check for a returned ICMP error when reading from the socket.
 * @param socket A socket
 * @return TRUE if the socket is ready, otherwise FALSE.
 */
int check_udp_socket(int socket) {

  char buf[STRLEN]= {0};

  /* We have to send something and if the UDP server is down/unreachable
   *  the remote host should send an ICMP error. We then need to call read
   *  to get the ICMP error as a ECONNREFUSED errno. This test is asynchronous
   *  so we must wait, but we do not want to block to long either and it is
   *  probably better to report a server falsely up than to block to long.
   */
  sock_write(socket, buf, 1, 0);
  if(sock_read(socket, buf, STRLEN, 2) < 0) {
    switch(errno) {
      case ECONNREFUSED: return FALSE;
      default:           break;
    }
  }

  return TRUE;

}


/**
 * Create a non-blocking socket against hostname:port with the given
 * type. The type should be either SOCK_STREAM or SOCK_DGRAM.
 * @param hostname The host to open a socket at
 * @param port The port number to connect to
 * @param type Socket type to use (SOCK_STREAM|SOCK_DGRAM)
 * @param timeout If not connected within timeout seconds abort and return -1
 * @return The socket or -1 if an error occured.
 */
int create_socket(const char *hostname, int port, int type, int timeout) {

  int s;
  struct sockaddr_in sin;
  struct sockaddr_in *sa;
  struct addrinfo hints;
  struct addrinfo *result;

  ASSERT(hostname);

  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = AF_INET;
  if(getaddrinfo(hostname, NULL, &hints, &result) != 0) {
    return -1;
  }

  if((s= socket(AF_INET, type, 0)) < 0) {
    freeaddrinfo(result);
    return -1;
  }

  sa = (struct sockaddr_in *)result->ai_addr;
  memcpy(&sin, sa, result->ai_addrlen);
  sin.sin_family= AF_INET;
  sin.sin_port= htons(port);
  freeaddrinfo(result);

  if(! set_noblock(s)) {
    goto error;
  }

  if(fcntl(s, F_SETFD, FD_CLOEXEC) == -1)
    goto error; 

  if(do_connect(s, (struct sockaddr *)&sin, sizeof(sin), timeout) < 0) {
    goto error;
  }

  return s;

  error:
  close_socket(s);
  return -1;

}


/**
 * Open a socket using the given Port_T structure. The protocol,
 * destination and type are selected appropriately.
 * @param p connection description
 * @return The socket or -1 if an error occured.
 */
int create_generic_socket(Port_T p) {

  int socket_fd= -1;

  ASSERT(p);

  switch(p->family) {
  case AF_UNIX:
      socket_fd= create_unix_socket(p->pathname, p->timeout);
      break;
  case AF_INET:
      socket_fd= create_socket(p->hostname, p->port, p->type, p->timeout);
      break;
  default:
      socket_fd= -1;
  }

  return socket_fd;

}


/**
 * Create a non-blocking UNIX socket.
 * @param pathname The pathname to use for the unix socket
 * @param timeout If not connected within timeout seconds abort and return -1
 * @return The socket or -1 if an error occured.
 */
int create_unix_socket(const char *pathname, int timeout) {

  int s;
  struct sockaddr_un unixsocket;

  ASSERT(pathname);

  if((s= socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
    LogError("%s: Cannot create socket -- %s\n", prog, STRERROR);
    return -1;
  }

  unixsocket.sun_family= AF_UNIX;
  snprintf(unixsocket.sun_path, sizeof(unixsocket.sun_path), "%s", pathname);

  if(! set_noblock(s)) {
    goto error;
  }

  if(do_connect(s, (struct sockaddr *)&unixsocket, sizeof(unixsocket), timeout) < 0) {
    goto error;
  }

  return s;

  error:
  close_socket(s);
  return -1;

}


/**
 * Create a non-blocking server socket and bind it to the specified local
 * port number, with the specified backlog. Set a socket option to
 * make the port reusable again. If a bind address is given the socket
 * will only accept connect requests to this addresses. If the bind
 * address is NULL it will accept connections on any/all local
 * addresses
 * @param port The localhost port number to open
 * @param backlog The maximum queue length for incomming connections
 * @param bindAddr the local address the server will bind to
 * @return The socket ready for accept, or -1 if an error occured.
 */
int create_server_socket(int port, int backlog, const char *bindAddr) {
  int s, status, flag = 1;
  struct sockaddr_in myaddr;

  if((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    LogError("%s: Cannot create socket -- %s\n", prog, STRERROR);
    return -1;
  }

  memset(&myaddr, 0, sizeof(struct sockaddr_in));

  if(bindAddr) {
    struct sockaddr_in *sa;
    struct addrinfo hints;
    struct addrinfo *result;    

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;
    if((status = getaddrinfo(bindAddr, NULL, &hints, &result)) != 0) {
      LogError("%s: Cannot translate '%s' to IP address -- %s\n", prog, bindAddr, status == EAI_SYSTEM ? STRERROR : gai_strerror(status));
      goto error;
    }
    sa = (struct sockaddr_in *)result->ai_addr;
    memcpy(&myaddr, sa, result->ai_addrlen);
    freeaddrinfo(result);
  } else {
    myaddr.sin_addr.s_addr= htonl(INADDR_ANY);
  }
  myaddr.sin_family= AF_INET;
  myaddr.sin_port= htons(port);

  if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag)) < 0)  {
    LogError("%s: Cannot set reuseaddr option -- %s\n", prog, STRERROR);
    goto error;
  }

  if(! set_noblock(s))
    goto error;

  if(fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
    LogError("%s: Cannot set close on exec option -- %s\n", prog, STRERROR);
    goto error; 
  }

  if(bind(s, (struct sockaddr *)&myaddr, sizeof(struct sockaddr_in)) < 0) {
    LogError("%s: Cannot bind -- %s\n", prog, STRERROR);
    goto error;
  }

  if(listen(s, backlog) < 0) {
    LogError("%s: Cannot listen -- %s\n", prog, STRERROR);
    goto error;
  }

  return s;

  error:
  if (close(s) < 0)
    LogError("%s: Socket %d close failed -- %s\n", prog, s, STRERROR);

  return -1;

}


/**
 * Shutdown a socket and close the descriptor.
 * @param socket The socket to shutdown and close
 * @return TRUE if the close succeed otherwise FALSE
 */
int close_socket(int socket) {
  int r;

  shutdown(socket, 2);

  /* Try to close even if shutdown failed so we won't leak file descriptors */
  do {
    r = close(socket);
  } while(r == -1 && errno == EINTR);
  if (r == -1)
    LogError("%s: Socket %d close failed -- %s\n", prog, socket, STRERROR);

  return r;
}


/**
 * Enable nonblocking i|o on the given socket.
 * @param socket A socket
 * @return TRUE if success, otherwise FALSE
 */
int set_noblock(int socket) {
  int flags = fcntl(socket, F_GETFL, 0);

  if (fcntl(socket, F_SETFL, flags|O_NONBLOCK) == -1) {
    LogError("%s: Cannot set nonblocking -- %s\n", prog, STRERROR);
    return FALSE;
  }
  return TRUE;
}


/**
 * Disable nonblocking i|o on the given socket
 * @param socket A socket
 * @return TRUE if success, otherwise FALSE
 */
int set_block(int socket) {

  int flags;

  flags= fcntl(socket, F_GETFL, 0);
  flags &= ~O_NONBLOCK;

  return (fcntl(socket, F_SETFL, flags) == 0);

}


/**
 * Check if data is available, if not, wait timeout seconds for data
 * to be present.
 * @param socket A socket
 * @param timeout How long to wait before timeout (value in seconds)
 * @return Return TRUE if the event occured, otherwise FALSE.
 */
int can_read(int socket, int timeout) {
        return can_read_ms(socket, timeout * 1000);
}


/**
 * Check if data can be sent to the socket, if not, wait timeout
 * seconds for the socket to be ready.
 * @param socket A socket
 * @param timeout How long to wait before timeout (value in seconds)
 * @return Return TRUE if the event occured, otherwise FALSE.
 */
int can_write(int socket, int timeout) {
  int r = 0;
  struct pollfd fds[1];

  fds[0].fd = socket;
  fds[0].events = POLLOUT;
  do {
    r = poll(fds, 1, timeout * 1000);
  } while (r == -1 && errno == EINTR);
  return (r > 0);
}


/**
 * Write <code>size</code> bytes from the <code>buffer</code> to the
 * <code>socket</code> 
 * @param socket the socket to write to
 * @param buffer The buffer to write
 * @param size Number of bytes to send
 * @param timeout Seconds to wait for data to be written
 * @return The number of bytes sent or -1 if an error occured.
 */
ssize_t sock_write(int socket, const void *buffer, size_t size, int timeout) {

  ssize_t n = 0;

  if(size<=0)
      return 0;

  errno= 0;
  do {
    n= write(socket, buffer, size);
  } while(n == -1 && errno == EINTR);

  if(n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
    if(! can_write(socket, timeout)) {
      return -1;
    }
    do {
      n= write(socket, buffer, size);
    } while(n == -1 && errno == EINTR);
  }

  return n;

}


/**
 * Read up to size bytes from the <code>socket</code> into the
 * <code>buffer</code>. If data is not available wait for
 * <code>timeout</code> seconds.
 * @param socket the Socket to read data from
 * @param buffer The buffer to write the data to
 * @param size Number of bytes to read from the socket
 * @param timeout Seconds to wait for data to be available
 * @return The number of bytes read or -1 if an error occured. 
 */
ssize_t sock_read(int socket, void *buffer, int size, int timeout) {

  ssize_t n;

  if(size<=0)
      return 0;

  errno= 0;
  do {
    n= read(socket, buffer, size);
  } while(n == -1 && errno == EINTR);

  if(n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
    if(! can_read(socket, timeout)) {
      return -1;
    }
    do {
      n= read(socket, buffer, size);
    } while(n == -1 && errno == EINTR);
  }

  return n;

}


/**
 * Write <code>size</code> bytes from the <code>buffer</code> to the
 * <code>socket</code>. The given socket <b>must</b> be a connected
 * UDP socket
 * @param socket the socket to write to
 * @param buffer The buffer to write
 * @param size Number of bytes to send
 * @param timeout Seconds to wait for data to be written
 * @return The number of bytes sent or -1 if an error occured.
 */
int udp_write(int socket, void *b, size_t len, int timeout) {

  int i, n;

  ASSERT(timeout>=0);

  for(i= 4; i>=1; i--) {

    do {
      n = (int)sock_write(socket, b, len, 0);
    } while(n == -1 && errno == EINTR);

    if(n == -1 && (errno != EAGAIN || errno != EWOULDBLOCK))
      return -1;

    /* Simple retransmit scheme, wait for the server to reply 
    back to our socket. This assume a request-response pattern, 
    which really is the only pattern we can support */
    if(can_read(socket, (int)(timeout/i))) return n;
    DEBUG("udp_write: Resending request\n");

  }

  errno= EHOSTUNREACH;

  return -1;

}


/**
 * Create a ICMP socket against hostname, send echo and wait for response.
 * The 'count' echo requests  is send and we expect at least one reply.
 * @param hostname The host to open a socket at
 * @param timeout If response will not come within timeout seconds abort
 * @param count How many pings to send
 * @return response time on succes, -1 on error, -2 when monit has no
 * permissions for raw socket (normally requires root or net_icmpaccess
 * privilege on Solaris)
 */
double icmp_echo(const char *hostname, int timeout, int count) {
  struct sockaddr_in sout;
  struct sockaddr_in *sa;
  struct addrinfo hints;
  struct addrinfo *result;
  struct ip *iphdrin;
  int len_out = offsetof(struct icmp, icmp_data) + DATALEN;
  int len_in = sizeof(struct ip) + sizeof(struct icmp);
  struct icmp *icmpin = NULL;
  struct icmp *icmpout = NULL;
  uint16_t id_in, id_out, seq_in;
  int r, i, s, n = 0, status, read_timeout;
  struct timeval t_in, t_out;
  char buf[STRLEN];
  double response = -1.;
#if ! defined NETBSD && ! defined AIX
  int sol_ip;
  unsigned ttl = 255;
#endif

  ASSERT(hostname);   
  ASSERT(len_out < sizeof(buf));

  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = AF_INET;
  if ((status = getaddrinfo(hostname, NULL, &hints, &result)) != 0) {
    LogError("ICMP echo for %s -- getaddrinfo failed: %s\n", hostname, status == EAI_SYSTEM ? STRERROR : gai_strerror(status));
    return response;
  }

  if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
    if (errno == EACCES || errno == EPERM) {
      DEBUG("ICMP echo for %s -- cannot create socket: %s\n", hostname, STRERROR);
      response = -2.;
    } else {
      LogError("ICMP echo for %s -- canot create socket: %s\n", hostname, STRERROR);
    }
    goto error2;
  }

#if ! defined NETBSD && ! defined AIX
#ifdef HAVE_SOL_IP
  sol_ip = SOL_IP;
#else
  {
    struct protoent *pent;
    pent = getprotobyname("ip");
    sol_ip = pent ? pent->p_proto : 0;
  }
#endif
  if (setsockopt(s, sol_ip, IP_TTL, (char *)&ttl, sizeof(ttl)) < 0) {
    LogError("ICMP echo for %s -- setsockopt failed: %s\n", hostname, STRERROR);
    goto error1;
  }
#endif

  id_out = getpid() & 0xFFFF;
  icmpout = (struct icmp *)buf;
  for (i = 0; i < count; i++) {
    unsigned char *data = (unsigned char *)icmpout->icmp_data;

    icmpout->icmp_code  = 0;
    icmpout->icmp_type  = ICMP_ECHO;
    icmpout->icmp_id    = htons(id_out);
    icmpout->icmp_seq   = htons(i);
    icmpout->icmp_cksum = 0;

    /* Add originate timestamp to data section */
    gettimeofday(&t_out, NULL);
    memcpy(data, &t_out, sizeof(struct timeval));
    data += sizeof(struct timeval);

    /* Initialize rest of data section to numeric sequence */
    for (int j = 0; j < DATALEN - sizeof(struct timeval); j++)
      data[j] = j;

    icmpout->icmp_cksum = checksum_ip((unsigned char *)icmpout, len_out);

    sa = (struct sockaddr_in *)result->ai_addr;
    memcpy(&sout, sa, result->ai_addrlen);
    sout.sin_family = AF_INET;
    sout.sin_port   = 0;

    do {
      n = (int)sendto(s, (char *)icmpout, len_out, 0, (struct sockaddr *)&sout, sizeof(struct sockaddr));
    } while(n == -1 && errno == EINTR);
    if (n < 0) {
      LogError("ICMP echo request for %s %d/%d failed -- %s\n", hostname, i + 1, count, STRERROR);
      continue;
    }
    read_timeout = timeout * 1000;
readnext:
    if (can_read_ms(s, read_timeout)) {
      socklen_t size = sizeof(struct sockaddr_in);
      do {
        n = (int)recvfrom(s, buf, STRLEN, 0, (struct sockaddr *)&sout, &size);
      } while(n == -1 && errno == EINTR);
      if (n < 0) {
        LogError("ICMP echo response for %s %d/%d failed -- %s\n", hostname, i + 1, count, STRERROR);
        continue;
      } else if (n < len_in) {
        LogError("ICMP echo response for %s %d/%d failed -- received %d bytes, expected at least %d bytes\n", hostname, i + 1, count, n, len_in);
        continue;
      }

      iphdrin = (struct ip *)buf;
      icmpin  = (struct icmp *)(buf + iphdrin->ip_hl * 4);
      id_in   = ntohs(icmpin->icmp_id);
      seq_in  = ntohs(icmpin->icmp_seq);
      gettimeofday(&t_in, NULL);

      /* The read from connection-less raw socket via recvfrom() provides messages regardless of origin, the source IP address is set in sout, we have to check the IP and skip responses belonging to other ICMP conversations */
      if (sout.sin_addr.s_addr != sa->sin_addr.s_addr || icmpin->icmp_type != ICMP_ECHOREPLY || id_in != id_out || seq_in >= (uint16_t)count) {
        if ((read_timeout = timeout * 1000. - ((t_in.tv_sec - t_out.tv_sec) * 1000. + (t_in.tv_usec - t_out.tv_usec) / 1000.)) > 0)
          goto readnext; // Try to read next packet, but don't exceed the timeout while waiting for our response so we won't loop forever if the socket is flooded with other ICMP packets
      } else {
        memcpy(&t_out, icmpin->icmp_data, sizeof(struct timeval));
        response = (double)(t_in.tv_sec - t_out.tv_sec) + (double)(t_in.tv_usec - t_out.tv_usec) / 1000000;
        DEBUG("ICMP echo response for %s %d/%d succeeded -- received id=%d sequence=%d response_time=%fs\n", hostname, i + 1, count, id_in, seq_in, response);
        break; // Wait for one response only
      }
    } else
      LogError("ICMP echo response for %s %d/%d timed out -- no response within %d seconds\n", hostname, i + 1, count, timeout);
  }

error1:
  do {
    r = close(s);
  } while(r == -1 && errno == EINTR);
  if (r == -1)
    LogError("%s: Socket %d close failed -- %s\n", prog, s, STRERROR);

error2:
  freeaddrinfo(result);

  return response;
}


