/***************************************************************************/
/*                                                                         */
/* Project:     OpenSLP - OpenSource implementation of Service Location    */
/*              Protocol Version 2                                         */
/*                                                                         */
/* File:        slpd_main.c                                                */
/*                                                                         */
/* Abstract:    Main daemon loop                                           */
/*                                                                         */
/*-------------------------------------------------------------------------*/
/*                                                                         */
/*     Please submit patches to http://www.openslp.org                     */
/*                                                                         */
/*-------------------------------------------------------------------------*/
/*                                                                         */
/* Copyright (C) 2000 Caldera Systems, Inc                                 */
/* 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 copyright  */
/*      notice, this list of conditions and the following disclaimer in    */
/*      the documentation and/or other materials provided with the         */
/*      distribution.                                                      */
/*                                                                         */
/*      Neither the name of Caldera Systems nor the names of its           */
/*      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 CALDERA      */
/* SYSTEMS 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.    */
/*                                                                         */
/***************************************************************************/

#include "slpd.h"

/*=========================================================================*/
/* slpd includes                                                           */
/*=========================================================================*/
#include "slpd_log.h"
#include "slpd_socket.h"
#include "slpd_incoming.h"
#include "slpd_outgoing.h"
#include "slpd_database.h"
#include "slpd_cmdline.h"
#include "slpd_knownda.h"
#include "slpd_property.h"
#ifdef ENABLE_SLPv2_SECURITY
#include "slpd_spi.h"
#endif

/*=========================================================================*/
/* common code includes                                                    */
/*=========================================================================*/
#include "slp_xmalloc.h"
#include "slp_xid.h"


/*==========================================================================*/
int G_SIGALRM;
int G_SIGTERM;
int G_SIGHUP;                                                                                                 
#ifdef DEBUG
int G_SIGINT;		/* Signal being used for dumping registrations */
#endif 
/*==========================================================================*/


/*-------------------------------------------------------------------------*/
void LoadFdSets(SLPList* socklist, 
                int* highfd, 
                fd_set* readfds, 
                fd_set* writefds)
/*-------------------------------------------------------------------------*/
{
    SLPDSocket* sock = 0;
    SLPDSocket* del = 0;

    sock = (SLPDSocket*)socklist->head;
    while(sock)
    {
        if(sock->fd > *highfd)
        {
            *highfd = sock->fd;
        }

        switch(sock->state)
        {
        case DATAGRAM_UNICAST:
        case DATAGRAM_MULTICAST:
        case DATAGRAM_BROADCAST:
            FD_SET(sock->fd,readfds);
            break;

        case SOCKET_LISTEN:
            if(socklist->count < SLPD_MAX_SOCKETS)
            {
                FD_SET(sock->fd,readfds);
            }
            break;

        case STREAM_READ:
        case STREAM_READ_FIRST:
            FD_SET(sock->fd,readfds);
            break;

        case STREAM_WRITE:
        case STREAM_WRITE_FIRST:
        case STREAM_CONNECT_BLOCK:
            FD_SET(sock->fd,writefds);
            break;

        case SOCKET_CLOSE:
            del = sock;
            break;

        default:
            break;
        }

        sock = (SLPDSocket*)sock->listitem.next;

        if(del)
        {
            SLPDSocketFree((SLPDSocket*)SLPListUnlink(socklist,(SLPListItem*)del));     
            del = 0;
        }
    }
}


/*------------------------------------------------------------------------*/
void HandleSigTerm()
/*------------------------------------------------------------------------*/
{
    struct timeval  timeout;
    fd_set          readfds;
    fd_set          writefds;
    int             highfd          = 0;
    int             fdcount         = 0;

    SLPDLog("****************************************\n");
    SLPDLogTime();
    SLPDLog("SLPD daemon shutting down\n");
    SLPDLog("****************************************\n");

    /* close all incoming sockets */
    SLPDIncomingDeinit();

    /* unregister with all DAs */
    SLPDKnownDADeinit();

    timeout.tv_sec  = 5;
    timeout.tv_usec = 0; 

    /* Do a dead DA passive advert to tell everyone we're goin' down */
    SLPDKnownDAPassiveDAAdvert(0, 1);

    /* if possible wait until all outgoing socket are done and closed */
    while(SLPDOutgoingDeinit(1))
    {
        FD_ZERO(&writefds);
        FD_ZERO(&readfds);
        LoadFdSets(&G_OutgoingSocketList, &highfd, &readfds,&writefds);
        fdcount = select(highfd+1,&readfds,&writefds,0,&timeout);
        if(fdcount == 0)
        {
            break;
        }

        SLPDOutgoingHandler(&fdcount,&readfds,&writefds);
    }

    SLPDOutgoingDeinit(0);

    SLPDLog("****************************************\n");
    SLPDLogTime();
    SLPDLog("SLPD daemon shut down\n");
    SLPDLog("****************************************\n");

#ifdef DEBUG
    #ifdef ENABLE_SLPv2_SECURITY
    SLPDSpiDeinit();
    #endif
    SLPDDatabaseDeinit();
    SLPDPropertyDeinit();
    SLPDLogFileClose();
    xmalloc_deinit();    
#endif

}

/*------------------------------------------------------------------------*/
void HandleSigHup()
/*------------------------------------------------------------------------*/
{
    /* Reinitialize */
    SLPDLog("****************************************\n");
    SLPDLogTime();
    SLPDLog("SLPD daemon reset by SIGHUP\n");
    SLPDLog("****************************************\n\n");

    /* unregister with all DAs */
    SLPDKnownDADeinit();

    /* re-read properties */
    SLPDPropertyInit(G_SlpdCommandLine.cfgfile);

#ifdef ENABLE_SLPv2_SECURITY
    /* Re-initialize SPI stuff*/
    SLPDSpiInit(G_SlpdCommandLine.spifile);
#endif
    
    /* Re-read the static registration file (slp.reg)*/
    SLPDDatabaseReInit(G_SlpdCommandLine.regfile);

    /* Rebuild Known DA database */
    SLPDKnownDAInit();

    SLPDLog("****************************************\n");
    SLPDLogTime();
    SLPDLog("SLPD daemon reset finished\n");
    SLPDLog("****************************************\n\n");
}

/*------------------------------------------------------------------------*/
void HandleSigAlrm()
/*------------------------------------------------------------------------*/
{
    SLPDIncomingAge(SLPD_AGE_INTERVAL);
    SLPDOutgoingAge(SLPD_AGE_INTERVAL);
    SLPDKnownDAImmortalRefresh(SLPD_AGE_INTERVAL);
    SLPDKnownDAPassiveDAAdvert(SLPD_AGE_INTERVAL,0);
    SLPDKnownDAActiveDiscovery(SLPD_AGE_INTERVAL);
    SLPDDatabaseAge(SLPD_AGE_INTERVAL,G_SlpdProperty.isDA);
}


#ifdef DEBUG
/*--------------------------------------------------------------------------*/
void HandleSigInt()
/*--------------------------------------------------------------------------*/
{
    SLPDIncomingSocketDump();
    SLPDOutgoingSocketDump();
    SLPDKnownDADump();
    SLPDDatabaseDump();
}
#endif


#ifndef _WIN32
/*-------------------------------------------------------------------------*/
int CheckPid(const char* pidfile)
/* Check a pid file to see if slpd is already running                      */
/*                                                                         */
/* Returns: 0 on success.  non-zero on failure                             */
/*-------------------------------------------------------------------------*/
{
    pid_t   pid;
    FILE*   fd;
    char    pidstr[14];

    /*------------------------------------------*/
    /* make sure that we're not running already */
    /*------------------------------------------*/
    /* read the pid from the file */
    fd = fopen(pidfile,"r");
    if(fd)
    {
        memset(pidstr,0,14);
        fread(pidstr,13,1,fd);
        pid = atoi(pidstr);
        if(pid)
        {
            if(kill(pid,0) == 0)
            {
                /* we are already running */
                return -1;
            }
        }

        fclose(fd);
    }

    return 0;
}


/*-------------------------------------------------------------------------*/
int WritePid(const char* pidfile, pid_t pid)
/* Write the pid file                                                      */
/*                                                                         */
/* Returns: 0 on success.  non-zero on failure                             */
/*-------------------------------------------------------------------------*/
{
    FILE*   fd;
    char    pidstr[14];

    /* write my pid to the pidfile */
    fd = fopen(pidfile,"w");
    if(fd)
    {
        sprintf(pidstr,"%i",(int)pid);
        fwrite(pidstr,strlen(pidstr),1,fd);
        fclose(fd);
    }

    return 0;
}


