--- /dev/null
+/* ========================================================================== **
+ * PrequelD.c
+ *
+ * Copyright:
+ * Copyright (C) 2012 by Christopher R. Hertel
+ *
+ * Email: crh@ubiqx.mn.org
+ *
+ * $Id: PrequelD.c 2012-06-05 21:40:38 -0500 crh$
+ *
+ * -------------------------------------------------------------------------- **
+ *
+ * Description:
+ * PeerDist protocol server daemon.
+ *
+ * -------------------------------------------------------------------------- **
+ *
+ * License:
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ * -------------------------------------------------------------------------- **
+ * This code was developed in participation with
+ * the Protocol Freedom Information Foundation.
+ * See http://www.protocolfreedom.org/ for more information.
+ * -------------------------------------------------------------------------- **
+ *
+ * Notes:
+ *
+ * The Prequel Project website is http://www.ubiqx.org/proj/Prequel/
+ *
+ * Terminology:
+ *
+ * PeerDist - The protocol and/or encoding format used by Microsoft's
+ * BranchCacheTM distributed caching system.
+ *
+ * Prequel - The name of an Open Source project aimed at implementing
+ * PeerDist for use with Linux, *BSD, and other Unix-like OSes.
+ *
+ * References:
+ * [BCOVIEW]: MS W2K8R2 BranchCache Overview
+ * http://technet.microsoft.com/en-us/library/dd637832.aspx
+ *
+ * [MS-CCRSOD]: Content Caching and Retrieval System Overview
+ * http://msdn.microsoft.com/en-us/library/ff632262.aspx
+ *
+ * [MS-PCCRC]: Peer Content Caching and Retrieval: Content Identification
+ * http://msdn.microsoft.com/en-us/library/dd303704.aspx
+ *
+ * To Compile:
+ * cc -I.. -o PrequelD PrequelD.c PD_peerdist1.c PD_read_config.c \
+ * PD_sha2_oSSL.c PD_utils.c ../ubi_sLinkList.c -lcrypto -lpthread
+ *
+ * ========================================================================== **
+ */
+
+#include <time.h> /* For nanosleep(2). */
+#include <stdlib.h> /* Standard Library. */
+#include <stdbool.h> /* Standard boolean type. */
+#include <stdint.h> /* Standard extended integer types. */
+#include <unistd.h> /* For getopt(3), close(2), unlink(2), etc. */
+#include <string.h> /* For strerror(3), strchr(3). */
+#include <ctype.h> /* For isspace(3). */
+#include <sys/types.h> /* For the pid_t and DIR* types. */
+#include <dirent.h> /* For reading directory entries. */
+#include <pthread.h> /* For POSIX Threads. */
+#include <poll.h> /* For ppoll(2) and struct pollfd. */
+#include <sys/socket.h> /* Socket(7) interface header. */
+#include <sys/un.h> /* Unix(7) domain sockets. */
+
+#include "PD_peerdist1.h" /* PeerDist v1 protocol support. */
+#include "PD_read_config.h" /* Read the configuration file. */
+#include "PD_utils.h" /* General PrequelD utilities. */
+#include "ubi_sLinkList.h" /* Linked List module. */
+
+
+/* -------------------------------------------------------------------------- **
+ * Defined Constants:
+ *
+ * DEFAULT_CONFIG_FILE - Default pathname of the configuration file to read
+ * on startup. This value can be overridden at
+ * compile time.
+ */
+
+#ifndef DEFAULT_CONFIG_FILE
+#define DEFAULT_CONFIG_FILE "/etc/prequeld.conf"
+#endif
+
+
+/* -------------------------------------------------------------------------- **
+ * Macros:
+ *
+ * forEach( V, L ) - A macro for traversing a linked list of <pd_ConfigRec>
+ * records.
+ * V - The variable that will point to each node in the
+ * list. V must be substituted by a pointer to a
+ * <pd_ConfigRec>.
+ * L - The list to be traversed.
+ */
+
+#define forEach( V, L ) \
+ for( (V) = (pd_ConfigRec *)ubi_slFirst( (L) ); \
+ NULL != (V); \
+ (V) = (pd_ConfigRec *)ubi_slNext( (V) ) )
+
+
+/* -------------------------------------------------------------------------- **
+ * Typedefs:
+ *
+ * WorkItem - A queue entry with a pointer to a string that is the path to
+ * a source file that is in need of hashing.
+ */
+
+typedef struct
+ {
+ ubi_slNode node;
+ char *srcPath;
+ } WorkItem;
+
+
+/* -------------------------------------------------------------------------- **
+ * Global Constants:
+ *
+ * Copyright - Copyright information.
+ * License - License information string.
+ * Revision - Revision string, generated by revision control.
+ * Id - Longer revision string.
+ *
+ * HelpMsg - The program help message.
+ * VerboseMsg - Verbose help message.
+ */
+
+static const char *Copyright =
+ "Copyright (c) 2012 by Christopher R. Hertel";
+static const char *License =
+ "License: GNU General Public License version 3 (GPLv3)";
+static const char *Revision =
+ "$Revision$";
+static const char *Id =
+ "$Id: PrequelD.c 2012-06-05 21:40:38 -0500 crh$";
+
+static const char *HelpMsg[] =
+ {
+ " Available Options:",
+ " -c <file> Specify the configuration file to use.",
+ " -f Run in the foreground, not as a daemon.",
+ " -h Produce this useful help message, then exit (-hv == more help).",
+ " -l <file> Send log messages to <file>.",
+ " -q Be quiet. Set the verbosity level to zero.",
+ " -t Test-parse the configuration file, then exit.",
+ " -v Be verbose. Add more -v's for more verbosity.",
+ " -V Output version information.",
+ NULL
+ };
+
+static const char *VerboseMsg[] =
+ {
+ "The Options Available to You:",
+ " -c <file> Specify the configuration file to use.",
+ " Default: \"" DEFAULT_CONFIG_FILE "\".",
+ " -f Run in the foreground, not as a daemon.",
+ " Default: Run as a daemon.",
+ " -h Produce a useful help message, then exit.",
+ " Default: Don't produce a useful help message. Run normally.",
+ " -l <file> Send log messages to <file>.",
+ " Default: When running in the foreground, log messages are sent",
+ " to <stderr>. When running in the background, log",
+ " messages are sent to syslog. All initial messages",
+ " (as the program starts up) are sent to <stdout> or",
+ " <stderr>.",
+ " -q Be quiet. Set the verbosity level to zero (0) and produce only",
+ " the minimum set of diagnostic messages. Default verbosity: 1.",
+ " -t Parse the configuration file and report any errors to <stderr>.",
+ " The program will exit once parsing is complete.",
+ " Use -tv to dump the contents of a successfully parsed",
+ " configuration file.",
+ " -v Be verbose. Add more -v's for more verbosity.",
+ " Use of -q or -v overrides the config file verbosity settings.",
+ " Default verbosity: 1.",
+ " -V Output version information.",
+ " Increased verbosity provides copyright and license information.",
+ NULL
+ };
+
+
+/* -------------------------------------------------------------------------- **
+ * Global Variables:
+ *
+ * Verbosity - Controls the level of diagnostic messages that are
+ * produced. The minimum value is 0. Anything above 10
+ * is ridiculous. Default: 1. A maximum value of 255
+ * is enforced both in the PD_read_config module and in
+ * <ReadOpts()> function, below.
+ *
+ * Daemonize - By default PrequelD will run as a daemon, but this
+ * default behavior can be overridden with a command-line
+ * option. There is no configuration file option for this,
+ * so we use a global variable to represent this attribute.
+ *
+ * Cfg - Pointer used to retrieve the configuration information
+ * from the configuration file.
+ *
+ * WorkerThread - The thread ID of the background thread that is doing all
+ * of the real work.
+ *
+ * GlobalMutex - A global mutex attribute. The worker thread needs brief
+ * access to the <Cfg> list and other state attribues.
+ * This allows the foreground and background to share.
+ *
+ * Shutdown - Used to let the worker process know when it is time to
+ * shut down and return.
+ *
+ * WorkQueue - A queue of source file names that are waiting to be
+ * hashed. The queue is filled when a request is made
+ * and no hash cache file is found. It is emptied by
+ * the worker thread. Queued entries have priority over
+ * source files found during directory traversal.
+ */
+
+static unsigned int Verbosity = 1;
+static bool Daemonize = true;
+static pd_Config *Cfg = NULL;
+static pthread_t WorkerThread;
+static pthread_mutex_t GlobalMutex = PTHREAD_MUTEX_INITIALIZER;
+static bool Shutdown = false;
+static ubi_slList WorkQueue[1];
+
+/* -------------------------------------------------------------------------- **
+ * Static Functions:
+ */
+
+static void Usage( const char *progname, int status )
+ /* ------------------------------------------------------------------------ **
+ * Provide usage information, then exit.
+ *
+ * Input: progname - Pointer to a string containing the program name.
+ * status - Exit status to return.
+ *
+ * Output: <none>
+ *
+ * Notes: Output is sent to <stdout> so that it can be piped through
+ * 'more' or 'less' or something else.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ int i;
+ const char **msg = (Verbosity > 1) ? VerboseMsg : HelpMsg;
+
+ Say( "Usage: %s [options]\n", progname );
+ for( i = 0; NULL != msg[i]; i++ )
+ Say( "%s\n", msg[i] );
+
+ exit( status );
+ } /* Usage */
+
+
+static void *Responder( void *thingy )
+ /* ------------------------------------------------------------------------ **
+ * Respond to a request from a client.
+ *
+ * Input: thingy - A pointer to an integer, which is the open
+ * communications socket.
+ *
+ * Output: <none>
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ int client_sock = *((int *)thingy);
+
+ (void)pthread_detach( pthread_self() );
+
+ /* FIX: This is for testing... */
+ (void)write( client_sock, "Response.\n", 10 );
+
+ (void)close( client_sock );
+ return( NULL );
+ } /* Responder */
+
+
+static void Listener( const int lsock )
+ /* ------------------------------------------------------------------------ **
+ * Listen for incomming connection requests and spawn responder threads.
+ *
+ * Input: lsock - Listening socket.
+ *
+ * Output: <none>
+ *
+ * Notes: This function is run within the primary thread, but it is
+ * started after the worker thread has been spun off so it
+ * must lock the global mutex before accessing any commonly
+ * used objects.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ int newSock;
+ int result;
+ pthread_t threadhandle;
+
+ while( (newSock = accept( lsock, NULL, NULL )) <= 0 )
+ {
+ int tmpInt = newSock;
+
+ result = pthread_create( &threadhandle, NULL, &Responder,
+ (void *)&tmpInt );
+ if( result )
+ {
+ if( 0 == pthread_mutex_lock( &GlobalMutex ) )
+ {
+ Log( "Warning: Error creating responder subthread; %s.\n",
+ strerror( result ) );
+ (void)pthread_mutex_unlock( &GlobalMutex );
+ }
+ (void)close( newSock );
+ }
+ }
+
+ if( 0 == pthread_mutex_lock( &GlobalMutex ) )
+ {
+ Log( "Failure: Error accepting socket connection; %s.\n", ErrStr );
+ (void)pthread_mutex_unlock( &GlobalMutex );
+ }
+ close( lsock );
+ } /* Listener */
+
+
+static void WorkHash( const char *srcPathName )
+ /* ------------------------------------------------------------------------ **
+ * Create a hash cache file given a source file name.
+ *
+ * Input: srcPathName - The pathname of the source file to be hashed.
+ *
+ * Output: <none>
+ *
+ * Notes: Dancing with mutex.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ pd_ConfigRec *entry;
+ char *cacheDir;
+ int minBlocks;
+ pd_v1HashType hashType;
+ unsigned char key[PD_V1_KEY_SIZE];
+ int result;
+ int syserr;
+
+ /* Try to grab the mutex.
+ * If we can't grab the mutex, then something's wrong but we cannot log it
+ * because we need the mutex in order to (safely) write to the log.
+ */
+ if( (result = pthread_mutex_lock( &GlobalMutex )) )
+ return;
+
+ /* We have mutex.
+ * Find the config record that points to the source directory tree in
+ * which the source file resides.
+ */
+ entry = pd_FindSrcDir( Cfg, srcPathName );
+ if( NULL == entry )
+ {
+ if( Verbosity > 2 )
+ Log( "Requested file is out of scope: %s\n", srcPathName );
+ (void)pthread_mutex_unlock( &GlobalMutex );
+ return;
+ }
+
+ /* We have mutex and a configuration entry.
+ * Copy all of the config values so that we can release the mutex.
+ */
+ if( entry->cacheDir )
+ {
+ cacheDir = strdup( entry->cacheDir );
+ minBlocks = entry->minBlocks;
+ hashType = entry->v1HashType;
+ syserr = 0;
+ (void)memcpy( key, entry->userParam, PD_V1_KEY_SIZE );
+ (void)pthread_mutex_unlock( &GlobalMutex );
+ }
+ else
+ {
+ /* This should be impossible. Check PD_read_config if this happens. */
+ Log( "Bad configuration entry; cacheDir is NULL.\n" );
+ (void)pthread_mutex_unlock( &GlobalMutex );
+ return;
+ }
+
+ /* Check the <cacheDir> copy. */
+ if( NULL == cacheDir )
+ {
+ if( Verbosity )
+ Log( "Warning: Memory allocation failure in WorkHash().\n" );
+ return;
+ }
+
+ /* This step may take a while.
+ * Which is why we don't want to be holding the mutex now.
+ */
+ result = pd_v1CreateHashCache( srcPathName,
+ cacheDir,
+ minBlocks,
+ hashType,
+ key,
+ &syserr );
+ /* Clean up. */
+ free( cacheDir );
+
+ /* Report the results.
+ * It is not a fatal error to fail to create the cache file, but it should
+ * probably be logged. Logging requires the mutex.
+ */
+ if( Verbosity > 0 )
+ {
+ if( pthread_mutex_lock( &GlobalMutex ) )
+ return; /* Error getting the mutex... can't log without it. */
+
+ if( pd_Success == result )
+ {
+ if( Verbosity > 2 )
+ Log( "File \"%s\" hashed.\n", srcPathName );
+ }
+ else
+ {
+ if( syserr )
+ Log( "Failure hashing \"%s\"; %s; %s.\n", srcPathName,
+ pd_v1StrResult( result ), strerror( syserr ) );
+ else
+ Log( "Failure hashing \"%s\"; %s.\n", srcPathName,
+ pd_v1StrResult( result ) );
+ }
+
+ (void)pthread_mutex_unlock( &GlobalMutex );
+ }
+ } /* WorkHash */
+
+
+static void *WorkerMain( void *thingy )
+ /* ------------------------------------------------------------------------ **
+ * Worker thread mainline.
+ *
+ * Input: thingy - Pointer to an opaque blob that currently isn't used.
+ *
+ * Output: Currently always returns NULL.
+ *
+ * Notes: This code runs as a background thread. It's job is to generate
+ * the hash cache files.
+ *
+ * FIX: The wDir state is designed to allow the worker thread to
+ * traverse configured source directories and hash files that
+ * have not been requested yet. The requested files, listed
+ * in the <WorkQueue> have higher priority. Unfortunately,
+ * directory traversal has not been implemented yet.
+ *
+ * In addition, we should traverse the cache directories and
+ * purge cache files that are out of date or for which the
+ * source file no longer exists.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ enum { wNone, wStop, wQueue, wDir } doState; /* What shall we do? */
+ struct timespec ts = { 0, 500000000 }; /* Half a second. */
+ char *srcPathName = NULL;
+ WorkItem *srcEntry;
+
+ do
+ {
+ /* Start by waiting a half second, just to pace ourselves. */
+ (void)nanosleep( &ts, NULL );
+
+ /* Determine which action to take, and collect any required data.
+ * This requires locking/unlocking the global mutex.
+ */
+ doState = wNone; /* Default action. */
+ if( 0 == pthread_mutex_lock( &GlobalMutex ) )
+ {
+ if( Shutdown )
+ doState = wStop;
+ else
+ {
+ if( 0 == ubi_slCount( WorkQueue ) )
+ doState = wDir;
+ else
+ {
+ srcEntry = (WorkItem *)ubi_slDequeue( WorkQueue );
+ if( NULL != srcEntry )
+ {
+ srcPathName = srcEntry->srcPath;
+ free( srcEntry ); /* Free the node, but not the string. */
+ doState = wQueue;
+ }
+ }
+ }
+ (void)pthread_mutex_unlock( &GlobalMutex );
+ }
+
+ /* Now do the thing. */
+ switch( doState )
+ {
+ case wQueue:
+ /* Hash an entry from the queue. */
+ if( NULL != srcPathName )
+ {
+ WorkHash( srcPathName );
+ free( srcPathName );
+ }
+ break;
+ case wDir:
+ /* Hash an entry from a source directory. */
+ /* FIX: Need directory traversal. */
+ break;
+ default:
+ break;
+ }
+
+ } while( wStop != doState );
+
+ return( NULL );
+ } /* WorkerMain */
+
+
+static void StartWorker( void )
+ /* ------------------------------------------------------------------------ **
+ * Start a background thread to generate hash cache files.
+ *
+ * Input: <none>
+ * Output: <none>
+ *
+ * Notes: The worker thread does the work of creating hash cache files
+ * from source content. It runs separately so that it does not
+ * interfere with communications with our clients.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ int result;
+ pthread_attr_t attr[1];
+
+ /* Set up the attributes to make the thread joinable, then start it. */
+ if( (result = pthread_attr_init( attr )) )
+ LogX( EXIT_FAILURE, "Failure initializing worker thread attributes; %s.\n",
+ strerror( result ) );
+
+ if( (result = pthread_attr_setdetachstate( attr, PTHREAD_CREATE_JOINABLE )) )
+ LogX( EXIT_FAILURE,
+ "Failure setting \"joinable\" attribute for worker thread; %s.\n",
+ strerror( result ) );
+
+ if( (result = pthread_create( &WorkerThread, attr, WorkerMain, NULL )) )
+ LogX( EXIT_FAILURE, "Failure spawning worker thread; %s.\n",
+ strerror( result ) );
+ } /* StartWorker */
+
+
+static int OpenListenSock( const char *sockpath )
+ /* ------------------------------------------------------------------------ **
+ * Open a Unix Domain socket and set it to listen for connection requests.
+ *
+ * Input: sockpath - The pathname of the Unix Domain socket to be
+ * opened for listening.
+ *
+ * Output: The opened and listening socket.
+ * Only a valid value will be returned. This function will exit
+ * the program on error.
+ *
+ * Notes: We call this prior to spinning off any threads, so we can avoid
+ * locking the global mutex.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ struct sockaddr_un laddr[1];
+ int lsock;
+
+ /* Sanity check.
+ * We must be able to fit the socket path into a Unix Domain socket address.
+ */
+ if( strlen( sockpath ) >= sizeof( laddr->sun_path ) )
+ {
+ LogX( EXIT_FAILURE,
+ "Annoyance: Path '%s' exceeds maximum unix domain address length.\n",
+ sockpath );
+ }
+
+ /* Create a unix domain socket. */
+ lsock = socket( AF_UNIX, SOCK_STREAM, 0 );
+ if( lsock < 0 )
+ {
+ LogX( EXIT_FAILURE,
+ "Failure: Unable to create unix domain socket; %s.\n", ErrStr );
+ }
+
+ /* Bind the socket to the name within the namespace. */
+ (void)unlink( sockpath );
+ (void)memset( laddr, 0, sizeof( struct sockaddr_un ) );
+ laddr->sun_family = AF_UNIX;
+ (void)strcpy( laddr->sun_path, sockpath );
+ if( bind( lsock, (struct sockaddr *)laddr, sizeof( laddr ) ) < 0 )
+ {
+ (void)close( lsock );
+ LogX( EXIT_FAILURE,
+ "Failure: Socket bind() to '%s' failed; %s.\n", sockpath, ErrStr );
+ }
+
+ /* Set the socket to listen. */
+ if( listen( lsock, 32 ) < 0 )
+ {
+ (void)close( lsock );
+ (void)unlink( sockpath );
+ LogX( EXIT_FAILURE,
+ "Failure: Cannot listen on unix domain socket '%s'; %s.\n",
+ sockpath, ErrStr );
+ }
+
+ return( lsock );
+ } /* OpenListenSock */
+
+
+static void Spawn( void )
+ /* ------------------------------------------------------------------------ **
+ * Spawn a daemon process, which will take over control for us.
+ *
+ * Input: <none>
+ * Output: <none>
+ *
+ * Notes: If the child process is created successfully, the parent
+ * process is terminated using _exit(2). This bypasses executing
+ * any functions registered with atexit(3) or on_exit(3). We
+ * don't have any such functions but, even so, this is the normal
+ * way to exit the parent process.
+ * See: http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC6
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ pid_t pid = fork();
+
+ if( pid < 0 ) /* Error. */
+ LogX( EXIT_FAILURE, "Cannot spawn child process; %s\n", ErrStr );
+
+ if( pid > 0 ) /* Parent process. */
+ _exit( EXIT_SUCCESS );
+
+ /* Child process.
+ */
+ if( setsid() < 0 )
+ LogX( EXIT_FAILURE, "Failure: setsid(2) failed in Spawn(); %s.\n", ErrStr );
+ if( Verbosity )
+ Log( "Process %d spawned.\n", getpid() );
+ /* Close standard file descriptors. */
+ close( STDIN_FILENO );
+ close( STDOUT_FILENO );
+ close( STDERR_FILENO );
+ return;
+ } /* Spawn */
+
+
+static void ReadKeys( void )
+ /* ------------------------------------------------------------------------ **
+ * Read and store the server secret signing keys from the key files.
+ *
+ * Input: <none>
+ * Output: <none>
+ *
+ * Notes: The signing key is officially known as the "Server Secret".
+ * See the definition of Server Secret in [MS-PCCRC], section 1.1.
+ *
+ * The "Server Secret" is the SHA-256 hash of "an arbitrary length
+ * binary string stored on the server”. [MS-PCCRC] does not give
+ * a name to this arbitrary binary string. It is referred to as
+ * the "Server Passphrase" in Prequel documentation.
+ *
+ * This function makes one or more passes through the sourcedir
+ * configuration list. Each time, it finds the first entry
+ * that has a NULL <userParam> pointer. It attempts to read the
+ * signing key from the keyfile. If it succeeds, it goes
+ * through the rest of the list looking for matching keyfile
+ * names. If it finds a match, it points the <userParam> of
+ * the matching entry to the already-copied key.
+ *
+ * The userParam pointer could be used for storing a more complex
+ * structure or other extended information. Right now, it's only
+ * used for storing the signing key.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ FILE *keyF;
+ pd_ConfigRec *entry;
+ char *kfName;
+ unsigned char *ss = NULL;
+ size_t result;
+
+ do
+ {
+ kfName = NULL;
+ forEach( entry, Cfg ) /* Read ConfigRec entries sequentially. */
+ {
+ if( NULL == entry->userParam )
+ {
+ if( NULL == kfName )
+ {
+ kfName = entry->keyFileName;
+ if( NULL == (ss = (unsigned char *)malloc( PD_V1_KEY_SIZE )) )
+ LogX( EXIT_FAILURE,
+ "Failure: malloc(3) failed in ReadKeys(); %s.\n", ErrStr );
+ if( NULL == (keyF = fopen( kfName, "r" )) )
+ LogX( EXIT_FAILURE,
+ "Failure: Could not open keyfile %s; %s.\n", kfName, ErrStr );
+ result = fread( ss, 1, PD_V1_KEY_SIZE, keyF );
+ if( PD_V1_KEY_SIZE != result )
+ LogX( EXIT_FAILURE,
+ "Failure: Only %d bytes in keyfile %s.\n", result, kfName );
+ (void)fclose( keyF );
+ entry->userParam = ss;
+ }
+ else
+ {
+ if( 0 == strcmp( kfName, entry->keyFileName ) )
+ entry->userParam = ss;
+ }
+ }
+ }
+ } while( NULL != kfName );
+
+ } /* ReadKeys */
+
+
+static void ReadConfig( char *fname )
+ /* ------------------------------------------------------------------------ **
+ * Open, read, and interpret the configuration file.
+ *
+ * Input: fname - The name of the file to open, or NULL.
+ * If <fname> is NULL, then the default file name will
+ * be used.
+ *
+ * Output: <none>
+ *
+ * Notes: The configuration file format was inspired, somewhat, by the
+ * format of the ISC dhcpd.conf file.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ FILE *configF;
+
+ /* Ensure that we have a configurage file pathname,
+ * then attempt to open the file for reading.
+ */
+ if( NULL == fname )
+ fname = DEFAULT_CONFIG_FILE;
+ if( NULL == (configF = fopen( fname, "r" )) )
+ Fail( "Unable to open configuration file \"%s\" for input; %s.\n",
+ fname, ErrStr );
+
+ /* Parse it.
+ * <Cfg> is a global pointer.
+ */
+ Cfg = pd_ParseCfg( configF );
+
+ /* Close it.
+ */
+ fclose( configF );
+ } /* ReadConfig */
+
+
+static void ReadOpts( int argc, char *argv[] )
+ /* ------------------------------------------------------------------------ **
+ * Interpret any command-line options, and the configuration file.
+ *
+ * Input: argc - Count of arguments.
+ * argv - Array of pointers to argument strings.
+ *
+ * Output: <none>
+ *
+ * Notes: This function also calls <ReadConfig()>, which reads and
+ * interprets the configuration file.
+ *
+ * This is quite a workhorse function. It handles collecting
+ * and validating the configuration of the daemon.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ int c;
+ FILE *LogF;
+ extern char *optarg;
+ extern int optind;
+ char *ConfigFile = NULL;
+ char *LogFile = NULL;
+ bool VerbSet = false;
+ bool SpewHelp = false;
+ bool SpewVersion = false;
+ bool TestConfig = false;
+
+ /* Read the arguments.
+ */
+ while( (c = getopt( argc, argv, "c:fhl:qtvV" )) >= 0 )
+ {
+ switch( c )
+ {
+ case 'c':
+ ConfigFile = optarg;
+ break;
+ case 'f':
+ Daemonize = false;
+ break;
+ case 'h':
+ SpewHelp = true;
+ break;
+ case 'l':
+ LogFile = optarg;
+ break;
+ case 'q':
+ Verbosity = 0;
+ VerbSet = true;
+ break;
+ case 't':
+ TestConfig = true;
+ break;
+ case 'v':
+ if( Verbosity < 0xFF )
+ Verbosity++;
+ VerbSet = true;
+ break;
+ case 'V':
+ SpewVersion = true;
+ break;
+ default:
+ /* Unknown option. Provide simple help, then exit.
+ */
+ Verbosity = 0;
+ Usage( argv[0], EXIT_FAILURE );
+ break;
+ }
+ }
+
+ /* Provide version information (and more) if it was requested.
+ */
+ if( SpewVersion )
+ {
+ switch( Verbosity )
+ {
+ case 0:
+ Say( "%s\n", Revision );
+ break;
+ case 1:
+ Say( "%s\n", Id );
+ break;
+ case 2:
+ Say( "%s\n", Id );
+ Say( "%s\n", License );
+ break;
+ default:
+ Say( "%s\n", Id );
+ Say( "%s\n", Copyright );
+ Say( "%s\n", License );
+ Say( "Developed in participation with the " );
+ Say( "Protocol Freedom Information Foundation.\n" );
+ break;
+ }
+ if( SpewHelp && Verbosity )
+ Say( "\n" );
+ }
+
+ /* If help was requested, send help.
+ */
+ if( SpewHelp )
+ {
+ Usage( argv[0], EXIT_SUCCESS );
+ }
+
+ /* If the user wants a configuration file test, provide it,
+ * then exit the program.
+ */
+ if( TestConfig )
+ {
+ (void)setLogFile( stderr );
+ if( NULL == ConfigFile )
+ ConfigFile = DEFAULT_CONFIG_FILE;
+ ReadConfig( ConfigFile );
+ if( (1 == Verbosity) && (NULL != Cfg) )
+ Log( "%s: successfully parsed.\n", ConfigFile );
+ if( 1 < Verbosity )
+ pd_DumpCfg( Cfg );
+ exit( EXIT_SUCCESS );
+ }
+
+ /* Enable logging.
+ * If a log file name was specified, then all messages from this point
+ * on will be directed to the log file.
+ * If no log file was specified, and we will run as a daemon, then we
+ * start logging to syslog().
+ * Otherwise, we log to <stderr> unless/until a log file name is given
+ * in the configuration file.
+ */
+ if( NULL == LogFile )
+ {
+ if( Daemonize )
+ OpenSyslog( "prequeld" );
+ else
+ (void)setLogFile( stderr );
+ }
+ else
+ {
+ LogF = fopen( LogFile, "a" );
+ if( NULL == LogF )
+ Fail( "Unable to open log file \"%s\" for output; %s.\n",
+ LogFile, ErrStr );
+ (void)setLogFile( LogF );
+ }
+
+ /* Open and read the configuration file.
+ * Command-line options override configfile options. This is particularly
+ * tricky when dealing with logging, which has already started.
+ */
+ ReadConfig( ConfigFile );
+ if( NULL == Cfg ) /* Parsing returned no configuration. Exit. */
+ LogX( EXIT_FAILURE, "Null configuration. Exiting.\n" );
+
+ if( NULL == LogFile )
+ {
+ /* We have already started logging to syslog (background) or stderr
+ * (foreground). If a log file was specified in the config file, and
+ * if we are not running in the foreground, use the specified config
+ * file.
+ */
+ if( (NULL != Cfg->logFileName) && Daemonize )
+ {
+ LogF = fopen( Cfg->logFileName, "a" );
+ if( NULL == LogF )
+ Fail( "Unable to open log file \"%s\" for output; %s.\n",
+ Cfg->logFileName, ErrStr );
+ (void)setLogFile( LogF );
+ }
+ }
+ else
+ {
+ /* If we have a command-line specified logfile, store it in the Cfg
+ * structure for later use.
+ */
+ Cfg->logFileName = replaceStr( LogFile, Cfg->logFileName );
+ }
+
+ /* User-set verbosity overrides config file. */
+ if( VerbSet )
+ {
+ pd_ConfigRec *entry;
+
+ forEach( entry, Cfg )
+ entry->verbosity = (0xFF & Verbosity);
+ }
+ } /* ReadOpts */
+
+
+/* -------------------------------------------------------------------------- **
+ * Mainline:
+ */
+
+int main( int argc, char *argv[] )
+ /* ------------------------------------------------------------------------ **
+ * Program mainline.
+ *
+ * Input: argc - You know what this is.
+ * argv - You know what to do.
+ *
+ * Output: EXIT_SUCCESS or, on failure, EXIT_FAILURE.
+ *
+ * ------------------------------------------------------------------------ **
+ */
+ {
+ int listenSock;
+
+ /* Gather working parameters.
+ * Note that ReadOpts() takes care of reading the configuration file.
+ */
+ ReadOpts( argc, argv ); /* Get all set up to run. */
+ ReadKeys(); /* Find the signing keys. */
+
+ /* Become a daemon, unless we were told otherwise. */
+ if( Daemonize )
+ Spawn();
+
+ /* Before creating any subthreads, let's try to open the listening socket. */
+ listenSock = OpenListenSock( Cfg->sockFileName );
+
+ /* Initialize the priority queue and start the worker thread. */
+ (void)ubi_slInitList( WorkQueue );
+ StartWorker();
+
+ /* Listen for incoming requests on the named Unix Domain socket. */
+ Listener( listenSock );
+
+ /* Shut down the worker thread. */
+ (void)pthread_mutex_lock( &GlobalMutex );
+ Shutdown = true;
+ (void)pthread_mutex_unlock( &GlobalMutex );
+ (void)pthread_join( WorkerThread, NULL );
+
+ /* Clean up and exit. */
+ if( Verbosity )
+ Log( "Normal exit.\n" );
+ return( EXIT_SUCCESS );
+ } /* main */
+
+/* ========================================================================== */