Annotation of embedaddon/strongswan/src/libimcv/swima/swima_collector.c, revision 1.1

1.1     ! misho       1: /*
        !             2:  * Copyright (C) 2017 Andreas Steffen
        !             3:  * HSR Hochschule fuer Technik Rapperswil
        !             4:  *
        !             5:  * This program is free software; you can redistribute it and/or modify it
        !             6:  * under the terms of the GNU General Public License as published by the
        !             7:  * Free Software Foundation; either version 2 of the License, or (at your
        !             8:  * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
        !             9:  *
        !            10:  * This program is distributed in the hope that it will be useful, but
        !            11:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
        !            12:  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
        !            13:  * for more details.
        !            14:  */
        !            15: 
        !            16: #define _GNU_SOURCE /* for asprintf() */
        !            17: 
        !            18: #include "swima_collector.h"
        !            19: 
        !            20: #include <swid_gen/swid_gen.h>
        !            21: 
        !            22: #include <collections/linked_list.h>
        !            23: #include <utils/debug.h>
        !            24: 
        !            25: #include <stdio.h>
        !            26: #include <fcntl.h>
        !            27: #include <unistd.h>
        !            28: #include <sys/stat.h>
        !            29: #include <libgen.h>
        !            30: #include <errno.h>
        !            31: 
        !            32: #define SOURCE_ID_GENERATOR            1
        !            33: #define SOURCE_ID_COLLECTOR            2
        !            34: 
        !            35: #ifndef SWID_DIRECTORY
        !            36: #define SWID_DIRECTORY NULL
        !            37: #endif
        !            38: 
        !            39: /**
        !            40:  * Directories to be skipped by collector
        !            41:  */
        !            42: static const char* skip_directories[] = {
        !            43:        "/usr/share/doc",
        !            44:        "/usr/share/help",
        !            45:        "/usr/share/icons",
        !            46:        "/usr/share/gnome/help"
        !            47: };
        !            48: 
        !            49: typedef struct private_swima_collector_t private_swima_collector_t;
        !            50: 
        !            51: /**
        !            52:  * Private data of a swima_collector_t object.
        !            53:  *
        !            54:  */
        !            55: struct private_swima_collector_t {
        !            56: 
        !            57:        /**
        !            58:         * Public swima_collector_t interface.
        !            59:         */
        !            60:        swima_collector_t public;
        !            61: 
        !            62:        /**
        !            63:         * Collect Software Identifiers only
        !            64:         */
        !            65:        bool sw_id_only;
        !            66: 
        !            67:        /**
        !            68:         * Software Collector Database [if it exists]
        !            69:         */
        !            70:        database_t *db;
        !            71: 
        !            72:        /**
        !            73:         * List of Software [Identifier] records
        !            74:         */
        !            75:        swima_inventory_t *inventory;
        !            76: 
        !            77:        /**
        !            78:         * List of Software [Identifier] events
        !            79:         */
        !            80:        swima_events_t *events;
        !            81: 
        !            82: };
        !            83: 
        !            84: /**
        !            85:  * Extract Software Identifier from SWID tag
        !            86:  */
        !            87: static status_t extract_sw_id(chunk_t swid_tag, chunk_t *sw_id)
        !            88: {
        !            89:        char *pos, *tag, *tagid, *regid;
        !            90:        size_t len, tagid_len, regid_len;
        !            91:        status_t status = NOT_FOUND;
        !            92: 
        !            93:        /* Copy at most 1023 bytes of the SWID tag and null-terminate it */
        !            94:        len = min(1023, swid_tag.len);
        !            95:        pos = tag = strndup(swid_tag.ptr, len);
        !            96: 
        !            97:        tagid= strstr(pos, "tagId=\"");
        !            98:        if (tagid == NULL)
        !            99:        {
        !           100:                goto end;
        !           101:        }
        !           102:        tagid += 7;
        !           103:        len -= tagid - pos - 7;
        !           104: 
        !           105:        pos = strchr(tagid, '"');
        !           106:        if (pos == NULL)
        !           107:        {
        !           108:                goto end;
        !           109:        }
        !           110:        tagid_len = pos - tagid;
        !           111: 
        !           112:        regid= strstr(pos, "regid=\"");
        !           113:        if (regid == NULL)
        !           114:        {
        !           115:                goto end;
        !           116:        }
        !           117:        regid += 7;
        !           118:        len -= regid - pos - 7;
        !           119: 
        !           120:        pos = strchr(regid, '"');
        !           121:        if (pos == NULL)
        !           122:        {
        !           123:                goto end;
        !           124:        }
        !           125:        regid_len = pos - regid;
        !           126: 
        !           127:        *sw_id = chunk_cat("ccc", chunk_create(regid, regid_len),
        !           128:                                                          chunk_from_chars('_','_'),
        !           129:                                                          chunk_create(tagid, tagid_len));
        !           130:        status = SUCCESS;
        !           131: end:
        !           132:        free(tag);
        !           133: 
        !           134:        return status;
        !           135: }
        !           136: 
        !           137: static status_t retrieve_inventory(private_swima_collector_t *this,
        !           138:                                                                   swima_inventory_t *targets)
        !           139: {
        !           140:        char *name;
        !           141:        uint32_t record_id, source;
        !           142:        swima_record_t *sw_record;
        !           143:        chunk_t sw_id;
        !           144:        enumerator_t *e;
        !           145: 
        !           146:        /* Retrieve complete software identifier inventory */
        !           147:        e = this->db->query(this->db,
        !           148:                        "SELECT id, name, source FROM sw_identifiers WHERE installed = 1 "
        !           149:                        "ORDER BY name ASC", DB_UINT, DB_TEXT, DB_UINT);
        !           150:        if (!e)
        !           151:        {
        !           152:                DBG1(DBG_IMC, "database query for installed sw_identifiers failed");
        !           153:                return FAILED;
        !           154:        }
        !           155:        while (e->enumerate(e, &record_id, &name, &source))
        !           156:        {
        !           157:                sw_id = chunk_from_str(name);
        !           158:                sw_record = swima_record_create(record_id, sw_id, chunk_empty);
        !           159:                sw_record->set_source_id(sw_record, source);
        !           160:                this->inventory->add(this->inventory, sw_record);
        !           161:        }
        !           162:        e->destroy(e);
        !           163: 
        !           164:        return SUCCESS;
        !           165: }
        !           166: 
        !           167: static status_t retrieve_events(private_swima_collector_t *this,
        !           168:                                                                swima_inventory_t *targets)
        !           169: {
        !           170:        enumerator_t *e;
        !           171:        char *name, *timestamp;
        !           172:        uint32_t record_id, source, action, eid, earliest_eid;
        !           173:        chunk_t sw_id, ev_ts;
        !           174:        swima_record_t *sw_record;
        !           175:        swima_event_t *sw_event;
        !           176: 
        !           177:        earliest_eid = targets->get_eid(targets, NULL);
        !           178: 
        !           179:        /* Retrieve complete software identifier inventory */
        !           180:        e = this->db->query(this->db,
        !           181:                "SELECT e.id, e.timestamp, i.id, i.name, i.source, s.action "
        !           182:                "FROM sw_events as s JOIN events AS e ON s.eid = e.id "
        !           183:                "JOIN sw_identifiers as i ON s.sw_id = i.id WHERE s.eid >= ?"
        !           184:                "ORDER BY s.eid, i.name, s.action ASC", DB_UINT, earliest_eid,
        !           185:                 DB_UINT, DB_TEXT, DB_UINT, DB_TEXT, DB_UINT, DB_UINT);
        !           186:        if (!e)
        !           187:        {
        !           188:                DBG1(DBG_IMC, "database query for sw_events failed");
        !           189:                return FAILED;
        !           190:        }
        !           191:        while (e->enumerate(e, &eid, &timestamp, &record_id, &name, &source, &action))
        !           192:        {
        !           193:                sw_id = chunk_from_str(name);
        !           194:                ev_ts = chunk_from_str(timestamp);
        !           195:                sw_record = swima_record_create(record_id, sw_id, chunk_empty);
        !           196:                sw_record->set_source_id(sw_record, source);
        !           197:                sw_event = swima_event_create(eid, ev_ts, action, sw_record);
        !           198:                this->events->add(this->events, sw_event);
        !           199:        }
        !           200:        e->destroy(e);
        !           201: 
        !           202:        return SUCCESS;
        !           203: }
        !           204: 
        !           205: static status_t generate_tags(private_swima_collector_t *this,
        !           206:                                                          swima_inventory_t *targets, bool pretty, bool full)
        !           207: {
        !           208:        swid_gen_t *swid_gen;
        !           209:        swima_record_t *target, *sw_record;
        !           210:        enumerator_t *enumerator;
        !           211:        status_t status = SUCCESS;
        !           212: 
        !           213:        swid_gen = swid_gen_create();
        !           214: 
        !           215:        if (targets->get_count(targets) == 0)
        !           216:        {
        !           217:                chunk_t out, sw_id, swid_tag = chunk_empty;
        !           218: 
        !           219:                DBG2(DBG_IMC, "SWID tag%s generation by package manager",
        !           220:                                           this->sw_id_only ? " ID" : "");
        !           221: 
        !           222:                enumerator = swid_gen->create_tag_enumerator(swid_gen, this->sw_id_only,
        !           223:                                                                                                         full, pretty);
        !           224:                if (enumerator)
        !           225:                {
        !           226:                        while (enumerator->enumerate(enumerator, &out))
        !           227:                        {
        !           228:                                if (this->sw_id_only)
        !           229:                                {
        !           230:                                        sw_id = out;
        !           231:                                }
        !           232:                                else
        !           233:                                {
        !           234:                                        swid_tag = out;
        !           235:                                        status = extract_sw_id(swid_tag, &sw_id);
        !           236:                                        if (status != SUCCESS)
        !           237:                                        {
        !           238:                                                DBG1(DBG_IMC, "software id could not be extracted "
        !           239:                                                                          "from tag");
        !           240:                                                chunk_free(&swid_tag);
        !           241:                                                break;
        !           242:                                        }
        !           243:                                }
        !           244:                                sw_record = swima_record_create(0, sw_id, chunk_empty);
        !           245:                                sw_record->set_source_id(sw_record, SOURCE_ID_GENERATOR);
        !           246:                                if (!this->sw_id_only)
        !           247:                                {
        !           248:                                        sw_record->set_record(sw_record, swid_tag);
        !           249:                                        chunk_free(&swid_tag);
        !           250:                                }
        !           251:                                this->inventory->add(this->inventory, sw_record);
        !           252:                                chunk_free(&sw_id);
        !           253:                        }
        !           254:                        enumerator->destroy(enumerator);
        !           255:                }
        !           256:                else
        !           257:                {
        !           258:                        status = NOT_SUPPORTED;
        !           259:                }
        !           260:        }
        !           261:        else if (!this->sw_id_only)
        !           262:        {
        !           263:                DBG2(DBG_IMC, "targeted SWID tag generation");
        !           264: 
        !           265:                enumerator = targets->create_enumerator(targets);
        !           266:                while (enumerator->enumerate(enumerator, &target))
        !           267:                {
        !           268:                        swima_record_t *sw_record;
        !           269:                        char *tag = NULL, *name, *package, *version;
        !           270:                        u_int installed;
        !           271:                        chunk_t sw_id;
        !           272:                        enumerator_t *e;
        !           273: 
        !           274:                        sw_id = target->get_sw_id(target, NULL);
        !           275:                        name = strndup(sw_id.ptr, sw_id.len);
        !           276: 
        !           277:                        if (this->db)
        !           278:                        {
        !           279:                                e = this->db->query(this->db,
        !           280:                                                "SELECT package, version, installed "
        !           281:                                                "FROM sw_identifiers WHERE name = ?", DB_TEXT, name,
        !           282:                                                 DB_TEXT, DB_TEXT, DB_UINT);
        !           283:                                if (!e)
        !           284:                                {
        !           285:                                        DBG1(DBG_IMC, "database query for sw_identifiers failed");
        !           286:                                        status = FAILED;
        !           287:                                        free(name);
        !           288:                                        break;
        !           289:                                }
        !           290:                                if (e->enumerate(e, &package, &version, &installed))
        !           291:                                {
        !           292:                                        tag = swid_gen->generate_tag(swid_gen, name, package,
        !           293:                                                                        version, full && installed, pretty);
        !           294:                                }
        !           295:                                e->destroy(e);
        !           296:                        }
        !           297:                        else
        !           298:                        {
        !           299:                                tag = swid_gen->generate_tag(swid_gen, name, NULL, NULL,
        !           300:                                                                                         full, pretty);
        !           301:                        }
        !           302:                        free(name);
        !           303: 
        !           304:                        if (tag)
        !           305:                        {
        !           306:                                DBG2(DBG_IMC, "  %.*s", sw_id.len, sw_id.ptr);
        !           307:                                sw_record = swima_record_create(0, sw_id, chunk_empty);
        !           308:                                sw_record->set_source_id(sw_record, SOURCE_ID_GENERATOR);
        !           309:                                sw_record->set_record(sw_record, chunk_from_str(tag));
        !           310:                                this->inventory->add(this->inventory, sw_record);
        !           311:                                free(tag);
        !           312:                        }
        !           313:                }
        !           314:                enumerator->destroy(enumerator);
        !           315:        }
        !           316:        swid_gen->destroy(swid_gen);
        !           317: 
        !           318:        return status;
        !           319: }
        !           320: 
        !           321: static bool collect_tags(private_swima_collector_t *this, char *pathname,
        !           322:                                                 swima_inventory_t *targets, bool is_swidtag_dir)
        !           323: {
        !           324:        char *rel_name, *abs_name, *suffix, *pos, *uri;
        !           325:        chunk_t *swid_tag, sw_id, sw_locator;
        !           326:        swima_record_t *sw_record;
        !           327:        struct stat st;
        !           328:        bool success = FALSE, skip, is_new_swidtag_dir;
        !           329:        enumerator_t *enumerator;
        !           330:        int i;
        !           331: 
        !           332:        if (!pathname)
        !           333:        {
        !           334:                return TRUE;
        !           335:        }
        !           336: 
        !           337:        enumerator = enumerator_create_directory(pathname);
        !           338:        if (!enumerator)
        !           339:        {
        !           340:                DBG1(DBG_IMC, "directory '%s' can not be opened, %s",
        !           341:                                           pathname, strerror(errno));
        !           342:                return FALSE;
        !           343:        }
        !           344: 
        !           345:        while (enumerator->enumerate(enumerator, &rel_name, &abs_name, &st))
        !           346:        {
        !           347:                if (S_ISDIR(st.st_mode))
        !           348:                {
        !           349:                        skip = FALSE;
        !           350: 
        !           351:                        for (i = 0; i < countof(skip_directories); i++)
        !           352:                        {
        !           353:                                if (streq(abs_name, skip_directories[i]))
        !           354:                                {
        !           355:                                        skip = TRUE;
        !           356:                                        break;
        !           357:                                }
        !           358:                        }
        !           359: 
        !           360:                        if (skip)
        !           361:                        {
        !           362:                                continue;
        !           363:                        }
        !           364: 
        !           365:                        is_new_swidtag_dir =  streq(rel_name, "swidtag");
        !           366:                        if (is_new_swidtag_dir)
        !           367:                        {
        !           368:                                DBG2(DBG_IMC, "entering %s", pathname);
        !           369:                        }
        !           370:                        if (!collect_tags(this, abs_name, targets, is_swidtag_dir ||
        !           371:                                                                                                           is_new_swidtag_dir))
        !           372:                        {
        !           373:                                goto end;
        !           374:                        }
        !           375:                        if (is_new_swidtag_dir)
        !           376:                        {
        !           377:                                DBG2(DBG_IMC, "leaving %s", pathname);
        !           378:                        }
        !           379:                }
        !           380: 
        !           381:                if (!is_swidtag_dir)
        !           382:                {
        !           383:                        continue;
        !           384:                }
        !           385: 
        !           386:                /* found a swidtag file? */
        !           387:                suffix = strstr(rel_name, ".swidtag");
        !           388:                if (!suffix)
        !           389:                {
        !           390:                        continue;
        !           391:                }
        !           392: 
        !           393:                /* load the swidtag file */
        !           394:                swid_tag = chunk_map(abs_name, FALSE);
        !           395:                if (!swid_tag)
        !           396:                {
        !           397:                        DBG1(DBG_IMC, "  opening '%s' failed: %s", abs_name,
        !           398:                                                  strerror(errno));
        !           399:                        goto end;
        !           400:                }
        !           401: 
        !           402:                /* extract software identity from SWID tag */
        !           403:                if (extract_sw_id(*swid_tag, &sw_id) != SUCCESS)
        !           404:                {
        !           405:                        DBG1(DBG_IMC, "software id could not be extracted from SWID tag");
        !           406:                        chunk_unmap(swid_tag);
        !           407:                        goto end;
        !           408:                }
        !           409: 
        !           410:                /* In case of a targeted request */
        !           411:                if (targets->get_count(targets))
        !           412:                {
        !           413:                        enumerator_t *target_enumerator;
        !           414:                        swima_record_t *target;
        !           415:                        bool match = FALSE;
        !           416: 
        !           417:                        target_enumerator = targets->create_enumerator(targets);
        !           418:                        while (target_enumerator->enumerate(target_enumerator, &target))
        !           419:                        {
        !           420:                                if (chunk_equals(target->get_sw_id(target, NULL), sw_id))
        !           421:                                {
        !           422:                                        DBG2(DBG_IMC, "  %.*s", sw_id.len, sw_id.ptr);
        !           423:                                        match = TRUE;
        !           424:                                        break;
        !           425:                                }
        !           426:                        }
        !           427:                        target_enumerator->destroy(target_enumerator);
        !           428: 
        !           429:                        if (!match)
        !           430:                        {
        !           431:                                chunk_unmap(swid_tag);
        !           432:                                chunk_free(&sw_id);
        !           433:                                continue;
        !           434:                        }
        !           435:                }
        !           436:                DBG2(DBG_IMC, "  %s", rel_name);
        !           437: 
        !           438:                sw_locator = chunk_empty;
        !           439:                pos = strstr(pathname, "/swidtag");
        !           440:                if (pos &&
        !           441:                        asprintf(&uri, "file://%.*s", (int)(pos - pathname), pathname) > 0)
        !           442:                {
        !           443:                        sw_locator = chunk_from_str(uri);
        !           444:                }
        !           445:                sw_record = swima_record_create(0, sw_id, sw_locator);
        !           446:                sw_record->set_source_id(sw_record, SOURCE_ID_COLLECTOR);
        !           447:                if (!this->sw_id_only)
        !           448:                {
        !           449:                        sw_record->set_record(sw_record, *swid_tag);
        !           450:                }
        !           451:                this->inventory->add(this->inventory, sw_record);
        !           452: 
        !           453:                chunk_unmap(swid_tag);
        !           454:                chunk_free(&sw_id);
        !           455:                chunk_free(&sw_locator);
        !           456:        }
        !           457:        success = TRUE;
        !           458: 
        !           459: end:
        !           460:        enumerator->destroy(enumerator);
        !           461: 
        !           462:        return success;
        !           463: }
        !           464: 
        !           465: METHOD(swima_collector_t, collect_inventory, swima_inventory_t*,
        !           466:        private_swima_collector_t *this, bool sw_id_only, swima_inventory_t *targets)
        !           467: {
        !           468:        bool pretty, full;
        !           469:        char *directory;
        !           470:        status_t status;
        !           471: 
        !           472:        directory = lib->settings->get_str(lib->settings,
        !           473:                                                                        "%s.plugins.imc-swima.swid_directory",
        !           474:                                                                         SWID_DIRECTORY, lib->ns);
        !           475:        pretty = lib->settings->get_bool(lib->settings,
        !           476:                                                                        "%s.plugins.imc-swima.swid_pretty",
        !           477:                                                                         FALSE, lib->ns);
        !           478:        full = lib->settings->get_bool(lib->settings,
        !           479:                                                                        "%s.plugins.imc-swima.swid_full",
        !           480:                                                                         FALSE, lib->ns);
        !           481: 
        !           482:        /**
        !           483:         * Re-initialize collector
        !           484:         */
        !           485:        this->sw_id_only = sw_id_only;
        !           486:        this->inventory->clear(this->inventory);
        !           487: 
        !           488:        /**
        !           489:         * Source 1: Tags are generated by a package manager
        !           490:         */
        !           491:        if (sw_id_only && this->db)
        !           492:        {
        !           493:                status = retrieve_inventory(this, targets);
        !           494:        }
        !           495:        else
        !           496:        {
        !           497:                status = generate_tags(this, targets, pretty, full);
        !           498:        }
        !           499: 
        !           500:        /**
        !           501:         * Source 2: Collect swidtag files by iteratively entering all
        !           502:         *           directories in the tree under the "directory" path.
        !           503:         */
        !           504:        DBG2(DBG_IMC, "SWID tag%s collection", sw_id_only ? " ID" : "");
        !           505:        collect_tags(this, directory, targets, FALSE);
        !           506: 
        !           507:        return status == SUCCESS ? this->inventory : NULL;
        !           508: }
        !           509: 
        !           510: METHOD(swima_collector_t, collect_events, swima_events_t*,
        !           511:        private_swima_collector_t *this, bool sw_id_only, swima_inventory_t *targets)
        !           512: {
        !           513:        if (!sw_id_only || !this->db)
        !           514:        {
        !           515:                return NULL;
        !           516:        }
        !           517: 
        !           518:        /**
        !           519:         * Re-initialize collector
        !           520:         */
        !           521:        this->sw_id_only = sw_id_only;
        !           522:        this->events->clear(this->events);
        !           523: 
        !           524:        return retrieve_events(this, targets) == SUCCESS ? this->events : NULL;
        !           525: }
        !           526: 
        !           527: METHOD(swima_collector_t, destroy, void,
        !           528:        private_swima_collector_t *this)
        !           529: {
        !           530:        DESTROY_IF(this->db);
        !           531:        this->inventory->destroy(this->inventory);
        !           532:        this->events->destroy(this->events);
        !           533:        free(this);
        !           534: }
        !           535: 
        !           536: /**
        !           537:  * See header
        !           538:  */
        !           539: swima_collector_t *swima_collector_create(void)
        !           540: {
        !           541:        private_swima_collector_t *this;
        !           542:        char *database;
        !           543:        uint32_t last_eid = 1, eid_epoch = 0x11223344;
        !           544: 
        !           545:        INIT(this,
        !           546:                .public = {
        !           547:                        .collect_inventory = _collect_inventory,
        !           548:                        .collect_events = _collect_events,
        !           549:                        .destroy = _destroy,
        !           550:                },
        !           551:                .inventory = swima_inventory_create(),
        !           552:                .events = swima_events_create(),
        !           553:        );
        !           554: 
        !           555:        database = lib->settings->get_str(lib->settings,
        !           556:                                        "%s.plugins.imc-swima.swid_database", NULL, lib->ns);
        !           557: 
        !           558:        /* If we have an URI, try to connect to sw_collector database */
        !           559:        if (database)
        !           560:        {
        !           561:                database_t *db = lib->db->create(lib->db, database);
        !           562: 
        !           563:                if (db)
        !           564:                {
        !           565:                        enumerator_t *e;
        !           566: 
        !           567:                        /* Get last event ID and corresponding epoch */
        !           568:                        e = db->query(db,
        !           569:                                        "SELECT id, epoch FROM events ORDER BY timestamp DESC",
        !           570:                                         DB_UINT, DB_UINT);
        !           571:                        if (!e || !e->enumerate(e, &last_eid, &eid_epoch))
        !           572:                        {
        !           573:                                DBG1(DBG_IMC, "database query for last event failed");
        !           574:                                DESTROY_IF(e);
        !           575:                                db->destroy(db);
        !           576:                        }
        !           577:                        else
        !           578:                        {
        !           579:                                /* The query worked, attach collector database permanently */
        !           580:                                e->destroy(e);
        !           581:                                this->db = db;
        !           582:                        }
        !           583:                }
        !           584:                else
        !           585:                {
        !           586:                        DBG1(DBG_IMC, "opening sw-collector database URI '%s' failed",
        !           587:                                                   database);
        !           588:                }
        !           589:        }
        !           590:        if (!this->db)
        !           591:        {
        !           592:                /* Set the event ID epoch and last event ID manually */
        !           593:                eid_epoch = lib->settings->get_int(lib->settings,
        !           594:                                                                "%s.plugins.imc-swima.eid_epoch",
        !           595:                                                                 eid_epoch, lib->ns);
        !           596:        }
        !           597:        this->inventory->set_eid(this->inventory, last_eid, eid_epoch);
        !           598:        this->events->set_eid(this->events, last_eid, eid_epoch);
        !           599: 
        !           600:        return &this->public;
        !           601: }

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