/*
Copyright (c) 2002-2010, Dirk Krause
All rights reserved.

Redistribution and use in source and binary forms,
with or without modification, are permitted provided
that the following conditions are met:

* Redistributions of source code must retain the above
  copyright notice, this list of conditions and the
  following disclaimer.
* Redistributions in binary form must reproduce the above 
  opyright notice, this list of conditions and the following
  disclaimer in the documentation and/or other materials
  provided with the distribution.
* Neither the name of the Dirk Krause nor the names of
  contributors may be used to endorse or promote
  products derived from this software without specific
  prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/



/**	@file	rshdown.c	The rshdown program (Linux/Unix) or service
	(Windows).
*/



#include <dk.h>

#ifdef ON_WINDOWS_SYSTEM
#undef ON_WINDOWS_SYSTEM
#endif

#if defined(_WIN32) || defined(WIN32)
#define ON_WINDOWS_SYSTEM	1
#else
#if defined(_WIN64) || defined(WIN64)
#define ON_WINDOWS_SYSTEM	1
#endif
#endif

#if ON_WINDOWS_SYSTEM

/* ========== Start of Windows code */


#include "rshdown.h"


#include <windows.h>
#include <winbase.h>
#include <process.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <conio.h>

#include "rshdownm.h"




#line 80 "rshdown.ctr"




/**	Typedef necessary for sizeof() operator.
*/
typedef struct sockaddr_in SOIN;

/**	Size of Internet socket address.
*/
#define SZ_SOIN (sizeof(SOIN))



/**	Service name.
*/
static WCHAR service_name[] = { L"RshDown" };



/**	Text constants used by program.
*/
static WCHAR *n[] = {
/*   0 */	L"sender",		/* sender of the packet */
/*   1 */	L"file",		/* file to compare data packet */
/*   2 */	L"port",		/* local port to listen */
/*   3 */	L"Software\\RshDown",	/* registry key */
/*   4 */	L"SeShutdownPrivilege",	/* shutdown privilege
/    5 */	L"Power supply failure!",	/* message for shutdown */
NULL
};



/**	Service status.
*/
SERVICE_STATUS st;


/**	Service status handle.
*/
SERVICE_STATUS_HANDLE hSt;



/**	Event: Service must stop.
*/
static volatile HANDLE hEventMustStop = NULL;



/**	Event: Service did stop.
*/
static volatile HANDLE hEventDidStop = NULL;



/**	Buffer for datagram from file.
*/
static char filebuffer[RSHDOWN_DATA_BUFFER_SIZE];



/**	Buffer for datagram from network.
*/
static char netbuffer[RSHDOWN_DATA_BUFFER_SIZE];



/**	Number of bytes used in file buffer.
*/
static size_t u_filebuffer = 0;



/**	Log one syslog event.
	@param	t	Message type.
	@param	e	Event ID.
	@param	s1	Additional string 1.
	@param	s2	Additional string 2
*/
static
void
log_event(WORD t, DWORD e, WCHAR *s1, WCHAR *s2)
{
  HANDLE hEventLog;
  WCHAR *msgs[16];
  int i;
  hEventLog = RegisterEventSource(NULL, service_name);
  if(hEventLog != NULL) {
    for(i = 0; i < 16; i++) msgs[i] = NULL;
    i = 0;
    if(s1) {
      msgs[0] = s1; i = 1;
      if(s2) {
        msgs[1] = s2; i = 2;
      }
    }
    ReportEvent(
      hEventLog,		/* hEventLog */
      t,			/* wType */
      CAT_RSHDOWN,		/* wCategoy */
      e,			/* dwEventID */
      NULL,			/* lpUserSid */
      (WORD)i,			/* wNumStrings */
      (DWORD)0,			/* dwDataSize */
      ((i > 0) ? msgs : NULL),	/* lpStrings */
      NULL			/* lpRawData */
    );
    DeregisterEventSource(hEventLog);
  }
}



/**	Callback function for service events.
	@param	ctrl_code	Event code.
*/
VOID
CALLBACK
service_ctrl_handler(IN DWORD ctrl_code)
{
  int i;
  DWORD res;
  int success = 0;

  st.dwWaitHint = 0; st.dwCheckPoint = 0;
  switch(ctrl_code) {
    case SERVICE_CONTROL_SHUTDOWN:
    case SERVICE_CONTROL_STOP:
    {
      st.dwCurrentState = SERVICE_STOP_PENDING;
      st.dwWaitHint = RSHDOWN_STOP_WAIT_HINT;
      if((hEventMustStop != NULL) && (hEventDidStop != NULL)) {
        SetEvent(hEventMustStop);
        for(i = 0; i < RSHDOWN_MAX_ATTEMPTS_TO_STOP; i++) {
          res =
	  WaitForSingleObjectEx(hEventDidStop, RSHDOWN_STOP_SLEEP_TIME, FALSE);
	  switch(res) {
	    case WAIT_OBJECT_0: {
	      i = RSHDOWN_MAX_ATTEMPTS_TO_STOP;
	      success = 1;
	    } break;
	    default: {
	      SetServiceStatus(hSt, &st);
	      st.dwCheckPoint++;
	    } break;
	  }
        }
	if(!success) {
	  log_event(EVENTLOG_ERROR_TYPE,MSG_BG_THREAD_NOT_RESPONDING,NULL,NULL);
	}
      } else {
	log_event(EVENTLOG_ERROR_TYPE,MSG_ALREADY_STOPPED,NULL,NULL);
      }
      st.dwCurrentState = SERVICE_STOPPED;
      st.dwWaitHint = 0;
      CloseHandle(hEventMustStop); hEventMustStop = 0;
      CloseHandle(hEventDidStop); hEventDidStop = 0;
    } break;
  }
  SetServiceStatus(hSt, &st);
}



