From 531f27a8c3d6c988650e09bb126f792b8bac5421 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Tue, 27 May 2008 14:49:45 +0200 Subject: implemented wildcards inside certificate name check authentication --- runtime/net.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++++ runtime/net.h | 27 +++++- runtime/nsd_gtls.c | 15 +-- runtime/rsyslog.h | 2 + 4 files changed, 311 insertions(+), 7 deletions(-) (limited to 'runtime') diff --git a/runtime/net.c b/runtime/net.c index cbff1003..43da9fe5 100644 --- a/runtime/net.c +++ b/runtime/net.c @@ -92,6 +92,114 @@ int ACLDontResolve = 0; /* add hostname to acl instead of resolving it /* ------------------------------ begin permitted peers code ------------------------------ */ +/* add a wildcard entry to this permitted peer. Entries are always + * added at the tail of the list. pszStr and lenStr identify the wildcard + * entry to be added. Note that the string is NOT \0 terminated, so + * we must rely on lenStr for when it is finished. + * rgerhards, 2008-05-27 + */ +static rsRetVal +AddPermittedPeerWildcard(permittedPeers_t *pPeer, uchar* pszStr, size_t lenStr) +{ + permittedPeerWildcard_t *pNew = NULL; + size_t iSrc; + size_t iDst; + DEFiRet; + + assert(pPeer != NULL); + assert(pszStr != NULL); + + CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t))); + + if(lenStr == 0) { /* empty domain components are permitted */ + pNew->wildcardType = PEER_WILDCARD_EMPTY_COMPONENT; + FINALIZE; + } else { + /* alloc memory for the domain component. We may waste a byte or + * two, but that's ok. + */ + CHKmalloc(pNew->pszDomainPart = malloc(lenStr +1 )); + } + + if(pszStr[0] == '*') { + pNew->wildcardType = PEER_WILDCARD_AT_START; + iSrc = 1; /* skip '*' */ + } else { + iSrc = 0; + } + + for(iDst = 0 ; iSrc < lenStr && pszStr[iSrc] != '*' ; ++iSrc, ++iDst) { + pNew->pszDomainPart[iDst] = pszStr[iSrc]; + } + + if(iSrc < lenStr) { + if(iSrc + 1 == lenStr && pszStr[iSrc] == '*') { + if(pNew->wildcardType == PEER_WILDCARD_AT_START) { + ABORT_FINALIZE(RS_RET_INVALID_WILDCARD); + } else { + pNew->wildcardType = PEER_WILDCARD_AT_END; + } + } else { + /* we have an invalid wildcard, something follows the asterisk! */ + ABORT_FINALIZE(RS_RET_INVALID_WILDCARD); + } + } + + if(lenStr == 1 && pNew->wildcardType == PEER_WILDCARD_AT_START) { + pNew->wildcardType = PEER_WILDCARD_MATCH_ALL; + } + + /* if we reach this point, we had a valid wildcard. We now need to + * properly terminate the domain component string. + */ + pNew->pszDomainPart[iDst] = '\0'; + pNew->lenDomainPart = strlen((char*)pNew->pszDomainPart); + +finalize_it: + if(iRet != RS_RET_OK) { + if(pNew != NULL) { + if(pNew->pszDomainPart != NULL) + free(pNew->pszDomainPart); + free(pNew); + } + } else { + /* enqueue the element */ + if(pPeer->pWildcardRoot == NULL) { + pPeer->pWildcardRoot = pNew; + } else { + pPeer->pWildcardLast->pNext = pNew; + } + pPeer->pWildcardLast = pNew; + } + + RETiRet; +} + + +/* Destruct a permitted peer's wildcard list -- rgerhards, 2008-05-27 */ +static rsRetVal +DestructPermittedPeerWildcards(permittedPeers_t *pPeer) +{ + permittedPeerWildcard_t *pCurr; + permittedPeerWildcard_t *pDel; + DEFiRet; + + assert(pPeer != NULL); + + for(pCurr = pPeer->pWildcardRoot ; pCurr != NULL ; /*EMPTY*/) { + pDel = pCurr; + pCurr = pCurr->pNext; + free(pDel->pszDomainPart); + free(pDel); + } + + pPeer->pWildcardRoot = NULL; + pPeer->pWildcardLast = NULL; + + RETiRet; +} + + /* add a permitted peer. PermittedPeers is an interim solution until we can provide * access control via enhanced RainerScript methods. * Note: the provided string is handed over to this function, caller must @@ -137,6 +245,7 @@ DestructPermittedPeers(permittedPeers_t **ppRootPeer) for(pCurr = *ppRootPeer ; pCurr != NULL ; /*EMPTY*/) { pDel = pCurr; pCurr = pCurr->pNext; + DestructPermittedPeerWildcards(pDel); free(pDel->pszID); free(pDel); } @@ -147,6 +256,170 @@ DestructPermittedPeers(permittedPeers_t **ppRootPeer) } +/* Compile a wildcard. The function first checks if there is a wildcard + * present and compiles it only if so ;) It sets the etryType status + * accordingly. + * rgerhards, 2008-05-27 + */ +static rsRetVal +PermittedPeerWildcardCompile(permittedPeers_t *pPeer) +{ + uchar *pC; + uchar *pStart; + DEFiRet; + + assert(pPeer != NULL); + assert(pPeer->pszID != NULL); + + /* first check if we have a wildcard */ + for(pC = pPeer->pszID ; *pC != '\0' && *pC != '*' ; ++pC) + /*EMPTY, just skip*/; + + if(*pC == '\0') { + /* no wildcard found, we are mostly done */ + pPeer->etryType = PERM_PEER_TYPE_PLAIN; + FINALIZE; + } + + /* if we reach this point, the string contains wildcards. So let's + * compile the structure. To do so, we must parse from dot to dot + * and create a wildcard entry for each domain component we find. + * We must also flag problems if we have an asterisk in the middle + * of the text (it is supported at the start or end only). + */ + pPeer->etryType = PERM_PEER_TYPE_WILDCARD; + + for(pC = pPeer->pszID ; *pC != '\0' ; ++pC) { + pStart = pC; + /* find end of domain component */ + for( ; *pC != '\0' && *pC != '.' ; ++pC) + /*EMPTY, just skip*/; + CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, pC - pStart)); + /* now check if we have an empty component at end of string */ + if(*pC == '.' && *(pC + 1) == '\0') { + /* pStart is a dummy, it is not used if length is 0 */ + CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, 0)); + } + } + +finalize_it: + if(iRet != RS_RET_OK) { + errmsg.LogError(NO_ERRCODE, "error compiling wildcard expression '%s'", + pPeer->pszID); + } + RETiRet; +} + + +/* Do a (potential) wildcard match. The function first checks if the wildcard + * has already been compiled and, if not, compiles it. If the peer entry in + * question does NOT contain a wildcard, a simple strcmp() is done. + * *pbIsMatching is set to 0 if there is no match and something else otherwise. + * rgerhards, 2008-05-27 */ +static rsRetVal +PermittedPeerWildcardMatch(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching) +{ + permittedPeerWildcard_t *pWildcard; + uchar *pC; + uchar *pStart; /* start of current domain component */ + size_t iWildcard, iName; /* work indexes for backward comparisons */ + DEFiRet; + + assert(pPeer != NULL); + assert(pszNameToMatch != NULL); + assert(pbIsMatching != NULL); + + if(pPeer->etryType == PERM_PEER_TYPE_UNDECIDED) { + PermittedPeerWildcardCompile(pPeer); + } + + if(pPeer->etryType == PERM_PEER_TYPE_PLAIN) { + *pbIsMatching = !strcmp((char*)pPeer->pszID, (char*)pszNameToMatch); + FINALIZE; + } + + /* we have a wildcard, so we need to extract the domain components and + * check then against the provided wildcards. + */ + pWildcard = pPeer->pWildcardRoot; + pC = pszNameToMatch; + while(*pC != '\0') { + if(pWildcard == NULL) { + /* we have more domain components than we have wildcards --> no match */ + *pbIsMatching = 0; + FINALIZE; + } + pStart = pC; + while(*pC != '\0' && *pC != '.') { + ++pC; + } + + /* got the component, now do the match */ + switch(pWildcard->wildcardType) { + case PEER_WILDCARD_NONE: + if( pWildcard->lenDomainPart != (size_t) (pC - pStart) + || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pC - pStart)) { + *pbIsMatching = 0; + FINALIZE; + } + break; + case PEER_WILDCARD_AT_START: + /* we need to do the backwards-matching manually */ + if(pWildcard->lenDomainPart > (size_t) (pC - pStart)) { + *pbIsMatching = 0; + FINALIZE; + } + iName = (size_t) (pC - pStart) - pWildcard->lenDomainPart; + iWildcard = 0; + while(iWildcard < pWildcard->lenDomainPart) { + if(pWildcard->pszDomainPart[iWildcard] != pStart[iName]) { + *pbIsMatching = 0; + FINALIZE; + } + ++iName; + ++iWildcard; + } + break; + case PEER_WILDCARD_AT_END: + if( pWildcard->lenDomainPart > (size_t) (pC - pStart) + || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pWildcard->lenDomainPart)) { + *pbIsMatching = 0; + FINALIZE; + } + break; + case PEER_WILDCARD_MATCH_ALL: + /* everything is OK, just continue */ + break; + case PEER_WILDCARD_EMPTY_COMPONENT: + if(pC - pStart > 0) { + /* if it is not empty, it is no match... */ + *pbIsMatching = 0; + FINALIZE; + } + break; + } + pWildcard = pWildcard->pNext; /* we processed this entry */ + + /* skip '.' if we had it and so prepare for next iteration */ + if(*pC == '.') + ++pC; + } + + if(pWildcard != NULL) { + /* we have more domain components than in the name to be + * checked. So this is no match. + */ + *pbIsMatching = 0; + FINALIZE; + } + + *pbIsMatching = 1; /* finally... it matches ;) */ + +finalize_it: + RETiRet; +} + + /* ------------------------------ end permitted peers code ------------------------------ */ @@ -1159,6 +1432,7 @@ CODESTARTobjQueryInterface(net) pIf->getLocalHostname = getLocalHostname; pIf->AddPermittedPeer = AddPermittedPeer; pIf->DestructPermittedPeers = DestructPermittedPeers; + pIf->PermittedPeerWildcardMatch = PermittedPeerWildcardMatch; finalize_it: ENDobjQueryInterface(net) diff --git a/runtime/net.h b/runtime/net.h index 673f45a9..0d36e824 100644 --- a/runtime/net.h +++ b/runtime/net.h @@ -91,6 +91,23 @@ struct AllowedSenders { }; +/* this structure is a helper to implement wildcards in permittedPeers_t. It specifies + * the domain component and the matching mode. + * rgerhards, 2008-05-27 + */ +struct permittedPeerWildcard_s { + uchar *pszDomainPart; + size_t lenDomainPart; + enum { + PEER_WILDCARD_NONE = 0, /**< no wildcard in this entry */ + PEER_WILDCARD_AT_START = 1, /**< wildcard at start of entry (*name) */ + PEER_WILDCARD_AT_END = 2, /**< wildcard at end of entry (name*) */ + PEER_WILDCARD_MATCH_ALL = 3, /**< only * wildcard, matches all values */ + PEER_WILDCARD_EMPTY_COMPONENT = 4/**< special case: domain component empty (e.g. "..") */ + } wildcardType; + permittedPeerWildcard_t *pNext; +}; + /* for fingerprints and hostnames, we need to have a temporary linked list of * permitted values. Unforutnately, we must also duplicate this in the netstream * drivers. However, this is the best interim solution (with the least effort). @@ -101,7 +118,14 @@ struct AllowedSenders { */ struct permittedPeers_s { uchar *pszID; + enum { + PERM_PEER_TYPE_UNDECIDED = 0, /**< we have not yet decided the type (fine in some auth modes) */ + PERM_PEER_TYPE_PLAIN = 1, /**< just plain text contained */ + PERM_PEER_TYPE_WILDCARD = 2, /**< wildcards are contained, wildcard struture is filled */ + } etryType; permittedPeers_t *pNext; + permittedPeerWildcard_t *pWildcardRoot; /**< root of the wildcard, NULL if not initialized */ + permittedPeerWildcard_t *pWildcardLast; /**< end of the wildcard list, NULL if not initialized */ }; @@ -121,6 +145,7 @@ BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ /* permitted peer handling should be replaced by something better (see comments above) */ rsRetVal (*AddPermittedPeer)(permittedPeers_t **ppRootPeer, uchar *pszID); rsRetVal (*DestructPermittedPeers)(permittedPeers_t **ppRootPeer); + rsRetVal (*PermittedPeerWildcardMatch)(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching); /* data members - these should go away over time... TODO */ int *pACLAddHostnameOnFail; /* add hostname to acl when DNS resolving has failed */ int *pACLDontResolve; /* add hostname to acl instead of resolving it to IP(s) */ @@ -128,7 +153,7 @@ BEGINinterface(net) /* name must also be changed in ENDinterface macro! */ struct AllowedSenders *pAllowedSenders_TCP; struct AllowedSenders *pAllowedSenders_GSS; ENDinterface(net) -#define netCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */ +#define netCURR_IF_VERSION 4 /* increment whenever you change the interface structure! */ /* prototypes */ PROTOTYPEObj(net); diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c index d1f87e90..e3ff3477 100644 --- a/runtime/nsd_gtls.c +++ b/runtime/nsd_gtls.c @@ -60,6 +60,7 @@ MODULE_TYPE_LIB DEFobjStaticHelpers DEFobjCurrIf(errmsg) DEFobjCurrIf(glbl) +DEFobjCurrIf(net) DEFobjCurrIf(nsd_ptcp) static int bGlblSrvrInitDone = 0; /**< 0 - server global init not yet done, 1 - already done */ @@ -741,12 +742,11 @@ gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatc if(pThis->pPermPeers) { /* do we have configured peer IDs? */ pPeer = pThis->pPermPeers; - while(pPeer != NULL && !*pbFoundPositiveMatch) { - if(!strcmp((char*)pszPeerID, (char*)pPeer->pszID)) { - *pbFoundPositiveMatch = 1; - } else { - pPeer = pPeer->pNext; - } + while(pPeer != NULL) { + CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch)); + if(*pbFoundPositiveMatch) + break; + pPeer = pPeer->pNext; } } else { /* we do not have configured peer IDs, so we use defaults */ @@ -756,6 +756,7 @@ gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatc } } +finalize_it: RETiRet; } @@ -1520,6 +1521,7 @@ CODESTARTObjClassExit(nsd_gtls) /* release objects we no longer need */ objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME); + objRelease(net, LM_NET_FILENAME); objRelease(glbl, CORE_COMPONENT); objRelease(errmsg, CORE_COMPONENT); ENDObjClassExit(nsd_gtls) @@ -1533,6 +1535,7 @@ BEGINObjClassInit(nsd_gtls, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ /* request objects we use */ CHKiRet(objUse(errmsg, CORE_COMPONENT)); CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(net, LM_NET_FILENAME)); CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME)); /* now do global TLS init stuff */ diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h index 7b6d08ff..f296a608 100644 --- a/runtime/rsyslog.h +++ b/runtime/rsyslog.h @@ -82,6 +82,7 @@ typedef struct objInfo_s objInfo_t; typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */ typedef rsRetVal (*errLogFunc_t)(uchar*); /* this is a trick to store a function ptr to a function returning a function ptr... */ typedef struct permittedPeers_s permittedPeers_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */ +typedef struct permittedPeerWildcard_s permittedPeerWildcard_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */ typedef struct tcpsrv_s tcpsrv_t; /* some universal 64 bit define... */ @@ -236,6 +237,7 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth RS_RET_SYS_ERR = -2095, /**< system error occured (e.g. time() returned -1, quite unexpected) */ RS_RET_FILE_NO_STAT = -2096, /**< can not stat() a file */ RS_RET_FILE_TOO_LARGE = -2097, /**< a file is larger than permitted */ + RS_RET_INVALID_WILDCARD = -2098, /**< a wildcard entry is invalid */ /* RainerScript error messages (range 1000.. 1999) */ RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */ -- cgit