/* SAMHAIN file system integrity testing                                   */
/* Copyright (C) 2000 Rainer Wichmann                                      */
/*                                                                         */
/*  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., 675 Mass Ave, Cambridge, MA 02139, USA.              */

#include "config_xor.h"

#include <string.h>

#include "samhain.h"
#include "sh_error.h"
#include "sh_utils.h"
#include "sh_sem.h"

#undef  FIL__
#define FIL__  _("sh_err_console.c")

#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>

extern int  OnlyStderr;
 
#if !defined(O_NONBLOCK)
#if defined(O_NDELAY)
#define O_NONBLOCK  O_NDELAY
#else
#define O_NONBLOCK  0
#endif
#endif

#if defined(WITH_MESSAGE_QUEUE) 

#if defined(HAVE_SYS_MSG_H)

#include <sys/ipc.h>
#include <sys/msg.h>

#if !defined(EIDRM)
#define EIDRM (EINVAL)
#endif

struct sh_msgbuf {
  long mtype;
  char mtext[1];  /* <-- sizeof(mtext) will be  1+MY_MAX_MSG */
};

static int msgq_enabled = S_FALSE;

/* The identifier of the message queue
 */
static int msgid = -1;

/* Open the SysV message queue, creating it when neccesary
 */
static int open_queue(void)
{
  key_t            key;
#if defined(WITH_TPT) 
  char errbuf[SH_ERRBUF_SIZE];
#endif

  SL_ENTER(_("open_queue"));

  /* get key
   */
  key = ftok (DEFAULT_DATAROOT, '#');

  if (key == (key_t) -1)
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<ftok: %s> errno=<%d>\n"), 
	    sh_error_message(errno, errbuf, sizeof(errbuf)), errno));
      SL_RETURN(-1, _("open_queue"));
    }

  /* get message identifier
   */
  msgid = msgget (key, IPC_CREAT|MESSAGE_QUEUE_MODE);

  if (msgid < 0)
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<msgget: %s> errno=<%d>\n"), 
	    sh_error_message(errno, errbuf, sizeof(errbuf)), errno));
      SL_RETURN(-1, _("open_queue"));
    }

  SL_RETURN(0, _("open_queue"));
}

/* Close the SysV message queue and/or semaphore
 */
void close_ipc (void)
{
  if (msgid != (-1))
    (void) msgctl (msgid, IPC_RMID, NULL);
  sh_sem_close();
  return;
}

/* Enable the message queue
 */
int enable_msgq(const char * foo)
{
  int i;

  SL_ENTER(_("enable_msgq"));
  i = sh_util_flagval(foo, &msgq_enabled);
  SL_RETURN(i, _("enable_msgq"));
}

#define MY_MAX_MSG    1022

static void remove_message()
{
  int rc;
  struct {
    long    mtype;       /* Message type. */
    char    mtext[128];  /* Message text. */
  } recv_msg;

  recv_msg.mtype = 1;
  do {
    rc = msgrcv(msgid, &recv_msg, sizeof(recv_msg.mtext), 1, 
		MSG_NOERROR|IPC_NOWAIT);
  } while (rc < 0 && errno == EINTR);

  memset(&recv_msg, 0, sizeof(recv_msg));
  return;
}

