/* 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"

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

#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.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>

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_ipc(void)
{
  key_t            key;
  int              error;
  char errbuf[SH_ERRBUF_SIZE];

  SL_ENTER(_("open_ipc"));

  /* get key
   */
  key = ftok ("/tmp", '#');
  if (key == (key_t) -1)
    {
      error = errno;
      TPT(( 0, FIL__, __LINE__, _("msg=<ftok: %s> errno=<%d>\n"), 
	    sh_error_message(error, errbuf, sizeof(errbuf)), error));
      SL_RETURN(-1, _("open_ipc"));
    }

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

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

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

/* Close the SysV message queue
 */
void close_ipc (void)
{
  SL_ENTER(_("close_ipc"));

  if (msgid != (-1))
    (void) msgctl (msgid, IPC_RMID, NULL);
  SL_RET0(_("close_ipc"));
}

/* 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    254 */
#define MY_MAX_MSG    1022

static int push_message_queue (char * msg)
{
  struct sh_msgbuf*   recv_msg = NULL;
  int              rc       = -1;
  static int       status   = -1;
  int              error;
  int              count    = 0;
  char errbuf[SH_ERRBUF_SIZE];

  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_ipc();
    }

  if (status < 0)
    {
      TPT(( 0, FIL__, __LINE__, _("msg=<open_ipc() 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)
    {
      SH_FREE(recv_msg);
      SL_RETURN(-1, _("push_message_queue"));
    }

  /* send the message
   */ 
  do {
    errno = 0;
    rc = msgsnd(msgid, recv_msg, strlen(recv_msg->mtext)+1, IPC_NOWAIT);
  }
  while (rc < 0 && errno == EINTR);
  
  if (rc == -1 && errno != EAGAIN) 
    {
      /* EIDRM is not in OpenBSD
       */
      if (errno == EINVAL
#if defined(EIDRM)
	  || errno == EIDRM
#endif
	  )
	{
	  TPT(( 0, FIL__, __LINE__, _("msg=<msg_queue not open>\n"))); 
	  status = open_ipc();
	  if (status == 0)
	    {
	      ++count;
	      goto send_it;
	    }
	}
      else
	{
	  error = errno;
	  TPT(( 0, FIL__, __LINE__, _("msg=<msgsnd: %s> errno=<%d>\n"), 
		sh_error_message(error, errbuf, sizeof(errbuf)), error));
	  SH_FREE(recv_msg);
	  SL_RETURN(-1, _("push_message_queue"));
	}
    }

  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

#endif

static int count_dev_console = 0;

void reset_count_dev_console(void)
{
  count_dev_console = 0;
  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 ()
{
  if (! sh.srvcons.name || 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

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

  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

  sa_new.sa_handler = SIG_IGN;
  (void) sigemptyset(&sa_new.sa_mask);
  sa_new.sa_flags   = 0;

  /* Ignore SIGPIPE in case the console is actually a pipe.
   */
  (void) retry_sigaction(FIL__, __LINE__, SIGPIPE, &sa_new, &sa_old);
 
  if (sh.flag.isdaemon == S_FALSE || OnlyStderr == S_TRUE)
    {
      len = strlen(errmsg);
      (void) write(STDERR_FILENO, errmsg, len);
      (void) write(STDERR_FILENO, "\n", 1);
      /* 
       * fprintf (stderr, "%s\n", errmsg); 
       */
      (void) retry_sigaction(FIL__, __LINE__, SIGPIPE, &sa_old, NULL);
      blockMe = 0;
      SL_RETURN(0, _("sh_log_console"));
    }

  /* --- daemon && initialized ---
   */
  if ( (OnlyStderr == S_FALSE) ) 
    {
      fd[0] = open ( sh.srvcons.name, O_WRONLY|O_APPEND|O_NOCTTY|O_NONBLOCK);
      if (fd[0] >= 0) {
	sflags = (int) retry_fcntl(FIL__, __LINE__, fd[0], F_GETFL, 0);
	if (sflags >= 0)
	  {
	    (void) retry_fcntl(FIL__, __LINE__, fd[0], 
			       F_SETFL, sflags & ~O_NONBLOCK);
	  }
      }

      if (sh.srvcons.alt != NULL && sh.srvcons.alt[0] != '\0')
	{
	  fd[1] = open (sh.srvcons.alt, O_WRONLY|O_APPEND|O_NOCTTY|O_NONBLOCK);
	  if (fd[1] >= 0) {
	    sflags = (int) retry_fcntl(FIL__, __LINE__, fd[1], F_GETFL, 0);
	    if (sflags >= 0)
	      {
		(void) retry_fcntl(FIL__, __LINE__, fd[1], 
				   F_SETFL, sflags & ~O_NONBLOCK);
	      }
	    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)
	    {
	      (void) write(fd[cc], errmsg, strlen(errmsg));
	      (void) write(fd[cc], "\r\n",              2);
	      (void) close(fd[cc]);
	      service_failure[cc] = 0;
	    }
	}
    }
  else
    retval = 0;

  (void) retry_sigaction(FIL__, __LINE__, SIGPIPE, &sa_old, NULL);
  blockMe = 0;
  SL_RETURN(retval, _("sh_log_console"));
}