/**	Initialize Internet socket address.
	@param	s	Internet socket address.
*/
static
void
initializeSOIN(SOIN *s)
{
  memset((void *)s, 0, SZ_SOIN);
  s->sin_family = AF_INET;
  s->sin_addr.s_addr = htonl(INADDR_ANY);
  s->sin_port = htons(0);
}



/**	Get string from registry.
	@param	b	Result buffer.
	@param	sz	Size of \a b in WCHAR.
	@param	hk	Registry key.
	@param	k	Entry name (key).
	@return	TRUE on success, FALSE on error.
*/
static
BOOL
regWstring(WCHAR *b, size_t /* WCHAR */ sz, HKEY hk, WCHAR *k)
{
  BOOL back = FALSE;
  LONG res;
  DWORD dwType;
  DWORD dwSz;
  dwType = REG_SZ; dwSz = sz * sizeof(WCHAR);
  res = RegQueryValueEx(hk, k, NULL, &dwType, (LPBYTE)b, &dwSz);
  if(res == ERROR_SUCCESS) {
    if(dwType == REG_SZ) {
      if(dwSz > 0) {
        back = TRUE;
	dwSz = dwSz / sizeof(WCHAR);
	b[(dwSz < sz) ? dwSz : (sz - 1)] = L'\0';
      }
    }
  }
  return back;
}



/**	Convert dotted decimal notation to IP address.
	@param	hn	Host name (IP address in dotted decimal notation).
	@return	IP4 address in _host_ byte order on success, 0UL on error.
*/
static
unsigned long
dotted_string_to_ip (char *hn)
{
  unsigned long back = 0UL;
  unsigned long u1, u2, u3, u4, u;
  int ende, state; char *ptr;
  
  if(hn) {
    state = 0;
    u = u1 = u2 = u3 = u4 = 0UL;
    ptr = hn; ende = 0;
    while(!ende) {
      if(*ptr) {
        if(isdigit(*ptr)) {
          u = 0UL;
          switch(*ptr) {
            case '0': u = 0UL; break;
            case '1': u = 1UL; break;
            case '2': u = 2UL; break;
            case '3': u = 3UL; break;
            case '4': u = 4UL; break;
            case '5': u = 5UL; break;
            case '6': u = 6UL; break;
            case '7': u = 7UL; break;
            case '8': u = 8UL; break;
            case '9': u = 9UL; break;
          }
          switch(state) {
            case 0: u1 = 10UL * u1 + u; break;
            case 1: u2 = 10UL * u2 + u; break;
            case 2: u3 = 10UL * u3 + u; break;
            case 3: u4 = 10UL * u4 + u; break;
          }
        } else {
          if(*ptr == '.') {
            state++;
            if(state >= 4) {
              ende = 1;
            }
          }
        }
        ptr++;
      } else {
        ende = 1;
      }
    }
  }
  u1 = u1 << 24; u1 = u1 & 0xFF000000UL;
  u2 = u2 << 16; u2 = u2 & 0x00FF0000UL;
  u3 = u3 <<  8; u3 = u3 & 0x0000FF00UL;
  u4 = u4 & 0x000000FFUL;
  back = u1 | u2 | u3 | u4;
  return back;
}



/**	Set IP address in socket address.
	@param	sin	Internet socket address.
	@param	str	String containing host name or IP address.
	@return	1 on success, 0 on error.
*/
static
int
set_ip_address(struct sockaddr_in *sin, char *str)
{
  int back = 0;
  
  sin->sin_addr.s_addr = htonl(dotted_string_to_ip(str));
  if(sin->sin_addr.s_addr) {
    back = 1;
  } else {
    struct hostent *hp; unsigned long *lp;
    hp = gethostbyname(str);
    if(hp) {
      if(hp->h_addr_list) {
        if(hp->h_length) {
          lp = (unsigned long*)(*(hp->h_addr_list));
          if(lp) {
            sin->sin_addr.s_addr = *lp;
            back = 1;
          }
        }
      }
    }
  }
  return back;
}