static int push_message_queue (const char * msg)
{
  struct sh_msgbuf*   recv_msg = NULL;
  int              rc       = -1;
  static int       status   = -1;
  int              count    = 0;
#if defined(WITH_TPT) 
  char errbuf[SH_ERRBUF_SIZE];
#endif

  SL_ENTER(_("push_message_queue"));

  if (msgq_enabled == -1)
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<msg_queue not enabled>\n"))); 
      SL_RETURN(0, _("push_message_queue"));
    }

  if (status < 0)
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<msg_queue not open>\n"))); 
      status = open_queue();
    }

  if (status < 0)
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<open_queue() failed>\n"))); 
      SL_RETURN(-1, _("push_message_queue"));
    }

  /* struct msgbuf {
   *   long mtype;
   *   char mtext[1];  <-- sizeof(mtext) will be  1+MY_MAX_MSG
   * } */

  recv_msg = (struct sh_msgbuf*) SH_ALLOC(sizeof(struct sh_msgbuf)+MY_MAX_MSG);
  recv_msg->mtype = 1;
  sl_strlcpy (recv_msg->mtext, msg, MY_MAX_MSG+1);

  count = 0;

 send_it:

  if (count > 1)
    {
      memset(recv_msg, 0, MY_MAX_MSG+1);
      SH_FREE(recv_msg);
      SL_RETURN(-1, _("push_message_queue"));
    }

  do { errno = 0;
    rc = msgsnd(msgid, recv_msg, strlen(recv_msg->mtext)+1, IPC_NOWAIT);

    if (rc == -1 && errno == EAGAIN)
      remove_message();
  } while (rc < 0 && (errno == EINTR && errno == EAGAIN));
  
  if (rc == -1 && errno != EAGAIN) 
    {
      /* EIDRM is not in OpenBSD */
      if (errno == EINVAL || errno == EIDRM) {
	TPT(( 0, FIL__, __LINE__, _("msg=<msg_queue not open>\n"))); 
	status = open_queue();
	if (status == 0) {
	  ++count;
	  goto send_it; }
      } else {
	TPT(( 0, FIL__, __LINE__, _("msg=<msgsnd: %s> errno=<%d>\n"), 
	      sh_error_message(errno, errbuf, sizeof(errbuf)), errno));
	memset(recv_msg, 0, MY_MAX_MSG+1);
	SH_FREE(recv_msg);
	SL_RETURN(-1, _("push_message_queue"));
      }
    }

  memset(recv_msg, 0, MY_MAX_MSG+1);
  SH_FREE(recv_msg);

  SL_RETURN(0, _("push_message_queue"));
}
/* if defined(HAVE_SYS_MSG_H) */
#else

#error **********************************************
#error
#error The sys/msg.h header was not found, 
#error cannot compile with --enable-message-queue
#error
#error **********************************************

#endif

#else /* no message queue */

void close_ipc() { sh_sem_close(); return; }

#endif

static int count_dev_console = 0;

typedef enum { SH_LOG_UNIX, SH_LOG_OTHER, SH_LOG_INDEF } sh_log_devtype;

static sh_log_devtype dt[2] = { SH_LOG_INDEF, SH_LOG_INDEF };
static int            st[2] = { SOCK_DGRAM, SOCK_DGRAM };

void reset_count_dev_console(void)
{
  count_dev_console = 0;
  dt[0] = SH_LOG_INDEF;
  dt[1] = SH_LOG_INDEF;
  st[0] = SOCK_DGRAM;
  st[1] = SOCK_DGRAM;
  
  return;
}

/* ---- Set the console device. ----
 */
int sh_log_set_console (const char * address)
{
  SL_ENTER(_("sh_log_set_console"));
  if (address != NULL && count_dev_console < 2 
      && sl_strlen(address) < SH_PATHBUF)
    {
      if (count_dev_console == 0)
        (void) sl_strlcpy (sh.srvcons.name, address, SH_PATHBUF);
      else
        (void) sl_strlcpy (sh.srvcons.alt,  address, SH_PATHBUF);

      ++count_dev_console;
      SL_RETURN(0, _("sh_log_set_console"));
    }
  SL_RETURN((-1), _("sh_log_set_console"));
}

#if defined(WITH_TRACE) || defined(WITH_TPT)
char *  sh_log_console_name (void)
{
  if (sh.srvcons.name[0] == '\0' ||
      0 == strcmp(sh.srvcons.name, _("NULL")))
    return (_("/dev/console"));
  return sh.srvcons.name;
}
#endif

#ifndef STDERR_FILENO 
#define STDERR_FILENO   2
#endif

static int  find_socktype(const char * name)
{
#ifdef SOCK_SEQPACKET
    int socktypes[3] = { SOCK_DGRAM, SOCK_SEQPACKET, SOCK_STREAM };
    int try = 3;
#else
    int socktypes[2] = { SOCK_DGRAM, SOCK_STREAM };
    int try = 2;
#endif
    int i;
    for (i = 0; i < try; ++i) {
	struct sockaddr_un addr;
	int fd;
    
	if ( (fd = socket(AF_UNIX, socktypes[i], 0)) == -1) {
	    return -1;
	}
	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_UNIX;
	sl_strlcpy(addr.sun_path, name, sizeof(addr.sun_path));
	if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
	    close(fd);
	    return socktypes[i];
	}
	close(fd);
    }
    return -1;
}

