Annotation of embedaddon/lighttpd/src/mod_secdownload.c, revision 1.1

1.1     ! misho       1: #include "first.h"
        !             2: 
        !             3: #include "base.h"
        !             4: #include "log.h"
        !             5: #include "buffer.h"
        !             6: #include "base64.h"
        !             7: 
        !             8: #include "plugin.h"
        !             9: 
        !            10: #include <ctype.h>
        !            11: #include <stdlib.h>
        !            12: #include <string.h>
        !            13: 
        !            14: #if defined(USE_OPENSSL)
        !            15: #include <openssl/evp.h>
        !            16: #include <openssl/hmac.h>
        !            17: #endif
        !            18: 
        !            19: #include "md5.h"
        !            20: 
        !            21: #define HASHLEN 16
        !            22: typedef unsigned char HASH[HASHLEN];
        !            23: #define HASHHEXLEN 32
        !            24: typedef char HASHHEX[HASHHEXLEN+1];
        !            25: 
        !            26: /*
        !            27:  * mod_secdownload verifies a checksum associated with a timestamp
        !            28:  * and a path.
        !            29:  *
        !            30:  * It takes an URL of the form:
        !            31:  *   securl := <uri-prefix> <mac> <protected-path>
        !            32:  *   uri-prefix := '/' any*         # whatever was configured: must start with a '/')
        !            33:  *   mac := [a-zA-Z0-9_-]{mac_len}  # mac length depends on selected algorithm
        !            34:  *   protected-path := '/' <timestamp> <rel-path>
        !            35:  *   timestamp := [a-f0-9]{8}       # timestamp when the checksum was calculated
        !            36:  *                                  # to prevent access after timeout (active requests
        !            37:  *                                  # will finish successfully even after the timeout)
        !            38:  *   rel-path := '/' any*           # the protected path; changing the path breaks the
        !            39:  *                                  # checksum
        !            40:  *
        !            41:  * The timestamp is the `epoch` timestamp in hex, i.e. time in seconds
        !            42:  * since 00:00:00 UTC on 1 January 1970.
        !            43:  *
        !            44:  * mod_secdownload supports various MAC algorithms:
        !            45:  *
        !            46:  * # md5
        !            47:  * mac_len := 32 (and hex only)
        !            48:  * mac := md5-hex(<secrect><rel-path><timestamp>)   # lowercase hex
        !            49:  * perl example:
        !            50:     use Digest::MD5 qw(md5_hex);
        !            51:     my $secret = "verysecret";
        !            52:     my $rel_path = "/index.html"
        !            53:     my $xtime = sprintf("%08x", time);
        !            54:     my $url = '/'. md5_hex($secret . $rel_path . $xtime) . '/' . $xtime . $rel_path;
        !            55:  *
        !            56:  * # hmac-sha1
        !            57:  * mac_len := 27  (no base64 padding)
        !            58:  * mac := base64-url(hmac-sha1(<secret>, <protected-path>))
        !            59:  * perl example:
        !            60:     use Digest::SHA qw(hmac_sha1);
        !            61:     use MIME::Base64 qw(encode_base64url);
        !            62:     my $secret = "verysecret";
        !            63:     my $rel_path = "/index.html"
        !            64:     my $protected_path = '/' . sprintf("%08x", time) . $rel_path;
        !            65:     my $url = '/'. encode_base64url(hmac_sha1($protected_path, $secret)) . $protected_path;
        !            66:  *
        !            67:  * # hmac-256
        !            68:  * mac_len := 43  (no base64 padding)
        !            69:  * mac := base64-url(hmac-256(<secret>, <protected-path>))
        !            70:     use Digest::SHA qw(hmac_sha256);
        !            71:     use MIME::Base64 qw(encode_base64url);
        !            72:     my $secret = "verysecret";
        !            73:     my $rel_path = "/index.html"
        !            74:     my $protected_path = '/' . sprintf("%08x", time) . $rel_path;
        !            75:     my $url = '/'. encode_base64url(hmac_sha256($protected_path, $secret)) . $protected_path;
        !            76:  *
        !            77:  */
        !            78: 
        !            79: /* plugin config for all request/connections */
        !            80: 
        !            81: typedef enum {
        !            82:        SECDL_INVALID = 0,
        !            83:        SECDL_MD5 = 1,
        !            84:        SECDL_HMAC_SHA1 = 2,
        !            85:        SECDL_HMAC_SHA256 = 3,
        !            86: } secdl_algorithm;
        !            87: 
        !            88: typedef struct {
        !            89:        buffer *doc_root;
        !            90:        buffer *secret;
        !            91:        buffer *uri_prefix;
        !            92:        secdl_algorithm algorithm;
        !            93: 
        !            94:        unsigned int timeout;
        !            95: } plugin_config;
        !            96: 
        !            97: typedef struct {
        !            98:        PLUGIN_DATA;
        !            99: 
        !           100:        plugin_config **config_storage;
        !           101: 
        !           102:        plugin_config conf;
        !           103: } plugin_data;
        !           104: 
        !           105: static int const_time_memeq(const char *a, const char *b, size_t len) {
        !           106:        /* constant time memory compare, unless the compiler figures it out */
        !           107:        char diff = 0;
        !           108:        size_t i;
        !           109:        for (i = 0; i < len; ++i) {
        !           110:                diff |= (a[i] ^ b[i]);
        !           111:        }
        !           112:        return 0 == diff;
        !           113: }
        !           114: 
        !           115: static const char* secdl_algorithm_names[] = {
        !           116:        "invalid",
        !           117:        "md5",
        !           118:        "hmac-sha1",
        !           119:        "hmac-sha256",
        !           120: };
        !           121: 
        !           122: static secdl_algorithm algorithm_from_string(buffer *name) {
        !           123:        size_t ndx;
        !           124: 
        !           125:        if (buffer_string_is_empty(name)) return SECDL_INVALID;
        !           126: 
        !           127:        for (ndx = 1; ndx < sizeof(secdl_algorithm_names)/sizeof(secdl_algorithm_names[0]); ++ndx) {
        !           128:                if (0 == strcmp(secdl_algorithm_names[ndx], name->ptr)) return (secdl_algorithm)ndx;
        !           129:        }
        !           130: 
        !           131:        return SECDL_INVALID;
        !           132: }
        !           133: 
        !           134: static size_t secdl_algorithm_mac_length(secdl_algorithm alg) {
        !           135:        switch (alg) {
        !           136:        case SECDL_INVALID:
        !           137:                break;
        !           138:        case SECDL_MD5:
        !           139:                return 32;
        !           140:        case SECDL_HMAC_SHA1:
        !           141:                return 27;
        !           142:        case SECDL_HMAC_SHA256:
        !           143:                return 43;
        !           144:        }
        !           145:        return 0;
        !           146: }
        !           147: 
        !           148: static int secdl_verify_mac(server *srv, plugin_config *config, const char* protected_path, const char* mac, size_t maclen) {
        !           149:        UNUSED(srv);
        !           150:        if (0 == maclen || secdl_algorithm_mac_length(config->algorithm) != maclen) return 0;
        !           151: 
        !           152:        switch (config->algorithm) {
        !           153:        case SECDL_INVALID:
        !           154:                break;
        !           155:        case SECDL_MD5:
        !           156:                {
        !           157:                        li_MD5_CTX Md5Ctx;
        !           158:                        HASH HA1;
        !           159:                        char hexmd5[33];
        !           160:                        const char *ts_str;
        !           161:                        const char *rel_uri;
        !           162: 
        !           163:                        /* legacy message:
        !           164:                         *   protected_path := '/' <timestamp-hex> <rel-path>
        !           165:                         *   timestamp-hex := [0-9a-f]{8}
        !           166:                         *   rel-path := '/' any*
        !           167:                         *   (the protected path was already verified)
        !           168:                         * message = <secret><rel-path><timestamp-hex>
        !           169:                         */
        !           170:                        ts_str = protected_path + 1;
        !           171:                        rel_uri = ts_str + 8;
        !           172: 
        !           173:                        li_MD5_Init(&Md5Ctx);
        !           174:                        li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(config->secret));
        !           175:                        li_MD5_Update(&Md5Ctx, rel_uri, strlen(rel_uri));
        !           176:                        li_MD5_Update(&Md5Ctx, ts_str, 8);
        !           177:                        li_MD5_Final(HA1, &Md5Ctx);
        !           178: 
        !           179:                        li_tohex(hexmd5, sizeof(hexmd5), (const char *)HA1, 16);
        !           180: 
        !           181:                        return (32 == maclen) && const_time_memeq(mac, hexmd5, 32);
        !           182:                }
        !           183:        case SECDL_HMAC_SHA1:
        !           184: #if defined(USE_OPENSSL)
        !           185:                {
        !           186:                        unsigned char digest[20];
        !           187:                        char base64_digest[27];
        !           188: 
        !           189:                        if (NULL == HMAC(
        !           190:                                        EVP_sha1(),
        !           191:                                        (unsigned char const*) CONST_BUF_LEN(config->secret),
        !           192:                                        (unsigned char const*) protected_path, strlen(protected_path),
        !           193:                                        digest, NULL)) {
        !           194:                                log_error_write(srv, __FILE__, __LINE__, "s",
        !           195:                                        "hmac-sha1: HMAC() failed");
        !           196:                                return 0;
        !           197:                        }
        !           198: 
        !           199:                        li_to_base64_no_padding(base64_digest, 27, digest, 20, BASE64_URL);
        !           200: 
        !           201:                        return (27 == maclen) && const_time_memeq(mac, base64_digest, 27);
        !           202:                }
        !           203: #endif
        !           204:                break;
        !           205:        case SECDL_HMAC_SHA256:
        !           206: #if defined(USE_OPENSSL)
        !           207:                {
        !           208:                        unsigned char digest[32];
        !           209:                        char base64_digest[43];
        !           210: 
        !           211:                        if (NULL == HMAC(
        !           212:                                        EVP_sha256(),
        !           213:                                        (unsigned char const*) CONST_BUF_LEN(config->secret),
        !           214:                                        (unsigned char const*) protected_path, strlen(protected_path),
        !           215:                                        digest, NULL)) {
        !           216:                                log_error_write(srv, __FILE__, __LINE__, "s",
        !           217:                                        "hmac-sha256: HMAC() failed");
        !           218:                                return 0;
        !           219:                        }
        !           220: 
        !           221:                        li_to_base64_no_padding(base64_digest, 43, digest, 32, BASE64_URL);
        !           222: 
        !           223:                        return (43 == maclen) && const_time_memeq(mac, base64_digest, 43);
        !           224:                }
        !           225: #endif
        !           226:                break;
        !           227:        }
        !           228: 
        !           229:        return 0;
        !           230: }
        !           231: 
        !           232: /* init the plugin data */
        !           233: INIT_FUNC(mod_secdownload_init) {
        !           234:        plugin_data *p;
        !           235: 
        !           236:        p = calloc(1, sizeof(*p));
        !           237: 
        !           238:        return p;
        !           239: }
        !           240: 
        !           241: /* detroy the plugin data */
        !           242: FREE_FUNC(mod_secdownload_free) {
        !           243:        plugin_data *p = p_d;
        !           244:        UNUSED(srv);
        !           245: 
        !           246:        if (!p) return HANDLER_GO_ON;
        !           247: 
        !           248:        if (p->config_storage) {
        !           249:                size_t i;
        !           250:                for (i = 0; i < srv->config_context->used; i++) {
        !           251:                        plugin_config *s = p->config_storage[i];
        !           252: 
        !           253:                        if (NULL == s) continue;
        !           254: 
        !           255:                        buffer_free(s->secret);
        !           256:                        buffer_free(s->doc_root);
        !           257:                        buffer_free(s->uri_prefix);
        !           258: 
        !           259:                        free(s);
        !           260:                }
        !           261:                free(p->config_storage);
        !           262:        }
        !           263: 
        !           264:        free(p);
        !           265: 
        !           266:        return HANDLER_GO_ON;
        !           267: }
        !           268: 
        !           269: /* handle plugin config and check values */
        !           270: 
        !           271: SETDEFAULTS_FUNC(mod_secdownload_set_defaults) {
        !           272:        plugin_data *p = p_d;
        !           273:        size_t i = 0;
        !           274: 
        !           275:        config_values_t cv[] = {
        !           276:                { "secdownload.secret",        NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
        !           277:                { "secdownload.document-root", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
        !           278:                { "secdownload.uri-prefix",    NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
        !           279:                { "secdownload.timeout",       NULL, T_CONFIG_INT,    T_CONFIG_SCOPE_CONNECTION }, /* 3 */
        !           280:                { "secdownload.algorithm",     NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
        !           281:                { NULL,                        NULL, T_CONFIG_UNSET,  T_CONFIG_SCOPE_UNSET      }
        !           282:        };
        !           283: 
        !           284:        if (!p) return HANDLER_ERROR;
        !           285: 
        !           286:        p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
        !           287: 
        !           288:        for (i = 0; i < srv->config_context->used; i++) {
        !           289:                data_config const* config = (data_config const*)srv->config_context->data[i];
        !           290:                plugin_config *s;
        !           291:                buffer *algorithm = buffer_init();
        !           292: 
        !           293:                s = calloc(1, sizeof(plugin_config));
        !           294:                s->secret        = buffer_init();
        !           295:                s->doc_root      = buffer_init();
        !           296:                s->uri_prefix    = buffer_init();
        !           297:                s->timeout       = 60;
        !           298: 
        !           299:                cv[0].destination = s->secret;
        !           300:                cv[1].destination = s->doc_root;
        !           301:                cv[2].destination = s->uri_prefix;
        !           302:                cv[3].destination = &(s->timeout);
        !           303:                cv[4].destination = algorithm;
        !           304: 
        !           305:                p->config_storage[i] = s;
        !           306: 
        !           307:                if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
        !           308:                        buffer_free(algorithm);
        !           309:                        return HANDLER_ERROR;
        !           310:                }
        !           311: 
        !           312:                if (!buffer_is_empty(algorithm)) {
        !           313:                        s->algorithm = algorithm_from_string(algorithm);
        !           314:                        switch (s->algorithm) {
        !           315:                        case SECDL_INVALID:
        !           316:                                log_error_write(srv, __FILE__, __LINE__, "sb",
        !           317:                                        "invalid secdownload.algorithm:",
        !           318:                                        algorithm);
        !           319:                                buffer_free(algorithm);
        !           320:                                return HANDLER_ERROR;
        !           321: #if !defined(USE_OPENSSL)
        !           322:                        case SECDL_HMAC_SHA1:
        !           323:                        case SECDL_HMAC_SHA256:
        !           324:                                log_error_write(srv, __FILE__, __LINE__, "sb",
        !           325:                                        "unsupported secdownload.algorithm:",
        !           326:                                        algorithm);
        !           327:                                buffer_free(algorithm);
        !           328:                                return HANDLER_ERROR;
        !           329: #endif
        !           330:                        default:
        !           331:                                break;
        !           332:                        }
        !           333:                }
        !           334: 
        !           335:                buffer_free(algorithm);
        !           336:        }
        !           337: 
        !           338:        return HANDLER_GO_ON;
        !           339: }
        !           340: 
        !           341: /**
        !           342:  * checks if the supplied string is a hex string
        !           343:  *
        !           344:  * @param str a possible hex string
        !           345:  * @return if the supplied string is a valid hex string 1 is returned otherwise 0
        !           346:  */
        !           347: 
        !           348: static int is_hex_len(const char *str, size_t len) {
        !           349:        size_t i;
        !           350: 
        !           351:        if (NULL == str) return 0;
        !           352: 
        !           353:        for (i = 0; i < len && *str; i++, str++) {
        !           354:                /* illegal characters */
        !           355:                if (!((*str >= '0' && *str <= '9') ||
        !           356:                      (*str >= 'a' && *str <= 'f') ||
        !           357:                      (*str >= 'A' && *str <= 'F'))
        !           358:                    ) {
        !           359:                        return 0;
        !           360:                }
        !           361:        }
        !           362: 
        !           363:        return i == len;
        !           364: }
        !           365: 
        !           366: /**
        !           367:  * checks if the supplied string is a base64 (modified URL) string
        !           368:  *
        !           369:  * @param str a possible base64 (modified URL) string
        !           370:  * @return if the supplied string is a valid base64 (modified URL) string 1 is returned otherwise 0
        !           371:  */
        !           372: 
        !           373: static int is_base64_len(const char *str, size_t len) {
        !           374:        size_t i;
        !           375: 
        !           376:        if (NULL == str) return 0;
        !           377: 
        !           378:        for (i = 0; i < len && *str; i++, str++) {
        !           379:                /* illegal characters */
        !           380:                if (!((*str >= '0' && *str <= '9') ||
        !           381:                      (*str >= 'a' && *str <= 'z') ||
        !           382:                      (*str >= 'A' && *str <= 'Z') ||
        !           383:                      (*str == '-') || (*str == '_'))
        !           384:                    ) {
        !           385:                        return 0;
        !           386:                }
        !           387:        }
        !           388: 
        !           389:        return i == len;
        !           390: }
        !           391: 
        !           392: #define PATCH(x) \
        !           393:        p->conf.x = s->x;
        !           394: static int mod_secdownload_patch_connection(server *srv, connection *con, plugin_data *p) {
        !           395:        size_t i, j;
        !           396:        plugin_config *s = p->config_storage[0];
        !           397: 
        !           398:        PATCH(secret);
        !           399:        PATCH(doc_root);
        !           400:        PATCH(uri_prefix);
        !           401:        PATCH(timeout);
        !           402:        PATCH(algorithm);
        !           403: 
        !           404:        /* skip the first, the global context */
        !           405:        for (i = 1; i < srv->config_context->used; i++) {
        !           406:                data_config *dc = (data_config *)srv->config_context->data[i];
        !           407:                s = p->config_storage[i];
        !           408: 
        !           409:                /* condition didn't match */
        !           410:                if (!config_check_cond(srv, con, dc)) continue;
        !           411: 
        !           412:                /* merge config */
        !           413:                for (j = 0; j < dc->value->used; j++) {
        !           414:                        data_unset *du = dc->value->data[j];
        !           415: 
        !           416:                        if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.secret"))) {
        !           417:                                PATCH(secret);
        !           418:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.document-root"))) {
        !           419:                                PATCH(doc_root);
        !           420:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.uri-prefix"))) {
        !           421:                                PATCH(uri_prefix);
        !           422:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.timeout"))) {
        !           423:                                PATCH(timeout);
        !           424:                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("secdownload.algorithm"))) {
        !           425:                                PATCH(algorithm);
        !           426:                        }
        !           427:                }
        !           428:        }
        !           429: 
        !           430:        return 0;
        !           431: }
        !           432: #undef PATCH
        !           433: 
        !           434: 
        !           435: URIHANDLER_FUNC(mod_secdownload_uri_handler) {
        !           436:        plugin_data *p = p_d;
        !           437:        const char *rel_uri, *ts_str, *mac_str, *protected_path;
        !           438:        time_t ts = 0;
        !           439:        size_t i, mac_len;
        !           440: 
        !           441:        if (con->mode != DIRECT) return HANDLER_GO_ON;
        !           442: 
        !           443:        if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON;
        !           444: 
        !           445:        mod_secdownload_patch_connection(srv, con, p);
        !           446: 
        !           447:        if (buffer_string_is_empty(p->conf.uri_prefix)) return HANDLER_GO_ON;
        !           448: 
        !           449:        if (buffer_string_is_empty(p->conf.secret)) {
        !           450:                log_error_write(srv, __FILE__, __LINE__, "s",
        !           451:                                "secdownload.secret has to be set");
        !           452:                con->http_status = 500;
        !           453:                return HANDLER_FINISHED;
        !           454:        }
        !           455: 
        !           456:        if (buffer_string_is_empty(p->conf.doc_root)) {
        !           457:                log_error_write(srv, __FILE__, __LINE__, "s",
        !           458:                                "secdownload.document-root has to be set");
        !           459:                con->http_status = 500;
        !           460:                return HANDLER_FINISHED;
        !           461:        }
        !           462: 
        !           463:        if (SECDL_INVALID == p->conf.algorithm) {
        !           464:                log_error_write(srv, __FILE__, __LINE__, "s",
        !           465:                                "secdownload.algorithm has to be set");
        !           466:                con->http_status = 500;
        !           467:                return HANDLER_FINISHED;
        !           468:        }
        !           469: 
        !           470:        mac_len = secdl_algorithm_mac_length(p->conf.algorithm);
        !           471: 
        !           472:        if (0 != strncmp(con->uri.path->ptr, p->conf.uri_prefix->ptr, buffer_string_length(p->conf.uri_prefix))) return HANDLER_GO_ON;
        !           473: 
        !           474:        mac_str = con->uri.path->ptr + buffer_string_length(p->conf.uri_prefix);
        !           475: 
        !           476:        if (!is_base64_len(mac_str, mac_len)) return HANDLER_GO_ON;
        !           477: 
        !           478:        protected_path = mac_str + mac_len;
        !           479:        if (*protected_path != '/') return HANDLER_GO_ON;
        !           480: 
        !           481:        ts_str = protected_path + 1;
        !           482:        if (!is_hex_len(ts_str, 8)) return HANDLER_GO_ON;
        !           483:        if (*(ts_str + 8) != '/') return HANDLER_GO_ON;
        !           484: 
        !           485:        for (i = 0; i < 8; i++) {
        !           486:                ts = (ts << 4) + hex2int(ts_str[i]);
        !           487:        }
        !           488: 
        !           489:        /* timed-out */
        !           490:        if ( (srv->cur_ts > ts && (unsigned int) (srv->cur_ts - ts) > p->conf.timeout) ||
        !           491:             (srv->cur_ts < ts && (unsigned int) (ts - srv->cur_ts) > p->conf.timeout) ) {
        !           492:                /* "Gone" as the url will never be valid again instead of "408 - Timeout" where the request may be repeated */
        !           493:                con->http_status = 410;
        !           494: 
        !           495:                return HANDLER_FINISHED;
        !           496:        }
        !           497: 
        !           498:        rel_uri = ts_str + 8;
        !           499: 
        !           500:        if (!secdl_verify_mac(srv, &p->conf, protected_path, mac_str, mac_len)) {
        !           501:                con->http_status = 403;
        !           502: 
        !           503:                if (con->conf.log_request_handling) {
        !           504:                        log_error_write(srv, __FILE__, __LINE__, "sb",
        !           505:                                "mac invalid:",
        !           506:                                con->uri.path);
        !           507:                }
        !           508: 
        !           509:                return HANDLER_FINISHED;
        !           510:        }
        !           511: 
        !           512:        /* starting with the last / we should have relative-path to the docroot
        !           513:         */
        !           514: 
        !           515:        buffer_copy_buffer(con->physical.doc_root, p->conf.doc_root);
        !           516:        buffer_copy_buffer(con->physical.basedir, p->conf.doc_root);
        !           517:        buffer_copy_string(con->physical.rel_path, rel_uri);
        !           518:        buffer_copy_buffer(con->physical.path, con->physical.doc_root);
        !           519:        buffer_append_string_buffer(con->physical.path, con->physical.rel_path);
        !           520: 
        !           521:        return HANDLER_GO_ON;
        !           522: }
        !           523: 
        !           524: /* this function is called at dlopen() time and inits the callbacks */
        !           525: 
        !           526: int mod_secdownload_plugin_init(plugin *p);
        !           527: int mod_secdownload_plugin_init(plugin *p) {
        !           528:        p->version     = LIGHTTPD_VERSION_ID;
        !           529:        p->name        = buffer_init_string("secdownload");
        !           530: 
        !           531:        p->init        = mod_secdownload_init;
        !           532:        p->handle_physical  = mod_secdownload_uri_handler;
        !           533:        p->set_defaults  = mod_secdownload_set_defaults;
        !           534:        p->cleanup     = mod_secdownload_free;
        !           535: 
        !           536:        p->data        = NULL;
        !           537: 
        !           538:        return 0;
        !           539: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>