/*********************************************************************** * * Copyright (C) 2006 Novell, Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2.1 * of the License. * * This library 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 * Library Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, Novell, Inc. * * To contact Novell about this file by physical or electronic mail, * you may find current contact information at www.novell.com. * * Author: Juan Carlos Luciani * ***********************************************************************/ //===[ Include files ]===================================================== #include "internal.h" #include //===[ External data ]===================================================== //===[ External prototypes ]=============================================== //===[ Manifest constants ]================================================ #define MAXFD 64 #define MIN_THREADS 1 #define MAX_THREADS 4096 #define DEFAULT_BEGIN_THREADS 5 #define DEFAULT_GROW_THREADS 5 #define DOMAIN_SOCKET_FILE_NAME "/var/lib/CASA/authtoken/validate/socket" //===[ Type definitions ]================================================== //===[ Function prototypes ]=============================================== void* WorkerThread(void*); //===[ Global variables ]================================================== // Usage string char usage[] = "\nCasaAuthtokenValidateD: usage: [-p ListenPort] [-b BeginThreads] [-g GrowThreads] [-m MaxThreads] [-D DebugLevel] [-d] [-s]\n"; // Worker thread pool configuration parameters int beginThreads = DEFAULT_BEGIN_THREADS; int growThreads = DEFAULT_GROW_THREADS; int maxThreads = MAX_THREADS; int minWaitingThreads = beginThreads; int maxWaitingThreads = beginThreads * 4; // Worker thread pool operating parameters double numThreads = 0; double numBusyThreads = 0; double numPerishingThreads = 0; // Listen Port Number //int listenPortNumber = 5000; unsigned short int listenPortNumber = 0; // Parameter indicating whether or not the server needs to run // as a daemon. bool daemonize = false; // Name to use for logging purposes char appName[] = "CasaAuthtokenValidateD"; // Debug Level int DebugLevel = 0; bool UseSyslog = false; // Variables for daemon auto-restart after crash feature static bool autoRestartAfterCrash = true; // Synchronization variables pthread_mutex_t interlockedMutex; pthread_mutex_t serverMutex; pthread_cond_t serverCondition; // Operating parameters bool singleThreaded = false; bool terminating = false; // Java parameters JavaVM *g_jvm = NULL; JNIEnv *g_env = NULL; char classpath[] = "-Djava.class.path=/usr/share/java/CASA/authtoken/CasaJaasSupport.jar:/usr/share/java/CASA/authtoken/CasaAuthToken.jar:/usr/share/java/log4j.jar:/usr/share/java/commons-logging.jar:/usr/share/java/CASA/authtoken/external/apache.org/xmlsec-1.4.0.jar:/usr/share/java/xerces-j2.jar:/etc/CASA/authtoken/keys/client"; // Java AuthenticationToken Class and method name //char authTokenClassName[] = "jtest"; //char authTokenClassValidateMethodName[] = "test4"; char authTokenClassName[] = "com/novell/casa/authtoksvc/AuthToken"; char authTokenClassValidateMethodName[] = "validate"; //++======================================================================= void ServiceRequests(void) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { DbgTrace(1, "ServiceRequests- Start\n", 0); // We are now attached to the JVM, find the helper class that // we need. jclass helperClass = g_env->FindClass(authTokenClassName); if (helperClass) { // Helper class found, now get the id of the method that we invoke jmethodID mId = g_env->GetStaticMethodID(helperClass, authTokenClassValidateMethodName, "(Ljava/lang/String;)Ljava/lang/String;"); if (mId) { // Loop until told to terminate while (!terminating) { // Get a request that needs servicing uint32_t requestId = IpcServerGetRequest(); if (requestId != 0) { // We got a request that needs servicing, now get the // data associated with it. char *pReqData; int dataLen = IpcServerGetRequestData(requestId, &pReqData); if (dataLen != 0) { // Lets push the jvm local frame to allow us to clean up our local // references later. g_env->PushLocalFrame(10); // Encapsulate the request data into a string object jstring inString = g_env->NewStringUTF(pReqData); if (inString) { // Invoke our helper method jstring outString = (jstring) g_env->CallStaticObjectMethod(helperClass, mId, inString); // Check if an excption occurred if (g_env->ExceptionCheck() == JNI_TRUE) { // There is a pending exception, display the info which in turn clears it. g_env->ExceptionDescribe(); IpcServerAbortRequest(requestId); } else { if (outString) { // The helper method succeded, complete the request // with the data returned. const char *pOutChars = g_env->GetStringUTFChars(outString, NULL); if (pOutChars) { IpcServerCompleteRequest(requestId, (char*) pOutChars); g_env->ReleaseStringUTFChars(outString, pOutChars); } else { DbgTrace(0, "ServiceRequests- Unable to get UTF characters\n", 0); IpcServerAbortRequest(requestId); } } else { // The helper method failed, just abort the request. IpcServerAbortRequest(requestId); } } } else { DbgTrace(0, "ServiceRequests- UTF String allocation failure\n", 0); IpcServerAbortRequest(requestId); } // Pop the jvm local frame to clean up our local references g_env->PopLocalFrame(NULL); } else { DbgTrace(0, "ServiceRequests- Error obtaining Request data\n", 0); IpcServerAbortRequest(requestId); } } else { // No need to service requests any longer break; } } } else { DbgTrace(0, "ServiceRequests- Failed to get method id\n", 0); } } else { DbgTrace(0, "ServiceRequests- Failed to find helper class\n", 0); } DbgTrace(1, "ServiceRequests- End\n", 0); } /*-- ServiceRequests() --*/ //++======================================================================= void GrowWorkerThreadPool(int growNumber) // // Arguments: // // Returns: // // Abstract: // // Notes: The serverMutex needs to be held when calling this // procedure. // // L2 //=======================================================================-- { DbgTrace(1, "GrowWorkerThreadPool- Start\n", 0); for (int i = 0; i < growNumber; i++) { int threadCreateStatus; pthread_t thread; if ((threadCreateStatus = pthread_create(&thread, NULL, (void*(*)(void*))WorkerThread, NULL) == 0)) { // Worker thread created numThreads ++; } else { DbgTrace(0, "GrowWorkerThreadPool- Thread creation failed, status = %0d\n", threadCreateStatus); } } // Let our server know if we ended up with no worker threads if (numThreads == 0) pthread_cond_signal(&serverCondition); DbgTrace(1, "GrowWorkerThreadPool- End\n", 0); } /*-- GrowWorkerThreadPool() --*/ //++======================================================================= void WorkerThreadBusy(void) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { DbgTrace(1, "WorkerThreadBusy- Start\n", 0); // Acquire our mutex pthread_mutex_lock(&serverMutex); // Increment the numBusyThread count and grow the number of worker threads // if necessary. numBusyThreads ++; if ((numThreads - numBusyThreads) < minWaitingThreads) GrowWorkerThreadPool(growThreads); // Release our mutex pthread_mutex_unlock(&serverMutex); DbgTrace(1, "WorkerThreadBusy- End\n", 0); } /*-- WorkerThreadBusy() --*/ //++======================================================================= bool WorkerThreadWaiting(void) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { bool retValue; DbgTrace(1, "WorkerThreadWaiting- Start\n", 0); // Acquire our mutex pthread_mutex_lock(&serverMutex); // Decrement the numBusyThread count numBusyThreads --; // Check if we have too many idle workers if ((numThreads - numBusyThreads - numPerishingThreads) > maxWaitingThreads && numThreads > beginThreads) { // We want to let this worker perish numPerishingThreads ++; retValue = true; } else retValue = false; // Release our mutex pthread_mutex_unlock(&serverMutex); DbgTrace(1, "WorkerThreadWaiting- End, retValue = %X\n", retValue); return retValue; } /*-- WorkerThreadWaiting() --*/ //++======================================================================= void* WorkerThread(void*) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { bool perishingThread = false; DbgTrace(1, "WorkerThread- Start\n", 0); // Set the thread in the detached state so that it is cleaned up when it exits pthread_detach(pthread_self()); // Attach the thread to the JVM JNIEnv *env; JavaVMAttachArgs attachArgs = {0}; attachArgs.version = JNI_VERSION_1_4; if (g_jvm->AttachCurrentThread((void**) &env, &attachArgs) >= 0) { // We are now attached to the JVM, find the helper class that // we need. jclass helperClass = env->FindClass(authTokenClassName); if (helperClass) { // Helper class found, now get the id of the method that we invoke jmethodID mId = env->GetStaticMethodID(helperClass, authTokenClassValidateMethodName, "(Ljava/lang/String;)Ljava/lang/String;"); if (mId) { // Loop until told to terminate while (!terminating) { // Get a request that needs servicing int32_t requestId = IpcServerGetRequest(); if (requestId != 0) { // We got a request that needs servicing, now get the // data associated with it. char *pReqData; int dataLen = IpcServerGetRequestData(requestId, &pReqData); if (dataLen != 0) { // Indicate that we are now busy WorkerThreadBusy(); // Lets push the jvm local frame to allow us to clean up our local // references later. env->PushLocalFrame(10); // Encapsulate the request data into a string object jstring inString = env->NewStringUTF(pReqData); if (inString) { // Invoke our helper method jstring outString = (jstring) env->CallStaticObjectMethod(helperClass, mId, inString); // Check if an excption occurred if (env->ExceptionCheck() == JNI_TRUE) { // There is a pending exception, display the info which in turn clears it. env->ExceptionDescribe(); IpcServerAbortRequest(requestId); } else { if (outString) { // The helper method succeded, complete the request // with the data returned. const char *pOutChars = env->GetStringUTFChars(outString, NULL); if (pOutChars) { IpcServerCompleteRequest(requestId, (char*) pOutChars); env->ReleaseStringUTFChars(outString, pOutChars); } else { DbgTrace(0, "WorkerThread- Unable to get UTF characters\n", 0); IpcServerAbortRequest(requestId); } } else { // The helper method failed, just abort the request. IpcServerAbortRequest(requestId); } } } else { DbgTrace(0, "WorkerThread- UTF String allocation failure\n", 0); IpcServerAbortRequest(requestId); } // Pop the jvm local frame to clean up our local references env->PopLocalFrame(NULL); // Indicate that we are no longer busy and get indication of // whether or not we should continue to try to process requests. if (WorkerThreadWaiting() == true) { DbgTrace(1, "WorkerThread- Requested to terminate\n", 0); // Remember that we are a perishing thread so that we can reduce the // count as we exit. perishingThread = true; break; } } else { DbgTrace(0, "WorkerThread- Error obtaining Request data\n", 0); IpcServerAbortRequest(requestId); } } else { // No need to service requests any longer break; } } } else { DbgTrace(0, "WorkerThread- Failed to get method id\n", 0); } } else { DbgTrace(0, "WorkerThread- Failed to find helper class\n", 0); } // Detach from the JVM g_jvm->DetachCurrentThread(); } else { DbgTrace(0, "WorkerThread- Failed to attach to JVM\n", 0); } // Decrement the number of worker threads and signal our main thread // to terminate itself if we are the last worker thread. pthread_mutex_lock(&serverMutex); if (perishingThread) numPerishingThreads --; numThreads --; if (numThreads == 0) pthread_cond_signal(&serverCondition); pthread_mutex_unlock(&serverMutex); DbgTrace(1, "WorkerThread- End\n", 0); // Exit pthread_exit(NULL); return 0; // never-reached! } /*-- WorkerThread() --*/ //++======================================================================= void SigTermHandler( int signum) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { DbgTrace(1, "SigTermHandler- Start\n", 0); // Indicate that we are terminating terminating = true; // Shutdown the IPC Server IpcServerShutdown(); DbgTrace(1, "SigTermHandler- End\n", 0); } /*-- SigTermHandler() --*/ //++======================================================================= int InitJavaInvoke(void) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { int retStatus = -1; DbgTrace(1, "InitJavaInvoke- Start\n", 0); JavaVMOption options[6]; options[0].optionString = classpath; options[1].optionString = "-Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser"; options[2].optionString = "-Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"; options[3].optionString = "-Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl"; //options[4].optionString = "-Xcheck:jni"; //options[5].optionString = "-Djaxp.debug=1"; JavaVMInitArgs vm_args; vm_args.version = JNI_VERSION_1_4; vm_args.options = options; vm_args.nOptions = 4; vm_args.ignoreUnrecognized = true; if (JNI_CreateJavaVM(&g_jvm, (void**)&g_env, &vm_args) >= 0) { // Success retStatus = 0; } else { DbgTrace(0, "InitJavaInvoke- Error creating Java VM\n", 0); } DbgTrace(1, "InitJavaInvoke- End, retStatus = %0X\n", retStatus); return retStatus; } /*-- InitJavaInvoke() --*/ //++======================================================================= void UnInitJavaInvoke(void) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { DbgTrace(1, "UnInitJavaInvoke- Start\n", 0); // Destroy the jvm if (g_jvm) { g_jvm->DestroyJavaVM(); g_jvm = NULL; } g_env = NULL; DbgTrace(1, "UnInitJavaInvoke- End\n", 0); } /*-- UnInitJavaInvoke() --*/ //++======================================================================= void DaemonInit( const char *pname) // // Arguments: // // Returns: // // Abstract: // // Notes: Copy of daemon_init() in Richard Stevens Unix Network // Programming Book. // // L2 //=======================================================================-- { pid_t pid; char *pNoAutoRestartEnvvar; DbgTrace(1, "DaemonInit- Start\n", 0); // Determine if we need to disable the auto-restart after crash feature if ((pNoAutoRestartEnvvar = getenv("CASA_NO_AUTORESTART_AFTER_CRASH")) != NULL && strcmp(pNoAutoRestartEnvvar, "0") != 0) { DbgTrace(1, "DaemonInit- Disabling daemon auto-restart after crash feature\n", 0); autoRestartAfterCrash = false; } // Fork to run in the background, check for error. if ((pid = fork()) == -1) { DbgTrace(0, "DaemonInit- Fork error = %d\n", errno); exit(0); } else if (pid != 0) { // The fork succeeded and we are the parent process, terminate // ourselves. exit(0); } /* 1st child continues */ // Become the session leader and set to ignore SIGHUP setsid(); signal(SIGHUP, SIG_IGN); // Fork again to guarantee that the daemon can not acquire a // controlling terminal. if ((pid = fork()) == -1) { DbgTrace(0, "DaemonInit- Fork error = %d\n", errno); exit(0); } else if (pid != 0) { // The fork succeeded and we are the parent process, terminate // ourselves. exit(0); } /* 2nd child continues */ // Close any open descriptors for (int i = 0; i < MAXFD; i++) close(i); // Spawn a worker if ((pid = fork()) == -1) { DbgTrace(0, "DaemonInit- Fork error = %d\n", errno); exit(0); } else if (pid == 0) { // The fork succeeded and we are the worker, continue. } else { // We are the parent of the server, check if we must execute the auto-restart // server after crash logic. if (autoRestartAfterCrash) { // Execute auto-restart server after crash logic while (1) { int childExitStatus; // Wait for children to exit pid = wait(&childExitStatus); if (pid != -1) { // Fork worker if ((pid = fork()) == -1) { DbgTrace(0, "DaemonInit- Fork error = %d\n", errno); exit(0); } else if (pid == 0) { // The fork succeeded and we are the server, exit the loop // to start. goto childContinue; } // We are the parent process, continue to watch for a terminated child process. syslog(LOG_USER | LOG_INFO, "CasaAuthtokenValidateD: Worker re-started after it terminated unexpectedly"); sleep(1); // To keep from consuming too many cycles } else { // Check if we must exit the loop if (errno != EINTR) break; } } } // Terminate ourselves. exit(0); } childContinue: // Set flag to inform DbgTrace macros to use Syslog UseSyslog = true; // Change the working directory chdir("/var/lib/CASA/authtoken/validate"); // Clear our file mode creation mask umask(0); // Get ready to log openlog(appName, LOG_CONS | LOG_NOWAIT | LOG_ODELAY| LOG_PID, LOG_USER); if (DebugLevel == 0) setlogmask(LOG_UPTO(LOG_INFO)); else setlogmask(LOG_UPTO(LOG_DEBUG)); DbgTrace(1, "DaemonInit- End\n", 0); } /*-- DaemonInit() --*/ //++======================================================================= int main( int argc, char* argv[]) // // Arguments: // // Returns: // // Abstract: // // Notes: // // L2 //=======================================================================-- { int optionsSpecified = 0; bool doneScanning = false; bool invalidOption = false; int option; //printf("**** AuthTokenValidate Daemon ****\n"); // Scan through the options specified while (!doneScanning) { long int value = 0; opterr = 0; option = getopt(argc, argv, "m:p:b:g:D:ds"); // Proceed based on the result switch (option) { case 'p': // Port number option, record location of // argument. errno = 0; value = strtol(optarg, (char**) NULL, 10); if (errno == 0 && value > 0 && value <= USHRT_MAX) { listenPortNumber = (unsigned short int) value; } else { fprintf(stderr, "Specified ListenPort parameter out of range, using default value"); } optionsSpecified ++; break; case 'b': // Begin threads option, override the default parameter // with the value of the option. errno = 0; value = strtol(optarg, (char**) NULL, 10); if (errno == 0 && value >= MIN_THREADS && value <= MAX_THREADS) { beginThreads = (int) value; } else { fprintf(stderr, "Specified BeginThreads parameter out of range, using default value"); } optionsSpecified ++; break; case 'g': // Grow threads option, override the default parameter // with the value of the option. errno = 0; value = strtol(optarg, (char**) NULL, 10); if (errno == 0 && value >= MIN_THREADS && value <= MAX_THREADS) { growThreads = (int) value; } else { fprintf(stderr, "Specified GrowThreads parameter out of range, using default value"); } optionsSpecified ++; break; case 'm': // Max threads option, override the default parameter // with the value of the option. errno = 0; value = strtol(optarg, (char**) NULL, 10); if (errno == 0 && value >= MIN_THREADS && value <= MAX_THREADS) { maxThreads = (int) value; } else { fprintf(stderr, "Specified MaxThreads parameter out of range, using default value"); } optionsSpecified ++; break; case 'd': // Run as daemon option daemonize = true; optionsSpecified ++; break; case 's': // Run single threaded singleThreaded = true; optionsSpecified ++; break; case 'D': // Set the debug level DebugLevel = atoi(optarg); optionsSpecified++; break; case '?': // Invalid option detected doneScanning = true; invalidOption = true; break; default: // Done scanning doneScanning = true; break; } } // Do some sanity checking if (!invalidOption && beginThreads > 0 && maxThreads > (growThreads+beginThreads) && beginThreads <= maxThreads) { // The server is ready to start, check if we must // run it as a daemon. if (daemonize) DaemonInit(argv[0]); // Set a handler for SIGTERM signal(SIGTERM, SigTermHandler); // Initialize our mutexes pthread_mutex_init(&interlockedMutex, NULL); pthread_mutex_init(&serverMutex, NULL); // Initialize the JVM if (InitJavaInvoke() == 0) { // Initialize the condition that we will use to wait // for the exit of all of our worker threads. if (pthread_cond_init(&serverCondition, NULL) == 0) { // Initialize the IPC Server if (IpcServerInit(appName, DebugLevel, UseSyslog) == 0) { // Now setup the appropriate listen address int setAddressResult; if (listenPortNumber == 0) setAddressResult = IpcServerSetUnAddress(DOMAIN_SOCKET_FILE_NAME); else setAddressResult = IpcServerSetInAddress(listenPortNumber); if (setAddressResult == 0) { // Now start the IPC server if (IpcServerStart() == 0) { // Proceed according to run model configured if (singleThreaded) { // Runninf in single-threaded model ServiceRequests(); } else { // Running in multi-threaded model // // Acquire our mutex pthread_mutex_lock(&serverMutex); // Start worker threads GrowWorkerThreadPool(beginThreads); // Wait for the worker threads to terminate pthread_cond_wait(&serverCondition, &serverMutex); // Release our mutex pthread_mutex_unlock(&serverMutex); } DbgTrace(0, "main- Exiting, numThreads = %d\n", numThreads); } } else { DbgTrace(0, "main- Setting of listen address failed\n", 0); } } else { DbgTrace(0, "main- Initialization of Ipc server failed\n", 0); } } else { DbgTrace(0, "main- Condition initialization failed\n", 0); } // Un-initialize JVM UnInitJavaInvoke(); } else { DbgTrace(0, "main- JVM initialization failed\n", 0); } } else { // Invalid option detected or the user failed to // specify the listening port number. fprintf(stderr, usage, argv[0]); } return 0; } /*-- main() --*/ //========================================================================= //=========================================================================