Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Alexander_Naumov:SLE-12:Update
openslp.6820
openslp.initda.diff
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File openslp.initda.diff of Package openslp.6820
--- ./common/slp_message.h.orig 2016-09-13 10:56:06.324486007 +0000 +++ ./common/slp_message.h 2016-09-13 10:56:14.214459554 +0000 @@ -126,6 +126,7 @@ #define SLP_REG_SOURCE_REMOTE 1 /* from a remote host */ #define SLP_REG_SOURCE_LOCAL 2 /* from localhost or IPC */ #define SLP_REG_SOURCE_STATIC 3 /* from the slp.reg file */ +#define SLP_REG_SOURCE_PULL_PEER_DA 4 /* from another DA pulled at startup */ #define SLP_REG_WATCH_TCP (1<<0) #define SLP_REG_WATCH_UDP (1<<1) --- ./common/slp_property.c.orig 2016-09-13 10:56:06.324486007 +0000 +++ ./common/slp_property.c 2016-09-13 10:56:14.215459551 +0000 @@ -176,6 +176,11 @@ static int SetDefaultValues(void) /* Additional properties that are specific to IPv6 */ {"net.slp.useIPv6", "false", 0}, {"net.slp.useIPv4", "true", 0}, + + {"net.slp.DASyncReg", "false", 0}, + {"net.slp.isDABackup", "false", 0}, + {"net.slp.DABackupInterval", "900", 0}, + {"net.slp.DABackupLocalReg", "false", 0}, }; int i; --- ./etc/slp.conf.orig 2012-11-28 17:07:04.000000000 +0000 +++ ./etc/slp.conf 2016-09-13 10:56:14.215459551 +0000 @@ -23,6 +23,20 @@ # which DAs to use. (Default is to use dynamic DA discovery) ;net.slp.DAAddresses = myDa1,myDa2,myDa3 +# Enables backup of registrations to /etc/slp.reg.d/slpd/DABackup. +;net.slp.isDABackup = true + +# A 32 bit integer giving the number of seconds for the DABackup file update. +# Default is 15 minutes (900 seconds). Ignored if isDA is false. +;net.slp.DABackupInterval = 900 + +# Include local registrations in the backup, too. The default is false. +;net.slp.DABackupLocalReg = true + +# Enables slpd to sync service registration between SLP DAs on startup +# Default is false +;net.slp.DASyncReg = true + #---------------------------------------------------------------------------- # DA Specific Configuration --- ./slpd/Makefile.am.orig 2012-11-28 17:07:04.000000000 +0000 +++ ./slpd/Makefile.am 2016-09-13 10:56:14.216459548 +0000 @@ -73,7 +73,8 @@ slpd_SOURCES = \ slpd_property.c \ slpd_regfile.c \ slpd_socket.c\ - slpd_index.c + slpd_index.c \ + slpd_initda.c noinst_HEADERS = \ $(slp_predicate_HDRS) \ @@ -90,7 +91,8 @@ noinst_HEADERS = \ slpd_regfile.h \ slpd_incoming.h \ slpd_socket.h\ - slpd_index.h + slpd_index.h \ + slpd_initda.h #if you're building on Irix, replace .la with .a below slpd_LDADD = ../common/libcommonslpd.la ../libslpattr/libslpattr.la --- ./slpd/slpd_database.c.orig 2016-09-13 10:56:06.332485980 +0000 +++ ./slpd/slpd_database.c 2016-09-13 10:56:14.217459544 +0000 @@ -50,6 +50,7 @@ #define _GNU_SOURCE #include <string.h> #include <dirent.h> +#include <time.h> #include <sys/socket.h> #include <linux/netlink.h> #include <linux/inet_diag.h> @@ -531,7 +532,7 @@ int SLPDDatabaseReg(SLPMessage * msg, SL { /* check to ensure the source addr is the same as the original */ - if (G_SlpdProperty.checkSourceAddr) + if (G_SlpdProperty.checkSourceAddr && entryreg->source != SLP_REG_SOURCE_PULL_PEER_DA) { if ((entry->msg->peer.ss_family == AF_INET && msg->peer.ss_family == AF_INET @@ -567,6 +568,16 @@ int SLPDDatabaseReg(SLPMessage * msg, SL return SLP_ERROR_AUTHENTICATION_FAILED; } #endif + if (reg->source == SLP_REG_SOURCE_PULL_PEER_DA && entryreg->source != SLP_REG_SOURCE_PULL_PEER_DA) + { + /* Do not update not-pulled registrations with pulled ones */ + SLPDatabaseClose(dh); + freeNormalisedSrvtype(pNormalisedSrvtype); + if (attr) + SLPAttrFree(attr); + return SLP_ERROR_OK; + } + /* Remove the identical entry */ SLPDDatabaseRemove(dh, entry); break; @@ -697,7 +708,7 @@ int SLPDDatabaseDeReg(SLPMessage * msg) { /* Check to ensure the source addr is the same as */ /* the original */ - if (G_SlpdProperty.checkSourceAddr) + if (G_SlpdProperty.checkSourceAddr && entryreg->source != SLP_REG_SOURCE_PULL_PEER_DA) { if ((entry->msg->peer.ss_family == AF_INET && msg->peer.ss_family == AF_INET @@ -1054,7 +1065,7 @@ static int SLPDDatabaseSrvRqstStartScan( #ifdef ENABLE_PREDICATES SLPDPredicateTreeNode *parse_tree, #endif - SLPDDatabaseSrvRqstResult ** result) + SLPDDatabaseSrvRqstResult ** result, int nopulled) { SLPDatabaseHandle dh; SLPDatabaseEntry * entry; @@ -1074,6 +1085,9 @@ static int SLPDDatabaseSrvRqstStartScan( if (entry == 0) return 0; /* This is the only successful way out */ + if (nopulled && entry->msg->body.srvreg.source == SLP_REG_SOURCE_PULL_PEER_DA) + continue; + if (SLPDDatabaseSrvRqstTestEntry(msg, #ifdef ENABLE_PREDICATES parse_tree, @@ -1150,6 +1164,20 @@ int SLPDDatabaseSrvRqstStart(SLPMessage /* rewind enumeration in case we had to reallocate */ SLPDatabaseRewind(dh); + if (srvrqst->predicatelen == 29 && !strncmp(srvrqst->predicate, "(!(openslp-pulled-from-da=*))", 29)) + { + /* this is the special "no pulled entries" predicate used in DA syncing */ + start_result = SLPDDatabaseSrvRqstStartScan(msg, +#ifdef ENABLE_PREDICATES + predicate_parse_tree, +#endif + result, 1); + if (start_result == 0) + return 0; + G_SlpdDatabase.urlcount *= 2; + continue; + } + /* Check if we can use the srvtype index */ if (G_SlpdProperty.srvtypeIsIndexed) { @@ -1277,7 +1305,7 @@ int SLPDDatabaseSrvRqstStart(SLPMessage #ifdef ENABLE_PREDICATES predicate_parse_tree, #endif - result); + result, 0); } #ifdef ENABLE_PREDICATES if (predicate_parse_tree) @@ -1883,7 +1911,7 @@ int SLPDDatabaseReInit() if (regfileFP) { rewind(regfileFP); - while (SLPDRegFileReadSrvReg(regfileFP, &msg, &buf) == 0) + while (SLPDRegFileReadSrvReg(regfileFP, SLP_REG_SOURCE_STATIC, &msg, &buf) == 0) { if (SLPDDatabaseReg(msg, buf) != SLP_ERROR_OK) { @@ -1905,7 +1933,7 @@ int SLPDDatabaseReInit() strcmp(filename+strlen(filename)-4, ".reg") == 0 && (fp = fopen(filename,"rb")) != 0) { - while (SLPDRegFileReadSrvReg(fp, &msg, &buf) == 0) + while (SLPDRegFileReadSrvReg(fp, SLP_REG_SOURCE_STATIC, &msg, &buf) == 0) { if (SLPDDatabaseReg(msg, buf) != SLP_ERROR_OK) { @@ -2211,6 +2239,65 @@ void SLPDDatabaseWatcher(void) } +void SLPDDatabaseReadDABackup(FILE *fp) +{ + SLPMessage * msg; + SLPBuffer buf; + time_t timediff; + long l; + + SLPDLog("Reading registration backup file...\n"); + rewind(fp); + if (fscanf(fp, "# Update timestamp: %ld\n", &l) != 1) + return; + timediff = time(NULL) - (time_t)l; + if (timediff < 0) + timediff = 0; + while (SLPDRegFileReadSrvReg(fp, SLP_REG_SOURCE_REMOTE, &msg, &buf) == 0) + { + if (!G_SlpdProperty.DABackupLocalReg && msg->body.srvreg.source == SLP_REG_SOURCE_LOCAL) + { + SLPMessageFree(msg); + SLPBufferFree(buf); + continue; + } + msg->body.srvreg.urlentry.lifetime -= timediff; + if (msg->body.srvreg.urlentry.lifetime > 0) + SLPDDatabaseReg(msg, buf); + else + { + SLPMessageFree(msg); + SLPBufferFree(buf); + } + } +} + +void SLPDDatabaseWriteDABackup(FILE *fp) +{ + SLPDatabaseHandle dh; + SLPDatabaseEntry* entry; + + SLPDLog("Writing registration backup file...\n"); + rewind(fp); + (void)ftruncate(fileno(fp), 0); + fprintf(fp, "# Update timestamp: %ld\n\n", (long)time(NULL)); + dh = SLPDatabaseOpen(&G_SlpdDatabase.database); + if (dh) + { + while ((entry = SLPDatabaseEnum(dh)) != NULL) + { + if (entry->msg->body.srvreg.source == SLP_REG_SOURCE_STATIC) + continue; + if (!G_SlpdProperty.DABackupLocalReg && entry->msg->body.srvreg.source == SLP_REG_SOURCE_LOCAL) + continue; + SLPDRegFileWriteSrvReg(fp, entry->msg); + } + SLPDatabaseClose(dh); + } + fflush(fp); +} + + #ifdef DEBUG /** Cleans up all resources used by the database. */ --- ./slpd/slpd_database.h.orig 2016-09-13 10:56:06.326486000 +0000 +++ ./slpd/slpd_database.h 2016-09-13 10:56:14.217459544 +0000 @@ -105,7 +105,8 @@ int SLPDDatabaseIsEmpty(void); int SLPDDatabaseInit(const char * regfile); int SLPDDatabaseReInit(); void SLPDDatabaseWatcher(void); - +void SLPDDatabaseReadDABackup(FILE *fp); +void SLPDDatabaseWriteDABackup(FILE *fp); #ifdef DEBUG void SLPDDatabaseDeinit(void); --- ./slpd/slpd_initda.c.orig 2016-09-13 10:56:14.218459541 +0000 +++ ./slpd/slpd_initda.c 2016-09-13 10:56:14.218459541 +0000 @@ -0,0 +1,396 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "slpd.h" +#include "slp_message.h" +#include "slp_property.h" +#include "slp_network.h" +#include "slpd_database.h" +#include "slpd_regfile.h" +#include "slpd_property.h" +#include "slpd_log.h" + + +#define SLP_NETWORK_TIMED_OUT -19 +#define SLP_MEMORY_ALLOC_FAILED -21 +#define SLP_NETWORK_ERROR -23 + +static int SLPDUnicastRqstRply(int sock, struct sockaddr_storage * destaddr, struct sockaddr_storage * localaddr, + const char * langtag, char * buf, char buftype, int bufsize, + int (*callback)(SLPMessage * message, void *cookie), void * cookie) +{ + struct timeval timeout; + SLPBuffer sendbuf = 0; + SLPBuffer recvbuf = 0; + SLPMessage * message = 0; + int result = 0; + int langtaglen = 0; + int xid = 0; + int mtu = 0; + int size = 0; + int timeouts[1]; + + /* Save off a few things we don't want to recalculate */ + langtaglen = strlen(langtag); + xid = SLPXidGenerate(); + mtu = SLPPropertyAsInteger("net.slp.MTU"); + sendbuf = SLPBufferAlloc(mtu); + if(sendbuf == 0) + { + result = SLP_MEMORY_ALLOC_FAILED; + goto FINISHED; + } + SLPPropertyAsIntegerVector("net.slp.unicastTimeouts", timeouts, 1); + timeout.tv_sec = timeouts[0] / 1000; + timeout.tv_usec = (timeouts[0] % 1000) * 1000; + size = 16 + langtaglen + bufsize; + if((sendbuf = SLPBufferRealloc(sendbuf, size)) == 0) + { + result = SLP_MEMORY_ALLOC_FAILED; + goto FINISHED; + } + sendbuf->curpos = sendbuf->start; + + /* Add the header to the send buffer */ + /*version*/ + *sendbuf->curpos++ = 2; + /*function id*/ + *sendbuf->curpos++ = buftype; + /*length*/ + PutUINT24(&sendbuf->curpos, size); + /*flags*/ + PutUINT16(&sendbuf->curpos, SLP_FLAG_UCAST); /*this is a unicast */ + /*ext offset*/ + PutUINT24(&sendbuf->curpos, 0); + /*xid*/ + PutUINT16(&sendbuf->curpos, xid); + /*lang tag len*/ + PutUINT16(&sendbuf->curpos, langtaglen); + /*lang tag*/ + memcpy(sendbuf->curpos, langtag, langtaglen); + sendbuf->curpos += langtaglen; + /*prlist*/ + PutUINT16(&sendbuf->curpos, 0); + + /* Add the rest of the message */ + memcpy(sendbuf->curpos, buf, bufsize); + sendbuf->curpos += bufsize; + + /* send the send buffer */ + result = SLPNetworkSendMessage(sock, SOCK_STREAM, sendbuf, sendbuf->curpos - sendbuf->start, destaddr, &timeout); + if (result != 0) + { + result = errno == ETIMEDOUT ? SLP_NETWORK_TIMED_OUT : SLP_NETWORK_ERROR; + goto FINISHED; + } + result = SLPNetworkRecvMessage(sock, SOCK_STREAM, &recvbuf, destaddr, &timeout); + if (result != 0) + { + result = errno == ETIMEDOUT ? SLP_NETWORK_TIMED_OUT : SLP_NETWORK_ERROR; + goto FINISHED; + } + if(AS_UINT16(recvbuf->start + 10) != xid) + { + result = SLP_NETWORK_ERROR; + goto FINISHED; + } + message = SLPMessageAlloc(); + result = SLPMessageParseBuffer(destaddr, localaddr, recvbuf, message); + if (result == 0) + { + result = callback(message, cookie); + } +FINISHED: + SLPMessageFree(message); + SLPBufferFree(sendbuf); + SLPBufferFree(recvbuf); + return result; +} + +typedef struct SLPURL { + struct SLPURL *next; + struct SLPURL *last; + char *serviceURL; + char *attrs; + char* scopelist; + char* serviceType; + unsigned short ltime; +} SLPUrl; + + +/* Cache collection structure */ +typedef struct SLPUrlList { + char * services; /* list of all services */ + SLPUrl * slpUrl; /* linked list of URLs for all services */ + SLPUrl * currentSLPUrl; /* next location to be used for update */ + char * currentScope; + char * currentServiceType; +} SLPUrlList; + +static SLPUrl * AllocateSLPUrl() +{ + SLPUrl* slpUrl = (SLPUrl*)malloc(sizeof(SLPUrl)); + slpUrl->serviceURL = NULL; + slpUrl->ltime = 0; + slpUrl->scopelist = NULL; + slpUrl->serviceType = NULL; + slpUrl->attrs = NULL; + slpUrl->next = NULL; + slpUrl->last = NULL; + return slpUrl; +} + + +static void CleanUpSLPUrlList(SLPUrlList* slpUrlList) +{ + SLPUrl* slpUrl,*slpUrlNext; + + slpUrl = slpUrlList->slpUrl; + while(slpUrl) + { + if (slpUrl->serviceURL) + free(slpUrl->serviceURL); + if (slpUrl->scopelist) + free(slpUrl->scopelist); + if (slpUrl->serviceType) + free(slpUrl->serviceType); + if (slpUrl->attrs) + free(slpUrl->attrs); + slpUrlNext = slpUrl->next; + free(slpUrl); + slpUrl = slpUrlNext; + } + slpUrlList->slpUrl = NULL; + if (slpUrlList->currentScope != NULL) + free(slpUrlList->currentScope); + if(slpUrlList->services != NULL) + free(slpUrlList->services); + if(slpUrlList->currentServiceType != NULL) + free(slpUrlList->currentServiceType); + free(slpUrlList); +} + +static int SLPSrvTCallBack(SLPMessage * message, void * cookie) +{ + if (message->header.functionid != SLP_FUNCT_SRVTYPERPLY) + return SLP_NETWORK_ERROR; + if (message->body.srvtyperply.errorcode != 0) + return message->body.srvtyperply.errorcode; + /* null terminate as in libslp */ + ((char *)message->body.srvtyperply.srvtypelist)[message->body.srvtyperply.srvtypelistlen] = 0; + ((SLPUrlList*)cookie)->services = strdup(message->body.srvtyperply.srvtypelist); + return 0; +} + +static int SLPSrvCallBack(SLPMessage * message, void * cookie) +{ + SLPUrl *slpUrl = NULL; + char scopelist[4096]; + int i; + SLPUrlEntry *srvurl; + + if (message->header.functionid != SLP_FUNCT_SRVRPLY) + return SLP_NETWORK_ERROR; + if (message->body.srvrply.errorcode != 0) + return message->body.srvrply.errorcode; + for (i=0; i<message->body.srvrply.urlcount; i++) + { + srvurl = message->body.srvrply.urlarray + i; + /* null terminate url as in libslp, overwrites authcount */ + ((char *)srvurl->url)[srvurl->urllen] = 0; + for (slpUrl = ((SLPUrlList*)cookie)->slpUrl; slpUrl ; slpUrl = slpUrl->next) + { + /* Check whether the same service URL is available as part of different scope*/ + if( (slpUrl->serviceURL != NULL) && ( strcasecmp( slpUrl->serviceURL,srvurl->url) == 0)) + break; + } + if (slpUrl) + { + snprintf(scopelist,sizeof(scopelist),"%s,%s",slpUrl->scopelist,((SLPUrlList*)cookie)->currentScope); + free(slpUrl->scopelist); + slpUrl->scopelist = strdup(scopelist); + } + else + { + slpUrl = AllocateSLPUrl(); + slpUrl->serviceURL = strdup(srvurl->url); + slpUrl->ltime = srvurl->lifetime; + slpUrl->scopelist = strdup(((SLPUrlList*)cookie)->currentScope); + slpUrl->serviceType = strdup(((SLPUrlList*)cookie)->currentServiceType); + slpUrl->attrs = NULL; + slpUrl->next = ((SLPUrlList*)cookie)->slpUrl; + if(((SLPUrlList*)cookie)->slpUrl) + ((SLPUrlList*)cookie)->slpUrl->last = slpUrl; + ((SLPUrlList*)cookie)->slpUrl= slpUrl; + } + } + return 0; +} + +static int SLPSrvAttrCallBack(SLPMessage * message, void * cookie) +{ + SLPUrl *lslpUrl = ((SLPUrlList*)cookie)->currentSLPUrl; + if (message->header.functionid != SLP_FUNCT_ATTRRPLY) + return SLP_NETWORK_ERROR; + if (message->body.attrrply.errorcode != 0) + return message->body.attrrply.errorcode; + + /* null terminate as in libslp */ + ((char *)message->body.attrrply.attrlist)[message->body.attrrply.attrlistlen] = 0; + lslpUrl->attrs = strdup(message->body.attrrply.attrlist); + return 0; +} + +static char * createreq(int * sizep, char * url, char * scope, char * predicate, char * spi) +{ + char *buf, *cur; + int urllen = url ? strlen(url) : 0; + int scopelen = scope ? strlen(scope) : 0; + int predicatelen = predicate ? strlen(predicate) : 0; + int spilen = spi ? strlen(spi) : 0; + buf = malloc(2 + urllen + 2 + scopelen + 2 + predicatelen + 2 + spilen); + cur = buf; + if (url) + { + TO_UINT16(cur, urllen); + cur += 2; + memcpy(cur, url, urllen); + cur += urllen; + } + if (scope) + { + TO_UINT16(cur, scopelen); + cur += 2; + memcpy(cur, scope, scopelen); + cur += scopelen; + } + if (predicate) + { + TO_UINT16(cur, predicatelen); + cur += 2; + memcpy(cur, predicate, predicatelen); + cur += predicatelen; + } + if (spi) + { + TO_UINT16(cur, spilen); + cur += 2; + memcpy(cur, spi, spilen); + cur += spilen; + } + *sizep = cur - buf; + return buf; +} + + +int getSLPServiceURLs(int sock, struct sockaddr_storage * destaddr, struct sockaddr_storage * localaddr) +{ + char *strng, *services; + int gresult = 0, result; + SLPUrl * slpUrl; + char *scope = NULL, *scopelist = NULL; + char scopeptr[4096],serviceptr[4096]; + SLPMessage * msg; + SLPBuffer buf; + const char *langtag; + char *outbuf; + int bufsize; + char *srvtype, *srvtype_end; + + SLPUrlList* slpUrlList = (SLPUrlList*)malloc(sizeof(SLPUrlList)); + slpUrlList->slpUrl = NULL; + slpUrlList->services = NULL; + slpUrlList->currentServiceType = NULL; + slpUrlList->currentScope = NULL; + + langtag = SLPPropertyGet("net.slp.locale", 0, 0); + + scopelist = strdup(G_SlpdProperty.useScopes); + + for(scope = strtok_r(scopelist,",",(char**)&scopeptr); scope ; scope = strtok_r(NULL,",",(char**)&scopeptr)) + { + slpUrlList->currentScope = strdup(scope); + outbuf = createreq(&bufsize, "", scope, NULL, NULL); + TO_UINT16(outbuf, 0xffff); /* 0xffff indicates all service types */ + result = SLPDUnicastRqstRply(sock, destaddr, localaddr, langtag, outbuf, SLP_FUNCT_SRVTYPERQST, bufsize, SLPSrvTCallBack, slpUrlList); + free(outbuf); + if (result) + { + gresult = result; /* remember error with that scope */ + SLPDLog("Error: SLPFindSrvTypes %d\n",result); + continue; + } + if(slpUrlList->services) + { + services = strdup(slpUrlList->services); + for(strng = strtok_r(services, ",",(char**)&serviceptr); strng ; strng = strtok_r(NULL, ",",(char**)&serviceptr)) + { + if (!strcasecmp(strng, SLP_SA_SERVICE_TYPE)) + continue; + if (!strcasecmp(strng, SLP_DA_SERVICE_TYPE)) + continue; + slpUrlList->currentServiceType = strdup(strng); + outbuf = createreq(&bufsize, strng, scope, "", ""); + result = SLPDUnicastRqstRply(sock, destaddr, localaddr, langtag, outbuf, SLP_FUNCT_SRVRQST, bufsize, SLPSrvCallBack, slpUrlList); + free(outbuf); + free(slpUrlList->currentServiceType); + slpUrlList->currentServiceType = NULL; + if(result != 0) + { + SLPDLog("Error: SLPFindSrvs %d\n", result); + continue; + } + } + free(services); + } + if (slpUrlList->currentScope != NULL) + { + free(slpUrlList->currentScope); + slpUrlList->currentScope = NULL; + } + if(slpUrlList->services != NULL) + { + free(slpUrlList->services); + slpUrlList->services = NULL; + } + } + + /* we now have collected all services, fetch the attributes */ + + for(slpUrl = slpUrlList->slpUrl; slpUrl ; slpUrl = slpUrl->next) + { + slpUrl->attrs = NULL; + slpUrlList->currentSLPUrl = slpUrl; + outbuf = createreq(&bufsize, slpUrl->serviceURL, slpUrl->scopelist, "", ""); + result = SLPDUnicastRqstRply(sock, destaddr, localaddr, langtag, outbuf, SLP_FUNCT_ATTRRQST, bufsize, SLPSrvAttrCallBack, slpUrlList); + free(outbuf); + if(result != 0) + { + SLPDLog("Error: SLPFindAttrs %d\n", result); + continue; + } + srvtype = strdup(slpUrl->serviceURL); + srvtype_end = strstr(srvtype, "://"); + if (srvtype_end) + *srvtype_end = 0; + if (SLPDCreateSrvReg(SLP_REG_SOURCE_PULL_PEER_DA, + strlen(slpUrl->serviceURL), slpUrl->serviceURL, + strlen(langtag), (char *)langtag, + strlen(srvtype), srvtype, + strlen(slpUrl->scopelist), slpUrl->scopelist, + slpUrl->attrs ? strlen(slpUrl->attrs) : 0, slpUrl->attrs, + slpUrl->ltime, &msg, &buf) == 0) + { + msg->peer = *destaddr; + SLPDDatabaseReg(msg, buf); + } + free(srvtype); + } + + CleanUpSLPUrlList(slpUrlList); + if(scopelist != NULL) + free(scopelist); + return gresult; +} + --- ./slpd/slpd_initda.h.orig 2016-09-13 10:56:14.218459541 +0000 +++ ./slpd/slpd_initda.h 2016-09-13 10:56:14.218459541 +0000 @@ -0,0 +1,16 @@ +#ifndef SLPD_INITDA_H_INCLUDED +#define SLPD_INITDA_H_INCLUDED + +#include "slpd.h" + +/*=========================================================================*/ +/* common code includes */ +/*=========================================================================*/ +#include "slpd_socket.h" + + +int getSLPServiceURLs(int sock, struct sockaddr_storage *destaddr, struct sockaddr_storage *localaddr); + +#endif /* SLPD_INITDA_H_INCLUDED */ + +/*=========================================================================*/ --- ./slpd/slpd_log.c.orig 2016-09-13 10:56:06.326486000 +0000 +++ ./slpd/slpd_log.c 2016-09-13 10:56:14.219459537 +0000 @@ -509,6 +509,12 @@ void SLPDLogRegistration(const char * pr case SLP_REG_SOURCE_STATIC: SLPDLog("static (slp.reg)\n"); break; + + case SLP_REG_SOURCE_PULL_PEER_DA: + SLPDLog("pulled from peer DA (%s)\n", + SLPNetSockAddrStorageToString(&entry->msg->peer, + addr_str, sizeof(addr_str))); + break; } SLPDLogBuffer(" service-url = ", entry->msg->body.srvreg.urlentry.urllen, --- ./slpd/slpd_main.c.orig 2016-09-13 10:56:06.326486000 +0000 +++ ./slpd/slpd_main.c 2016-09-13 10:57:32.988195569 +0000 @@ -57,6 +57,7 @@ #include "slp_xmalloc.h" #include "slp_xid.h" #include "slp_net.h" +#include "slp_network.h" int G_SIGALRM; int G_SIGTERM; @@ -65,8 +66,27 @@ int G_SIGHUP; int G_SIGINT; /* Signal being used for dumping registrations */ int G_SIGUSR1; /* Signal being used to dump information about the database */ #endif +#include "slpd_initda.h" char *reg_file_dir = "/etc/slp.reg.d"; +FILE *DABackupfp; + +static void SLPDOpenDABackupFile() +{ + FILE *fp; + char filename[1024]; + snprintf(filename, sizeof(filename), "%s/slpd/%s", reg_file_dir, "DABackup"); + + fp = fopen(filename, "a+"); + if (!DABackupfp && !fp) + SLPDLog("Could not open DABackup file\n"); + if (fp) + { + if (DABackupfp) + fclose(DABackupfp); + DABackupfp = fp; + } +} /** Configures fd_set objects with sockets. * @@ -214,6 +234,10 @@ void HandleSigTerm(void) SLPDLog("SLPD daemon shutting down\n"); SLPDLog("****************************************\n"); + /* write backup file if configured */ + if (G_SlpdProperty.isDA && G_SlpdProperty.isDABackup && DABackupfp) + SLPDDatabaseWriteDABackup(DABackupfp); + /* unregister with all DAs */ SLPDKnownDADeinit(); @@ -282,6 +306,10 @@ void HandleSigHup(void) SLPDLog("SLPD daemon reset by SIGHUP\n"); SLPDLog("****************************************\n\n"); + /* write backup file if configured */ + if (G_SlpdProperty.isDA && G_SlpdProperty.isDABackup && DABackupfp) + SLPDDatabaseWriteDABackup(DABackupfp); + /* unregister with all DAs */ SLPDKnownDADeinit(); @@ -298,6 +326,10 @@ void HandleSigHup(void) /* Re-read the static registration file (slp.reg)*/ SLPDDatabaseReInit(); + /* Re-read the backup file if configured */ + if (G_SlpdProperty.isDA && G_SlpdProperty.isDABackup && DABackupfp) + SLPDDatabaseReadDABackup(DABackupfp); + /* Reopen listening sockets */ SLPDIncomingReinit(); @@ -666,6 +698,9 @@ int main(int argc, char * argv[]) if (G_SlpdProperty.port != SLP_RESERVED_PORT) SLPDLog("Using port %d instead of default %d\n", G_SlpdProperty.port, SLP_RESERVED_PORT); + if (G_SlpdProperty.isDA && G_SlpdProperty.isDABackup) + SLPDOpenDABackupFile(); + /* init watcher */ SLPDDatabaseWatcher(); @@ -677,6 +712,43 @@ int main(int argc, char * argv[]) if (SetUpSignalHandlers()) SLPDFatal("Error setting up signal handlers.\n"); + if (G_SlpdProperty.isDA && G_SlpdProperty.isDABackup && DABackupfp) + { + SLPDDatabaseReadDABackup(DABackupfp); + } + + if((G_SlpdProperty.isDA) && (G_SlpdProperty.DASyncReg)) + { + /* HACK: at that point in time all outgoing sockets are DA connections + * and the incoming sockets are our interfaces */ + SLPDLog("Pulling service list from other DAs...\n"); + SLPDSocket* sock = (SLPDSocket*)G_OutgoingSocketList.head; + while (sock) + { + SLPDSocket* isock = (SLPDSocket*)G_IncomingSocketList.head; + + /* make sure we're not connecting to ourself */ + while (isock) + { + if (SLPNetCompareAddrs(&sock->peeraddr, &isock->peeraddr) == 0) + break; + isock = (SLPDSocket*)isock->listitem.next; + } + if (!isock) + { + int s = SLPNetworkConnectStream(&sock->peeraddr, 0); + if (s >= 0) + { + int result = getSLPServiceURLs(s, &sock->peeraddr, &sock->localaddr); + close(s); + if (result == 0) + break; + } + } + sock = (SLPDSocket*)sock->listitem.next; + } + } + /* Set up alarm to age database -- a shorter start, so SAs register with us quickly on our startup */ alarm(2); @@ -748,6 +820,22 @@ HANDLE_SIGNAL: } #endif + if (G_SlpdProperty.isDA && G_SlpdProperty.isDABackup && DABackupfp) + { + static time_t lastbck; + time_t now; + + now = time(NULL); + if (!lastbck) + lastbck = now; + if (now - lastbck > G_SlpdProperty.DABackupInterval) + { + SLPDLog("Updating registration backup file\n"); + SLPDDatabaseWriteDABackup(DABackupfp); + lastbck = now; + } + } + } /* End of main loop */ /* Got SIGTERM */ --- ./slpd/slpd_property.c.orig 2016-09-13 10:56:06.330485986 +0000 +++ ./slpd/slpd_property.c 2016-09-13 10:56:14.220459534 +0000 @@ -248,6 +248,11 @@ void SLPDPropertyReinit(void) /* set up hostname */ G_SlpdProperty.myHostname = SLPDGetCanonHostname(); G_SlpdProperty.myHostnameLen = strlen(G_SlpdProperty.myHostname); + + G_SlpdProperty.DASyncReg = SLPPropertyAsBoolean("net.slp.DASyncReg"); + G_SlpdProperty.isDABackup = SLPPropertyAsBoolean("net.slp.isDABackup"); + G_SlpdProperty.DABackupInterval = SLPPropertyAsInteger("net.slp.DABackupInterval"); + G_SlpdProperty.DABackupLocalReg = SLPPropertyAsBoolean("net.slp.DABackupLocalReg"); } /** Initialize the slpd property management subsystem. --- ./slpd/slpd_property.h.orig 2016-09-13 10:56:06.330485986 +0000 +++ ./slpd/slpd_property.h 2016-09-13 10:56:14.220459534 +0000 @@ -117,6 +117,11 @@ typedef struct _SLPDProperty int MTU; int useDHCP; int oversizedUDP; + + int DASyncReg; + int isDABackup; + int DABackupInterval; + int DABackupLocalReg; } SLPDProperty; extern SLPDProperty G_SlpdProperty; --- ./slpd/slpd_regfile.c.orig 2016-09-13 10:56:06.327485996 +0000 +++ ./slpd/slpd_regfile.c 2016-09-13 10:56:14.221459531 +0000 @@ -108,15 +108,190 @@ static char * RegFileReadLine(FILE * fd, return line; } +/** Create a SrcReg Message from given data. + * Don't look at this too hard or you'll be sick. This is by far + * the most horrible code in OpenSLP. Please volunteer to rewrite it! + * + * "THANK GOODNESS this function is only called at startup" -- Matt + * + * @note Eventually the caller needs to call SLPBufferFree and + * SLPMessageFree to free memory. + */ +int SLPDCreateSrvReg(int source, int urllen, char * url, + int langtaglen, char * langtag, + int srvtypelen, char * srvtype, + int scopelistlen, char * scopelist, + int attrlistlen, char * attrlist, + int lifetime, SLPMessage ** msg, SLPBuffer * buf) +{ + struct sockaddr_storage peer; + int result = 0; + size_t bufsize = 0; + SLPBuffer tmp; +#ifdef ENABLE_SLPv2_SECURITY + unsigned char * urlauth = 0; + int urlauthlen = 0; + unsigned char * attrauth = 0; + int attrauthlen = 0; +#endif + +#ifdef ENABLE_SLPv2_SECURITY + /* generate authentication blocks */ + if (G_SlpdProperty.securityEnabled) + { + SLPAuthSignUrl(G_SlpdSpiHandle, 0, 0, urllen, url, + &urlauthlen, &urlauth); + SLPAuthSignString(G_SlpdSpiHandle, 0, 0, attrlistlen, attrlist, + &attrauthlen, &attrauth); + } +#endif + + /* allocate buffer for the SrvReg Message */ + bufsize = 14 + langtaglen; /* 14 bytes for header */ + bufsize += urllen + 6; /* 1 byte for reserved */ + /* 2 bytes for lifetime */ + /* 2 bytes for urllen */ + /* 1 byte for authcount */ + bufsize += srvtypelen + 2; /* 2 bytes for len field */ + bufsize += scopelistlen + 2; /* 2 bytes for len field */ + bufsize += attrlistlen + 2; /* 2 bytes for len field */ + bufsize += 1; /* 1 byte for authcount */ + +#ifdef ENABLE_SLPv2_SECURITY + bufsize += urlauthlen; + bufsize += attrauthlen; +#endif + + tmp = *buf = SLPBufferAlloc(bufsize); + if (tmp == 0) + { + result = SLP_ERROR_INTERNAL_ERROR; + goto CLEANUP; + } + + /* now build the SrvReg Message */ + + /* version */ + *tmp->curpos++ = 2; + + /* function id */ + *tmp->curpos++ = SLP_FUNCT_SRVREG; + + /* length */ + PutUINT24(&tmp->curpos, bufsize); + + /* flags */ + PutUINT16(&tmp->curpos, 0); + + /* ext offset */ + PutUINT24(&tmp->curpos, 0); + + /* xid */ + PutUINT16(&tmp->curpos, 0); + + /* lang tag len */ + PutUINT16(&tmp->curpos, langtaglen); + + /* lang tag */ + memcpy(tmp->curpos, langtag, langtaglen); + tmp->curpos += langtaglen; + + /* url-entry reserved */ + *tmp->curpos++ = 0; + + /* url-entry lifetime */ + PutUINT16(&tmp->curpos, lifetime); + + /* url-entry urllen */ + PutUINT16(&tmp->curpos, urllen); + + /* url-entry url */ + memcpy(tmp->curpos, url, urllen); + tmp->curpos += urllen; + + /* url-entry authblock */ +#ifdef ENABLE_SLPv2_SECURITY + if (urlauth) + { + /* authcount */ + *tmp->curpos++ = 1; + + /* authblock */ + memcpy(tmp->curpos, urlauth, urlauthlen); + tmp->curpos += urlauthlen; + } + else +#endif + *tmp->curpos++ = 0; + + /* service type */ + PutUINT16(&tmp->curpos, srvtypelen); + memcpy(tmp->curpos, srvtype, srvtypelen); + tmp->curpos += srvtypelen; + + /* scope list */ + PutUINT16(&tmp->curpos, scopelistlen); + memcpy(tmp->curpos, scopelist, scopelistlen); + tmp->curpos += scopelistlen; + + /* attr list */ + PutUINT16(&tmp->curpos, attrlistlen); + memcpy(tmp->curpos, attrlist, attrlistlen); + tmp->curpos += attrlistlen; + + /* attribute auth block */ +#ifdef ENABLE_SLPv2_SECURITY + if (attrauth) + { + /* authcount */ + *tmp->curpos++ = 1; + + /* authblock */ + memcpy(tmp->curpos, attrauth, attrauthlen); + tmp->curpos += attrauthlen; + } + else +#endif + *tmp->curpos++ = 0; + + /* okay, now comes the really stupid (and lazy part) */ + *msg = SLPMessageAlloc(); + if (*msg == 0) + { + SLPBufferFree(*buf); + *buf = 0; + result = SLP_ERROR_INTERNAL_ERROR; + goto CLEANUP; + } + + /* this should be ok even if we are not supporting IPv4, + * since it's a static service + */ + memset(&peer, 0, sizeof(struct sockaddr_in)); + peer.ss_family = AF_UNSPEC; + ((struct sockaddr_in *)&peer)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + result = SLPMessageParseBuffer(&peer, &peer, *buf, *msg); + (*msg)->body.srvreg.source = source; + +CLEANUP: + +#ifdef ENABLE_SLPv2_SECURITY + xfree(urlauth); + xfree(attrauth); +#endif + + return result; +} + + + /** Read service registrations from a text file. * * A really big and nasty function that reads service registrations from - * from a file. Don't look at this too hard or you'll be sick. This is by - * far the most horrible code in OpenSLP. Please volunteer to rewrite it! - * - * "THANK GOODNESS this function is only called at startup" -- Matt + * from a file. * * @param[in] fd - The file to read from. + * @param[in] source - The registration type (SLP_REG_SOURCE_STATIC) * @param[out] msg - A message describing the SrvReg in buf. * @param[out] buf - The buffer used to hold @p message data. * @@ -126,16 +301,14 @@ static char * RegFileReadLine(FILE * fd, * @note Eventually the caller needs to call SLPBufferFree and * SLPMessageFree to free memory. */ -int SLPDRegFileReadSrvReg(FILE * fd, SLPMessage ** msg, SLPBuffer * buf) +int SLPDRegFileReadSrvReg(FILE * fd, int source, SLPMessage ** msg, SLPBuffer * buf) { char * slider1; char * slider2; char * p; char line[4096]; - struct sockaddr_storage peer; int result = 0; - size_t bufsize = 0; size_t langtaglen = 0; char * langtag = 0; size_t scopelistlen = 0; @@ -147,14 +320,8 @@ int SLPDRegFileReadSrvReg(FILE * fd, SLP char * srvtype = 0; size_t attrlistlen = 0; char * attrlist = 0; - SLPBuffer tmp; + char * peerip = 0; -#ifdef ENABLE_SLPv2_SECURITY - unsigned char * urlauth = 0; - int urlauthlen = 0; - unsigned char * attrauth = 0; - int attrauthlen = 0; -#endif int watchport = 0; int watchflags = 0; @@ -184,7 +351,7 @@ int SLPDRegFileReadSrvReg(FILE * fd, SLP goto CLEANUP; } /* replace "$HOSTNAME" string in url */ - while ((p = strchr(url, '$')) && !strncmp(p, "$HOSTNAME", 9)) + while (source == SLP_REG_SOURCE_STATIC && (p = strchr(url, '$')) && !strncmp(p, "$HOSTNAME", 9)) { char *_url = xmalloc(strlen(url) - 9 + G_SlpdProperty.myHostnameLen + 1); strncpy(_url, url, p - url); @@ -326,6 +493,15 @@ int SLPDRegFileReadSrvReg(FILE * fd, SLP } } } + else if(strncasecmp(slider1,"slp-source",10) == 0 && source != SLP_REG_SOURCE_STATIC) + { + slider2 = strchr(slider1,'='); + if(slider2) + { + slider2++; + peerip=xstrdup(TrimWhitespace(slider2)); + } + } else if(strncasecmp(slider1, "tcp-port", 8) == 0 || strncasecmp(slider1, "watch-port-tcp", 14) == 0) { slider2 = strchr(slider1,'='); @@ -383,7 +559,7 @@ int SLPDRegFileReadSrvReg(FILE * fd, SLP /* we need special case for keywords (why do we need these) they seem like a waste of code. Why not just use booleans */ - if (strchr(slider1, '=')) + if (strchr(slider1, '=') && source == SLP_REG_SOURCE_STATIC) { /* normal attribute (with '=') */ strcat(attrlist, "("); @@ -411,146 +587,37 @@ int SLPDRegFileReadSrvReg(FILE * fd, SLP scopelistlen = G_SlpdProperty.useScopesLen; } -#ifdef ENABLE_SLPv2_SECURITY - /* generate authentication blocks */ - if (G_SlpdProperty.securityEnabled) - { - SLPAuthSignUrl(G_SlpdSpiHandle, 0, 0, urllen, url, - &urlauthlen, &urlauth); - SLPAuthSignString(G_SlpdSpiHandle, 0, 0, attrlistlen, attrlist, - &attrauthlen, &attrauth); - } -#endif - - /* allocate buffer for the SrvReg Message */ - bufsize = 14 + langtaglen; /* 14 bytes for header */ - bufsize += urllen + 6; /* 1 byte for reserved */ - /* 2 bytes for lifetime */ - /* 2 bytes for urllen */ - /* 1 byte for authcount */ - bufsize += srvtypelen + 2; /* 2 bytes for len field */ - bufsize += scopelistlen + 2; /* 2 bytes for len field */ - bufsize += attrlistlen + 2; /* 2 bytes for len field */ - bufsize += 1; /* 1 byte for authcount */ - -#ifdef ENABLE_SLPv2_SECURITY - bufsize += urlauthlen; - bufsize += attrauthlen; -#endif + result = SLPDCreateSrvReg(source, urllen, url, langtaglen, langtag, srvtypelen, srvtype, + scopelistlen, scopelist, attrlistlen, attrlist, lifetime, msg, buf); - tmp = *buf = SLPBufferAlloc(bufsize); - if (tmp == 0) - { - result = SLP_ERROR_INTERNAL_ERROR; + if (result) goto CLEANUP; - } - - /* now build the SrvReg Message */ - - /* version */ - *tmp->curpos++ = 2; - - /* function id */ - *tmp->curpos++ = SLP_FUNCT_SRVREG; - - /* length */ - PutUINT24(&tmp->curpos, bufsize); - - /* flags */ - PutUINT16(&tmp->curpos, 0); - - /* ext offset */ - PutUINT24(&tmp->curpos, 0); - - /* xid */ - PutUINT16(&tmp->curpos, 0); - - /* lang tag len */ - PutUINT16(&tmp->curpos, langtaglen); - - /* lang tag */ - memcpy(tmp->curpos, langtag, langtaglen); - tmp->curpos += langtaglen; - - /* url-entry reserved */ - *tmp->curpos++ = 0; - - /* url-entry lifetime */ - PutUINT16(&tmp->curpos, lifetime); - - /* url-entry urllen */ - PutUINT16(&tmp->curpos, urllen); - - /* url-entry url */ - memcpy(tmp->curpos, url, urllen); - tmp->curpos += urllen; - - /* url-entry authblock */ -#ifdef ENABLE_SLPv2_SECURITY - if (urlauth) - { - /* authcount */ - *tmp->curpos++ = 1; - - /* authblock */ - memcpy(tmp->curpos, urlauth, urlauthlen); - tmp->curpos += urlauthlen; - } - else -#endif - *tmp->curpos++ = 0; - /* service type */ - PutUINT16(&tmp->curpos, srvtypelen); - memcpy(tmp->curpos, srvtype, srvtypelen); - tmp->curpos += srvtypelen; - - /* scope list */ - PutUINT16(&tmp->curpos, scopelistlen); - memcpy(tmp->curpos, scopelist, scopelistlen); - tmp->curpos += scopelistlen; - - /* attr list */ - PutUINT16(&tmp->curpos, attrlistlen); - memcpy(tmp->curpos, attrlist, attrlistlen); - tmp->curpos += attrlistlen; - - /* attribute auth block */ -#ifdef ENABLE_SLPv2_SECURITY - if (attrauth) + if (source == SLP_REG_SOURCE_STATIC) { - /* authcount */ - *tmp->curpos++ = 1; - - /* authblock */ - memcpy(tmp->curpos, attrauth, attrauthlen); - tmp->curpos += attrauthlen; + (*msg)->body.srvreg.watchflags = watchflags ? (watchflags | SLP_REG_WATCH_DEAD) : 0; + (*msg)->body.srvreg.watchport = watchport; } - else -#endif - *tmp->curpos++ = 0; - /* okay, now comes the really stupid (and lazy part) */ - *msg = SLPMessageAlloc(); - if (*msg == 0) + if (peerip && source != SLP_REG_SOURCE_STATIC) { - SLPBufferFree(*buf); - *buf = 0; - result = SLP_ERROR_INTERNAL_ERROR; - goto CLEANUP; + if (!strncmp(peerip, "pulled-from-da-", 15)) + { + int one = 1; + SLPIfaceStringToSockaddrs(peerip + 15, &(*msg)->peer, &one); + (*msg)->body.srvreg.source = SLP_REG_SOURCE_PULL_PEER_DA; + } + else if (!strcmp(peerip, "local")) + { + (*msg)->body.srvreg.source = SLP_REG_SOURCE_LOCAL; + } + else + { + int one = 1; + SLPIfaceStringToSockaddrs(peerip, &(*msg)->peer, &one); + } } - /* this should be ok even if we are not supporting IPv4, - * since it's a static service - */ - memset(&peer, 0, sizeof(struct sockaddr_in)); - peer.ss_family = AF_UNSPEC; - ((struct sockaddr_in *)&peer)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); - result = SLPMessageParseBuffer(&peer, &peer, *buf, *msg); - (*msg)->body.srvreg.source = SLP_REG_SOURCE_STATIC; - (*msg)->body.srvreg.watchflags = watchflags ? (watchflags | SLP_REG_WATCH_DEAD) : 0; - (*msg)->body.srvreg.watchport = watchport; - CLEANUP: /* check for errors and free memory */ @@ -578,12 +645,31 @@ CLEANUP: xfree(url); xfree(srvtype); xfree(attrlist); + xfree(peerip); -#ifdef ENABLE_SLPv2_SECURITY - xfree(urlauth); - xfree(attrauth); -#endif + return result; +} + +int SLPDRegFileWriteSrvReg(FILE * fd, SLPMessage * msg) +{ + int result = 0; + char addr_str[INET6_ADDRSTRLEN]; + if (fd) + { + fprintf(fd, "%s,%s,%d\n", msg->body.srvreg.urlentry.url, msg->header.langtag, msg->body.srvreg.urlentry.lifetime); + if (msg->body.srvreg.source == SLP_REG_SOURCE_PULL_PEER_DA) + fprintf(fd, "slp-source=pulled-from-da-%s\n", SLPNetSockAddrStorageToString(&msg->peer, addr_str, sizeof(addr_str))); + else if (msg->body.srvreg.source == SLP_REG_SOURCE_LOCAL) + fprintf(fd, "slp-source=local\n"); + else + fprintf(fd, "slp-source=%s\n", SLPNetSockAddrStorageToString(&msg->peer, addr_str, sizeof(addr_str))); + if (msg->body.srvreg.scopelistlen) + fprintf(fd, "scopes=%.*s\n", (int)msg->body.srvreg.scopelistlen, msg->body.srvreg.scopelist); + if(msg->body.srvreg.attrlistlen) + fprintf(fd, "%.*s\n", (int)msg->body.srvreg.attrlistlen, msg->body.srvreg.attrlist); + fprintf(fd, "\n"); + } return result; } --- ./slpd/slpd_regfile.h.orig 2012-11-28 17:07:04.000000000 +0000 +++ ./slpd/slpd_regfile.h 2016-09-13 10:56:14.221459531 +0000 @@ -53,7 +53,12 @@ #include "slp_message.h" #include "slpd.h" -int SLPDRegFileReadSrvReg(FILE * fd, SLPMessage ** msg, SLPBuffer * buf); +int SLPDCreateSrvReg(int source, int urllen, char * url, int langtaglen, char * langtag, + int srvtypelen, char * srvtype, int scopelistlen, char * scopelist, + int attrlistlen, char * attrlist, int lifetime, SLPMessage ** msg, SLPBuffer * buf); + +int SLPDRegFileReadSrvReg(FILE * fd, int source, SLPMessage ** msg, SLPBuffer * buf); +int SLPDRegFileWriteSrvReg(FILE * fd, SLPMessage * msg); /*! @} */
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor