diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/Makefile.am | 33 | ||||
-rw-r--r-- | tools/logsigner.c | 159 | ||||
-rw-r--r-- | tools/omfile.c | 215 | ||||
-rw-r--r-- | tools/pmrfc3164.c | 2 | ||||
-rw-r--r-- | tools/rscryutil.c | 512 | ||||
-rw-r--r-- | tools/rscryutil.rst | 199 | ||||
-rw-r--r-- | tools/rsgtutil.c | 431 | ||||
-rw-r--r-- | tools/rsgtutil.rst | 177 | ||||
-rw-r--r-- | tools/rsyslog.conf.5 | 2 | ||||
-rw-r--r-- | tools/syslogd.c | 53 |
10 files changed, 1732 insertions, 51 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am index 9d9bd352..938782f7 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -41,7 +41,12 @@ rsyslogd_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) # otherwise dependencies are not properly calculated (resulting in a # potentially incomplete build, a problem we had several times...) rsyslogd_LDADD = ../grammar/libgrammar.la ../runtime/librsyslog.la $(ZLIB_LIBS) $(PTHREADS_LIBS) $(RSRT_LIBS) $(SOL_LIBS) $(LIBEE_LIBS) $(LIBLOGNORM_LIBS) $(LIBUUID_LIBS) -rsyslogd_LDFLAGS = -export-dynamic +rsyslogd_LDFLAGS = -export-dynamic `libgcrypt-config --libs` +#rsyslogd_LDFLAGS = -export-dynamic $(LIBGCRYPT_LIBS) + +EXTRA_DIST = $(man_MANS) \ + rsgtutil.rst \ + recover_qi.pl if ENABLE_DIAGTOOLS sbin_PROGRAMS += rsyslog_diag_hostname msggen zpipe @@ -58,7 +63,27 @@ logctl_SOURCES = logctl.c logctl_CPPFLAGS = $(LIBMONGO_CLIENT_CFLAGS) logctl_LDADD = $(LIBMONGO_CLIENT_LIBS) endif +if ENABLE_GUARDTIME +bin_PROGRAMS += rsgtutil +rsgtutil = rsgtutil.c +rsgtutil_CPPFLAGS = $(RSRT_CFLAGS) $(GUARDTIME_CFLAGS) +rsgtutil_LDADD = ../runtime/librsgt.la $(GUARDTIME_LIBS) +rsgtutil.1: rsgtutil.rst + $(AM_V_GEN) $(RST2MAN) $< $@ +man1_MANS = rsgtutil.1 +CLEANFILES = rsgtutil.1 +EXTRA_DIST+= rsgtutil.1 +endif +if ENABLE_LIBGCRYPT +bin_PROGRAMS += rscryutil +rscryutil = rscryutil.c +rscryutil_CPPFLAGS = -I../runtime $(RSRT_CFLAGS) $(LIBGCRYPT_CFLAGS) +rscryutil_LDFLAGS = `libgcrypt-config --libs` +rscryutil_LDADD = ../runtime/libgcry.la $(LIBGCRYPT_LIBS) +rscryutil.1: rscryutil.rst + $(AM_V_GEN) $(RST2MAN) $< $@ +man1_MANS = rscryutil.1 +CLEANFILES = rscryutil.1 +EXTRA_DIST+= rscryutil.1 +endif endif - -EXTRA_DIST = $(man_MANS) \ - recover_qi.pl diff --git a/tools/logsigner.c b/tools/logsigner.c new file mode 100644 index 00000000..f6887696 --- /dev/null +++ b/tools/logsigner.c @@ -0,0 +1,159 @@ +/* This is a tool for offline signing logfiles via the guardtime API. + * + * NOTE: this currently is only a PoC and WiP! NOT suitable for + * production use! + * + * Current hardcoded timestamper (use this if you do not have an + * idea of which one else to use): + * http://stamper.guardtime.net/gt-signingservice + * Check the GuardTime website for the URLs of nearest public services. + * + * Copyright 2013 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed 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 + * -or- + * see COPYING.ASL20 in the source distribution + * + * 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 exprs or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <gt_base.h> +#include <gt_http.h> + +#include "librsgt.h" + + +#if 0 +void +outputhash(GTDataHash *hash) +{ + int i; + for(i = 0 ; i < hash->digest_length ; ++i) + printf("%2.2x", hash->digest[i]); + printf("\n"); +} + +void +timestampIt(GTDataHash *hash) +{ + int r = GT_OK; + GTTimestamp *timestamp = NULL; + unsigned char *der = NULL; + char *sigFile = "logsigner.TIMESTAMP"; + size_t der_len; + + /* Get the timestamp. */ + r = GTHTTP_createTimestampHash(hash, + "http://stamper.guardtime.net/gt-signingservice", ×tamp); + + if(r != GT_OK) { + fprintf(stderr, "GTHTTP_createTimestampHash() failed: %d (%s)\n", + r, GTHTTP_getErrorString(r)); + goto done; + } + + /* Encode timestamp. */ + r = GTTimestamp_getDEREncoded(timestamp, &der, &der_len); + if(r != GT_OK) { + fprintf(stderr, "GTTimestamp_getDEREncoded() failed: %d (%s)\n", + r, GT_getErrorString(r)); + goto done; + } + + /* Save DER-encoded timestamp to file. */ + r = GT_saveFile(sigFile, der, der_len); + if(r != GT_OK) { + fprintf(stderr, "Cannot save timestamp to file %s: %d (%s)\n", + sigFile, r, GT_getErrorString(r)); + if(r == GT_IO_ERROR) { + fprintf(stderr, "\t%d (%s)\n", errno, strerror(errno)); + } + goto done; + } + printf("Timestamping succeeded!\n"); +done: + GT_free(der); + GTTimestamp_free(timestamp); +} + + +void +sign(const char *buf, const size_t len) +{ + int r; + GTDataHash *hash = NULL; + + printf("hash for '%s' is ", buf); + r = GTDataHash_create(GT_HASHALG_SHA256, (const unsigned char*)buf, len, &hash); + if(r != GT_OK) { + fprintf(stderr, "GTTDataHash_create() failed: %d (%s)\n", + r, GT_getErrorString(r)); + goto done; + } + outputhash(hash); + timestampIt(hash); /* of course, this needs to be moved to once at end ;) */ +done: GTDataHash_free(hash); +} +#endif + +void +processFile(char *name) +{ + FILE *fp; + size_t len; + char line[64*1024+1]; + gtctx ctx = NULL; + + ctx = rsgtCtxNew((unsigned char*)"SIGFILE", GT_HASHALG_SHA256); + sigblkInit(ctx); + if(!strcmp(name, "-")) + fp = stdin; + else + fp = fopen(name, "r"); + + while(1) { + if(fgets(line, sizeof(line), fp) == NULL) { + if(!feof(fp)) + perror(name); + break; + } + len = strlen(line); + if(line[len-1] == '\n') { + --len; + line[len] = '\0'; + } + //sign(line, len); + sigblkAddRecord(ctx, (unsigned char*)line, len); + } + + if(fp != stdin) + fclose(fp); + sigblkFinish(ctx); + rsgtCtxDel(ctx); +} + + +int +main(int argc, char *argv[]) +{ + rsgtInit("rsyslog logsigner " VERSION); + processFile("-"); + rsgtExit(); + return 0; +} diff --git a/tools/omfile.c b/tools/omfile.c index 1c65fc59..46d882bf 100644 --- a/tools/omfile.c +++ b/tools/omfile.c @@ -17,7 +17,7 @@ * pipes. These have been moved to ompipe, to reduced the entanglement * between the two different functionalities. -- rgerhards * - * Copyright 2007-2012 Adiscon GmbH. + * Copyright 2007-2013 Adiscon GmbH. * * This file is part of rsyslog. * @@ -69,6 +69,8 @@ #include "unicode-helper.h" #include "atomic.h" #include "statsobj.h" +#include "sigprov.h" +#include "cryprov.h" MODULE_TYPE_OUTPUT MODULE_TYPE_NOKEEP @@ -118,6 +120,7 @@ getClockFileAccess(void) struct s_dynaFileCacheEntry { uchar *pName; /* name currently open, if dynamic name */ strm_t *pStrm; /* our output stream */ + void *sigprovFileData; /* opaque data ptr for provider use */ uint64 clkTickAccessed;/* for LRU - based on clockFileAccess */ }; typedef struct s_dynaFileCacheEntry dynaFileCacheEntry; @@ -143,6 +146,18 @@ typedef struct _instanceData { gid_t fileGID; gid_t dirGID; int bFailOnChown; /* fail creation if chown fails? */ + uchar *sigprovName; /* signature provider */ + uchar *sigprovNameFull;/* full internal signature provider name */ + sigprov_if_t sigprov; /* ptr to signature provider interface */ + void *sigprovData; /* opaque data ptr for provider use */ + void *sigprovFileData;/* opaque data ptr for file instance */ + sbool useSigprov; /* quicker than checkig ptr (1 vs 8 bytes!) */ + uchar *cryprovName; /* crypto provider */ + uchar *cryprovNameFull;/* full internal crypto provider name */ + void *cryprovData; /* opaque data ptr for provider use */ + void *cryprovFileData;/* opaque data ptr for file instance */ + cryprov_if_t cryprov; /* ptr to crypto provider interface */ + sbool useCryprov; /* quicker than checkig ptr (1 vs 8 bytes!) */ int iCurrElt; /* currently active cache element (-1 = none) */ int iCurrCacheSize; /* currently cache size (1-based) */ int iDynaFileCacheSize; /* size of file handle cache */ @@ -228,7 +243,9 @@ static struct cnfparamdescr actpdescr[] = { { "sync", eCmdHdlrBinary, 0 }, /* legacy: actionfileenablesync */ { "file", eCmdHdlrString, 0 }, /* either "file" or ... */ { "dynafile", eCmdHdlrString, 0 }, /* "dynafile" MUST be present */ - { "template", eCmdHdlrGetWord, 0 }, + { "sig.provider", eCmdHdlrGetWord, 0 }, + { "cry.provider", eCmdHdlrGetWord, 0 }, + { "template", eCmdHdlrGetWord, 0 } }; static struct cnfparamblk actpblk = { CNFPARAMBLK_VERSION, @@ -416,15 +433,16 @@ finalize_it: * if the entry should be d_free()ed and 0 if not. */ static rsRetVal -dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry) +dynaFileDelCacheEntry(instanceData *pData, int iEntry, int bFreeEntry) { + dynaFileCacheEntry **pCache = pData->dynCache; DEFiRet; ASSERT(pCache != NULL); if(pCache[iEntry] == NULL) FINALIZE; - DBGPRINTF("Removed entry %d for file '%s' from dynaCache.\n", iEntry, + DBGPRINTF("Removing entry %d for file '%s' from dynaCache.\n", iEntry, pCache[iEntry]->pName == NULL ? UCHAR_CONSTANT("[OPEN FAILED]") : pCache[iEntry]->pName); if(pCache[iEntry]->pName != NULL) { @@ -434,8 +452,10 @@ dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry) if(pCache[iEntry]->pStrm != NULL) { strm.Destruct(&pCache[iEntry]->pStrm); - if(pCache[iEntry]->pStrm != NULL) /* safety check -- TODO: remove if no longer necessary */ - abort(); + if(pData->useSigprov) { + pData->sigprov.OnFileClose(pCache[iEntry]->sigprovFileData); + pCache[iEntry]->sigprovFileData = NULL; + } } if(bFreeEntry) { @@ -460,7 +480,7 @@ dynaFileFreeCacheEntries(instanceData *pData) BEGINfunc; for(i = 0 ; i < pData->iCurrCacheSize ; ++i) { - dynaFileDelCacheEntry(pData->dynCache, i, 1); + dynaFileDelCacheEntry(pData, i, 1); } pData->iCurrElt = -1; /* invalidate current element */ ENDfunc; @@ -481,6 +501,29 @@ static void dynaFileFreeCache(instanceData *pData) } +/* close current file */ +static rsRetVal +closeFile(instanceData *pData) +{ + DEFiRet; + if(pData->useSigprov) { + pData->sigprov.OnFileClose(pData->sigprovFileData); + pData->sigprovFileData = NULL; + } + strm.Destruct(&pData->pStrm); + RETiRet; +} + + +/* This prepares the signature provider to process a file */ +static rsRetVal +sigprovPrepare(instanceData *pData, uchar *fn) +{ + DEFiRet; + pData->sigprov.OnFileOpen(pData->sigprovData, fn, &pData->sigprovFileData); + RETiRet; +} + /* This is now shared code for all types of files. It simply prepares * file access, which, among others, means the the file wil be opened * and any directories in between will be created (based on config, of @@ -554,6 +597,10 @@ prepareFile(instanceData *pData, uchar *newFileName) CHKiRet(strm.SetbSync(pData->pStrm, pData->bSyncFile)); CHKiRet(strm.SetsType(pData->pStrm, STREAMTYPE_FILE_SINGLE)); CHKiRet(strm.SetiSizeLimit(pData->pStrm, pData->iSizeLimit)); + if(pData->useCryprov) { + CHKiRet(strm.Setcryprov(pData->pStrm, &pData->cryprov)); + CHKiRet(strm.SetcryprovData(pData->pStrm, pData->cryprovData)); + } /* set the flush interval only if we actually use it - otherwise it will activate * async processing, which is a real performance waste if we do not do buffered * writes! -- rgerhards, 2009-07-06 @@ -563,11 +610,14 @@ prepareFile(instanceData *pData, uchar *newFileName) if(pData->pszSizeLimitCmd != NULL) CHKiRet(strm.SetpszSizeLimitCmd(pData->pStrm, ustrdup(pData->pszSizeLimitCmd))); CHKiRet(strm.ConstructFinalize(pData->pStrm)); + + if(pData->useSigprov) + sigprovPrepare(pData, szNameBuf); finalize_it: if(iRet != RS_RET_OK) { if(pData->pStrm != NULL) { - strm.Destruct(&pData->pStrm); + closeFile(pData); } } RETiRet; @@ -598,9 +648,7 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts) pCache = pData->dynCache; - /* first check, if we still have the current file - * I *hope* this will be a performance enhancement. - */ + /* first check, if we still have the current file */ if( (pData->iCurrElt != -1) && !ustrcmp(newFileName, pCache[pData->iCurrElt]->pName)) { /* great, we are all set */ @@ -622,9 +670,11 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts) if(iFirstFree == -1) iFirstFree = i; } else { /* got an element, let's see if it matches */ - if(!ustrcmp(newFileName, pCache[i]->pName)) { // RG: name == NULL? + if(!ustrcmp(newFileName, pCache[i]->pName)) { /* we found our element! */ pData->pStrm = pCache[i]->pStrm; + if(pData->useSigprov) + pData->sigprovFileData = pCache[i]->sigprovFileData; pData->iCurrElt = i; pCache[i]->clkTickAccessed = getClockFileAccess(); /* update "timestamp" for LRU */ FINALIZE; @@ -651,7 +701,7 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts) * but it could be triggered in the common case of a failed open() system call. * rgerhards, 2010-03-22 */ - pData->pStrm = NULL; + pData->pStrm = NULL, pData->sigprovFileData = NULL; if(iFirstFree == -1 && (pData->iCurrCacheSize < pData->iDynaFileCacheSize)) { /* there is space left, so set it to that index */ @@ -664,14 +714,11 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts) * The cache array is only updated after the open was successful. -- rgerhards, 2010-03-21 */ if(iFirstFree == -1) { - dynaFileDelCacheEntry(pCache, iOldest, 0); + dynaFileDelCacheEntry(pData, iOldest, 0); STATSCOUNTER_INC(pData->ctrEvict, pData->mutCtrEvict); iFirstFree = iOldest; /* this one *is* now free ;) */ } else { /* we need to allocate memory for the cache structure */ - /* TODO: performance note: we could alloc all entries on startup, thus saving malloc - * overhead -- this may be something to consider in v5... - */ CHKmalloc(pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry))); } @@ -694,10 +741,12 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts) } if((pCache[iFirstFree]->pName = ustrdup(newFileName)) == NULL) { - strm.Destruct(&pData->pStrm); /* need to free failed entry! */ + closeFile(pData); /* need to free failed entry! */ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } pCache[iFirstFree]->pStrm = pData->pStrm; + if(pData->useSigprov) + pCache[iFirstFree]->sigprovFileData = pData->sigprovFileData; pCache[iFirstFree]->clkTickAccessed = getClockFileAccess(); pData->iCurrElt = iFirstFree; DBGPRINTF("Added new entry %d for file cache, file '%s'.\n", iFirstFree, newFileName); @@ -722,7 +771,9 @@ doWrite(instanceData *pData, uchar *pszBuf, int lenBuf) DBGPRINTF("write to stream, pData->pStrm %p, lenBuf %d\n", pData->pStrm, lenBuf); if(pData->pStrm != NULL){ CHKiRet(strm.Write(pData->pStrm, pszBuf, lenBuf)); - FINALIZE; + if(pData->useSigprov) { + CHKiRet(pData->sigprov.OnRecordWrite(pData->sigprovFileData, pszBuf, lenBuf)); + } } finalize_it: @@ -730,10 +781,7 @@ finalize_it: } -/* rgerhards 2004-11-11: write to a file output. This - * will be called for all outputs using file semantics, - * for example also for pipes. - */ +/* rgerhards 2004-11-11: write to a file output. */ static rsRetVal writeFile(uchar **ppString, unsigned iMsgOpts, instanceData *pData) { @@ -841,7 +889,21 @@ CODESTARTfreeInstance if(pData->bDynamicName) { dynaFileFreeCache(pData); } else if(pData->pStrm != NULL) - strm.Destruct(&pData->pStrm); + closeFile(pData); + if(pData->useSigprov) { + pData->sigprov.Destruct(&pData->sigprovData); + obj.ReleaseObj(__FILE__, pData->sigprovNameFull+2, pData->sigprovNameFull, + (void*) &pData->sigprov); + free(pData->sigprovName); + free(pData->sigprovNameFull); + } + if(pData->useCryprov) { + pData->cryprov.Destruct(&pData->cryprovData); + obj.ReleaseObj(__FILE__, pData->cryprovNameFull+2, pData->cryprovNameFull, + (void*) &pData->cryprov); + free(pData->cryprovName); + free(pData->cryprovNameFull); + } ENDfreeInstance @@ -907,6 +969,10 @@ setInstParamDefaults(instanceData *pData) pData->iIOBufSize = IOBUF_DFLT_SIZE; pData->iFlushInterval = FLUSH_INTRVL_DFLT; pData->bUseAsyncWriter = USE_ASYNCWRITER_DFLT; + pData->sigprovName = NULL; + pData->cryprovName = NULL; + pData->useSigprov = 0; + pData->useCryprov = 0; } @@ -946,6 +1012,92 @@ finalize_it: RETiRet; } +static inline void +initSigprov(instanceData *pData, struct nvlst *lst) +{ + uchar szDrvrName[1024]; + + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmsig_%s", pData->sigprovName) + == sizeof(szDrvrName)) { + errmsg.LogError(0, RS_RET_ERR, "omfile: signature provider " + "name is too long: '%s' - signatures disabled", + pData->sigprovName); + goto done; + } + pData->sigprovNameFull = ustrdup(szDrvrName); + + pData->sigprov.ifVersion = sigprovCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean enough. + */ + if(obj.UseObj(__FILE__, szDrvrName, szDrvrName, (void*) &pData->sigprov) + != RS_RET_OK) { + errmsg.LogError(0, RS_RET_LOAD_ERROR, "omfile: could not load " + "signature provider '%s' - signatures disabled", + szDrvrName); + goto done; + } + + if(pData->sigprov.Construct(&pData->sigprovData) != RS_RET_OK) { + errmsg.LogError(0, RS_RET_SIGPROV_ERR, "omfile: error constructing " + "signature provider %s dataset - signatures disabled", + szDrvrName); + goto done; + } + pData->sigprov.SetCnfParam(pData->sigprovData, lst); + + dbgprintf("loaded signature provider %s, data instance at %p\n", + szDrvrName, pData->sigprovData); + pData->useSigprov = 1; +done: return; +} + +static inline rsRetVal +initCryprov(instanceData *pData, struct nvlst *lst) +{ + uchar szDrvrName[1024]; + DEFiRet; + + if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmcry_%s", pData->cryprovName) + == sizeof(szDrvrName)) { + errmsg.LogError(0, RS_RET_ERR, "omfile: crypto provider " + "name is too long: '%s' - encryption disabled", + pData->cryprovName); + ABORT_FINALIZE(RS_RET_ERR); + } + pData->cryprovNameFull = ustrdup(szDrvrName); + + pData->cryprov.ifVersion = cryprovCURR_IF_VERSION; + /* The pDrvrName+2 below is a hack to obtain the object name. It + * safes us to have yet another variable with the name without "lm" in + * front of it. If we change the module load interface, we may re-think + * about this hack, but for the time being it is efficient and clean enough. + */ + if(obj.UseObj(__FILE__, szDrvrName, szDrvrName, (void*) &pData->cryprov) + != RS_RET_OK) { + errmsg.LogError(0, RS_RET_LOAD_ERROR, "omfile: could not load " + "crypto provider '%s' - encryption disabled", + szDrvrName); + ABORT_FINALIZE(RS_RET_CRYPROV_ERR); + } + + if(pData->cryprov.Construct(&pData->cryprovData) != RS_RET_OK) { + errmsg.LogError(0, RS_RET_CRYPROV_ERR, "omfile: error constructing " + "crypto provider %s dataset - encryption disabled", + szDrvrName); + ABORT_FINALIZE(RS_RET_CRYPROV_ERR); + } + CHKiRet(pData->cryprov.SetCnfParam(pData->cryprovData, lst)); + + dbgprintf("loaded crypto provider %s, data instance at %p\n", + szDrvrName, pData->cryprovData); + pData->useCryprov = 1; +finalize_it: + RETiRet; +} + BEGINnewActInst struct cnfparamvals *pvals; uchar *tplToUse; @@ -1013,6 +1165,10 @@ CODESTARTnewActInst pData->bDynamicName = 1; } else if(!strcmp(actpblk.descr[i].name, "template")) { pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "sig.provider")) { + pData->sigprovName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); + } else if(!strcmp(actpblk.descr[i].name, "cry.provider")) { + pData->cryprovName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL); } else { dbgprintf("omfile: program error, non-handled " "param '%s'\n", actpblk.descr[i].name); @@ -1025,6 +1181,14 @@ CODESTARTnewActInst ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } + if(pData->sigprovName != NULL) { + initSigprov(pData, lst); + } + + if(pData->cryprovName != NULL) { + CHKiRet(initCryprov(pData, lst)); + } + tplToUse = ustrdup((pData->tplName == NULL) ? getDfltTpl() : pData->tplName); CHKiRet(OMSRsetEntry(*ppOMSR, 0, tplToUse, OMSR_NO_RQD_TPL_OPTS)); @@ -1167,8 +1331,7 @@ CODESTARTdoHUP dynaFileFreeCacheEntries(pData); } else { if(pData->pStrm != NULL) { - strm.Destruct(&pData->pStrm); - pData->pStrm = NULL; + closeFile(pData); } } ENDdoHUP diff --git a/tools/pmrfc3164.c b/tools/pmrfc3164.c index bcded428..5dfa74f0 100644 --- a/tools/pmrfc3164.c +++ b/tools/pmrfc3164.c @@ -138,7 +138,7 @@ CODESTARTparse */ if(lenMsg > 0 && pMsg->msgFlags & PARSE_HOSTNAME) { i = 0; - while(i < lenMsg && (isalnum(p2parse[i]) || p2parse[i] == '.' || p2parse[i] == '.' + while(i < lenMsg && (isalnum(p2parse[i]) || p2parse[i] == '.' || p2parse[i] == '_' || p2parse[i] == '-') && i < (CONF_HOSTNAME_MAXSIZE - 1)) { bufParseHOSTNAME[i] = p2parse[i]; ++i; diff --git a/tools/rscryutil.c b/tools/rscryutil.c new file mode 100644 index 00000000..2591b2cc --- /dev/null +++ b/tools/rscryutil.c @@ -0,0 +1,512 @@ +/* This is a tool for processing rsyslog encrypted log files. + * + * Copyright 2013 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed 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 + * -or- + * see COPYING.ASL20 in the source distribution + * + * 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 exprs or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <getopt.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <gcrypt.h> + +#include "rsyslog.h" +#include "libgcry.h" + + +static enum { MD_DECRYPT, MD_WRITE_KEYFILE +} mode = MD_DECRYPT; +static int verbose = 0; +static gcry_cipher_hd_t gcry_chd; +static size_t blkLength; + +static char *keyfile = NULL; +static char *keyprog = NULL; +static int randomKeyLen = -1; +static char *cry_key = NULL; +static unsigned cry_keylen = 0; +static int cry_algo = GCRY_CIPHER_AES128; +static int cry_mode = GCRY_CIPHER_MODE_CBC; +static int optionForce = 0; + +/* rectype/value must be EIF_MAX_*_LEN+1 long! + * returns 0 on success or something else on error/EOF + */ +static int +eiGetRecord(FILE *eifp, char *rectype, char *value) +{ + int r; + unsigned short i, j; + char buf[EIF_MAX_RECTYPE_LEN+EIF_MAX_VALUE_LEN+128]; + /* large enough for any valid record */ + + if(fgets(buf, sizeof(buf), eifp) == NULL) { + r = 1; goto done; + } + + for(i = 0 ; i < EIF_MAX_RECTYPE_LEN && buf[i] != ':' ; ++i) + if(buf[i] == '\0') { + r = 2; goto done; + } else + rectype[i] = buf[i]; + rectype[i] = '\0'; + j = 0; + for(++i ; i < EIF_MAX_VALUE_LEN && buf[i] != '\n' ; ++i, ++j) + if(buf[i] == '\0') { + r = 3; goto done; + } else + value[j] = buf[i]; + value[j] = '\0'; + r = 0; +done: return r; +} + +static int +eiCheckFiletype(FILE *eifp) +{ + char rectype[EIF_MAX_RECTYPE_LEN+1]; + char value[EIF_MAX_VALUE_LEN+1]; + int r; + + if((r = eiGetRecord(eifp, rectype, value)) != 0) goto done; + if(strcmp(rectype, "FILETYPE") || strcmp(value, RSGCRY_FILETYPE_NAME)) { + fprintf(stderr, "invalid filetype \"cookie\" in encryption " + "info file\n"); + fprintf(stderr, "\trectype: '%s', value: '%s'\n", rectype, value); + r = 1; goto done; + } + r = 0; +done: return r; +} + +static int +eiGetIV(FILE *eifp, char *iv, size_t leniv) +{ + char rectype[EIF_MAX_RECTYPE_LEN+1]; + char value[EIF_MAX_VALUE_LEN+1]; + size_t valueLen; + unsigned short i, j; + int r; + unsigned char nibble; + + if((r = eiGetRecord(eifp, rectype, value)) != 0) goto done; + if(strcmp(rectype, "IV")) { + fprintf(stderr, "no IV record found when expected, record type " + "seen is '%s'\n", rectype); + r = 1; goto done; + } + valueLen = strlen(value); + if(valueLen/2 != leniv) { + fprintf(stderr, "length of IV is %d, expected %d\n", + valueLen/2, leniv); + r = 1; goto done; + } + + for(i = j = 0 ; i < valueLen ; ++i) { + if(value[i] >= '0' && value[i] <= '9') + nibble = value[i] - '0'; + else if(value[i] >= 'a' && value[i] <= 'f') + nibble = value[i] - 'a' + 10; + else { + fprintf(stderr, "invalid IV '%s'\n", value); + r = 1; goto done; + } + if(i % 2 == 0) + iv[j] = nibble << 4; + else + iv[j++] |= nibble; + } + r = 0; +done: return r; +} + +static int +eiGetEND(FILE *eifp, off64_t *offs) +{ + char rectype[EIF_MAX_RECTYPE_LEN+1]; + char value[EIF_MAX_VALUE_LEN+1]; + int r; + + if((r = eiGetRecord(eifp, rectype, value)) != 0) goto done; + if(strcmp(rectype, "END")) { + fprintf(stderr, "no END record found when expected, record type " + "seen is '%s'\n", rectype); + r = 1; goto done; + } + *offs = atoll(value); + r = 0; +done: return r; +} + +static int +initCrypt(FILE *eifp) +{ + int r = 0; + gcry_error_t gcryError; + char iv[4096]; + + blkLength = gcry_cipher_get_algo_blklen(cry_algo); + if(blkLength > sizeof(iv)) { + fprintf(stderr, "internal error[%s:%d]: block length %d too large for " + "iv buffer\n", __FILE__, __LINE__, blkLength); + r = 1; goto done; + } + if((r = eiGetIV(eifp, iv, blkLength)) != 0) goto done; + + size_t keyLength = gcry_cipher_get_algo_keylen(cry_algo); + if(strlen(cry_key) != keyLength) { + fprintf(stderr, "invalid key length; key is %u characters, but " + "exactly %u characters are required\n", cry_keylen, + keyLength); + r = 1; goto done; + } + + gcryError = gcry_cipher_open(&gcry_chd, cry_algo, cry_mode, 0); + if (gcryError) { + printf("gcry_cipher_open failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + r = 1; goto done; + } + + gcryError = gcry_cipher_setkey(gcry_chd, cry_key, keyLength); + if (gcryError) { + printf("gcry_cipher_setkey failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + r = 1; goto done; + } + + gcryError = gcry_cipher_setiv(gcry_chd, iv, blkLength); + if (gcryError) { + printf("gcry_cipher_setiv failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + r = 1; goto done; + } +done: return r; +} + +static inline void +removePadding(char *buf, size_t *plen) +{ + unsigned len = (unsigned) *plen; + unsigned iSrc, iDst; + char *frstNUL; + + frstNUL = memchr(buf, 0x00, *plen); + if(frstNUL == NULL) + goto done; + iDst = iSrc = frstNUL - buf; + + while(iSrc < len) { + if(buf[iSrc] != 0x00) + buf[iDst++] = buf[iSrc]; + ++iSrc; + } + + *plen = iDst; +done: return; +} + +static void +decryptBlock(FILE *fpin, FILE *fpout, off64_t blkEnd, off64_t *pCurrOffs) +{ + gcry_error_t gcryError; + size_t nRead, nWritten; + size_t toRead; + size_t leftTillBlkEnd; + char buf[64*1024]; + + leftTillBlkEnd = blkEnd - *pCurrOffs; + while(1) { + toRead = sizeof(buf) <= leftTillBlkEnd ? sizeof(buf) : leftTillBlkEnd; + toRead = toRead - toRead % blkLength; + nRead = fread(buf, 1, toRead, fpin); + if(nRead == 0) + break; + leftTillBlkEnd -= nRead, *pCurrOffs += nRead; + gcryError = gcry_cipher_decrypt( + gcry_chd, // gcry_cipher_hd_t + buf, // void * + nRead, // size_t + NULL, // const void * + 0); // size_t + if (gcryError) { + fprintf(stderr, "gcry_cipher_decrypt failed: %s/%s\n", + gcry_strsource(gcryError), + gcry_strerror(gcryError)); + return; + } + removePadding(buf, &nRead); + nWritten = fwrite(buf, 1, nRead, fpout); + if(nWritten != nRead) { + perror("fpout"); + return; + } + } +} + + +static int +doDecrypt(FILE *logfp, FILE *eifp, FILE *outfp) +{ + off64_t blkEnd; + off64_t currOffs = 0; + int r; + + while(1) { + /* process block */ + if(initCrypt(eifp) != 0) + goto done; + if((r = eiGetEND(eifp, &blkEnd)) != 0) goto done; + decryptBlock(logfp, outfp, blkEnd, &currOffs); + gcry_cipher_close(gcry_chd); + } + r = 0; +done: return r; +} + +static void +decrypt(char *name) +{ + FILE *logfp = NULL, *eifp = NULL; + int r = 0; + char eifname[4096]; + + if(!strcmp(name, "-")) { + fprintf(stderr, "decrypt mode cannot work on stdin\n"); + goto err; + } else { + if((logfp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + snprintf(eifname, sizeof(eifname), "%s%s", name, ENCINFO_SUFFIX); + eifname[sizeof(eifname)-1] = '\0'; + if((eifp = fopen(eifname, "r")) == NULL) { + perror(eifname); + goto err; + } + if(eiCheckFiletype(eifp) != 0) + goto err; + } + + doDecrypt(logfp, eifp, stdout); + + fclose(logfp); logfp = NULL; + fclose(eifp); eifp = NULL; + return; + +err: + fprintf(stderr, "error %d processing file %s\n", r, name); + if(logfp != NULL) + fclose(logfp); +} + +static void +write_keyfile(char *fn) +{ + int fd; + int r; + mode_t fmode; + + fmode = O_WRONLY|O_CREAT; + if(!optionForce) + fmode |= O_EXCL; + if((fd = open(fn, fmode, S_IRUSR)) == -1) { + fprintf(stderr, "error opening keyfile "); + perror(fn); + exit(1); + } + if((r = write(fd, cry_key, cry_keylen)) != (ssize_t)cry_keylen) { + fprintf(stderr, "error writing keyfile (ret=%d) ", r); + perror(fn); + exit(1); + } + close(fd); +} + +static void +getKeyFromFile(char *fn) +{ + int r; + r = gcryGetKeyFromFile(fn, &cry_key, &cry_keylen); + if(r != 0) { + fprintf(stderr, "Error %d reading key from file '%s'\n", r, fn); + exit(1); + } +} + +static void +getRandomKey(void) +{ + int fd; + cry_keylen = randomKeyLen; + cry_key = malloc(randomKeyLen); /* do NOT zero-out! */ + /* if we cannot obtain data from /dev/urandom, we use whatever + * is present at the current memory location as random data. Of + * course, this is very weak and we should consider a different + * option, especially when not running under Linux (for Linux, + * unavailability of /dev/urandom is just a theoretic thing, it + * will always work...). -- TODO -- rgerhards, 2013-03-06 + */ + if((fd = open("/dev/urandom", O_RDONLY)) > 0) { + if(read(fd, cry_key, randomKeyLen)) {}; /* keep compiler happy */ + close(fd); + } +} + + +static void +setKey() +{ + if(randomKeyLen != -1) + getRandomKey(); + else if(keyfile != NULL) + getKeyFromFile(keyfile); + else if(keyprog != NULL) + gcryGetKeyFromProg(keyprog, &cry_key, &cry_keylen); + if(cry_key == NULL) { + fprintf(stderr, "ERROR: key must be set via some method\n"); + exit(1); + } +} + +static struct option long_options[] = +{ + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"decrypt", no_argument, NULL, 'd'}, + {"force", no_argument, NULL, 'f'}, + {"write-keyfile", required_argument, NULL, 'W'}, + {"key", required_argument, NULL, 'K'}, + {"generate-random-key", required_argument, NULL, 'r'}, + {"keyfile", required_argument, NULL, 'k'}, + {"key-program", required_argument, NULL, 'p'}, + {"algo", required_argument, NULL, 'a'}, + {"mode", required_argument, NULL, 'm'}, + {NULL, 0, NULL, 0} +}; + +int +main(int argc, char *argv[]) +{ + int i; + int opt; + int temp; + char *newKeyFile = NULL; + + while(1) { + opt = getopt_long(argc, argv, "a:dfk:K:m:p:r:vVW:", long_options, NULL); + if(opt == -1) + break; + switch(opt) { + case 'd': + mode = MD_DECRYPT; + break; + case 'W': + mode = MD_WRITE_KEYFILE; + newKeyFile = optarg; + break; + case 'k': + keyfile = optarg; + break; + case 'p': + keyprog = optarg; + break; + case 'f': + optionForce = 1; + break; + case 'r': + randomKeyLen = atoi(optarg); + if(randomKeyLen > 64*1024) { + fprintf(stderr, "ERROR: keys larger than 64KiB are " + "not supported\n"); + exit(1); + } + break; + case 'K': + fprintf(stderr, "WARNING: specifying the actual key " + "via the command line is highly insecure\n" + "Do NOT use this for PRODUCTION use.\n"); + cry_key = optarg; + cry_keylen = strlen(cry_key); + break; + case 'a': + temp = rsgcryAlgoname2Algo(optarg); + if(temp == GCRY_CIPHER_NONE) { + fprintf(stderr, "ERROR: algorithm \"%s\" is not " + "kown/supported\n", optarg); + exit(1); + } + cry_algo = temp; + break; + case 'm': + temp = rsgcryModename2Mode(optarg); + if(temp == GCRY_CIPHER_MODE_NONE) { + fprintf(stderr, "ERROR: cipher mode \"%s\" is not " + "kown/supported\n", optarg); + exit(1); + } + cry_mode = temp; + break; + case 'v': + verbose = 1; + break; + case 'V': + fprintf(stderr, "rsgtutil " VERSION "\n"); + exit(0); + break; + case '?': + break; + default:fprintf(stderr, "getopt_long() returns unknown value %d\n", opt); + return 1; + } + } + + setKey(); + + if(mode == MD_WRITE_KEYFILE) { + if(optind != argc) { + fprintf(stderr, "ERROR: no file parameters permitted in " + "--write-keyfile mode\n"); + exit(1); + } + write_keyfile(newKeyFile); + } else { + if(optind == argc) + decrypt("-"); + else { + for(i = optind ; i < argc ; ++i) + decrypt(argv[i]); + } + } + + memset(cry_key, 0, cry_keylen); /* zero-out key store */ + cry_keylen = 0; + return 0; +} diff --git a/tools/rscryutil.rst b/tools/rscryutil.rst new file mode 100644 index 00000000..dfd447d2 --- /dev/null +++ b/tools/rscryutil.rst @@ -0,0 +1,199 @@ +========= +rscryutil +========= + +-------------------------- +Manage Encrypted Log Files +-------------------------- + +:Author: Rainer Gerhards <rgerhards@adiscon.com> +:Date: 2013-04-15 +:Manual section: 1 + +SYNOPSIS +======== + +:: + + rscryutil [OPTIONS] [FILE] ... + + +DESCRIPTION +=========== + +This tool performs various operations on encrypted log files. +Most importantly, it provides the ability to decrypt them. + + +OPTIONS +======= + +-d, --decrypt + Select decryption mode. This is the default mode. + +-W, --write-keyfile <file> + Utility function to write a key to a keyfile. The key can be obtained + via any method. + +-v, --verbose + Select verbose mode. + +-f, --force + Forces operations that otherwise would fail. + +-k, --keyfile <file> + Reads the key from <file>. File _must_ contain the key, only, no headers + or other meta information. Keyfiles can be generated via the + *--write-keyfile* option. + +-p, --key-program <path-to-program> + In this mode, the key is provided by a so-called "key program". This program + is executed and must return the key to (as well as some meta information) + via stdout. The core idea of key programs is that using this interface the + user can implement as complex (and secure) method to obtain keys as + desired, all without the need to make modifications to rsyslog. + +-K, --key <KEY> + TESTING AID, NOT FOR PRODUCTION USE. This uses the KEY specified + on the command line. This is the actual key, and as such this mode + is highly insecure. However, it can be useful for intial testing + steps. This option may be removed in the future. + +-a, --algo <algo> + Sets the encryption algorightm (cipher) to be used. See below + for supported algorithms. The default is "AES128". + +-m, --mode <mode> + Sets the ciphermode to be used. See below for supported modes. + The default is "CBC". + +-r, --generate-random-key <bytes> + Generates a random key of length <bytes>. This option is + meant to be used together with *--write-keyfile* (and it is hard + to envision any other valid use for it). + +OPERATION MODES +=============== + +The operation mode specifies what exactly the tool does with the provided +files. The default operation mode is "dump", but this may change in the future. +Thus, it is recommended to always set the operations mode explicitely. If +multiple operations mode are set on the command line, results are +unpredictable. + +decrypt +------- + +The provided log files are decrypted. Note that the *.encinfo* side files +must exist and be accessible in order for decryption to to work. + +write-keyfile +------------- + +In this mode no log files are processed; thus it is an error to specify +any on the command line. The specified keyfile is written. The key itself +is obtained via the usual key commands. If *--keyfile* is used, that +file is effectively copied. + +For security reasons, existing key files are _not_ overwritten. To permit +this, specify the *--force* option. When doing so, keep in mind that lost +keys cannot be recovered and data encrypted with them may also be considered +lost. + +Keyfiles are always created with 0400 permission, that is read access for only +the user. An exception is when an existing file is overwritten via the +*--force* option, in which case the former permissions still apply. + +EXIT CODES +========== + +The command returns an exit code of 0 if everything went fine, and some +other code in case of failures. + + +SUPPORTED ALGORITHMS +==================== + +We basically support what libgcrypt supports. This is: + + 3DES + CAST5 + BLOWFISH + AES128 + AES192 + AES256 + TWOFISH + TWOFISH128 + ARCFOUR + DES + SERPENT128 + SERPENT192 + SERPENT256 + RFC2268_40 + SEED + CAMELLIA128 + CAMELLIA192 + CAMELLIA256 + + +SUPPORTED CIPHER MODES +====================== + +We basically support what libgcrypt supports. This is: + + ECB + CFB + CBC + STREAM + OFB + CTR + AESWRAP + +EXAMPLES +======== + +**rscryutil logfile** + +Decrypts "logfile" and sends data to stdout. + + +**rscryutil --generate-random-key 16 --keyfile /some/secured/path/keyfile** + +Generates random key and stores it in the specified keyfile. + +LOG SIGNATURES +============== + +Encrypted log files can be used together with signing. To verify such a file, +it must be decrypted first, and the verification tool **rsgtutil(1)** must be +run on the decrypted file. + +SECURITY CONSIDERATIONS +======================= + +Specifying keys directly on the command line (*--key* option) is very +insecure and should +not be done, except for testing purposes with test keys. Even then it is +recommended to use keyfiles, which are also easy to handle during testing. +Keep in mind that command history is usally be kept by bash and can also +easily be monitored. + +Local keyfiles are also a security risk. At a minimum, they should be +used with very restrictive file permissions. For this reason, +the *rscryutil* tool creates them with read permissions for the user, +only, no matter what umask is set to. + +When selecting cipher algorithms and modes, care needs to be taken. The +defaults should be reasonable safe to use, but this tends to change over +time. Keep up with the most current crypto recommendations. + + +SEE ALSO +======== +**rsgtutil(1)**, **rsyslogd(8)** + +COPYRIGHT +========= + +This page is part of the *rsyslog* project, and is available under +LGPLv2. diff --git a/tools/rsgtutil.c b/tools/rsgtutil.c new file mode 100644 index 00000000..095b8066 --- /dev/null +++ b/tools/rsgtutil.c @@ -0,0 +1,431 @@ +/* This is a tool for dumpoing the content of GuardTime TLV + * files in a (somewhat) human-readable manner. + * + * Copyright 2013 Adiscon GmbH + * + * This file is part of rsyslog. + * + * Licensed 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 + * -or- + * see COPYING.ASL20 in the source distribution + * + * 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 exprs or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <gt_base.h> +#include <gt_http.h> +#include <getopt.h> + +#include "librsgt.h" + +typedef unsigned char uchar; + +static enum { MD_DUMP, MD_DETECT_FILE_TYPE, MD_SHOW_SIGBLK_PARAMS, + MD_VERIFY, MD_EXTEND +} mode = MD_DUMP; +static int verbose = 0; + +static void +dumpFile(char *name) +{ + FILE *fp; + uchar hdr[9]; + void *obj; + tlvrecord_t rec; + int r = -1; + + if(!strcmp(name, "-")) + fp = stdin; + else { + printf("Processing file %s:\n", name); + if((fp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + } + if((r = rsgt_tlvrdHeader(fp, hdr)) != 0) goto err; + printf("File Header: '%s'\n", hdr); + while(1) { /* we will err out on EOF */ + if((r = rsgt_tlvrd(fp, &rec, &obj)) != 0) { + if(feof(fp)) + break; + else + goto err; + } + rsgt_tlvprint(stdout, rec.tlvtype, obj, verbose); + rsgt_objfree(rec.tlvtype, obj); + } + + if(fp != stdin) + fclose(fp); + return; +err: fprintf(stderr, "error %d processing file %s\n", r, name); +} + +static void +showSigblkParams(char *name) +{ + FILE *fp; + block_sig_t *bs; + uint8_t bHasRecHashes, bHasIntermedHashes; + uint64_t blkCnt = 0; + int r = -1; + + if(!strcmp(name, "-")) + fp = stdin; + else { + if((fp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + } + if((r = rsgt_chkFileHdr(fp, "LOGSIG10")) != 0) goto err; + + while(1) { /* we will err out on EOF */ + if((r = rsgt_getBlockParams(fp, 0, &bs, &bHasRecHashes, + &bHasIntermedHashes)) != 0) + goto err; + ++blkCnt; + rsgt_printBLOCK_SIG(stdout, bs, verbose); + printf("\t***META INFORMATION:\n"); + printf("\tBlock Nbr in File...: %llu\n", blkCnt); + printf("\tHas Record Hashes...: %d\n", bHasRecHashes); + printf("\tHas Tree Hashes.....: %d\n", bHasIntermedHashes); + } + + if(fp != stdin) + fclose(fp); + return; +err: + if(r != RSGTE_EOF) + fprintf(stderr, "error %d processing file %s\n", r, name); +} + +static void +detectFileType(char *name) +{ + FILE *fp; + char *typeName; + char hdr[9]; + int r = -1; + + if(!strcmp(name, "-")) + fp = stdin; + else { + if((fp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + } + if((r = rsgt_tlvrdHeader(fp, (uchar*)hdr)) != 0) goto err; + if(!strcmp(hdr, "LOGSIG10")) + typeName = "Log Signature File, Version 10"; + else if(!strcmp(hdr, "GTSTAT10")) + typeName = "rsyslog GuardTime Signature State File, Version 10"; + else + typeName = "unknown"; + + printf("%s: %s [%s]\n", name, hdr, typeName); + + if(fp != stdin) + fclose(fp); + return; +err: fprintf(stderr, "error %d processing file %s\n", r, name); +} + +static inline int +doVerifyRec(FILE *logfp, FILE *sigfp, FILE *nsigfp, + block_sig_t *bs, gtfile gf, gterrctx_t *ectx, uint8_t bInBlock) +{ + int r; + size_t lenRec; + char line[128*1024]; + + if(fgets(line, sizeof(line), logfp) == NULL) { + if(feof(logfp)) { + r = RSGTE_EOF; + } else { + perror("log file input"); + r = RSGTE_IO; + } + goto done; + } + lenRec = strlen(line); + if(line[lenRec-1] == '\n') { + line[lenRec-1] = '\0'; + --lenRec; + rsgt_errctxSetErrRec(ectx, line); + } + + /* we need to preserve the first line (record) of each block for + * error-reporting purposes (bInBlock==0 meanst start of block) + */ + if(bInBlock == 0) + rsgt_errctxFrstRecInBlk(ectx, line); + + r = rsgt_vrfy_nextRec(bs, gf, sigfp, nsigfp, (unsigned char*)line, lenRec, ectx); +done: + return r; +} + +/* We handle both verify and extend with the same function as they + * are very similiar. + * + * note: here we need to have the LOG file name, not signature! + */ +static void +verify(char *name) +{ + FILE *logfp = NULL, *sigfp = NULL, *nsigfp = NULL; + block_sig_t *bs = NULL; + gtfile gf; + uint8_t bHasRecHashes, bHasIntermedHashes; + uint8_t bInBlock; + int r = 0; + char sigfname[4096]; + char oldsigfname[4096]; + char nsigfname[4096]; + gterrctx_t ectx; + + if(!strcmp(name, "-")) { + fprintf(stderr, "%s mode cannot work on stdin\n", + mode == MD_VERIFY ? "verify" : "extend"); + goto err; + } else { + snprintf(sigfname, sizeof(sigfname), "%s.gtsig", name); + sigfname[sizeof(sigfname)-1] = '\0'; + if((logfp = fopen(name, "r")) == NULL) { + perror(name); + goto err; + } + if((sigfp = fopen(sigfname, "r")) == NULL) { + perror(sigfname); + goto err; + } + if(mode == MD_EXTEND) { + snprintf(nsigfname, sizeof(nsigfname), "%s.gtsig.new", name); + nsigfname[sizeof(nsigfname)-1] = '\0'; + if((nsigfp = fopen(nsigfname, "w")) == NULL) { + perror(nsigfname); + goto err; + } + snprintf(oldsigfname, sizeof(oldsigfname), + "%s.gtsig.old", name); + oldsigfname[sizeof(oldsigfname)-1] = '\0'; + } + } + + rsgtInit("rsyslog rsgtutil " VERSION); + rsgt_errctxInit(&ectx); + ectx.verbose = verbose; + ectx.fp = stderr; + ectx.filename = strdup(sigfname); + + if((r = rsgt_chkFileHdr(sigfp, "LOGSIG10")) != 0) goto done; + if(mode == MD_EXTEND) { + if(fwrite("LOGSIG10", 8, 1, nsigfp) != 1) { + perror(nsigfname); + r = RSGTE_IO; + goto done; + } + } + gf = rsgt_vrfyConstruct_gf(); + if(gf == NULL) { + fprintf(stderr, "error initializing signature file structure\n"); + goto done; + } + + bInBlock = 0; + ectx.blkNum = 0; + ectx.recNumInFile = 0; + + while(!feof(logfp)) { + if(bInBlock == 0) { + if(bs != NULL) + rsgt_objfree(0x0902, bs); + if((r = rsgt_getBlockParams(sigfp, 1, &bs, &bHasRecHashes, + &bHasIntermedHashes)) != 0) + goto done; + rsgt_vrfyBlkInit(gf, bs, bHasRecHashes, bHasIntermedHashes); + ectx.recNum = 0; + ++ectx.blkNum; + } + ++ectx.recNum, ++ectx.recNumInFile; + if((r = doVerifyRec(logfp, sigfp, nsigfp, bs, gf, &ectx, bInBlock)) != 0) + goto done; + if(ectx.recNum == bs->recCount) { + if((r = verifyBLOCK_SIG(bs, gf, sigfp, nsigfp, + (mode == MD_EXTEND) ? 1 : 0, &ectx)) != 0) + goto done; + bInBlock = 0; + } else bInBlock = 1; + } + +done: + if(r != RSGTE_EOF) + goto err; + + fclose(logfp); logfp = NULL; + fclose(sigfp); sigfp = NULL; + if(nsigfp != NULL) { + fclose(nsigfp); nsigfp = NULL; + } + + /* everything went fine, so we rename files if we updated them */ + if(mode == MD_EXTEND) { + if(unlink(oldsigfname) != 0) { + if(errno != ENOENT) { + perror("unlink oldsig"); + r = RSGTE_IO; + goto err; + } + } + if(link(sigfname, oldsigfname) != 0) { + perror("link oldsig"); + r = RSGTE_IO; + goto err; + } + if(unlink(sigfname) != 0) { + perror("unlink cursig"); + r = RSGTE_IO; + goto err; + } + if(link(nsigfname, sigfname) != 0) { + perror("link newsig"); + fprintf(stderr, "WARNING: current sig file has been " + "renamed to %s - you need to manually recover " + "it.\n", oldsigfname); + r = RSGTE_IO; + goto err; + } + if(unlink(nsigfname) != 0) { + perror("unlink newsig"); + fprintf(stderr, "WARNING: current sig file has been " + "renamed to %s - you need to manually recover " + "it.\n", oldsigfname); + r = RSGTE_IO; + goto err; + } + } + rsgtExit(); + rsgt_errctxExit(&ectx); + return; + +err: + fprintf(stderr, "error %d processing file %s\n", r, name); + if(logfp != NULL) + fclose(logfp); + if(sigfp != NULL) + fclose(sigfp); + if(nsigfp != NULL) { + fclose(nsigfp); + unlink(nsigfname); + } + rsgtExit(); + rsgt_errctxExit(&ectx); +} + +static void +processFile(char *name) +{ + switch(mode) { + case MD_DETECT_FILE_TYPE: + detectFileType(name); + break; + case MD_DUMP: + dumpFile(name); + break; + case MD_SHOW_SIGBLK_PARAMS: + showSigblkParams(name); + break; + case MD_VERIFY: + case MD_EXTEND: + verify(name); + break; + } +} + + +static struct option long_options[] = +{ + {"dump", no_argument, NULL, 'D'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"detect-file-type", no_argument, NULL, 'T'}, + {"show-sigblock-params", no_argument, NULL, 'B'}, + {"verify", no_argument, NULL, 't'}, /* 't' as in "test signatures" */ + {"extend", no_argument, NULL, 'e'}, + {"publications-server", optional_argument, NULL, 'P'}, + {"show-verified", no_argument, NULL, 's'}, + {NULL, 0, NULL, 0} +}; + +int +main(int argc, char *argv[]) +{ + int i; + int opt; + + while(1) { + opt = getopt_long(argc, argv, "DvVTBtPs", long_options, NULL); + if(opt == -1) + break; + switch(opt) { + case 'v': + verbose = 1; + break; + case 's': + rsgt_read_showVerified = 1; + break; + case 'V': + fprintf(stderr, "rsgtutil " VERSION "\n"); + exit(0); + case 'D': + mode = MD_DUMP; + break; + case 'B': + mode = MD_SHOW_SIGBLK_PARAMS; + break; + case 'P': + rsgt_read_puburl = optarg; + break; + case 'T': + mode = MD_DETECT_FILE_TYPE; + break; + case 't': + mode = MD_VERIFY; + break; + case 'e': + mode = MD_EXTEND; + break; + case '?': + break; + default:fprintf(stderr, "getopt_long() returns unknown value %d\n", opt); + return 1; + } + } + + if(optind == argc) + processFile("-"); + else { + for(i = optind ; i < argc ; ++i) + processFile(argv[i]); + } + + return 0; +} diff --git a/tools/rsgtutil.rst b/tools/rsgtutil.rst new file mode 100644 index 00000000..37958450 --- /dev/null +++ b/tools/rsgtutil.rst @@ -0,0 +1,177 @@ +======== +rsgtutil +======== + +----------------------------------- +Manage (GuardTime) Signed Log Files +----------------------------------- + +:Author: Rainer Gerhards <rgerhards@adiscon.com> +:Date: 2013-03-25 +:Manual section: 1 + +SYNOPSIS +======== + +:: + + rsgtutil [OPTIONS] [FILE] ... + + +DESCRIPTION +=========== + +This tool performs various maintenance operations on signed log files. +It specifically supports the GuardTime signature provider. + +The *rsgtutil* tool is the primary tool to verify log file signatures, +dump signature file contents and carry out other maintenance operations. +The tool offers different operation modes, which are selected via +command line options. + +The processing of multiple files is permitted. Depending on operation +mode, either the signature file or the base log file must be specified. +Within a single call, only a single operations mode is permitted. To +use different modes on different files, multiple calles, one for each +mode, must be made. + +If no file is specified on the command line, stdin is used instead. Note +that not all operation modes support stdin. + +OPTIONS +======= + +-D, --dump + Select "dump" operations mode. + +-t, --verify + Select "verify" operations mode. + +-T, --detect-file-type + Select "detect-file-type" operations mode. + +-B, --show-sigblock-params + Select "show-sigblock-params" operations mode. + +-s, --show-verified + Prints out information about correctly verified blocks (by default, only + errors are printed). + +-v, --verbose + Select verbose mode. Most importantly, hashes and signatures are printed + in full length (can be **very** lengthy) rather than the usual abbreviation. + +-e, --extend + Select extend mode. This extends the RFC3161 signatures. Note that this + mode also implies a full verification. If there are verify errors, extending + will also fail. + +-P <URL>, --publications-server <URL> + Sets the publications server. If not set but required by the operation a + default server is used. The default server is not necessarily optimal + in regard to performance and reliability. + + +OPERATION MODES +=============== + +The operation mode specifies what exactly the tool does with the provided +files. The default operation mode is "dump", but this may change in the future. +Thus, it is recommended to always set the operations mode explicitely. If +multiple operations mode are set on the command line, results are +unpredictable. + +dump +---- + +The provided *signature* files are dumped. For each top-level record, the*u +type code is printed as well as q short description. If there is additional +information available, it will be printed in tab-indented lines below the +main record dump. The actual *log* files need not to be present. + +verify +------ + +This mode does not work with stdin. On the command line, the *log* file names +are specified. The corresponding *signature* files (ending on ".gtsig") must also +be preset at the same location as the log file. In verify mode, both the log +and signature file is read and the validity of the log file checked. If verification +errors are detected these are printed and processing of the file aborted. By default, +each file is verified individually, without taking cross-file hash chains into +account (so the order of files on the command line does not matter). + +Note that the actual amount of what can be verified depends on the parameters with +which the signature file was written. If record and tree hashes are present, they +will be verified and thus fine-granular error reporting is possible. If they are +not present, only the block signature itself is verified. + +By default, only errors are printed. To also print successful verifications, use the +**--show-verified** option. + + +extend +------ +This extends the RFC3161 signatures. This includes a full verification +of the file. If there are verification errors, extending will also fail. +Note that a signature can only be extended when the required hash has been +published. Currently, these hashes are created at the 15th of each month at +0:00hrs UTC. It takes another few days to get them finally published. As such, +it can be assumed that extending is only possible after this happend (which +means it may take slightly above a month). + +To prevent data corruption, a copy of the signature file is created during +extension. So there must be enough disk space available for both files, +otherwise the operation will fail. If the log file is named logfile, the +signature file is logfile.gtsig and the temporary work file is named +logfile.gtsig.new. When extending finished successfully, the original +signature file (logfile.gtsig in our example) is renamed with the .old +postfix (logfile.gtsig.old) and the temporary file written under the +original name. The .old file can be deleted. It is just kept as a +precaution to prevent signature loss. Note that any already existing +.old or .new files are overwritten by these operations. + + +detect-file-type +---------------- +This mode is used to detect the type of some well-know files used inside the +signature system. The detection is based on the file header. This mode is +primarily a debug aid. + + +show-sigblock-params +-------------------- +This mode is used to print signature block parameters. It is similar to *dump* +mode, but will ignore everything except signature blocks. Also, some additional +meta information is printed. This mode is primarily a debug aid. + +EXIT CODES +========== + +The command returns an exit code of 0 if everything went fine, and some +other code in case of failures. + + +EXAMPLES +======== + +**rsgtutil --verify logfile** + +This verifies the file "logfile" via its associated signature file +"logfile.gtsig". If errors are detected, these are reported to stderr. +Otherwise, rsgtutil terminates without messages. + +**rsgtutil --dump logfile.gtsig** + +This dumps the content of the signature file "logfile.gtsig". The +actual log file is not being processed and does not even need to be +present. + +SEE ALSO +======== +**rsyslogd(8)** + +COPYRIGHT +========= + +This page is part of the *rsyslog* project, and is available under +LGPLv2. diff --git a/tools/rsyslog.conf.5 b/tools/rsyslog.conf.5 index fe9e083b..07da6ffd 100644 --- a/tools/rsyslog.conf.5 +++ b/tools/rsyslog.conf.5 @@ -218,7 +218,7 @@ beginning with a slash ('/'). .B Example: .RS -*.* /var/log/traditionalfile.log;RSYSLOG_TraditionalFormat # log to a file in the traditional format +*.* /var/log/traditionalfile.log;RSYSLOG_TraditionalFileFormat # log to a file in the traditional format .RE Note: if you would like to use high-precision timestamps in your log files, diff --git a/tools/syslogd.c b/tools/syslogd.c index c2928a8f..77adb2cb 100644 --- a/tools/syslogd.c +++ b/tools/syslogd.c @@ -218,7 +218,7 @@ static ratelimit_t *dflt_ratelimiter = NULL; /* ratelimiter for submits without static ratelimit_t *internalMsg_ratelimiter = NULL; /* ratelimiter for rsyslog-own messages */ int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */ int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */ -static int NoFork = 0; /* don't fork - don't run in daemon mode - read-only after startup */ +static int doFork = 1; /* fork - run in daemon mode - read-only after startup */ int bHaveMainQueue = 0;/* set to 1 if the main queue - in queueing mode - is available * If the main queue is either not yet ready or not running in * queueing mode (mode DIRECT!), then this is set to 0. @@ -473,7 +473,7 @@ logmsgInternal(int iErr, int pri, uchar *msg, int flags) * permits us to process unmodified config files which otherwise contain a * supressor statement. */ - if(((Debug == DEBUG_FULL || NoFork) && ourConf->globals.bErrMsgToStderr) || iConfigVerify) { + if(((Debug == DEBUG_FULL || !doFork) && ourConf->globals.bErrMsgToStderr) || iConfigVerify) { if(LOG_PRI(pri) == LOG_ERR) fprintf(stderr, "rsyslogd: %s\n", msg); } @@ -567,6 +567,7 @@ msgConsumer(void __attribute__((unused)) *notNeeded, batch_t *pBatch, int *pbShu assert(pBatch != NULL); pBatch->pbShutdownImmediate = pbShutdownImmediate; /* TODO: move this to batch creation! */ preprocessBatch(pBatch); +dbgprintf("DDDD: batches ShutdownImmediate is %p\n", pBatch->pbShutdownImmediate); ruleset.ProcessBatch(pBatch); //TODO: the BATCH_STATE_COMM must be set somewhere down the road, but we //do not have this yet and so we emulate -- 2010-06-10 @@ -1528,6 +1529,13 @@ queryLocalHostname(void) */ glbl.SetLocalHostName(LocalHostName); glbl.SetLocalDomain(LocalDomain); + + if ( strlen((char*)LocalDomain) ) { + CHKmalloc(LocalFQDNName = (uchar*)malloc(strlen((char*)LocalDomain)+strlen((char*)LocalHostName)+1)); + if ( sprintf((char*)LocalFQDNName,"%s.%s",(char*)LocalHostName,(char*)LocalDomain) ) + glbl.SetLocalFQDNName(LocalFQDNName); + } + glbl.GenerateLocalHostNameProperty(); /* must be redone after conf processing, FQDN setting may have changed */ finalize_it: RETiRet; @@ -1614,8 +1622,7 @@ doGlblProcessInit(void) thrdInit(); - if( !(Debug == DEBUG_FULL || NoFork) ) - { + if(doFork) { DBGPRINTF("Checking pidfile '%s'.\n", PidFile); if (!check_pid(PidFile)) { @@ -1627,16 +1634,23 @@ doGlblProcessInit(void) /* stop writing debug messages to stdout (if debugging is on) */ stddbg = -1; + dbgprintf("ready for forking\n"); if (fork()) { /* Parent process */ - sleep(300); - /* Not reached unless something major went wrong. 5 - * minutes should be a fair amount of time to wait. - * Please note that this procedure is important since - * the father must not exit before syslogd isn't - * initialized or the klogd won't be able to flush its - * logs. -Joey + dbgprintf("parent process going to sleep for 60 secs\n"); + sleep(60); + /* Not reached unless something major went wrong. 1 + * minute should be a fair amount of time to wait. + * The parent should not exit before rsyslogd is + * properly initilized (at least almost) or the init + * system may get a wrong impression of our readyness. + * Note that we exit before being completely initialized, + * but at this point it is very, very unlikely that something + * bad can happen. We do this here, because otherwise we would + * need to have much more code to handle priv drop (which we + * don't consider worth for the init system, especially as it + * is going away on the majority of distros). */ exit(1); /* "good" exit - after forking, not diasabling anything */ } @@ -1645,6 +1659,7 @@ doGlblProcessInit(void) close(0); /* we keep stdout and stderr open in case we have to emit something */ i = 3; + dbgprintf("in child, finalizing initialization\n"); /* if (sd_booted()) */ { const char *e; @@ -1678,7 +1693,8 @@ doGlblProcessInit(void) i = SD_LISTEN_FDS_START + sd_fds; } for ( ; i < num_fds; i++) - (void) close(i); + if(i != dbgGetDbglogFd()) + close(i); untty(); } else { @@ -1889,7 +1905,7 @@ int realMain(int argc, char **argv) fprintf(stderr, "rsyslogd: error -m is no longer supported - use immark instead"); break; case 'n': /* don't fork */ - NoFork = 1; + doFork = 0; break; case 'N': /* enable config verify mode */ iConfigVerify = atoi(arg); @@ -1993,17 +2009,16 @@ int realMain(int argc, char **argv) if(!iConfigVerify) CHKiRet(doGlblProcessInit()); + /* Send a signal to the parent so it can terminate. */ + if(glblGetOurPid() != ppid) + kill(ppid, SIGTERM); + CHKiRet(init()); if(Debug && debugging_on) { dbgprintf("Debugging enabled, SIGUSR1 to turn off debugging.\n"); } - /* Send a signal to the parent so it can terminate. */ - if(glblGetOurPid() != ppid) - kill(ppid, SIGTERM); - - /* END OF INTIALIZATION */ DBGPRINTF("initialization completed, transitioning to regular run mode\n"); @@ -2014,7 +2029,7 @@ int realMain(int argc, char **argv) * is still in its infancy (and not really done), we currently accept this issue. * rgerhards, 2009-06-29 */ - if(!(Debug == DEBUG_FULL || NoFork)) { + if(doFork) { close(1); close(2); ourConf->globals.bErrMsgToStderr = 0; |