blob: 39b0fbf522479144c7be413e9b5a1605fb83668e [file] [log] [blame]
//******************************************************************************
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
// @@@ END COPYRIGHT @@@
//******************************************************************************
// LCOV_EXCL_START -- lets be a little paranoid and not let any in-line funcs
// from the header files slip into the coverage count
#include "ldapconfigfile.h"
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <vector>
// LCOV_EXCL_STOP
#define DEFAULT_PORT 389
#define DEFAULT_NETWORK_TIMEOUT 30
#define DEFAULT_TIMEOUT 30
#define DEFAULT_TIMELIMIT 30
#define DEFAULT_RETRYCOUNT 5
#define DEFAULT_RETRYDELAY 2
#define DEFAULT_EXCLUDELISTSIZE 3
enum Check_Status {
Check_OK = 0,
Check_MissingHostName = 2,
Check_MissingUniqueIdentifier,
Check_MissingSection,
Check_MissingCACERTFilename,
Check_CantOpenLDAPRC,
Check_MissingLDAPRC
};
enum Parse_Status {
Parse_Unknown = -2,
Parse_Next = 2,
Parse_SwitchToPrimary,
Parse_SwitchToSecondary,
Parse_SwitchToDefaults,
Parse_BadAttributeName,
Parse_MissingValue,
Parse_ValueOutofRange,
Parse_MissingCACERTFilename
};
enum {
OneDay = 86400,
OneHour = 3600,
FiveMinutes = 300,
ThirtyMinutes = 1800
};
static Check_Status checkRequiredAttributes(LDAPFileContents & config);
static inline bool icmpPrefix(const char *buf, const char *prefix);
static void initializeConfig(LDAPFileContents &config);
static void initializeLDAPHost(LDAPHostConfig &hostConfig);
static inline bool isDefaultsSectionLabel(const char *str);
static inline bool isPrimarySectionLabel(const char *str);
static inline bool isSecondarySectionLabel(const char *str);
static char *getAuthConfigFilename(void);
static LDAPConfigFileErrorCode parse(
FILE * fp,
LDAPFileContents & config,
int & lineNumber,
string & badLine);
static Parse_Status parseDefaultsLine(
LDAPFileContents & config,
char * inbuf);
static inline int parseIntValue(
char * inBuf,
const char * nameString);
static Parse_Status parseLDAPConfigLine(
LDAPFileContents & config,
LDAPHostConfig & configLDAP,
char * inbuf);
static Parse_Status parseSectionLine(
LDAPFileContents & config,
char * inbuf);
static char * parseStringValue(
char * inbuf,
const char * nameString);
static bool parseTLSCACertFileLine(
char * buffer,
const char * prefix,
string & TLSCACertFilename);
static Check_Status readLDAPRC(LDAPFileContents & config);
static void reset();
static inline int stricmp(
const char * left,
const char * right);
static inline char *trimLeadingandTrailingBlanks(char *buf);
static bool LDAPFileContentsInitialized = false;
static bool freeConfigFilename = false;
static char * configFilename = NULL;
static bool defaultSectionLineRead = false;
static vector<string> loadBalanceHostNames;
// *****************************************************************************
// * *
// * Function: LDAPHostConfig::selfCheck *
// * *
// * *
// * Determines if this instance of LDAPHostConfig is consistent. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <isInitialized> bool In *
// * if true, instance must be initialized to be consistent. *
// * *
// *****************************************************************************
// * *
// * Returns: *
// * *
// * TRUE: LDAPHostConfig instance is consistent *
// * FALSE: LDAPHostConfig instance is not consistent *
// * *
// *****************************************************************************
bool LDAPHostConfig::selfCheck(bool isInitialized) const
{
// If we have not read from the configuration file but we expect
// initialization to be complete, then we have an error. If we are still
// initializing, it is OK if we have not yet read the configuration file.
if (!this->sectionRead)
{
if (isInitialized)
return false;
else
return true;
}
// We only check two items, host name and unique identifier. We don't have
// defaults for these and at least one must be defined in the configuration
// file. Note, we check for these when we first read the file, but this
// is a check to verify our cache is still good.
if (this->hostName.size() == 0)
return false;
if (this->uniqueIdentifier.size() == 0)
return false;
return true;
}
//******************* End of LDAPHostConfig::selfCheck *************************
#pragma page "LDAPConfigFile::TestGetConfigFilename"
// *****************************************************************************
// * *
// * Function: LDAPConfigFile::TestGetConfigFilename *
// * *
// * Returns name used for LDAP config file. Test only. *
// * *
// *****************************************************************************
// LCOV_EXCL_START
const char * TestGetConfigFilename()
{
return configFilename;
}
// LCOV_EXCL_STOP
//************* End of LDAPConfigFile::TestGetConfigFilename *******************
// *****************************************************************************
// * *
// * Function: LDAPConfigFile::LDAPConfigFile *
// * *
// * Constructor of LDAPConfigFile object. *
// * *
// *****************************************************************************
LDAPConfigFile::LDAPConfigFile()
: configFilename(NULL),
isInitialized_(false)
{
}
//******************* End of LDAPConfigFile::LDAPConfigFile ********************
// *****************************************************************************
// * *
// * Function: LDAPConfigFile::LDAPConfigFile *
// * *
// * Destructor of LDAPConfigFile object. *
// * *
// *****************************************************************************
LDAPConfigFile::~LDAPConfigFile()
{
if (freeConfigFilename)
delete configFilename;
}
//******************* End of LDAPConfigFile::~LDAPConfigFile *******************
#pragma page "LDAPConfigFile::GetDefaultConfiguration"
// *****************************************************************************
// * *
// * Function: LDAPConfigFile::GetDefaultConfiguration *
// * *
// * Creates an LDAP configuration using default values. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <configType> LDAPConfigType In *
// * is the configuration type of the node to be obtained. If configType *
// * is UnknownConfiguration, a configType is chosen based on the setting *
// * in the configuration file data. *
// * *
// *****************************************************************************
void LDAPConfigFile::GetDefaultConfiguration(LDAPFileContents &defaultConfig)
{
initializeConfig(defaultConfig);
}
//*************** End of LDAPConfigFile::GetDefaultConfiguration ***************
// *****************************************************************************
// * *
// * Function: read *
// * *
// * Reads and validates a Database Authentication LDAP configuration file. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <filename> string & In/Out *
// * if non-empty, is the name of the file to read and parse. If empty, *
// * on return is the filename that was parsed. *
// * *
// * <config> LDAPFileContents & Out *
// * if validation is successful, passes back the configuration values. *
// * *
// * <lineNumber> int Out *
// * if validation fails, passes back the line number where the error *
// * was detected. *
// * *
// * <badLine> string & Out *
// * if validation fails, passes back the line that failed. *
// * *
// *****************************************************************************
// * *
// * Returns: LDAPConfigFileErrorCode *
// * *
// *****************************************************************************
LDAPConfigFileErrorCode LDAPConfigFile::read(
string & filename,
LDAPFileContents & config,
int & lineNumber,
string & badLine)
{
// Get the name of the file that contains the configuration. If we can't
// get the name, give up.
const char *connFilename;
if (filename.size() != 0)
connFilename = filename.c_str();
else
{
connFilename = getAuthConfigFilename();
filename = connFilename;
}
// If we could not get a filename, return an error.
if (connFilename == NULL || strlen(connFilename) == 0)
return LDAPConfigFile_NoFileProvided;
struct stat sts;
// If the filename does not exist, return an error.
if ((stat(connFilename,&sts)) == -1 && errno == ENOENT)
return LDAPConfigFile_FileNotFound;
FILE *fp;
LDAPConfigFileErrorCode returnCode = LDAPConfigFile_CantOpenFile;
try
{
// Open the file read-only. If the open fails, give up.
fp = fopen(connFilename,"r");
if (fp == NULL)
return LDAPConfigFile_CantOpenFile;
// Clear global parsing values.
reset();
//
// Parse the file. Parsing looks for syntactical errors such as
// bad attribute names or section names, missing required values,
// values not legal, etc.
defaultSectionLineRead = false;
returnCode = parse(fp,config,lineNumber,badLine);
}
catch (...)
{
fclose(fp);
return returnCode;
}
// Check to see if we exited loop due to a read error
if (ferror(fp))
return LDAPConfigFile_CantReadFile;
fclose(fp);
// If there was a parsing error, return it.
if (returnCode != LDAPConfigFile_OK)
return returnCode;
//
// Although the file is syntactically correct, there may be semantic errors.
// Examples include missing required attributes (e.g. LDAPHostName) or
// missing related fields (TLS_CACERTFilename is required if LDAPSSL is 2, etc.
//
Check_Status checkCode = checkRequiredAttributes(config);
switch (checkCode)
{
case Check_OK:
LDAPFileContentsInitialized = true;
break;
case Check_MissingHostName:
return LDAPConfigFile_MissingHostName;
case Check_MissingUniqueIdentifier:
return LDAPConfigFile_MissingUniqueIdentifier;
case Check_MissingSection:
return LDAPConfigFile_MissingSection;
case Check_MissingCACERTFilename:
return LDAPConfigFile_MissingCACERTFilename;
default:
return LDAPConfigFile_ParseError;
}
//
// The LDAP config file is syntactically and semantically correct.
// Environment errors (network, LDAP, etc.) still possible.
//
isInitialized_ = true;
return LDAPConfigFile_OK;
}
//********************** End of LDAPConfigFile::read ***************************
// *****************************************************************************
// * *
// * Begin private functions *
// * *
// *****************************************************************************
// *****************************************************************************
// * *
// * Function: checkRequiredAttributes *
// * *
// * Checks the attribute values read and looks for missing required *
// * values or missing references. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <config> LDAPFileContents & In/Out *
// * is the LDAP attributes. *
// * *
// *****************************************************************************
// * *
// * Returns: Check_Status *
// * *
// *****************************************************************************
static Check_Status checkRequiredAttributes(LDAPFileContents & config)
{
//
// If a section was defined, it must contain at least one LDAPHostName and
// at least one UniqueIdentifier (oxymoron?).
if (config.primary.sectionRead)
{
if (config.primary.hostName.size() == 0)
return Check_MissingHostName;
if (config.primary.uniqueIdentifier.size() == 0)
return Check_MissingUniqueIdentifier;
}
if (config.secondary.sectionRead)
{
if (config.secondary.hostName.size() == 0)
return Check_MissingHostName;
if (config.secondary.uniqueIdentifier.size() == 0)
return Check_MissingUniqueIdentifier;
}
//
// If only a secondary section was defined and no default section was
// specified in the defaults section, change the default from primary.
//
if (!config.primary.sectionRead && config.secondary.sectionRead &&
!defaultSectionLineRead)
config.defaultToPrimary = false;
// If the user specified a default section but did not define that section,
// we have a problem.
if (config.configSectionRead &&
((config.defaultToPrimary && !config.primary.sectionRead) ||
(!config.defaultToPrimary && !config.secondary.sectionRead)))
return Check_MissingSection;
// If there were no sections, mark the primary section as read.
if (!config.primary.sectionRead && !config.secondary.sectionRead)
config.primary.sectionRead = true;
//
// If TLS was requested but there is no CACERT filename specified,
// authentication will fail.
//
if ((config.primary.SSL_Level == YES_TLS ||
config.secondary.SSL_Level == YES_TLS) &&
config.TLS_CACERTFilename.size() == 0)
return Check_MissingCACERTFilename;
return Check_OK;
}
//******************** End of checkRequiredAttributes **************************
// *****************************************************************************
// * *
// * Function: getAuthConfigFilename *
// * *
// * This function returns the name of the text file which holds *
// * configuration information needed to access the identity store used *
// * by this instance. *
// * *
// * Three environment variables may affect where we expect to find the file: *
// * TRAFAUTH_CONFIGFILE -- If present, fully qualified name of config file *
// * no other env variables are used if this is present *
// * TRAFAUTH_CONFIGDIR -- If present, directory in which file will be found. *
// * overrides any other defaults from below *
// * TRAF_CONF -- Standard location: *
// * $TRAF_CONF/.traf_authentication_config *
// * *
// * Our algorithm for picking the name/location for the config file is: *
// * IF AUTHLDAP_CONFIGFILE is set to a non-empty string *
// * the contents of that var are used as the name. *
// * ELSE *
// * IF TRAFAUTH_CONFIGDIR is set to a non-empty string *
// * the contents are used as the dir and .traf_authentication_config *
// * is the name of the file in that dir *
// * ELSE *
// * use TRAF_CONF/.traf_authentication_config *
// * *
// *****************************************************************************
// * *
// * Returns: char *
// * *
// * NULL: Unable to get authentication configuration filename *
// * * : pointer to a fully qualified authentication configuration filename.*
// * *
// *****************************************************************************
static char *getAuthConfigFilename(void)
{
#define AUTH_CONFIG_SIMPLE_NAME "/.traf_authentication_config"
if (configFilename != NULL)
return configFilename;
freeConfigFilename = false;
size_t bufSize;
char *clusterName;
char *configDir;
char *configFile = getenv("TRAFAUTH_CONFIGFILE");
if (configFile != NULL)
{
configFile = trimLeadingandTrailingBlanks(configFile);
if (strlen(configFile) > 0)
{
// a full qualified name is specified. Just use it
configFilename = configFile;
return configFilename;
}
}
configDir = getenv("TRAFAUTH_CONFIGDIR");
if (configDir != NULL)
{
configDir = trimLeadingandTrailingBlanks(configDir);
if (strlen(configDir) > 0)
{
// Directory is specified. Use it with default file name
bufSize = strlen(configDir) + strlen(AUTH_CONFIG_SIMPLE_NAME) + 1;
configFilename = new char[bufSize];
if (configFilename == NULL)
return configFilename;
freeConfigFilename = true;
strcpy(configFilename,configDir);
strcat(configFilename,AUTH_CONFIG_SIMPLE_NAME);
return configFilename;
}
}
// If we get here the full name or the explicit directory were
// not specified. Append the name to the value of TRAF_CONF.
char *sqRoot = getenv("TRAF_CONF"); //ACH changing for Trafodion?
if (sqRoot == NULL || strlen(sqRoot) == 0)
return configFilename;
bufSize = strlen(sqRoot) + strlen(AUTH_CONFIG_SIMPLE_NAME) + 1;
configFilename = new char[bufSize];
if (configFilename == NULL)
return configFilename;
freeConfigFilename = true;
strcpy(configFilename,sqRoot);
strcat(configFilename,AUTH_CONFIG_SIMPLE_NAME);
return configFilename;
}
//************************* End of getLDAPConfigFile ***************************
// *****************************************************************************
// * *
// * Function: icmpPrefix *
// * *
// * Performs a case-insensitive compare of the start of 'buffer' with *
// * the contents of 'prefix'. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <buffer> const char * In *
// * is the character string to be compared. *
// * *
// * <prefix> const char * In *
// * is the text to be compared to. *
// * *
// *****************************************************************************
// * *
// * Returns: *
// * *
// * true: entire contents of 'prefix' is case-insensitive match to the *
// * start of buffer *
// * false: not a case-insentive match. *
// * *
// *****************************************************************************
static inline bool icmpPrefix(
const char *buffer,
const char *prefix)
{
while ((*prefix != 0) && (*buffer != 0))
{
// Upshift value locally and compare. Note we do not want to
// pre-upshift the buffer since it could contain case-sensitive
// values used by a shorter prefixed option.
if (toupper(*buffer) != toupper(*prefix))
return false;
buffer++;
prefix++;
}
// If we got to the end of the prefix, the start of the buffer matches
// the prefix.
if (*prefix == 0)
return true;
// Buffer does not start with prefix.
return false;
}
//************************** End of icmpPrefix *********************************
// *****************************************************************************
// * *
// * Function: initializeConfig *
// * *
// * Initialize the contents of the LDAP configuration buffer to default *
// * values. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <config> LDAPFileContent & Out *
// * passes back the buffer with default values. *
// * *
// *****************************************************************************
static void initializeConfig(LDAPFileContents &config)
{
config.configSectionRead = false;
config.refreshTime = ThirtyMinutes;
// Default to primary section
config.defaultToPrimary = true;
defaultSectionLineRead = false;
initializeLDAPHost(config.primary);
initializeLDAPHost(config.secondary);
}
//*********************** End of initializeConfig ******************************
// *****************************************************************************
// * *
// * Function: initializeLDAPHost *
// * *
// * Initialize the contents of the LDAP host configuration buffer to *
// * default values. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <hostConfig> LDAPHostConfig & Out *
// * passes back the buffer with default values. *
// * *
// *****************************************************************************
static void initializeLDAPHost(LDAPHostConfig &hostConfig)
{
hostConfig.hostName.clear();
hostConfig.isLoadBalancer.clear();
hostConfig.excludeBadHosts = true;
hostConfig.maxExcludeListSize = DEFAULT_EXCLUDELISTSIZE;
hostConfig.networkTimeout = DEFAULT_NETWORK_TIMEOUT;
hostConfig.portNumber = DEFAULT_PORT;
hostConfig.preserveConnection = false;
hostConfig.retryCount = DEFAULT_RETRYCOUNT;
hostConfig.retryDelay = DEFAULT_RETRYDELAY;
hostConfig.searchDN.clear();
hostConfig.searchPwd.clear();
hostConfig.sectionRead = false;
hostConfig.SSL_Level = NO_SSL;
hostConfig.timeLimit = DEFAULT_TIMELIMIT;
hostConfig.timeout = DEFAULT_TIMEOUT;
hostConfig.uniqueIdentifier.clear();
}
//********************** End of initializeLDAPHost *****************************
// *****************************************************************************
// * *
// * Function: isDefaultsSectionLabel *
// * *
// * This function determines if a string matches one of the names used *
// * to designate the defaults section of the LDAP configuration file. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <str> const char * In *
// * is the string to be checked. *
// * *
// *****************************************************************************
// * *
// * Returns: *
// * *
// * true: input string is a defaults label *
// * false: input string is not a defaults label *
// * *
// *****************************************************************************
static inline bool isDefaultsSectionLabel(const char *str)
{
if (stricmp(str,"DEFAULTS") == 0)
return true;
return false;
}
//******************** End of isDefaultsSectionLabel ***************************
// *****************************************************************************
// * *
// * Function: isLoadBalanceHost *
// * *
// * This function determines if a string matches one of the load balance *
// * host names. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <str> const char * In *
// * is the string to be checked. *
// * *
// * <hostNames> vector<string> & In *
// * is the list of host names. *
// * *
// *****************************************************************************
// * *
// * Returns: *
// * *
// * true: input string is a load balance host name. *
// * false: input string is not a load balance host name. *
// * *
// *****************************************************************************
static inline bool isLoadBalanceHost(
const char * str,
vector<string> &hostNames)
{
for (int hostIndex = 0; hostIndex < hostNames.size(); hostIndex++)
if (stricmp(str,hostNames[hostIndex].c_str()) == 0)
return true;
return false;
}
//******************** End of isDefaultsSectionLabel ***************************
// *****************************************************************************
// * *
// * Function: isPrimarySectionLabel *
// * *
// * This function determines if a string matches one of the names used *
// * to designate a section as the primary LDAP connection configuration. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <str> const char * In *
// * is the string to be checked. *
// * *
// *****************************************************************************
// * *
// * Returns: *
// * *
// * true: input string is a primary label *
// * false: input string is not a primary label *
// * *
// *****************************************************************************
static inline bool isPrimarySectionLabel(const char *str)
{
if (stricmp(str,"LOCAL") == 0 || stricmp(str,"ENTERPRISE") == 0)
return true;
return false;
}
//******************** End of isPrimarySectionLabel ****************************
// *****************************************************************************
// * *
// * Function: isSecondarySectionLabel *
// * *
// * This function determines if a string matches one of the names used *
// * to designate a section as the secondary LDAP connection configuration. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <str> const char * In *
// * is the string to be checked. *
// * *
// *****************************************************************************
// * *
// * Returns: *
// * *
// * true: input string is a secondary label *
// * false: input string is not a secondary label *
// * *
// *****************************************************************************
static inline bool isSecondarySectionLabel(const char *str)
{
if (stricmp(str,"REMOTE") == 0 || stricmp(str,"CLUSTER") == 0)
return true;
return false;
}
//******************* End of isSecondarySectionLabel ***************************
// *****************************************************************************
// * *
// * Function: parse *
// * *
// * Parses a Database Authentication LDAP configuration file. *
// * *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <fp> FILE * In *
// * is the file pointer for an open LDAP configuration file. *
// * *
// * <config> LDAPFileContents & Out *
// * if validation is successful, passes back the configuration values. *
// * *
// * <lineNumber> int Out *
// * if validation fails, passes back the line number where the error *
// * was detected. *
// * *
// * <badLine> string & Out *
// * if validation fails, passes back the line that failed. *
// * *
// *****************************************************************************
// * *
// * Returns: LDAPConfigFileErrorCode *
// * *
// *****************************************************************************
static LDAPConfigFileErrorCode parse(
FILE * fp,
LDAPFileContents & config,
int & lineNumber,
string & badLine)
{
// Initialize all the fields to the their default values in case the
// attribute is not specified in the configuration file.
initializeConfig(config);
// we start off with primary LDAP definition as the one to configure.
// a "section:" directive in the file is used to switch between
// primary and secondary
struct LDAPHostConfig *currentDefinition = &config.primary;
bool readingDefaultLines = false;
LDAPConfigFileErrorCode returnCode = LDAPConfigFile_OK;
Parse_Status status = Parse_Unknown;
char inBuf[300];
// Read each line of the file, skipping over comments (begins with #) and
// lines shorter than 3 characters. Compare to option prefixes, and
// store value in corresponding field. Note, if an option is specified more
// than once, we use the last value.
lineNumber = 0;//Let's start at the very beginning...
while (fgets(inBuf,sizeof(inBuf),fp) != NULL)
{
lineNumber++;
// Skip over leading blanks
char *ptr = inBuf;
while (*ptr == ' ')
ptr++;
// Skip comment lines
if (ptr[0] == '#' || strlen(ptr) < 3)
continue;
// Let's parse that line!
if (readingDefaultLines)
status = parseDefaultsLine(config,ptr);
else
status = parseLDAPConfigLine(config,*currentDefinition,ptr);
//
// Handle the parse status. Note, several of the values returned
// simply change the context, while others are fatal parsing errors.
//
switch (status)
{
case Parse_Next:
break;
case Parse_SwitchToPrimary:
currentDefinition = &config.primary;
readingDefaultLines = false;
break;
case Parse_SwitchToSecondary:
currentDefinition = &config.secondary;
readingDefaultLines = false;
break;
case Parse_SwitchToDefaults:
currentDefinition = NULL;
readingDefaultLines = true;
break;
case Parse_BadAttributeName:
badLine = inBuf;
return LDAPConfigFile_BadAttributeName;
case Parse_MissingValue:
badLine = inBuf;
return LDAPConfigFile_MissingValue;
case Parse_ValueOutofRange:
badLine = inBuf;
return LDAPConfigFile_ValueOutofRange;
case Parse_MissingCACERTFilename:
badLine = inBuf;
return LDAPConfigFile_MissingCACERTFilename;
default:
badLine = inBuf;
return LDAPConfigFile_BadAttributeName;
}
}
return LDAPConfigFile_OK;
}
//******************************* End of parse *********************************
// *****************************************************************************
// * *
// * Function: parseDefaultsLine *
// * *
// * Parses a text line in the Defaults section and updates the *
// * corresponding value in the LDAPFileContents structure. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <config> LDAPFileContents & Out *
// * passes back the buffer with corresponding value updated. *
// * *
// * <inBuf> const char * In *
// * is the line to be parsed. *
// * *
// *****************************************************************************
// * *
// * Returns: Parse_Status *
// * *
// *****************************************************************************
static Parse_Status parseDefaultsLine(
LDAPFileContents & config,
char * inBuf)
{
#define PREFIX_DEFAULT_SECTION "defaultsectionname:"
#define PREFIX_REFRESH_TIME "refreshtime:"
#define PREFIX_TLS_CACERTFILENAME "TLS_CACERTFILENAME:"
char *valuePtr = NULL;
if (icmpPrefix(inBuf,PREFIX_DEFAULT_SECTION))
{
valuePtr = parseStringValue(inBuf,PREFIX_DEFAULT_SECTION);
if (strlen(valuePtr) == 0)
return Parse_MissingValue;
if (isPrimarySectionLabel(valuePtr))
config.defaultToPrimary = true;
else
if (isSecondarySectionLabel(valuePtr))
config.defaultToPrimary = false;
else
return Parse_ValueOutofRange;
defaultSectionLineRead = true;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_REFRESH_TIME))
{
int refreshTime = parseIntValue(inBuf,PREFIX_REFRESH_TIME);
if (refreshTime < 0 || refreshTime > OneDay)
return Parse_ValueOutofRange;
config.refreshTime = refreshTime;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_TLS_CACERTFILENAME))
{
if (!parseTLSCACertFileLine(inBuf,PREFIX_TLS_CACERTFILENAME,
config.TLS_CACERTFilename))
return Parse_MissingCACERTFilename;
return Parse_Next;
}
// It is not a valid line in the Defaults section, but it could be another
// Section: line.
return parseSectionLine(config,inBuf);
}
//************************* End of parseDefaultsLine ***************************
// *****************************************************************************
// * *
// * Function: parseIntValue *
// * *
// * Parses an int value from a line. The name/prefix is skipped over *
// * and the numeric string is converted to a number. Characters following the *
// * numeric string are ignored. (Behavior retained for compatibility.) *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <inBuf> char * In *
// * is the line to be parsed. *
// * *
// * <nameString> const char * In *
// * is the Name/Prefix to skip over. *
// * *
// *****************************************************************************
// * *
// * Returns: int *
// * *
// * NOTE: 0 is returned if the numeric conversion fails. *
// * *
// *****************************************************************************
static inline int parseIntValue(
char * inBuf,
const char * nameString)
{
// endPtr is not used, but could be used to check for stray characters.
char *endPtr;
char *valuePtr;
valuePtr = inBuf + strlen(nameString);
return strtol(valuePtr,&endPtr,10);
}
//*************************** End of parseIntValue *****************************
// *****************************************************************************
// * *
// * Function: parseLDAPConfigLine *
// * *
// * Parses a text line in the one of the host sections and updates the *
// * corresponding value in the LDAPHostConfig structure. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <config> LDAPFileContents & Out *
// * passes back the buffer with corresponding value updated. *
// * *
// * <configLDAP> LDAPHostConfig & Out *
// * passes back the buffer with corresponding value updated. *
// * *
// * <inBuf> char * In *
// * is the line to be parsed. *
// * *
// *****************************************************************************
// * *
// * Returns: Parse_Status *
// * *
// *****************************************************************************
static Parse_Status parseLDAPConfigLine(
LDAPFileContents & config,
LDAPHostConfig & configLDAP,
char * inBuf)
{
//
// This is the list of attributes supported in the configuration sections.
// The attribute names must match (case insensitive) and contain a trailing
// colon (:), with no space before the colon.
//
// There are three subsets of configuration attributes:
//
// 1) Attributes used to connect to the OpenLDAP server
// 2) Attributes used by our code to manage the communication
// 3) Attributes that are no longer used, but we still accept
//
// OpenLDAP configuration attributes
#define PREFIX_HOST_NAME "ldaphostname:"
#define PREFIX_PORT "ldapport:"
#define PREFIX_SEARCH_DN "ldapsearchdn:"
#define PREFIX_SEARCH_PWD "ldapsearchpwd:"
#define PREFIX_SSL "ldapssl:"
#define PREFIX_UNIQUE_IDENTIFIER "uniqueidentifier:"
// Attributes used to manage the communication
#define PREFIX_NETWORK_TIMEOUT "ldapnetworktimeout:"
#define PREFIX_LDAP_TIMEOUT "ldaptimeout:"
#define PREFIX_TIMELIMIT "ldaptimelimit:"
#define PREFIX_RETRY_COUNT "retrycount:"
#define PREFIX_RETRY_DELAY "retrydelay:"
#define PREFIX_PRESERVE_CONNECTION "preserveconnection:"
// Attributes used to manage the hostnames
#define PREFIX_EXCLUDE_BAD_HOSTS "ExcludeBadHosts:"
#define PREFIX_LOAD_BALANCE_HOST_NAME "LoadBalanceHostName:"
#define PREFIX_MAX_EXCLUDE_LIST_SIZE "MaxExcludeListSize:"
char *valuePtr = NULL;
//
// For all attributes, if they appear more than once, it is not an error.
// For two of the attributes (LDAPHostName and UniqueIdentifier) we accept
// and store a list of values. For all other attributes the last value
// read is used.
//
//
// OpenLDAP configuration attributes
//
if (icmpPrefix(inBuf,PREFIX_HOST_NAME))
{
valuePtr = parseStringValue(inBuf,PREFIX_HOST_NAME);
if (strlen(valuePtr) == 0)
return Parse_MissingValue;
if (strlen(valuePtr) > MAX_HOSTNAME_LENGTH)
return Parse_ValueOutofRange;
configLDAP.hostName.push_back(valuePtr);
configLDAP.isLoadBalancer.push_back(isLoadBalanceHost(valuePtr,loadBalanceHostNames));
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_PORT))
{
configLDAP.portNumber = parseIntValue(inBuf,PREFIX_PORT);
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_SEARCH_DN))
{
configLDAP.searchDN = parseStringValue(inBuf,PREFIX_SEARCH_DN);
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_SEARCH_PWD))
{
configLDAP.searchPwd = parseStringValue(inBuf,PREFIX_SEARCH_PWD);
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_SSL))
{
int SSL_Level = parseIntValue(inBuf,PREFIX_SSL);
// unencrypted (NO_SSL), TLS (YES_TLS) and SSL (YES_SSL)
// are supported
if ((SSL_Level != NO_SSL) &&
(SSL_Level != YES_TLS) &&
(SSL_Level != YES_SSL))
return Parse_ValueOutofRange;
configLDAP.SSL_Level = SSL_Level;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_LOAD_BALANCE_HOST_NAME))
{
valuePtr = parseStringValue(inBuf,PREFIX_LOAD_BALANCE_HOST_NAME);
loadBalanceHostNames.push_back(valuePtr);
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_UNIQUE_IDENTIFIER))
{
valuePtr = parseStringValue(inBuf,PREFIX_UNIQUE_IDENTIFIER);
configLDAP.uniqueIdentifier.push_back(valuePtr);
return Parse_Next;
}
//
// Attributes used to manage the communication
//
if (icmpPrefix(inBuf,PREFIX_NETWORK_TIMEOUT))
{
int networkTimeout = parseIntValue(inBuf,PREFIX_NETWORK_TIMEOUT);
// -1 mean no timeout, wait forever
if ((networkTimeout > 0 && networkTimeout <= OneHour) || networkTimeout == -1)
configLDAP.networkTimeout = networkTimeout;
else
return Parse_ValueOutofRange;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_LDAP_TIMEOUT))
{
int LDAPTimeout = parseIntValue(inBuf,PREFIX_LDAP_TIMEOUT);
// -1 mean no timeout, wait forever
if ((LDAPTimeout > 0 && LDAPTimeout <= OneHour) || LDAPTimeout == -1)
configLDAP.timeout = LDAPTimeout;
else
return Parse_ValueOutofRange;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_TIMELIMIT))
{
int timeLimit = parseIntValue(inBuf,PREFIX_TIMELIMIT);
if (timeLimit > 0 && timeLimit <= OneHour)
configLDAP.timeLimit = timeLimit;
else
return Parse_ValueOutofRange;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_RETRY_COUNT))
{
int retryCount = parseIntValue(inBuf,PREFIX_RETRY_COUNT);
if (retryCount < 0 || retryCount > 10)
return Parse_ValueOutofRange;
configLDAP.retryCount = retryCount;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_RETRY_DELAY))
{
int retryDelay = parseIntValue(inBuf,PREFIX_RETRY_DELAY);
if (retryDelay < 0 || retryDelay > FiveMinutes)
return Parse_ValueOutofRange;
configLDAP.retryDelay = retryDelay;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_PRESERVE_CONNECTION))
{
valuePtr = parseStringValue(inBuf,PREFIX_PRESERVE_CONNECTION);
if (stricmp(valuePtr,"YES") == 0)
configLDAP.preserveConnection = true;
else
if (stricmp(valuePtr,"NO") == 0)
configLDAP.preserveConnection = false;
else
return Parse_ValueOutofRange;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_EXCLUDE_BAD_HOSTS))
{
valuePtr = parseStringValue(inBuf,PREFIX_EXCLUDE_BAD_HOSTS);
if (stricmp(valuePtr,"YES") == 0)
configLDAP.excludeBadHosts = true;
else
if (stricmp(valuePtr,"NO") == 0)
configLDAP.excludeBadHosts = false;
else
return Parse_ValueOutofRange;
return Parse_Next;
}
if (icmpPrefix(inBuf,PREFIX_MAX_EXCLUDE_LIST_SIZE))
{
int maxExcludeListSize = parseIntValue(inBuf,PREFIX_MAX_EXCLUDE_LIST_SIZE);
if (maxExcludeListSize < 0 || maxExcludeListSize > 1024)
return Parse_ValueOutofRange;
configLDAP.maxExcludeListSize = maxExcludeListSize;
return Parse_Next;
}
// Not a configuration attribute, see if it is a section line.
return parseSectionLine(config,inBuf);
}
//*********************** End of parseLDAPConfigLine ***************************
// *****************************************************************************
// * *
// * Function: parseSectionLine *
// * *
// * Parses a text line in the config file. Assumption is that it is a *
// * Section: line, otherwise an error is returned. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <config> LDAPFileContents & Out *
// * passes back the buffer with corresponding value updated. *
// * *
// * <inbuf> char * In *
// * is the line to be parsed. *
// * *
// *****************************************************************************
// * *
// * Returns: Parse_Status *
// * *
// *****************************************************************************
static Parse_Status parseSectionLine(
LDAPFileContents & config,
char * inBuf)
{
#define PREFIX_SECTION "section:"
//
// If this is not a section line, we have an unrecognized attribute name.
//
if (!icmpPrefix(inBuf,PREFIX_SECTION))
return Parse_BadAttributeName;
//
// It's a section line; see if we recognize the section name.
//
char *valuePtr = parseStringValue(inBuf,PREFIX_SECTION);
if (isPrimarySectionLabel(valuePtr))
{
config.primary.sectionRead = true;
return Parse_SwitchToPrimary;
}
if (isSecondarySectionLabel(valuePtr))
{
config.secondary.sectionRead = true;
return Parse_SwitchToSecondary;
}
if (isDefaultsSectionLabel(valuePtr))
{
config.configSectionRead = true;
return Parse_SwitchToDefaults;
}
//
// Nope, not a section name we know about.
//
return Parse_ValueOutofRange;
}
//************************* End of parseSectionLine ****************************
// *****************************************************************************
// * *
// * Function: parseStringValue *
// * *
// * Parses a string value from a line. The name/prefix is skipped over *
// * and leading and trailing blanks are removed from the value. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <inBuf> char * In *
// * is the line to be parsed. *
// * *
// * <nameString> const char * In *
// * is the Name/Prefix to skip over. *
// * *
// *****************************************************************************
// * *
// * Returns: char * *
// * *
// *****************************************************************************
static inline char * parseStringValue(
char * inBuf,
const char * nameString)
{
char * vPtr = inBuf + strlen(nameString);
vPtr = trimLeadingandTrailingBlanks(vPtr);
return vPtr;
}
//************************* End of parseStringValue ****************************
// *****************************************************************************
// * *
// * Function: parseTLSCACertFileLine *
// * *
// * Parses a text line in the Defaults section and updates the *
// * corresponding value in the LDAPFileContents structure. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <buffer> char * In *
// * is the line to be parsed. *
// * *
// * <prefix> const char * In *
// * is the value of the prefix for the TLS CACERTFLE label. *
// * *
// * <TLSCACertFilename> string & Out *
// * is the value of the prefix for the TLS CACERTFLE label. *
// * *
// * *
// *****************************************************************************
// * *
// * Returns: bool *
// * *
// * true: a file was found at the specified location. *
// * false: no file specified or file not found. *
// * *
// *****************************************************************************
static bool parseTLSCACertFileLine(
char * buffer,
const char * prefix,
string & TLSCACertFilename)
{
#define CACERTS "/cacerts/"
struct stat sts;
char *valuePtr = NULL;
valuePtr = parseStringValue(buffer,prefix);
if (strlen(valuePtr) == 0)
{
TLSCACertFilename = valuePtr;
return true;
}
// If the value is fully qualified (begins with slash), use as is
if (valuePtr[0] == '/')
{
TLSCACertFilename = valuePtr;
if ((stat(valuePtr,&sts)) == 0)
return true;
return false;
}
// Name is not fully qualified. See if specified filename can be found at
// $TRAF_HOME/cacerts.
char *sqRoot = getenv("TRAF_HOME");
if (sqRoot == NULL)
return false;
sqRoot = trimLeadingandTrailingBlanks(sqRoot);
if (strlen(sqRoot) == 0)
return false;
int bufSize = strlen(sqRoot) + strlen(CACERTS) + strlen(valuePtr) + 1;
char *certFilename = new char[bufSize];
if (certFilename == NULL)
return false;
strcpy(certFilename,sqRoot);
strcat(certFilename,CACERTS);
strcat(certFilename,valuePtr);
TLSCACertFilename = certFilename;
delete certFilename;
// If the filename exists in $TRAF_HOME/cacerts directory, use it.
if ((stat(TLSCACertFilename.c_str(),&sts)) == 0)
return true;
// Certificate was not in $TRAF_HOME/cacerts directory. Check $CACERTS_DIR.
char *CACertsDir = getenv("CACERTS_DIR");
if (CACertsDir == NULL)
return false;
CACertsDir = trimLeadingandTrailingBlanks(CACertsDir);
if (strlen(CACertsDir) == 0)
return false;
bufSize = strlen(CACertsDir) + strlen(valuePtr) + 2;
certFilename = new char[bufSize];
if (certFilename == NULL)
return false;
strcpy(certFilename,CACertsDir);
strcat(certFilename,"/");
strcat(certFilename,valuePtr);
TLSCACertFilename = certFilename;
delete certFilename;
if ((stat(TLSCACertFilename.c_str(),&sts)) == 0)
return true;
return false;
}
//********************** End of parseTLSCACertFileLine *************************
// *****************************************************************************
// * *
// * Function: readLDAPRC *
// * *
// * Older LDAP configuration files do not specify the CACERT filename even *
// * when TLS is used; instead, the filename is stored in $HOME/.ldparc. This *
// * function reads the .ldaprc file and extracts the CACERT filename. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <config> LDAPFileContents & Out *
// * passes back the buffer with CACERT filename value updated. *
// * *
// *****************************************************************************
// * *
// * Returns: Check_Status *
// * *
// *****************************************************************************
static Check_Status readLDAPRC(LDAPFileContents & config)
{
char *home = getenv("HOME");
if ((home == NULL) || (strlen(home) == 0))
return Check_MissingCACERTFilename;
string LDAPRCFilename(home);
LDAPRCFilename += "/.ldaprc";
struct stat sts;
// If the filename does not exist, return an error.
if ((stat(LDAPRCFilename.c_str(),&sts)) == -1 && errno == ENOENT)
// LCOV_EXCL_START
return Check_MissingLDAPRC;
// LCOV_EXCL_STOP
char inBuf[300];
// Open the file read-only. If the open fails, give up.
FILE *fp = fopen(LDAPRCFilename.c_str(),"r");
if (fp == NULL)
// LCOV_EXCL_START
return Check_CantOpenLDAPRC;
// LCOV_EXCL_STOP
while (fgets(inBuf, sizeof(inBuf),fp) != NULL)
if (icmpPrefix(inBuf,"TLS_CACERT"))
{
char *valuePtr = parseStringValue(inBuf,"TLS_CACERT");
if (strlen(valuePtr) == 0)
{
fclose(fp);
return Check_MissingCACERTFilename;
}
config.TLS_CACERTFilename = valuePtr;
fclose(fp);
return Check_OK;
}
fclose(fp);
return Check_MissingCACERTFilename;
}
//**************************** End of readLDAPRC *******************************
// *****************************************************************************
// * *
// * Function: reset *
// * *
// * Resets parsing values. Not thread safe. *
// * *
// *****************************************************************************
static void reset()
{
LDAPFileContentsInitialized = false;
defaultSectionLineRead = false;
loadBalanceHostNames.clear();
}
//****************************** End of reset **********************************
// *****************************************************************************
// * *
// * Function: stricmp *
// * *
// * Performs a case-insensitive compare of the two char * buffers *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <left> const char * In *
// * <right> const char * In *
// * the two buffers to be compared *
// * *
// *****************************************************************************
// * *
// * Returns: *
// * *
// * -1 : <left> is less than <right> *
// * 0 : <left> and <right> are (case-insensitive) equal *
// * 1 : <left> is greater than <right> *
// * *
// *****************************************************************************
static inline int stricmp(
const char *left,
const char *right)
{
while ((*left != 0) && (*right != 0))
{
// Upshift value locally and compare.
if (toupper(*left) != toupper(*right))
{
if (toupper(*left) < toupper(*right))
return -1;
else
return 1;
}
left++;
right++;
}
if ((*left == 0) && (*right == 0))
return 0;
if (*left == 0)
return -1;
return 1;
}
//****************************** End of stricmp ********************************
// *****************************************************************************
// * *
// * Function: trimLeadingandTrailingBlanks *
// * *
// * Removes trailing blanks (replaces with null) and returns pointer to *
// * first non-blank character in the buffer. *
// * *
// *****************************************************************************
// * *
// * Parameters: *
// * *
// * <buf> char * In/Out *
// * is a null-terminated character buffer whose leading and trailing *
// * blanks are to be trimmed. *
// * *
// * *
// *****************************************************************************
// * *
// * Returns: pointer to first non-blank character in the buffer. *
// * *
// *****************************************************************************
static inline char *trimLeadingandTrailingBlanks(char *buf)
{
// Skip over any blanks, point to first non-blank character
while ((*buf != 0) && (isspace(*buf)))
buf++;
// Point to last character in the buffer. If the last character is a blank,
// set to null and decrement the pointer until the character is non-blank.
char *endPtr = endPtr = buf + strlen(buf) - 1; //ACH extra endPtr?
while ((buf <= endPtr) && (isspace(*endPtr)))
*(endPtr--) = 0;
return buf;
}
//****************** End of trimLeadingandTrailingBlanks ***********************