/**	Set remote address.
	@param	rw	Remote socket address or address:port text.
	@param	s	Host name of remote host.
	@return	TRUE on success, FALSE on error.
*/
static
BOOL
setRemote(SOIN *rw, WCHAR *s)
{
  BOOL back = FALSE;
  WCHAR *ptr; char *cptr;
  unsigned u; unsigned short us;

  ptr = wcsrchr(s, L':');
  if(ptr) {
    *(ptr++) = L'\0';
    if(swscanf(ptr, L"%u", &u) == 1) {
      us = (unsigned short)u;
      rw->sin_port = htons(us);
      if(wcslen(s) < sizeof(netbuffer)) {
        ptr = s;
	cptr = netbuffer;
	while(*ptr) {
	  *(cptr++) = (char)(*(ptr++));
	} *cptr = '\0';
	if(set_ip_address(rw, netbuffer)) {
	  back = TRUE;
	} else {				
	  log_event(EVENTLOG_ERROR_TYPE,MSG_HOST_NOT_FOUND,s,NULL);
	}
      } else {					
	log_event(EVENTLOG_ERROR_TYPE,MSG_HOST_NAME_TOO_LONG,s,NULL);
      }
    } else {					
      log_event(EVENTLOG_ERROR_TYPE,MSG_NOT_A_NUMBER,ptr,NULL);
    }
  } else {					
    log_event(EVENTLOG_ERROR_TYPE,MSG_NOT_HOST_AND_PORT,s,NULL);
  }
  return back;
}



/**	Get configuration from registry entries.
	@param	rw	Remote address.
	@param	rf	Socket address.
	@param	la	Local address.
	@return	TRUE on success, FALSE on error.
*/
static
BOOL
setupFromRegistry(SOIN *rw, SOIN *rf, SOIN *la)
{
  BOOL back = FALSE;
  HKEY hk;
  DWORD dwDisp;
  LONG res;
  unsigned u;
  WCHAR rb[RSHDOWN_BUFFER_SIZE];
  FILE *fipo;
  
  initializeSOIN(rw);
  initializeSOIN(rf);
  initializeSOIN(la);
  res = RegCreateKeyEx(
    HKEY_LOCAL_MACHINE, n[3], 0, NULL, REG_OPTION_NON_VOLATILE,
    KEY_READ, NULL, &hk, &dwDisp
  );
  if(res == ERROR_SUCCESS) {
    if(regWstring(rb, SIZEOF(rb,WCHAR), hk, n[0])) {
      if(setRemote(rw, rb)) {
        if(regWstring(rb, SIZEOF(rb,WCHAR), hk, n[2])) {
	  if(swscanf(rb, L"%u", &u) == 1) {
	    la->sin_port = htons((unsigned short)u);
	    if(regWstring(rb, SIZEOF(rb,WCHAR), hk, n[1])) {
	      fipo = _wfopen(rb, L"rb");
	      if(fipo) {
	        u_filebuffer = fread(filebuffer, 1, sizeof(filebuffer), fipo);
		if(u_filebuffer > 0) {
		  back = TRUE;
		} else {					
		  log_event(EVENTLOG_ERROR_TYPE,MSG_FILE_EMPTY,rb,NULL);
		}
	        fclose(fipo);
	      } else {						
		log_event(EVENTLOG_ERROR_TYPE,MSG_FOPEN_FAILED,rb,NULL);
	      }
	    } else {						
	      log_event(EVENTLOG_ERROR_TYPE,MSG_FILE_NOT_SPECIFIED,NULL,NULL);
	    }
	  } else {
	    log_event(EVENTLOG_ERROR_TYPE,MSG_NOT_A_NUMBER,rb,NULL);
	  }
	} else {						
	  log_event(EVENTLOG_ERROR_TYPE,MSG_NO_LOCAL_PORT,NULL,NULL);
	}
      } else {							
      }
    } else {							
      log_event(EVENTLOG_ERROR_TYPE,MSG_NO_SENDER,NULL,NULL);
    }
    RegCloseKey(hk);
  } else {							
    log_event(EVENTLOG_ERROR_TYPE,MSG_REGKEY_FAILED,NULL,NULL);
  } 
  return back;
}



