Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15-SP2
clamav.13406
clamav-max-scantime.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File clamav-max-scantime.patch of Package clamav.13406
--- clamd/server-th.c.orig +++ clamd/server-th.c @@ -752,6 +752,19 @@ int recvloop_th(int *socketds, unsigned #endif /* set up limits */ + if ((opt = optget(opts, "MaxScanTime"))->active) { + if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, opt->numarg))) { + logg("!cl_engine_set_num(CL_ENGINE_MAX_SCANTIME) failed: %s\n", cl_strerror(ret)); + cl_engine_free(engine); + return 1; + } + } + val = cl_engine_get_num(engine, CL_ENGINE_MAX_SCANTIME, NULL); + if (val) + logg("Limits: Global time limit set to %llu milliseconds.\n", val); + else + logg("^Limits: Global time limit protection disabled.\n"); + if((opt = optget(opts, "MaxScanSize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, opt->numarg))) { logg("!cl_engine_set_num(CL_ENGINE_MAX_SCANSIZE) failed: %s\n", cl_strerror(ret)); --- clamscan/clamscan.c.orig +++ clamscan/clamscan.c @@ -275,6 +275,7 @@ void help(void) mprintf(" --nocerts Disable authenticode certificate chain verification in PE files\n"); mprintf(" --dumpcerts Dump authenticode certificate chain in PE files\n"); mprintf("\n"); + mprintf(" --max-scantime=#n Scan time longer than this will be skipped and assumed clean\n"); mprintf(" --max-filesize=#n Files larger than this will be skipped and assumed clean\n"); mprintf(" --max-scansize=#n The maximum amount of data to scan for each container file (**)\n"); mprintf(" --max-files=#n The maximum number of files to scan for each container file (**)\n"); --- clamscan/manager.c.orig +++ clamscan/manager.c @@ -870,6 +870,24 @@ int scanmanager(const struct optstruct * /* set limits */ + /* TODO: Remove deprecated option in a future feature release */ + if ((opt = optget(opts, "timelimit"))->active) { + if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, opt->numarg))) { + logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANTIME) failed: %s\n", cl_strerror(ret)); + + cl_engine_free(engine); + return 2; + } + } + if ((opt = optget(opts, "max-scantime"))->active) { + if ((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANTIME, opt->numarg))) { + logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANTIME) failed: %s\n", cl_strerror(ret)); + + cl_engine_free(engine); + return 2; + } + } + if((opt = optget(opts, "max-scansize"))->active) { if((ret = cl_engine_set_num(engine, CL_ENGINE_MAX_SCANSIZE, opt->numarg))) { logg("!cli_engine_set_num(CL_ENGINE_MAX_SCANSIZE) failed: %s\n", cl_strerror(ret)); @@ -988,15 +1006,6 @@ int scanmanager(const struct optstruct * cl_engine_free(engine); return 2; - } - } - - if ((opt = optget(opts, "timelimit"))->active) { - if ((ret = cl_engine_set_num(engine, CL_ENGINE_TIME_LIMIT, opt->numarg))) { - logg("!cli_engine_set_num(CL_ENGINE_TIME_LIMIT) failed: %s\n", cl_strerror(ret)); - - cl_engine_free(engine); - return 2; } } --- etc/clamd.conf.sample.orig +++ etc/clamd.conf.sample @@ -457,6 +457,16 @@ User vscan # The options below protect your system against Denial of Service attacks # using archive bombs. +# This option sets the maximum amount of time to a scan may take. +# In this version, this field only affects the scan time of ZIP archives. +# Value of 0 disables the limit +# Note: disabling this limit or setting it too high may result allow scanning +# of certain files to lock up the scanning process/threads results in a Denial +# of Service. +# Time is in milliseconds. +# Default: 120000 +#MaxScanTime 300000 + # This option sets the maximum amount of data to be scanned for each input # file. # Archives and other containers are recursively extracted and scanned up to --- libclamav/clamav.h.orig +++ libclamav/clamav.h @@ -245,7 +245,7 @@ enum cl_engine_field { CL_ENGINE_MAX_PARTITIONS, /* uint32_t */ CL_ENGINE_MAX_ICONSPE, /* uint32_t */ CL_ENGINE_MAX_RECHWP3, /* uint32_t */ - CL_ENGINE_TIME_LIMIT, /* uint32_t */ + CL_ENGINE_MAX_SCANTIME, /* uint32_t */ CL_ENGINE_PCRE_MATCH_LIMIT, /* uint64_t */ CL_ENGINE_PCRE_RECMATCH_LIMIT, /* uint64_t */ CL_ENGINE_PCRE_MAX_FILESIZE, /* uint64_t */ --- libclamav/default.h.orig +++ libclamav/default.h @@ -31,6 +31,7 @@ #define CLI_DEFAULT_BM_OFFMODE_FSIZE 262144 +#define CLI_DEFAULT_MAXSCANTIME 120000 #define CLI_DEFAULT_MAXSCANSIZE 104857600 #define CLI_DEFAULT_MAXFILESIZE 26214400 #define CLI_DEFAULT_MAXRECLEVEL 16 --- libclamav/matcher-pcre.c.orig +++ libclamav/matcher-pcre.c @@ -686,6 +686,12 @@ int cli_pcre_scanbuf(const unsigned char /* if the global flag is set, loop through the scanning */ do { + if (cli_checktimelimit(ctx) != CL_SUCCESS) { + cli_dbgmsg("cli_unzip: Time limit reached (max: %u)\n", ctx->engine->maxscantime); + ret = CL_ETIMEOUT; + break; + } + /* reset the match results */ if ((ret = cli_pcre_results_reset(&p_res, pd)) != CL_SUCCESS) break; --- libclamav/others.c.orig +++ libclamav/others.c @@ -258,7 +258,7 @@ const char *cl_strerror(int clerror) case CL_EMEM: return "Can't allocate memory"; case CL_ETIMEOUT: - return "Time limit reached"; + return "CL_ETIMEOUT: Time limit reached"; /* internal (needed for debug messages) */ case CL_EMAXREC: return "CL_EMAXREC"; @@ -328,6 +328,7 @@ struct cl_engine *cl_engine_new(void) } /* Setup default limits */ + new->maxscantime = CLI_DEFAULT_MAXSCANTIME; new->maxscansize = CLI_DEFAULT_MAXSCANSIZE; new->maxfilesize = CLI_DEFAULT_MAXFILESIZE; new->maxreclevel = CLI_DEFAULT_MAXRECLEVEL; @@ -620,8 +621,8 @@ int cl_engine_set_num(struct cl_engine * case CL_ENGINE_MAX_RECHWP3: engine->maxrechwp3 = (uint32_t)num; break; - case CL_ENGINE_TIME_LIMIT: - engine->time_limit = (uint32_t)num; + case CL_ENGINE_MAX_SCANTIME: + engine->maxscantime = (uint32_t)num; break; case CL_ENGINE_PCRE_MATCH_LIMIT: engine->pcre_match_limit = (uint64_t)num; @@ -721,8 +722,8 @@ long long cl_engine_get_num(const struct return engine->maxiconspe; case CL_ENGINE_MAX_RECHWP3: return engine->maxrechwp3; - case CL_ENGINE_TIME_LIMIT: - return engine->time_limit; + case CL_ENGINE_MAX_SCANTIME: + return engine->maxscantime; case CL_ENGINE_PCRE_MATCH_LIMIT: return engine->pcre_match_limit; case CL_ENGINE_PCRE_RECMATCH_LIMIT: @@ -802,6 +803,7 @@ struct cl_settings *cl_engine_settings_c settings->ac_maxdepth = engine->ac_maxdepth; settings->tmpdir = engine->tmpdir ? strdup(engine->tmpdir) : NULL; settings->keeptmp = engine->keeptmp; + settings->maxscantime = engine->maxscantime; settings->maxscansize = engine->maxscansize; settings->maxfilesize = engine->maxfilesize; settings->maxreclevel = engine->maxreclevel; @@ -856,6 +858,7 @@ int cl_engine_settings_apply(struct cl_e engine->ac_mindepth = settings->ac_mindepth; engine->ac_maxdepth = settings->ac_maxdepth; engine->keeptmp = settings->keeptmp; + engine->maxscantime = settings->maxscantime; engine->maxscansize = settings->maxscansize; engine->maxfilesize = settings->maxfilesize; engine->maxreclevel = settings->maxreclevel; @@ -944,8 +947,8 @@ void cli_check_blockmax(cli_ctx *ctx, in } } -int cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, unsigned long need2, unsigned long need3) { - int ret = CL_SUCCESS; +cl_error_t cli_checklimits(const char *who, cli_ctx *ctx, unsigned long need1, unsigned long need2, unsigned long need3) { + cl_error_t ret = CL_SUCCESS; unsigned long needed; /* if called without limits, go on, unpack, scan */ @@ -954,6 +957,9 @@ int cli_checklimits(const char *who, cli needed = (need1>need2)?need1:need2; needed = (needed>need3)?needed:need3; + /* Enforce timelimit */ + ret = cli_checktimelimit(ctx); + /* if we have global scan limits */ if(needed && ctx->engine->maxscansize) { /* if the remaining scansize is too small... */ @@ -982,8 +988,8 @@ int cli_checklimits(const char *who, cli return ret; } -int cli_updatelimits(cli_ctx *ctx, unsigned long needed) { - int ret=cli_checklimits("cli_updatelimits", ctx, needed, 0, 0); +cl_error_t cli_updatelimits(cli_ctx *ctx, unsigned long needed) { + cl_error_t ret = cli_checklimits("cli_updatelimits", ctx, needed, 0, 0); if (ret != CL_CLEAN) return ret; ctx->scannedfiles++; @@ -993,18 +999,33 @@ int cli_updatelimits(cli_ctx *ctx, unsig return CL_CLEAN; } -int cli_checktimelimit(cli_ctx *ctx) +/** + * @brief Check if we've exceeded the time limit. + * If ctx is NULL, there can be no timelimit so just return success. + * + * @param ctx The scanning context. + * @return cl_error_t CL_SUCCESS if has not exceeded, CL_ETIMEOUT if has exceeded. + */ +cl_error_t cli_checktimelimit(cli_ctx *ctx) { + cl_error_t ret = CL_SUCCESS; + + if (NULL == ctx) { + goto done; + } + if (ctx->time_limit.tv_sec != 0) { struct timeval now; if (gettimeofday(&now, NULL) == 0) { - if (now.tv_sec < ctx->time_limit.tv_sec) - return CL_SUCCESS; - if (now.tv_sec > ctx->time_limit.tv_sec || now.tv_usec > ctx->time_limit.tv_usec) - return CL_ETIMEOUT; + if (now.tv_sec > ctx->time_limit.tv_sec) + ret = CL_ETIMEOUT; + else if (now.tv_sec == ctx->time_limit.tv_sec && now.tv_usec > ctx->time_limit.tv_usec) + ret = CL_ETIMEOUT; } } - return CL_SUCCESS; + +done: + return ret; } /* @@ -1094,7 +1115,7 @@ void cli_virus_found_cb(cli_ctx * ctx) ctx->engine->cb_virus_found(fmap_fd(*ctx->fmap), (const char *)*ctx->virname, ctx->cb_ctx); } -int cli_append_possibly_unwanted(cli_ctx * ctx, const char * virname) +cl_error_t cli_append_possibly_unwanted(cli_ctx * ctx, const char * virname) { if (SCAN_ALL) return cli_append_virus(ctx, virname); --- libclamav/others.h.orig +++ libclamav/others.h @@ -286,6 +286,7 @@ struct cl_engine { uint64_t engine_options; /* Limits */ + uint32_t maxscantime; /* Time limit (in milliseconds) */ uint64_t maxscansize; /* during the scanning of archives this size * will never be exceeded */ @@ -405,9 +406,6 @@ struct cl_engine { uint32_t maxiconspe; /* max number of icons to scan for PE */ uint32_t maxrechwp3; /* max recursive calls for HWP3 parsing */ - /* millisecond time limit for preclassification scanning */ - uint32_t time_limit; - /* PCRE matching limitations */ uint64_t pcre_match_limit; uint64_t pcre_recmatch_limit; @@ -429,6 +427,7 @@ struct cl_settings { uint32_t ac_maxdepth; char *tmpdir; uint32_t keeptmp; + uint32_t maxscantime; uint64_t maxscansize; uint64_t maxfilesize; uint32_t maxreclevel; @@ -736,21 +735,20 @@ char *cli_gentemp(const char *dir); int cli_gentempfd(const char *dir, char **name, int *fd); unsigned int cli_rndnum(unsigned int max); int cli_filecopy(const char *src, const char *dest); -int cli_mapscan(fmap_t *map, off_t offset, size_t size, cli_ctx *ctx, cli_file_t type); bitset_t *cli_bitset_init(void); void cli_bitset_free(bitset_t *bs); int cli_bitset_set(bitset_t *bs, unsigned long bit_offset); int cli_bitset_test(bitset_t *bs, unsigned long bit_offset); const char* cli_ctime(const time_t *timep, char *buf, const size_t bufsize); void cli_check_blockmax(cli_ctx *, int); -int cli_checklimits(const char *, cli_ctx *, unsigned long, unsigned long, unsigned long); -int cli_updatelimits(cli_ctx *, unsigned long); +cl_error_t cli_checklimits(const char *, cli_ctx *, unsigned long, unsigned long, unsigned long); +cl_error_t cli_updatelimits(cli_ctx *, unsigned long); unsigned long cli_getsizelimit(cli_ctx *, unsigned long); int cli_matchregex(const char *str, const char *regex); void cli_qsort(void *a, size_t n, size_t es, int (*cmp)(const void *, const void *)); void cli_qsort_r(void *a, size_t n, size_t es, int (*cmp)(const void*, const void *, const void *), void *arg); -int cli_checktimelimit(cli_ctx *ctx); -int cli_append_possibly_unwanted(cli_ctx * ctx, const char * virname); +cl_error_t cli_checktimelimit(cli_ctx *ctx); +cl_error_t cli_append_possibly_unwanted(cli_ctx * ctx, const char * virname); /* symlink behaviour */ #define CLI_FTW_FOLLOW_FILE_SYMLINK 0x01 --- libclamav/scanners.c.orig +++ libclamav/scanners.c @@ -3496,7 +3496,6 @@ static int magic_scandesc(cli_ctx *ctx, case CL_ETMPFILE: case CL_ETMPDIR: case CL_EMEM: - case CL_ETIMEOUT: cli_dbgmsg("Descriptor[%d]: cli_scanraw error %s\n", fmap_fd(*ctx->fmap), cl_strerror(res)); cli_bitset_free(ctx->hook_lsig_matches); ctx->hook_lsig_matches = old_hook_lsig_matches; @@ -3509,7 +3508,15 @@ static int magic_scandesc(cli_ctx *ctx, cli_bitset_free(ctx->hook_lsig_matches); ctx->hook_lsig_matches = old_hook_lsig_matches; return magic_scandesc_cleanup(ctx, type, hash, hashed_size, cache_clean, ret, parent_property); - /* "MAX" conditions should still fully scan the current file */ + /* The CL_ETIMEOUT "MAX" conditions should set exceeds max flag and exit out quietly. */ + case CL_ETIMEOUT: + cli_check_blockmax(ctx, ret); + cli_bitset_free(ctx->hook_lsig_matches); + ctx->hook_lsig_matches = old_hook_lsig_matches; + cli_dbgmsg("Descriptor[%d]: Stopping after cli_scanraw reached %s\n", + fmap_fd(*ctx->fmap), cl_strerror(res)); + return magic_scandesc_cleanup(ctx, type, hash, hashed_size, cache_clean, CL_CLEAN, parent_property); + /* All other "MAX" conditions should still fully scan the current file */ case CL_EMAXREC: case CL_EMAXSIZE: case CL_EMAXFILES: @@ -3573,14 +3580,16 @@ static int magic_scandesc(cli_ctx *ctx, switch (ret) { - /* Malformed file cases */ - case CL_EFORMAT: - case CL_EREAD: - case CL_EUNPACK: /* Limits exceeded */ + case CL_ETIMEOUT: case CL_EMAXREC: case CL_EMAXSIZE: case CL_EMAXFILES: + cli_check_blockmax(ctx, ret); + /* Malformed file cases */ + case CL_EFORMAT: + case CL_EREAD: + case CL_EUNPACK: cli_dbgmsg("Descriptor[%d]: %s\n", fmap_fd(*ctx->fmap), cl_strerror(ret)); #if HAVE_JSON ctx->wrkproperty = parent_property; @@ -3862,12 +3871,12 @@ static int scan_common(int desc, cl_fmap } perf_init(&ctx); - if (ctx.options & CL_SCAN_FILE_PROPERTIES && ctx.engine->time_limit != 0) + if (ctx.engine->maxscantime != 0) { if (gettimeofday(&ctx.time_limit, NULL) == 0) { - uint32_t secs = ctx.engine->time_limit / 1000; - uint32_t usecs = (ctx.engine->time_limit % 1000) * 1000; + uint32_t secs = ctx.engine->maxscantime / 1000; + uint32_t usecs = (ctx.engine->maxscantime % 1000) * 1000; ctx.time_limit.tv_sec += secs; ctx.time_limit.tv_usec += usecs; if (ctx.time_limit.tv_usec >= 1000000) @@ -3879,7 +3888,7 @@ static int scan_common(int desc, cl_fmap else { char buf[64]; - cli_dbgmsg("scan_common; gettimeofday error: %s\n", cli_strerror(errno, buf, 64)); + cli_dbgmsg("scan_common: gettimeofday error: %s\n", cli_strerror(errno, buf, 64)); } } --- libclamav/unzip.c.orig +++ libclamav/unzip.c @@ -776,6 +776,11 @@ int cli_unzip(cli_ctx *ctx) { cli_dbgmsg("cli_unzip: Files limit reached (max: %u)\n", ctx->engine->maxfiles); ret=CL_EMAXFILES; } + + if (cli_checktimelimit(ctx) != CL_SUCCESS) { + cli_dbgmsg("cli_unzip: Time limit reached (max: %u)\n", ctx->engine->maxscantime); + ret = CL_ETIMEOUT; + } /* * Detect overlapping files and zip bombs. */ --- shared/optparser.c.orig +++ shared/optparser.c @@ -362,6 +362,8 @@ const struct clam_option __clam_options[ { "ForceToDisk", "force-to-disk", 0, CLOPT_TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option causes memory or nested map scans to dump the content to disk.\nIf you turn on this option, more data is written to disk and is available\nwhen the leave-temps option is enabled at the cost of more disk writes.", "no" }, + { "MaxScanTime", "max-scantime", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, 0, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum amount of time a scan may take to complete.\nIn this version, this field only affects the scan time of ZIP archives.\nThe value of 0 disables the limit.\nWARNING: disabling this limit or setting it too high may result allow scanning\nof certain files to lock up the scanning process/threads resulting in a Denial of Service.\nThe value is in milliseconds.", "120000"}, + { "MaxScanSize", "max-scansize", 0, CLOPT_TYPE_SIZE, MATCH_SIZE, CLI_DEFAULT_MAXSCANSIZE, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum amount of data to be scanned for each input file.\nArchives and other containers are recursively extracted and scanned up to this\nvalue.\nThe value of 0 disables the limit.\nWARNING: disabling this limit or setting it too high may result in severe\ndamage.", "100M" }, { "MaxFileSize", "max-filesize", 0, CLOPT_TYPE_SIZE, MATCH_SIZE, CLI_DEFAULT_MAXFILESIZE, NULL, 0, OPT_CLAMD | OPT_MILTER | OPT_CLAMSCAN, "Files/messages larger than this limit won't be scanned. Affects the input\nfile itself as well as files contained inside it (when the input file is\nan archive, a document or some other kind of container).\nThe value of 0 disables the limit.\nWARNING: disabling this limit or setting it too high may result in severe\ndamage to the system.", "25M" }, @@ -387,8 +389,6 @@ const struct clam_option __clam_options[ { "MaxRecHWP3", "max-rechwp3", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, CLI_DEFAULT_MAXRECHWP3, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum recursive calls to HWP3 parsing function.\nHWP3 files using more than this limit will be terminated and alert the user.\nScans will be unable to scan any HWP3 attachments if the recursive limit is reached.\nNegative values are not allowed.\nWARNING: setting this limit too high may result in severe damage or impact performance.", "16" }, - { "TimeLimit", "timelimit", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, 0, NULL, 0, OPT_CLAMSCAN, "This clamscan option is currently for testing only. It sets the engine parameter CL_ENGINE_TIME_LIMIT. The value is in milliseconds.", "0" }, - { "PCREMatchLimit", "pcre-match-limit", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, CLI_DEFAULT_PCRE_MATCH_LIMIT, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum calls to the PCRE match function during an instance of regex matching.\nInstances using more than this limit will be terminated and alert the user but the scan will continue.\nFor more information on match_limit, see the PCRE documentation.\nNegative values are not allowed.\nWARNING: setting this limit too high may severely impact performance.", "100000" }, { "PCRERecMatchLimit", "pcre-recmatch-limit", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, CLI_DEFAULT_PCRE_RECMATCH_LIMIT, NULL, 0, OPT_CLAMD | OPT_CLAMSCAN, "This option sets the maximum recursive calls to the PCRE match function during an instance of regex matching.\nInstances using more than this limit will be terminated and alert the user but the scan will continue.\nFor more information on match_limit_recursion, see the PCRE documentation.\nNegative values are not allowed and values > PCREMatchLimit are superfluous.\nWARNING: setting this limit too high may severely impact performance.", "5000" }, @@ -487,6 +487,7 @@ const struct clam_option __clam_options[ /* Deprecated options */ + { "TimeLimit", "timelimit", 0, CLOPT_TYPE_NUMBER, MATCH_NUMBER, 0, NULL, 0, OPT_CLAMSCAN | OPT_DEPRECATED, "Deprecated option to set the max-scantime.\nThe value is in milliseconds.", "120000" }, { "MailMaxRecursion", NULL, 0, CLOPT_TYPE_NUMBER, NULL, -1, NULL, 0, OPT_CLAMD | OPT_DEPRECATED, "", "" }, { "ArchiveMaxScanSize", NULL, 0, CLOPT_TYPE_SIZE, NULL, -1, NULL, 0, OPT_CLAMD | OPT_DEPRECATED, "", "" }, { "ArchiveMaxRecursion", NULL, 0, CLOPT_TYPE_NUMBER, NULL, -1, NULL, 0, OPT_CLAMD | OPT_DEPRECATED, "", "" }, --- win32/conf_examples/clamd.conf.sample.orig +++ win32/conf_examples/clamd.conf.sample @@ -432,6 +432,16 @@ TCPAddr 127.0.0.1 # The options below protect your system against Denial of Service attacks # using archive bombs. +# This option sets the maximum amount of time to a scan may take. +# In this version, this field only affects the scan time of ZIP archives. +# Value of 0 disables the limit +# Note: disabling this limit or setting it too high may result allow scanning +# of certain files to lock up the scanning process/threads resulting in a Denial +# of Service. +# Time is in milliseconds. +# Default: 120000 +#MaxScanTime 300000 + # This option sets the maximum amount of data to be scanned for each input file. # Archives and other containers are recursively extracted and scanned up to this # value.
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