Example #1
0
/**
 * Parse a TIMESTAMP-3339.
 * updates the parse pointer position. The pTime parameter
 * is guranteed to be updated only if a new valid timestamp
 * could be obtained (restriction added 2008-09-16 by rgerhards).
 * This method now also checks the maximum string length it is passed.
 * If a *valid* timestamp is found, the string length is decremented
 * by the number of characters processed. If it is not a valid timestamp,
 * the length is kept unmodified. -- rgerhards, 2009-09-23
 */
static rsRetVal
ParseTIMESTAMP3339(struct syslogTime *pTime, uchar** ppszTS, int *pLenStr)
{
	uchar *pszTS = *ppszTS;
	/* variables to temporarily hold time information while we parse */
	int year;
	int month;
	int day;
	int hour; /* 24 hour clock */
	int minute;
	int second;
	int secfrac;	/* fractional seconds (must be 32 bit!) */
	int secfracPrecision;
	char OffsetMode;	/* UTC offset + or - */
	char OffsetHour;	/* UTC offset in hours */
	int OffsetMinute;	/* UTC offset in minutes */
	int lenStr;
	/* end variables to temporarily hold time information while we parse */
	DEFiRet;

	assert(pTime != NULL);
	assert(ppszTS != NULL);
	assert(pszTS != NULL);

	lenStr = *pLenStr;
	year = srSLMGParseInt32(&pszTS, &lenStr);

	/* We take the liberty to accept slightly malformed timestamps e.g. in 
	 * the format of 2003-9-1T1:0:0. This doesn't hurt on receiving. Of course,
	 * with the current state of affairs, we would never run into this code
	 * here because at postion 11, there is no "T" in such cases ;)
	 */
	if(lenStr == 0 || *pszTS++ != '-')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;
	month = srSLMGParseInt32(&pszTS, &lenStr);
	if(month < 1 || month > 12)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != '-')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;
	day = srSLMGParseInt32(&pszTS, &lenStr);
	if(day < 1 || day > 31)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != 'T')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;

	hour = srSLMGParseInt32(&pszTS, &lenStr);
	if(hour < 0 || hour > 23)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != ':')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;
	minute = srSLMGParseInt32(&pszTS, &lenStr);
	if(minute < 0 || minute > 59)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != ':')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;
	second = srSLMGParseInt32(&pszTS, &lenStr);
	if(second < 0 || second > 60)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	/* Now let's see if we have secfrac */
	if(lenStr > 0 && *pszTS == '.') {
		--lenStr;
		uchar *pszStart = ++pszTS;
		secfrac = srSLMGParseInt32(&pszTS, &lenStr);
		secfracPrecision = (int) (pszTS - pszStart);
	} else {
		secfracPrecision = 0;
		secfrac = 0;
	}

	/* check the timezone */
	if(lenStr == 0)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(*pszTS == 'Z') {
		--lenStr;
		pszTS++; /* eat Z */
		OffsetMode = 'Z';
		OffsetHour = 0;
		OffsetMinute = 0;
	} else if((*pszTS == '+') || (*pszTS == '-')) {
		OffsetMode = *pszTS;
		--lenStr;
		pszTS++;

		OffsetHour = srSLMGParseInt32(&pszTS, &lenStr);
		if(OffsetHour < 0 || OffsetHour > 23)
			ABORT_FINALIZE(RS_RET_INVLD_TIME);

		if(lenStr == 0 || *pszTS != ':')
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		--lenStr;
		pszTS++;
		OffsetMinute = srSLMGParseInt32(&pszTS, &lenStr);
		if(OffsetMinute < 0 || OffsetMinute > 59)
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
	} else {
		/* there MUST be TZ information */
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	}

	/* OK, we actually have a 3339 timestamp, so let's indicated this */
	if(lenStr > 0) {
		if(*pszTS != ' ') /* if it is not a space, it can not be a "good" time - 2010-02-22 rgerhards */
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		++pszTS; /* just skip past it */
		--lenStr;
	}

	/* we had success, so update parse pointer and caller-provided timestamp */
	*ppszTS = pszTS;
	pTime->timeType = 2;
	pTime->year = year;
	pTime->month = month;
	pTime->day = day;
	pTime->hour = hour;
	pTime->minute = minute;
	pTime->second = second;
	pTime->secfrac = secfrac;
	pTime->secfracPrecision = secfracPrecision;
	pTime->OffsetMode = OffsetMode;
	pTime->OffsetHour = OffsetHour;
	pTime->OffsetMinute = OffsetMinute;
	*pLenStr = lenStr;