/**	Service function, run in a separated thread.
	@param	dataforthread	Data for the thread.
*/
static
void
__cdecl
thread_function(void *dataforthread)
{
  unsigned char can_continue = 0x01;	/* flag, can continue */
  unsigned char must_shutdown = 0x00;	/* flag whether or not to shut down */
  WORD version_requested;
  WSADATA wsa_data;
  struct sockaddr_in	rw;		/* remote wanted */
  struct sockaddr_in	rf;		/* remote found */
  struct sockaddr_in	la;		/* local address */
  int	 chars_in_netbuffer;
  SOCKET sock;
  FD_SET rfds, wfds, efds;
  struct timeval to;
  int lgt;
  int err;
  LONG res;
  HANDLE  current_proc;  /* the current process */
  HANDLE  proc_token;    /* process token */
  TOKEN_PRIVILEGES ns;  /* new state of token privileges */
  LUID  myluid;      /* lookup restult for SE_SHUTDOWN_NAME */
  
#line 525 "rshdown.ctr"

  
  version_requested = MAKEWORD(2,2);
  err = WSAStartup(version_requested, &wsa_data);
  if(err == 0) {				
    if(setupFromRegistry(&rw, &rf, &la)) {	
      sock = socket(AF_INET, SOCK_DGRAM, 0);
      if(sock != INVALID_SOCKET) {		
        if(bind(sock, (struct sockaddr *)(&la), SZ_SOIN) == 0) { 
	  can_continue = 0x01;
	  log_event(EVENTLOG_SUCCESS, MSG_STARTED, NULL, NULL);
	  while(can_continue) {
	    FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds);
	    FD_SET(sock, &rfds); FD_SET(sock, &efds);
	    to.tv_sec = 2;
	    to.tv_usec = 0;
            err = select((sock + 1), &rfds, &wfds, &efds, &to);
	    if((err != SOCKET_ERROR) && (err > 0)) {
	      
	      if((FD_ISSET(sock, &rfds)) || (FD_ISSET(sock, &efds))) {
	        memcpy((void *)(&rf), (void *)(&rw), SZ_SOIN);
		lgt = SZ_SOIN;
		chars_in_netbuffer = recvfrom(
		  sock, netbuffer, sizeof(netbuffer),
		  0, (struct sockaddr *)(&rf), &lgt
		);
		if(chars_in_netbuffer != SOCKET_ERROR) {
		  
		  if(rf.sin_addr.s_addr = rw.sin_addr.s_addr) {
		    if(rf.sin_port == rw.sin_port) {
		      if(chars_in_netbuffer == u_filebuffer) {
		        if(memcmp(
			     (void *)netbuffer, (void *)filebuffer, u_filebuffer
			   ) == 0)
			{
			  can_continue = 0x00;
			  must_shutdown = 0x01;
			  
			} else {	
			}
		      } else {		
		      }
		    } else {		
		    }
		  } else {		
		  }
		}
	      }
	    } else {
	    }
	    if(can_continue) {          
	      res = WaitForSingleObjectEx(hEventMustStop, 0, FALSE);
	      switch(res) {
	        case WAIT_OBJECT_0: {   
		  can_continue = 0x00;
		} break;
	      }
	    }
	  }
	  log_event(EVENTLOG_SUCCESS,MSG_GOING_DOWN,NULL,NULL);
	} else {			
	  log_event(EVENTLOG_ERROR_TYPE,MSG_BIND_FAILED,NULL,NULL);
	}
        closesocket(sock);
      } else {				
	log_event(EVENTLOG_ERROR_TYPE,MSG_SOCKET_FAILED,NULL,NULL);
      }
    }
    WSACleanup();
  } else {				
    log_event(EVENTLOG_ERROR_TYPE,MSG_WINSOCK_FAILED,NULL,NULL);
  }
  if(must_shutdown) {			
    /* ACTION: Start shutdown */
    current_proc = GetCurrentProcess();
    if(current_proc != (HANDLE)0) {
      LookupPrivilegeValue(NULL, n[4], &myluid);
      if(OpenProcessToken(current_proc, (TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY),&proc_token)) {
        ns.PrivilegeCount = 1;
        ns.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        ns.Privileges[0].Luid.HighPart = myluid.HighPart;
        ns.Privileges[0].Luid.LowPart = myluid.LowPart;
        if(AdjustTokenPrivileges(proc_token, FALSE, &ns, 0, NULL, NULL)) {
        } else {
          /* ERROR: Failed to obtain shutdown privilege */
	  log_event(EVENTLOG_ERROR_TYPE,MSG_PRIVILEGE_FAILED,NULL,NULL);
        }
        CloseHandle(proc_token);
      } else {
        /* ERROR: Failed to access current process token */
	log_event(EVENTLOG_ERROR_TYPE,MSG_PROCESS_TOKEN,NULL,NULL);
      }
    } else {
      /* ERROR: Failed to obtain handle for current process */
      log_event(EVENTLOG_ERROR_TYPE,MSG_PROCESS_HANDLE,NULL,NULL);
    }
    if(InitiateSystemShutdownEx(
      NULL,
      n[5],
      15,
      TRUE,
      FALSE,
      (SHTDN_REASON_MAJOR_POWER | SHTDN_REASON_MINOR_ENVIRONMENT)
    ))
    {
      /* Shutdown initiated successfully */
      log_event(EVENTLOG_SUCCESS, MSG_SHUTDOWN_INITIALIZED, NULL, NULL);
    } else {
      /* ERROR: Failed to initiate shutdown */
      log_event(EVENTLOG_ERROR_TYPE,MSG_SHUTDOWN_FAILED,NULL,NULL);
    }
  } 
  
#line 637 "rshdown.ctr"

  SetEvent(hEventDidStop);
  _endthread();
}



/**	The service function registers the service control handler
	and runs the service function in a separate thread.
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
*/
VOID
CALLBACK
service_function(DWORD argc, LPTSTR *argv)
{
  uintptr_t thread_id;
  memset((void *)(&st), 0, sizeof(SERVICE_STATUS));
  st.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  st.dwCurrentState = SERVICE_START_PENDING;
  st.dwControlsAccepted =
  SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
  st.dwWin32ExitCode = 0;
  st.dwServiceSpecificExitCode = 0;
  st.dwCheckPoint = 0;
  st.dwWaitHint = 0;
  hSt = RegisterServiceCtrlHandler(service_name, service_ctrl_handler);
  if(hSt != (SERVICE_STATUS_HANDLE)0) {
    st.dwCurrentState = SERVICE_STOPPED;
    hEventMustStop = CreateEvent(NULL, TRUE, FALSE, NULL);
    if(hEventMustStop != NULL) {
      hEventDidStop = CreateEvent(NULL, TRUE, FALSE, NULL);
      if(hEventDidStop != NULL) {
        ResetEvent(hEventMustStop);
	ResetEvent(hEventDidStop);
        thread_id = _beginthread(thread_function, 0, NULL);
	if(thread_id != -1L) {
	  st.dwCurrentState = SERVICE_RUNNING;
	} else {
	  CloseHandle(hEventDidStop); hEventDidStop = NULL;
	  CloseHandle(hEventMustStop); hEventMustStop = NULL;
	}
      } else {
        CloseHandle(hEventMustStop); hEventMustStop = NULL;
      }
    } else {
    }
    SetServiceStatus(hSt, &st);
  }
}



/**	Service dispatch table.
*/
static SERVICE_TABLE_ENTRY dispatch_table[] = {
  { service_name, service_function },
  { NULL, NULL }
};



/**	The main() function of the rshdown program (Windows).
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
	@return	0 on success, any other value indicates an error.
*/
int wmain(int argc, WCHAR *argv[])
{
  if(StartServiceCtrlDispatcher(dispatch_table) == 0) {
    fprintf(stderr, "ERROR: This program is a Windows service\n");
    fprintf(stderr, "and can not be run on the command line!\n");
    fflush(stderr);
  }
  exit(0); return 0;
}



/* vim: set ai sw=2 : */

/* ========== End of Windows code */


#else

/* ========== Start of Linux/Unix code */


#include <dk.h>
#include <dksf.h>
#include <dksignal.h>
#include <dkstr.h>
#include <dkslsupp.h>
#include <dkmem.h>

#include <stdio.h>

#if DK_HAVE_LIMITS_H
#include <limits.h>
#else
#if DK_HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#endif
#if DK_HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if DK_HAVE_UNISTD_H
#include <unistd.h>
#endif
#if DK_HAVE_SIGNAL_H
#include <signal.h>
#endif
#if DK_HAVE_SOCKET_H
#include <socket.h>
#endif
#if DK_HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if DK_HAVE_NETDB_H
#include <netdb.h>
#endif
#if DK_HAVE_STRING_H
#include <string.h>
#endif
#if DK_HAVE_STRINGS_H
#include <strings.h>
#endif
#if DK_HAVE_CTYPE_H
#include <ctype.h>
#endif
#if DK_HAVE_SYSLOG_H
#include <syslog.h>
#endif
#if DK_HAVE_ERRNO_H
#include <errno.h>
#endif

#include "dktools-version.h"




#line 780 "rshdown.ctr"




#ifndef MAXPATHLEN
#ifdef PATH_MAX
/**	Maximum file name length.
*/
#define MAXPATHLEN (PATH_MAX)
#else
/**	Maximum file name length.
*/
#define MAXPATHLEN 1024
#endif
#endif



/**	Flag: SIGHUP received.
*/
static
#if DK_HAVE_VOLATILE
volatile
#endif
int sighup_received = 0;



/**	Flag: SIGINT received.
*/
static
#if DK_HAVE_VOLATILE
volatile
#endif
int sigint_received = 0;



/**	Flag: SIGTERM received.
*/
static
#if DK_HAVE_VOLATILE
volatile
#endif
int sigterm_received = 0;



/**	Flag: SIGPIPE received.
*/
static
#if DK_HAVE_VOLATILE
volatile
#endif
int sigpipe_received = 0;



/**	SIGHUP handler.
	@param	signo	Signal number (SIGHUP).
*/
dk_signal_ret_t
handler_sighup DK_P1(int, signo)
{
  dksignal_refresh(signo, handler_sighup);
  sighup_received = 1;
}



/**	SIGINT handler.
	@param	signo	Signal number (SIGINT).
*/
dk_signal_ret_t
handler_sigint DK_P1(int, signo)
{
  dksignal_refresh(signo, handler_sigint);
  sigint_received = 1;
}



/**	SIGTERM handler.
	@param	signo	Signal number (SIGTERM).
*/
dk_signal_ret_t
handler_sigterm DK_P1(int, signo)
{
  dksignal_refresh(signo, handler_sigterm);
  sigterm_received = 1;
}



/**	SIGPIPE handler.
	@param	signo	Signal number (SIGPIPE).
*/
dk_signal_ret_t
handler_sigpipe DK_P1(int, signo)
{
  dksignal_refresh(signo, handler_sigpipe);
  sigpipe_received = 1;
}



/**	Flag set, configuration entries found.
*/
static int flags;



/**	Exit status.
*/
static int exval = 0;



/**	Program name.
*/
static char progname[] = { "rshdown" };



/**	Syslog facility.
*/
static int syslog_facility = LOG_DAEMON;



/**	Version number.
*/
static char versnumb[] = { VERSNUMB };



/**	Current line number in configuration file.
*/
static unsigned long lineno = 0UL;



#ifndef RSHDOWN_BUFFER_SIZE
/**	Size for datagram buffer.
*/
#define RSHDOWN_BUFFER_SIZE 512
#endif



/**	Flag: Local IP address is configured.
*/
#define FLAG_HAVE_LOCAL_IP		1

/**	Flag: Local port number is configured.
*/
#define FLAG_HAVE_LOCAL_PORT		2

/**	Flag: Remote IP address is configured.
*/
#define FLAG_HAVE_REMOTE_IP		4

/**	Flag: Remote port number is configured.
*/
#define FLAG_HAVE_REMOTE_PORT		8

/**	Flag: Have local datagram file name.
*/
#define FLAG_HAVE_LOCAL_DATA		16