/*-------------------------------------------------------------------------*/
int Daemonize(const char* pidfile)
/* Turn the calling process into a daemon (detach from tty setuid(), etc   */
/*                                                                         */      
/* Returns: zero on success non-zero if slpd could not daemonize (or if    */
/*          slpd is already running                             .          */
/*-------------------------------------------------------------------------*/
{
    FILE*   fd;
    struct  passwd* pwent;
    pid_t   pid;
    char    pidstr[14];

    /* fork() if we should detach */
    if(G_SlpdCommandLine.detach)
    {
        pid = fork();
    }
    else
    {
        pid = getpid();
    }

    /* parent or child? */
    switch(pid)
    {
    case -1:
        return -1;
    case 0:
        /* child lives */
        break;

    default:
        /* parent writes pid (or child) pid file and dies */
        fd = fopen(pidfile,"w");
        if(fd)
        {
            sprintf(pidstr,"%i",(int)pid);
            fwrite(pidstr,strlen(pidstr),1,fd);
            fclose(fd);
        }
        if(G_SlpdCommandLine.detach)
        {
            exit(0);
        }
        break;
    }

    close(0); 
    close(1); 
    close(2); 
    setsid(); /* will only fail if we are already the process group leader */

    /*----------------*/
    /* suid to daemon */
    /*----------------*/
    /* TODO: why do the following lines mess up my signal handlers? */
    pwent = getpwnam("daemon"); 
    if(pwent)
    {
        if(setgroups(1, &pwent->pw_gid) < 0 ||
           setgid(pwent->pw_gid) < 0 ||
           setuid(pwent->pw_uid) < 0)
        {
            /* TODO: should we log here and return fail */
	    exit(1);
        }
    }
    else
	exit(1);

    /*--------------------*/
    /* Set cwd to / (root)*/
    /*--------------------*/
    chdir("/");
    
    return 0;
}


/*--------------------------------------------------------------------------*/
void SignalHandler(int signum)
/*--------------------------------------------------------------------------*/
{
    switch(signum)
    {
    case SIGALRM:
        G_SIGALRM = 1;
        break;

    case SIGTERM:
        G_SIGTERM = 1;
        break;

    case SIGHUP:
        G_SIGHUP = 1;
        break;

#ifdef DEBUG
    case SIGINT:
        G_SIGINT = 1;
        break;
#endif

    case SIGPIPE:
    default:
        break;
    }
}


/*-------------------------------------------------------------------------*/
int SetUpSignalHandlers()
/*-------------------------------------------------------------------------*/
{
    int result;
    struct sigaction sa;

    sa.sa_handler    = SignalHandler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags      = 0;//SA_ONESHOT;
#if defined(HAVE_SA_RESTORER)
    sa.sa_restorer   = 0;
#endif

    result = sigaction(SIGALRM,&sa,0);
    result |= sigaction(SIGTERM,&sa,0);
    result |= sigaction(SIGPIPE,&sa,0);

#ifdef DEBUG
    result |= sigaction(SIGINT,&sa,0);
#endif

    signal(SIGHUP,SignalHandler);
    //result |= sigaction(SIGHUP,&sa,0);

    return result;
}

