Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE
openslp.13167
openslp.initda.diff
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File openslp.initda.diff of Package openslp.13167
--- ./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