/**	Flag: Have shutdown program name.
*/
#define FLAG_HAVE_SCRIPT		32

/**	The required configuration settings.
*/
#define FLAGS_REQUIRED			31



/**	Buffer for datagram from file.
*/
static char buffer_for_filedata[RSHDOWN_BUFFER_SIZE];



/**	Buffer for datagram received from network.
*/
static char buffer_for_network[RSHDOWN_BUFFER_SIZE];



/**	File name of datagram file.
*/
static char script_name[MAXPATHLEN];



/**	Number of bytes used in message buffer.
*/
static size_t buffer_used;



/**	Option names in configuration file.
*/
static char *keywords[] = {
  "sender-ip",
  "sender-port",
  "receiver-ip",
  "receiver-port",
  "file",
  "action",
  "syslog",
  NULL
};



/**	Sender address.
*/
static struct sockaddr_in	sender_addr;

/**	My own address.
*/
static struct sockaddr_in	this_addr;

/**	Receiver address.
*/
static struct sockaddr_in	receiver_addr;




#line 1014 "rshdown.ctr"

#line 1015 "rshdown.ctr"

#line 1016 "rshdown.ctr"

#line 1017 "rshdown.ctr"



/**	Convert dotted decimal notation string to IP4 address in _host_
	notation.
	@param	hn	Host name (IP4 address in dotted decimal notation.
	@return	IP4 address in _host_ byte order on success, 0UL on error.
*/
static
unsigned long
dotted_string_to_ip DK_P1(char *, hn)
{
  unsigned long back = 0UL;
  unsigned long u1 = 0U, u2 = 0U, u3 = 0U, u4 = 0U, u = 0U;
  int ende, state; char *ptr;
  if(hn) {
    state = 0;
    u = u1 = u2 = u3 = u4 = 0UL;
    ptr = hn; ende = 0;
    while(!ende) {
      if(*ptr) {
	if(isdigit(*ptr)) {
	  u = 0UL;
	  switch(*ptr) {
	    case '0': u = 0UL; break;
	    case '1': u = 1UL; break;
	    case '2': u = 2UL; break;
	    case '3': u = 3UL; break;
	    case '4': u = 4UL; break;
	    case '5': u = 5UL; break;
	    case '6': u = 6UL; break;
	    case '7': u = 7UL; break;
	    case '8': u = 8UL; break;
	    case '9': u = 9UL; break;
	  }
	  switch(state) {
	    case 0: u1 = 10UL * u1 + u; break;
	    case 1: u2 = 10UL * u2 + u; break;
	    case 2: u3 = 10UL * u3 + u; break;
	    case 3: u4 = 10UL * u4 + u; break;
	  }
	} else {
	  if(*ptr == '.') {
	    state++;
	    if(state >= 4) {
	      ende = 1;
	    }
	  }
	}
	ptr++;
      } else {
	ende = 1;
      }
    }
  }
  u1 = u1 << 24; u1 = u1 & 0xFF000000UL;
  u2 = u2 << 16; u2 = u2 & 0x00FF0000UL;
  u3 = u3 <<  8; u3 = u3 & 0x0000FF00UL;
  u4 = u4 & 0x000000FFUL;
  back = u1 | u2 | u3 | u4;
  return back;
}



/**	Convert host name string to IP address.
	@param	str	Host name as string.
	@return	IP4 address on success, 0UL on error.
*/
static unsigned long
string_to_ip DK_P1(char *,str)
{
  unsigned long back = 0UL;
  struct hostent *hp;
  unsigned long *lp;
  back = dotted_string_to_ip(str);
  if(back) {
    back = htonl(back);
  } else {
    hp = gethostbyname(str);
    if(hp) {
      if(hp->h_addr_list) {
        if(hp->h_length) {
	  lp = (unsigned long *)(*(hp->h_addr_list));
	  if(lp) {
	    back = *lp;
	  } else {
	    /* ERROR: Empty host list */
	  }
	} else {
	  /* ERROR: Empty address list */
	}
      } else {
        /* ERROR: Empty address list */
      }
    } else {
      /* ERROR: Host name not found */
    }
  }
  return back;
}