/*=========================================================================*/
int main(int argc, char* argv[])
/*=========================================================================*/
{
    fd_set          readfds;
    fd_set          writefds;
    int             highfd;
    int             fdcount         = 0;

#ifdef DEBUG
    xmalloc_init("/var/log/slpd_xmalloc.log",0);
#endif

    /*------------------------*/
    /* Parse the command line */
    /*------------------------*/
    if(SLPDParseCommandLine(argc,argv))
    {
        SLPDFatal("Invalid command line\n");
    }

    /*------------------------------*/
    /* Make sure we are root        */
    /*------------------------------*/
    if(getuid() != 0)
    {
        SLPDFatal("slpd must be started by root\n");
    }

    /*--------------------------------------*/
    /* Make sure we are not already running */
    /*--------------------------------------*/
    if(CheckPid(G_SlpdCommandLine.pidfile))
    {
        SLPDFatal("slpd is already running. Check %s\n",
                 G_SlpdCommandLine.pidfile);
    }

    /*------------------------------*/
    /* Initialize the log file      */
    /*------------------------------*/
    if(SLPDLogFileOpen(G_SlpdCommandLine.logfile, 1))
    {
        SLPDFatal("Could not open logfile %s\n",G_SlpdCommandLine.logfile);
    }

    /*------------------------*/
    /* Seed the XID generator */
    /*------------------------*/
    SLPXidSeed();

    /*---------------------*/
    /* Log startup message */
    /*---------------------*/
    SLPDLog("****************************************\n");
    SLPDLogTime();
    SLPDLog("SLPD daemon started\n");
    SLPDLog("****************************************\n");
    SLPDLog("Command line = %s\n",argv[0]);
    SLPDLog("Using configuration file = %s\n",G_SlpdCommandLine.cfgfile);
    SLPDLog("Using registration file = %s\n",G_SlpdCommandLine.regfile);
#ifdef ENABLE_SLPv2_SECURITY
    SLPDLog("Using SPI file = %s\n",G_SlpdCommandLine.spifile);
#endif
   
    /*--------------------------------------------------*/
    /* Initialize for the first time                    */
    /*--------------------------------------------------*/
    if(SLPDPropertyInit(G_SlpdCommandLine.cfgfile) ||
#ifdef ENABLE_SLPv2_SECURITY
       SLPDSpiInit(G_SlpdCommandLine.spifile) ||
#endif     
       SLPDDatabaseInit(G_SlpdCommandLine.regfile) ||
       SLPDIncomingInit() ||
       SLPDOutgoingInit() ||
       SLPDKnownDAInit())
    {
        SLPDFatal("slpd initialization failed\n");
    }
    SLPDLog("Agent Interfaces = %s\n",G_SlpdProperty.interfaces);
    SLPDLog("Agent URL = %s\n",G_SlpdProperty.myUrl);

    /*---------------------------*/
    /* make slpd run as a daemon */
    /*---------------------------*/
    if(Daemonize(G_SlpdCommandLine.pidfile))
    {
        SLPDFatal("Could not daemonize\n");
    }

    /*-----------------------*/
    /* Setup signal handlers */
    /*-----------------------*/
    if(SetUpSignalHandlers())
    {
        SLPDFatal("Error setting up signal handlers.\n");
    }

    /*------------------------------*/
    /* Set up alarm to age database */
    /*------------------------------*/
    alarm(SLPD_AGE_INTERVAL);

    /*-----------*/
    /* Main loop */
    /*-----------*/
    SLPDLog("Startup complete entering main run loop ...\n\n");
    G_SIGALRM   = 0;
    G_SIGTERM   = 0;
    G_SIGHUP    = 0;    
#ifdef DEBUG
    G_SIGINT    = 0;
#endif

    while(G_SIGTERM == 0)
    {
        /*--------------------------------------------------------*/
        /* Load the fdsets up with all valid sockets in the list  */
        /*--------------------------------------------------------*/
        highfd = 0;
        FD_ZERO(&readfds);
        FD_ZERO(&writefds);
        LoadFdSets(&G_IncomingSocketList, &highfd, &readfds,&writefds);
        LoadFdSets(&G_OutgoingSocketList, &highfd, &readfds,&writefds);

        /*--------------------------------------------------*/
        /* Before select(), check to see if we got a signal */
        /*--------------------------------------------------*/
        if(G_SIGALRM || G_SIGHUP)
        {
            goto HANDLE_SIGNAL;
        }

        /*-------------*/
        /* Main select */
        /*-------------*/
        fdcount = select(highfd+1,&readfds,&writefds,0,0);
        if(fdcount > 0) /* fdcount will be < 0 when interrupted by a signal */
        {
            SLPDIncomingHandler(&fdcount,&readfds,&writefds);
            SLPDOutgoingHandler(&fdcount,&readfds,&writefds);
        }

        /*----------------*/
        /* Handle signals */
        /*----------------*/
        HANDLE_SIGNAL:
        if(G_SIGHUP)
        {
            HandleSigHup();
            G_SIGHUP = 0;
        }
        if(G_SIGALRM)
        {
            HandleSigAlrm();
            G_SIGALRM = 0;
            alarm(SLPD_AGE_INTERVAL);
        }
#ifdef DEBUG
	if (G_SIGINT)
	{
	    HandleSigInt();
	    G_SIGINT = 0;
	}			
#endif

    } /* End of main loop */

    /* Got SIGTERM */
    HandleSigTerm();

    return 0;
}
#endif /*ifndef _WIN32 */