int sh_log_console_open (const char * name, int slot)
{
  int fd = -1;

  if (dt[slot] == SH_LOG_INDEF)
    {
      struct stat sb;
      if (retry_stat(FIL__, __LINE__, name, &sb) == 0)
	{
	  if ((sb.st_mode & S_IFMT) == S_IFSOCK)
	    {
	      dt[slot] = SH_LOG_UNIX;
	      st[slot] = find_socktype(name);
	      if (st[slot] == -1) {
	        sh_error_handle ((-1), FIL__, __LINE__, 0, MSG_E_SUBGEN,
		   _("Could not determine socket type."),  
		   name);
	      }
	    }
	  else
	    dt[slot] = SH_LOG_OTHER;
	}
    }
      
  if (dt[slot] == SH_LOG_OTHER) {
    fd = open ( name, O_WRONLY|O_APPEND|O_NOCTTY|O_NONBLOCK);
  }
  else if (dt[slot] == SH_LOG_UNIX && st[slot] != -1) {
    struct sockaddr_un addr;
    
    if ( (fd = socket(AF_UNIX, st[slot], 0)) == -1) {
	  char ebuf[SH_ERRBUF_SIZE];
	  int  errnum = errno;
	  sh_error_handle ((-1), FIL__, __LINE__, errnum, MSG_E_SUBGEN,
	    sh_error_message(errnum, ebuf, sizeof(ebuf)),  
	    name);
      return -1;
    }
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    sl_strlcpy(addr.sun_path, name, sizeof(addr.sun_path));
    if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
	  char ebuf[SH_ERRBUF_SIZE];
	  int  errnum = errno;
	  sh_error_handle ((-1), FIL__, __LINE__, errnum, MSG_E_SUBGEN,
	    sh_error_message(errnum, ebuf, sizeof(ebuf)),
	    name);
      return -1;
    }
  }
  return fd;
}

/* ---- Print out a message. ----
 */
int  sh_log_console (const /*@null@*/char *errmsg)
{
  static int service_failure[2] = { 0, 0};
  int    fd[2] = { -1, -1};
  int    cc;
  size_t len;
  int    ccMax = 1;
  int    retval = -1;
  /* static int logkey_seen = 0; */
  int    error;
  static int blockMe = 0;
  int    val_return;

  SL_ENTER(_("sh_log_console"));

  if (errmsg == NULL || blockMe == 1)
    {
      SL_RETURN(0, _("sh_log_console"));
    }
  else
    blockMe = 1;


#ifdef WITH_MESSAGE_QUEUE
  if (0 != push_message_queue (errmsg))
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<push_message_queue() failed>\n"))); 
    }
#endif

  if (sh.flag.isdaemon == S_FALSE || OnlyStderr == S_TRUE)
    {
      len = strlen(errmsg);
      do {
	val_return = write(STDERR_FILENO, errmsg, len);
      } while (val_return < 0 && errno == EINTR); 
      do {
	val_return = write(STDERR_FILENO, "\n", 1);
      } while (val_return < 0 && errno == EINTR); 
      /* 
       * fprintf (stderr, "%s\n", errmsg); 
       */
      blockMe = 0;
      SL_RETURN(0, _("sh_log_console"));
    }

  /* --- daemon && initialized ---
   */
  if ( OnlyStderr == S_FALSE ) 
    {
      fd[0] = sh_log_console_open ( sh.srvcons.name, 0);

      if (sh.srvcons.alt[0] != '\0')
	{
	  fd[1] = sh_log_console_open (sh.srvcons.alt, 1);
	  ccMax = 2;
	}

      for (cc = 0; cc < ccMax; ++cc)
	{
      
	  if (fd[cc] < 0 && service_failure[cc] == 0)
	    {
	      error = errno;
	      sh_error_handle ((-1), FIL__, __LINE__, error, MSG_SRV_FAIL,
			       _("console"), 
			       (cc == 0) ? sh.srvcons.name : sh.srvcons.alt);
	      service_failure[cc] = 1;
	    }

	  if (fd[cc] >= 0)
	    {
	      do {
		val_return = write(fd[cc], errmsg, strlen(errmsg));
	      } while (val_return < 0 && errno == EINTR);
	      if (dt[cc] != SH_LOG_UNIX || st[cc] == SOCK_STREAM) {
	        do {
	          val_return = write(fd[cc], "\r\n",              2);
	        } while (val_return < 0 && errno == EINTR);
	      }
	      (void) sl_close_fd(FIL__, __LINE__, fd[cc]);
	      service_failure[cc] = 0;
	    }
	}
    }
  else
    retval = 0;

  blockMe = 0;
  SL_RETURN(retval, _("sh_log_console"));
}