/**	Read configuration file.
	@param	file	Configuration file, opened for reading.
	@return	1 on success, 0 on error.
*/
static int
read_config_file DK_P1(FILE *,file)
{
  int back = 0;
  char buffer[MAXPATHLEN+256], *p1, *p2;
  int cc, act, i;
  unsigned long ul;
  unsigned u;
  unsigned short us;
  
#line 1134 "rshdown.ctr"

  
  cc = back = 1; flags = 0;
  sender_addr.sin_family = AF_INET;
  receiver_addr.sin_family= AF_INET;
  sender_addr.sin_port = 0;
  receiver_addr.sin_port = 0;
  sender_addr.sin_addr.s_addr = 0UL;
  receiver_addr.sin_addr.s_addr = 0UL;
  while(cc && back) {
    if(fgets(buffer,sizeof(buffer),file)) {
      dkstr_delcomm(buffer, '#');
      
#line 1147 "rshdown.ctr"
      lineno++; 
      p1 = dkstr_start(buffer, NULL);
      if(p1) {
        p2 = dkstr_next(p1, NULL);
	if(p2) {
	  dkstr_chomp(p2, NULL);
	  
	  act = dkstr_array_index(keywords, p1, 1);
	  
	  switch(act) {
	    case 0: {
	      
	      ul = string_to_ip(p2);
	      if(ul) {
	        sender_addr.sin_addr.s_addr = ul;
		flags |= FLAG_HAVE_REMOTE_IP;
		
	      } else {
	        back = 0;
fprintf(stderr, "Host \"%s\" not found!\n", p2); fflush(stderr);
	      }
	    } break;
	    case 1: {
	      
	      if(sscanf(p2, "%u", &u) ==1) {
	        us = u;
		us = htons(us);
		sender_addr.sin_port = us;
		flags |= FLAG_HAVE_REMOTE_PORT;
	      } else {
	        back = 0;
fprintf(stderr, "Illegal port number \"%s\"!\n", p2); fflush(stderr);
	      }
	    } break;
	    case 2: {
	      
	      ul = string_to_ip(p2);
	      if(ul) {
	        receiver_addr.sin_addr.s_addr = ul;
		flags |= FLAG_HAVE_LOCAL_IP;
		
	      } else {
	        back = 0;
fprintf(stderr, "Host \"%s\" not found!\n", p2); fflush(stderr);
	      }
	    } break;
	    case 3: {
	      
	      if(sscanf(p2, "%u", &u) ==1) {
	        us = u;
		us = htons(us);
		receiver_addr.sin_port = us;
		flags |= FLAG_HAVE_LOCAL_PORT;
	      } else {
	        back = 0;
fprintf(stderr, "Illegal port number \"%s\"!\n", p2); fflush(stderr);
	      }
	    } break;
	    case 4: {
	      FILE *file2;
	      
	      file2 = dksf_fopen(p2, "r");
	      if(file2) {
	        buffer_used = fread(
		  buffer_for_filedata,
		  1,
		  (sizeof(buffer_for_filedata)),
		  file2
		);
		if(buffer_used > 0) {
		  
		  flags |= FLAG_HAVE_LOCAL_DATA;
		} else {
		  back = 0;
fprintf(stderr, "Failed to read data from file \"%s\"!\n", p2); fflush(stderr);
		}
	        fclose(file2);
	      } else {
	        back = 0;
fprintf(stderr, "Failed to open file \"%s\"!\n", p2); fflush(stderr);
	      }
	    } break;
	    case 5: {
	      
	      if(strlen(p2) < sizeof(script_name)) {
	        strcpy(script_name, p2);
		flags |= FLAG_HAVE_SCRIPT;
	      } else {
	        back = 0;
fprintf(stderr, "Script name \"%s\" too long!\n", p2); fflush(stderr);
	      }
	    } break;
	    case 6: {
	      
	      i = dkslsupp_get_facility(p2);
	      if(i) {
	        syslog_facility = i;
	      } else {
fprintf(stderr, "Unknown syslog facility \"%s\"!\n", p2); fflush(stderr);
	      }
	    } break;
	    default: {
	      back = 0;
fprintf(stderr, "Unknown configuration keyword \"%s\"!\n", p1); fflush(stderr);
	    } break;
	  }
	} else {
	}
      } else {
      }
    } else {
      cc = 0;
    }
  }
  if(back) {
    if((flags & FLAGS_REQUIRED) != FLAGS_REQUIRED) {
      back = 0;
fprintf(stderr, "Incomplete configuration!\n");
fflush(stderr);
    }
  } 
  
  
#line 1269 "rshdown.ctr"

  return back;
}



/**	Type definition required to use sizeof() operator.
*/
typedef struct sockaddr_in SAIN;

/**	Conversion to struct sockaddr *.
*/
#define SA(x) ((struct sockaddr *)x)

/**	Size of Internet socket address.
*/
#define SZSAIN sizeof(SAIN)



/**	Run the service loop.
	@return	1 on success, 0 on error.
*/
static
int
run_the_service DK_P0()
{
  int back = 0;
  int sock;
  int sz;
  ssize_t bytes_read;
  int cc, action;

  action = 0;
  sock = socket(AF_INET, SOCK_DGRAM, 0);
  if(sock > -1) {
    if(bind(sock, SA(&receiver_addr), SZSAIN) == 0) {
      cc = 1; back = 1;
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_NOTICE, "Service starting. (%s %s)", progname, versnumb);
closelog();
#endif
      while(cc) {
        if(sighup_received) cc = 0;
	if(sigint_received) cc = 0;
	if(sigterm_received) cc = 0;
	if(sigpipe_received) cc = 0;
        if(cc) {
	  sz = SZSAIN;
	  DK_MEMCPY(&this_addr,&sender_addr,SZSAIN);
	  bytes_read = recvfrom(sock, buffer_for_network, sizeof(buffer_for_network), 0, SA(&this_addr), &sz);
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_NOTICE, "Received %lu bytes!",(unsigned long)bytes_read);
closelog();
#endif
	  if(bytes_read > 0) {
	    if((size_t)bytes_read == buffer_used) {
              if(sz == SZSAIN) {
	        if(this_addr.sin_family == sender_addr.sin_family) {
		  if(this_addr.sin_addr.s_addr == sender_addr.sin_addr.s_addr)
		  {
		    if(this_addr.sin_port == sender_addr.sin_port) {
      if(DK_MEMCMP(buffer_for_network,buffer_for_filedata,buffer_used) == 0) {
        cc = 0; action = 1;
      } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Illegal data!");
closelog();
#endif
		      }
		    } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Invalid sender address port!");
closelog();
#endif
		    }
		  }
		  else
		  {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Invalid sender address ip!");
closelog();
#endif
		  }
		} else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Invalid sender address family!");
closelog();
#endif
		}
	      } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Invalid sender address length!");
closelog();
#endif
	      }
	    } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Invalid data packet size!");