finalize_it:
	RETiRet;
}
Example #2
0
/**
 * Parse a TIMESTAMP-3164. The pTime parameter
 * is guranteed to be updated only if a new valid timestamp
 * could be obtained (restriction added 2008-09-16 by rgerhards). This
 * also means the caller *must* provide a valid (probably current) 
 * timstamp in pTime when calling this function. a 3164 timestamp contains
 * only partial information and only that partial information is updated.
 * So the "output timestamp" is a valid timestamp only if the "input
 * timestamp" was valid, too. The is actually an optimization, as it
 * permits us to use a pre-aquired timestamp and thus avoids to do
 * a (costly) time() call. Thanks to David Lang for insisting on
 * time() call reduction ;).
 * This method now also checks the maximum string length it is passed.
 * If a *valid* timestamp is found, the string length is decremented
 * by the number of characters processed. If it is not a valid timestamp,
 * the length is kept unmodified. -- rgerhards, 2009-09-23
 */
static rsRetVal
ParseTIMESTAMP3164(struct syslogTime *pTime, uchar** ppszTS, int *pLenStr)
{
	/* variables to temporarily hold time information while we parse */
	int month;
	int day;
	int year = 0; /* 0 means no year provided */
	int hour; /* 24 hour clock */
	int minute;
	int second;
	/* end variables to temporarily hold time information while we parse */
	int lenStr;
	uchar *pszTS;
	DEFiRet;

	assert(ppszTS != NULL);
	pszTS = *ppszTS;
	assert(pszTS != NULL);
	assert(pTime != NULL);
	assert(pLenStr != NULL);
	lenStr = *pLenStr;

	/* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec),
	 * we may see the following character sequences occur:
	 *
	 * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec
	 *
	 * We will use this for parsing, as it probably is the
	 * fastest way to parse it.
	 *
	 * 2009-08-17: we now do case-insensitive comparisons, as some devices obviously do not
	 * obey to the RFC-specified case. As we need to guess in any case, we can ignore case
	 * in the first place -- rgerhards
	 *
	 * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C...
	 * Fixed a bug that lead to invalid detection of the data. The issue was that
	 * we had an if(++pszTS == 'x') inside of some of the consturcts below. However,
	 * there were also some elseifs (doing the same ++), which than obviously did not
	 * check the orginal character but the next one. Now removed the ++ and put it
	 * into the statements below. Was a really nasty bug... I didn't detect it before
	 * june, when it first manifested. This also lead to invalid parsing of the rest
	 * of the message, as the time stamp was not detected to be correct. - rgerhards
	 */
	if(lenStr < 3)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	switch(*pszTS++)
	{
	case 'j':
	case 'J':
		if(*pszTS == 'a' || *pszTS == 'A') {
			++pszTS;
			if(*pszTS == 'n' || *pszTS == 'N') {
				++pszTS;
				month = 1;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else if(*pszTS == 'u' || *pszTS == 'U') {
			++pszTS;
			if(*pszTS == 'n' || *pszTS == 'N') {
				++pszTS;
				month = 6;
			} else if(*pszTS == 'l' || *pszTS == 'L') {
				++pszTS;
				month = 7;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'f':
	case 'F':
		if(*pszTS == 'e' || *pszTS == 'E') {
			++pszTS;
			if(*pszTS == 'b' || *pszTS == 'B') {
				++pszTS;
				month = 2;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'm':
	case 'M':
		if(*pszTS == 'a' || *pszTS == 'A') {
			++pszTS;
			if(*pszTS == 'r' || *pszTS == 'R') {
				++pszTS;
				month = 3;
			} else if(*pszTS == 'y' || *pszTS == 'Y') {
				++pszTS;
				month = 5;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'a':
	case 'A':
		if(*pszTS == 'p' || *pszTS == 'P') {
			++pszTS;
			if(*pszTS == 'r' || *pszTS == 'R') {
				++pszTS;
				month = 4;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else if(*pszTS == 'u' || *pszTS == 'U') {
			++pszTS;
			if(*pszTS == 'g' || *pszTS == 'G') {
				++pszTS;
				month = 8;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 's':
	case 'S':
		if(*pszTS == 'e' || *pszTS == 'E') {
			++pszTS;
			if(*pszTS == 'p' || *pszTS == 'P') {
				++pszTS;
				month = 9;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'o':
	case 'O':
		if(*pszTS == 'c' || *pszTS == 'C') {
			++pszTS;
			if(*pszTS == 't' || *pszTS == 'T') {
				++pszTS;
				month = 10;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'n':
	case 'N':
		if(*pszTS == 'o' || *pszTS == 'O') {
			++pszTS;
			if(*pszTS == 'v' || *pszTS == 'V') {
				++pszTS;
				month = 11;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'd':
	case 'D':
		if(*pszTS == 'e' || *pszTS == 'E') {
			++pszTS;
			if(*pszTS == 'c' || *pszTS == 'C') {
				++pszTS;
				month = 12;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	default:
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	}

	lenStr -= 3;

	/* done month */

	if(lenStr == 0 || *pszTS++ != ' ')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;

	/* we accept a slightly malformed timestamp when receiving. This is
	 * we accept one-digit days
	 */
	if(*pszTS == ' ') {
		--lenStr;
		++pszTS;
	}

	day = srSLMGParseInt32(&pszTS, &lenStr);
	if(day < 1 || day > 31)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != ' ')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;

	/* time part */
	hour = srSLMGParseInt32(&pszTS, &lenStr);
	if(hour > 1970 && hour < 2100) {
		/* if so, we assume this actually is a year. This is a format found
		 * e.g. in Cisco devices.
		 * (if you read this 2100+ trying to fix a bug, congratulate me
		 * to how long the code survived - me no longer ;)) -- rgerhards, 2008-11-18
		 */
		year = hour;

		/* re-query the hour, this time it must be valid */
		if(lenStr == 0 || *pszTS++ != ' ')
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		--lenStr;
		hour = srSLMGParseInt32(&pszTS, &lenStr);
	}

	if(hour < 0 || hour > 23)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != ':')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;
	minute = srSLMGParseInt32(&pszTS, &lenStr);
	if(minute < 0 || minute > 59)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != ':')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;
	second = srSLMGParseInt32(&pszTS, &lenStr);
	if(second < 0 || second > 60)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	/* we provide support for an extra ":" after the date. While this is an
	 * invalid format, it occurs frequently enough (e.g. with Cisco devices)
	 * to permit it as a valid case. -- rgerhards, 2008-09-12
	 */
	if(lenStr > 0 && *pszTS == ':') {
		++pszTS; /* just skip past it */
		--lenStr;
	}
	if(lenStr > 0) {
		if(*pszTS != ' ') /* if it is not a space, it can not be a "good" time - 2010-02-22 rgerhards */
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		++pszTS; /* just skip past it */
		--lenStr;
	}

	/* we had success, so update parse pointer and caller-provided timestamp
	 * fields we do not have are not updated in the caller's timestamp. This
	 * is the reason why the caller must pass in a correct timestamp.
	 */
	*ppszTS = pszTS; /* provide updated parse position back to caller */
	pTime->timeType = 1;
	pTime->month = month;
	if(year > 0)
		pTime->year = year; /* persist year if detected */
	pTime->day = day;
	pTime->hour = hour;
	pTime->minute = minute;
	pTime->second = second;
 	pTime->secfracPrecision = 0;
	pTime->secfrac = 0;
	*pLenStr = lenStr;

finalize_it:
	RETiRet;
}
Example #3
0
/**
 * Parse a TIMESTAMP-3164. The pTime parameter
 * is guranteed to be updated only if a new valid timestamp
 * could be obtained (restriction added 2008-09-16 by rgerhards). This
 * also means the caller *must* provide a valid (probably current) 
 * timstamp in pTime when calling this function. a 3164 timestamp contains
 * only partial information and only that partial information is updated.
 * So the "output timestamp" is a valid timestamp only if the "input
 * timestamp" was valid, too. The is actually an optimization, as it
 * permits us to use a pre-aquired timestamp and thus avoids to do
 * a (costly) time() call. Thanks to David Lang for insisting on
 * time() call reduction ;).
 * This method now also checks the maximum string length it is passed.
 * If a *valid* timestamp is found, the string length is decremented
 * by the number of characters processed. If it is not a valid timestamp,
 * the length is kept unmodified. -- rgerhards, 2009-09-23
 *
 * We support this format:
 * [yyyy] Mon mm [yyyy] hh:mm:ss[.subsec][ [yyyy ]/[TZSTRING:]]
 * Note that [yyyy] and [.subsec] are non-standard but frequently occur.
 * Also [yyyy] can only occur once -- if it occurs twice, we flag the
 * timestamp as invalid. if bParseTZ is true, we try to obtain a
 * TZSTRING. Note that in this case it MUST be terminated by a colon
 * (Cisco format). This option is a bit dangerous, as it could already
 * by the tag. So it MUST only be enabled in specialised parsers.
 * subsec, [yyyy] in front, TZSTRING was added in 2014-07-08 rgerhards
 * Similarly, we try to detect a year after the timestamp if
 * bDetectYearAfterTime is set. This is mutally exclusive with bParseTZ.
 * Note: bDetectYearAfterTime will misdetect hostnames in the range
 * 2000..2100 as years, so this option should explicitly be turned on
 * and is not meant for general consumption.
 */
static rsRetVal
ParseTIMESTAMP3164(struct syslogTime *pTime, uchar** ppszTS, int *pLenStr,
	const int bParseTZ,
	const int bDetectYearAfterTime)
{
	/* variables to temporarily hold time information while we parse */
	int month;
	int day;
	int year = 0; /* 0 means no year provided */
	int hour; /* 24 hour clock */
	int minute;
	int second;
	int secfrac;	/* fractional seconds (must be 32 bit!) */
	int secfracPrecision;
	char tzstring[16];
	char OffsetMode = '\0';	/* UTC offset: \0 -> indicate no update */
	char OffsetHour;	/* UTC offset in hours */
	int OffsetMinute;	/* UTC offset in minutes */
	/* end variables to temporarily hold time information while we parse */
	int lenStr;
	uchar *pszTS;
	DEFiRet;

	assert(ppszTS != NULL);
	pszTS = *ppszTS;
	assert(pszTS != NULL);
	assert(pTime != NULL);
	assert(pLenStr != NULL);
	lenStr = *pLenStr;

	if(lenStr < 3)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	/* first check if we have a year in front of the timestamp. some devices (e.g. Brocade)
	 * do this. As it is pretty straightforward to detect and chance of misinterpretation
	 * is low, we try to parse it.
	 */
	if(*pszTS >= '0' && *pszTS <= '9') {
		/* OK, either we have a prepended year or an invalid format! */
		year = srSLMGParseInt32(&pszTS, &lenStr);
		if(year < 1970 || year > 2100 || *pszTS != ' ')
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		++pszTS; /* skip SP */
	}

	/* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec),
	 * we may see the following character sequences occur:
	 *
	 * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec
	 *
	 * We will use this for parsing, as it probably is the
	 * fastest way to parse it.
	 *
	 * 2009-08-17: we now do case-insensitive comparisons, as some devices obviously do not
	 * obey to the RFC-specified case. As we need to guess in any case, we can ignore case
	 * in the first place -- rgerhards
	 *
	 * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C...
	 * Fixed a bug that lead to invalid detection of the data. The issue was that
	 * we had an if(++pszTS == 'x') inside of some of the consturcts below. However,
	 * there were also some elseifs (doing the same ++), which than obviously did not
	 * check the orginal character but the next one. Now removed the ++ and put it
	 * into the statements below. Was a really nasty bug... I didn't detect it before
	 * june, when it first manifested. This also lead to invalid parsing of the rest
	 * of the message, as the time stamp was not detected to be correct. - rgerhards
	 */
	switch(*pszTS++)
	{
	case 'j':
	case 'J':
		if(*pszTS == 'a' || *pszTS == 'A') {
			++pszTS;
			if(*pszTS == 'n' || *pszTS == 'N') {
				++pszTS;
				month = 1;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else if(*pszTS == 'u' || *pszTS == 'U') {
			++pszTS;
			if(*pszTS == 'n' || *pszTS == 'N') {
				++pszTS;
				month = 6;
			} else if(*pszTS == 'l' || *pszTS == 'L') {
				++pszTS;
				month = 7;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'f':
	case 'F':
		if(*pszTS == 'e' || *pszTS == 'E') {
			++pszTS;
			if(*pszTS == 'b' || *pszTS == 'B') {
				++pszTS;
				month = 2;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'm':
	case 'M':
		if(*pszTS == 'a' || *pszTS == 'A') {
			++pszTS;
			if(*pszTS == 'r' || *pszTS == 'R') {
				++pszTS;
				month = 3;
			} else if(*pszTS == 'y' || *pszTS == 'Y') {
				++pszTS;
				month = 5;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'a':
	case 'A':
		if(*pszTS == 'p' || *pszTS == 'P') {
			++pszTS;
			if(*pszTS == 'r' || *pszTS == 'R') {
				++pszTS;
				month = 4;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else if(*pszTS == 'u' || *pszTS == 'U') {
			++pszTS;
			if(*pszTS == 'g' || *pszTS == 'G') {
				++pszTS;
				month = 8;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 's':
	case 'S':
		if(*pszTS == 'e' || *pszTS == 'E') {
			++pszTS;
			if(*pszTS == 'p' || *pszTS == 'P') {
				++pszTS;
				month = 9;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'o':
	case 'O':
		if(*pszTS == 'c' || *pszTS == 'C') {
			++pszTS;
			if(*pszTS == 't' || *pszTS == 'T') {
				++pszTS;
				month = 10;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'n':
	case 'N':
		if(*pszTS == 'o' || *pszTS == 'O') {
			++pszTS;
			if(*pszTS == 'v' || *pszTS == 'V') {
				++pszTS;
				month = 11;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	case 'd':
	case 'D':
		if(*pszTS == 'e' || *pszTS == 'E') {
			++pszTS;
			if(*pszTS == 'c' || *pszTS == 'C') {
				++pszTS;
				month = 12;
			} else
				ABORT_FINALIZE(RS_RET_INVLD_TIME);
		} else
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		break;
	default:
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	}

	lenStr -= 3;

	/* done month */

	if(lenStr == 0 || *pszTS++ != ' ')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;

	/* we accept a slightly malformed timestamp when receiving. This is
	 * we accept one-digit days
	 */
	if(*pszTS == ' ') {
		--lenStr;
		++pszTS;
	}

	day = srSLMGParseInt32(&pszTS, &lenStr);
	if(day < 1 || day > 31)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != ' ')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;

	/* time part */
	hour = srSLMGParseInt32(&pszTS, &lenStr);
	if(year == 0 && hour > 1970 && hour < 2100) {
		/* if so, we assume this actually is a year. This is a format found
		 * e.g. in Cisco devices.
		 * (if you read this 2100+ trying to fix a bug, congratulate me
		 * to how long the code survived - me no longer ;)) -- rgerhards, 2008-11-18
		 */
		year = hour;

		/* re-query the hour, this time it must be valid */
		if(lenStr == 0 || *pszTS++ != ' ')
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		--lenStr;
		hour = srSLMGParseInt32(&pszTS, &lenStr);
	}

	if(hour < 0 || hour > 23)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != ':')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;
	minute = srSLMGParseInt32(&pszTS, &lenStr);
	if(minute < 0 || minute > 59)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	if(lenStr == 0 || *pszTS++ != ':')
		ABORT_FINALIZE(RS_RET_INVLD_TIME);
	--lenStr;
	second = srSLMGParseInt32(&pszTS, &lenStr);
	if(second < 0 || second > 60)
		ABORT_FINALIZE(RS_RET_INVLD_TIME);

	/* as an extension e.g. found in CISCO IOS, we support sub-second resultion.
	 * It's presence is indicated by a dot immediately following the second.
	 */
	if(lenStr > 0 && *pszTS == '.') {
		--lenStr;
		uchar *pszStart = ++pszTS;
		secfrac = srSLMGParseInt32(&pszTS, &lenStr);
		secfracPrecision = (int) (pszTS - pszStart);
	} else {
		secfracPrecision = 0;
		secfrac = 0;
	}

	/* try to parse the TZSTRING if we are instructed to do so */
	if(bParseTZ && lenStr > 2 && *pszTS == ' ') {
		int i;
		for(  ++pszTS, --lenStr, i = 0
		    ; lenStr > 0 && i < (int) sizeof(tzstring) - 1 && *pszTS != ':' && *pszTS != ' '
		    ; --lenStr)
			tzstring[i++] = *pszTS++;
		if(i > 0) {
			/* found TZ, apply it */
			tzinfo_t* tzinfo;
			tzstring[i] = '\0';
			if((tzinfo = glblFindTimezoneInfo((char*) tzstring)) == NULL) {
				DBGPRINTF("ParseTIMESTAMP3164: invalid TZ string '%s' -- ignored\n",
					  tzstring);
			} else {
				OffsetMode = tzinfo->offsMode;
				OffsetHour = tzinfo->offsHour;
				OffsetMinute = tzinfo->offsMin;
			}
		}
	}
	if(bDetectYearAfterTime && year == 0 && lenStr > 5 && *pszTS == ' ') {
		int j;
		int y = 0;
		for(j = 1 ; j < 5 ; ++j) {
			if(pszTS[j] < '0' || pszTS[j] > '9')
				break;
			y = 10 * y + pszTS[j] - '0';
		}
		if(lenStr > 6 && pszTS[5] != ' ')
			y = 0; /* no year! */
		if(2000 <= y && y < 2100) {
			year = y;
			pszTS += 5; /* we need to preserve the SP, checked below */
			lenStr -= 5;
		}
	}

	/* we provide support for an extra ":" after the date. While this is an
	 * invalid format, it occurs frequently enough (e.g. with Cisco devices)
	 * to permit it as a valid case. -- rgerhards, 2008-09-12
	 */
	if(lenStr > 0 && *pszTS == ':') {
		++pszTS; /* just skip past it */
		--lenStr;
	}
	if(lenStr > 0) {
		if(*pszTS != ' ') /* if it is not a space, it can not be a "good" time - 2010-02-22 rgerhards */
			ABORT_FINALIZE(RS_RET_INVLD_TIME);
		++pszTS; /* just skip past it */
		--lenStr;
	}

	/* we had success, so update parse pointer and caller-provided timestamp
	 * fields we do not have are not updated in the caller's timestamp. This
	 * is the reason why the caller must pass in a correct timestamp.
	 */
	*ppszTS = pszTS; /* provide updated parse position back to caller */
	pTime->timeType = 1;
	pTime->month = month;
	if(year > 0)
		pTime->year = year; /* persist year if detected */
	pTime->day = day;
	pTime->hour = hour;
	pTime->minute = minute;
	pTime->second = second;
	pTime->secfrac = secfrac;
	pTime->secfracPrecision = secfracPrecision;
	if(OffsetMode != '\0') { /* need to update TZ info? */
		pTime->OffsetMode = OffsetMode;
		pTime->OffsetHour = OffsetHour;
		pTime->OffsetMinute = OffsetMinute;
	}
	*pLenStr = lenStr;

finalize_it:
	RETiRet;
}