closelog();
#endif
	    }
	  } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Error while receiving data!");
closelog();
#endif
	  }
	}
      }
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_NOTICE, "Service going down (%s %s)",progname,versnumb);
closelog();
#endif
    } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Failed to bind local address!");
closelog();
#endif
    }
    close(sock);
  } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Failed to obtain socket (errno=%d)!",errno);
closelog();
#endif
  }
  if(back) {
    if(action) {
      if(flags & FLAG_HAVE_SCRIPT) {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_EMERG, "Running power-fail script!");
syslog(LOG_EMERG, "Script name %s", script_name);
closelog();
#endif
        (void)system(script_name);
      } else {
#if DK_HAVE_SYSLOG_H
#endif
#ifdef SIGPWR
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_EMERG, "Sending power-fail signal SIGPWR=%d to init process!",SIGPWR);
closelog();
	kill(1, SIGPWR);
#else
#ifdef SIGINFO
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_EMERG, "Sending power-fail SIGINFO=%d signal to init process!",SIGINFO);
closelog();
        kill(1, SIGINFO);
#else
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_EMERG, "Sending power-fail signal 9 to init process!");
closelog();
        kill(1, 9);
#endif
#endif
      }
    }
  }
  return back;
}



/**	Run the daemon.
	@return	1 on success, 0 on error.
*/
static int
run_daemon DK_P0()
{
  int back = 0;
  int maxfiles, i;
  dk_signal_disp_t disp_hup, disp_int, disp_term, disp_pipe;
  pid_t pid;
  (void)chdir("/");
  umask(077);
  maxfiles = (int)dksf_get_maxfiles();
  for(i = 0; i < maxfiles; i++) (void)close(i);
  disp_hup = disp_int = disp_term = disp_pipe = NULL;
  sighup_received = sigint_received = sigterm_received = sigpipe_received = 0;
  pid = fork();
  if(pid == 0) {
#if DK_HAVE_SETSID
    setsid();
#else
#if DK_HAVE_SETPGRP
    setpgrp();
#endif
#endif
    disp_hup = dksignal_set(SIGHUP,handler_sighup);
    pid = fork();
    if(pid == 0) {
      dksf_write_pid_file(progname, 1);
      disp_int = dksignal_set(SIGINT,handler_sigint);
      disp_term = dksignal_set(SIGTERM,handler_sigterm);
      disp_pipe = dksignal_set(SIGPIPE,handler_sigpipe);
      back = run_the_service();
      if(disp_pipe) { dksignal_set(SIGPIPE,disp_pipe); }
      if(disp_term) { dksignal_set(SIGTERM,disp_term); }
      if(disp_int)  { dksignal_set(SIGINT,disp_int); }
      dksf_write_pid_file(progname, 0);
    } else {
      if(pid > (pid_t)(-1)) {
        back = 1;
      } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Failed to create new process!");
closelog();
#endif
      }
    }
    if(disp_hup) { dksignal_set(SIGHUP,disp_hup); }
  } else {
    if(pid > (pid_t)(-1)) {
      back = 1;
    } else {
#if DK_HAVE_SYSLOG_H
openlog(progname, (LOG_CONS | LOG_NDELAY | LOG_PID), syslog_facility);
syslog(LOG_ERR, "Failed to create new process!");
closelog();
#endif
    }
  }
  return back;
}



/**	Run with specified configuration file.
	@param	filename	Configuration file name.
	@return	1 on success, 0 on error.
*/
static
int
run_for_config_file DK_P1(char *,filename)
{
  int back = 0;
  FILE *file;
  file = dksf_fopen(filename, "r");
  if(file) {
    if(read_config_file(file)) {
      back = run_daemon();
    }
    fclose(file);
  } else {
    /* ERROR: Failed to open config file */
fprintf(stderr, "Failed to open configuration file!\n");
fflush(stderr);
  }
  return back;
}




/**	Default configuration file name.
*/
static char default_config_file_name[] = {
  DK_SYSCONFDIR "/rshdown/rshdown.conf"
};



/**	The main() function of the rshdown program (Unix/Linux).
	@param	argc	Number of command line arguments.
	@param	argv	Command line arguments array.
	@return	0 on success, any other value indicates an error.
*/
#ifdef DK_HAVE_PROTOTYPES
int main(int argc, char *argv[])
#else
int main(argc, argv) int argc; char *argv[];
#endif
{
  if(argc > 1) {
    exval = run_for_config_file(argv[1]);
  } else {
    exval = run_for_config_file(default_config_file_name);
  }
  exval = (exval ? 0 : 1);
  exit(exval); return exval;
}



/* ========== End of Linux/Unix code */


#endif


