Annotation of embedaddon/strongswan/src/libcharon/plugins/connmark/connmark_listener.c, revision 1.1

1.1     ! misho       1: /*
        !             2:  * Copyright (C) 2015 Tobias Brunner
        !             3:  * HSR Hochschule fuer Technik Rapperswil
        !             4:  *
        !             5:  * Copyright (C) 2014 Martin Willi
        !             6:  * Copyright (C) 2014 revosec AG
        !             7:  *
        !             8:  * This program is free software; you can redistribute it and/or modify it
        !             9:  * under the terms of the GNU General Public License as published by the
        !            10:  * Free Software Foundation; either version 2 of the License, or (at your
        !            11:  * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
        !            12:  *
        !            13:  * This program is distributed in the hope that it will be useful, but
        !            14:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
        !            15:  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
        !            16:  * for more details.
        !            17:  */
        !            18: 
        !            19: #include "connmark_listener.h"
        !            20: 
        !            21: #include <daemon.h>
        !            22: 
        !            23: #include <errno.h>
        !            24: #include <libiptc/libiptc.h>
        !            25: #include <linux/netfilter/xt_esp.h>
        !            26: #include <linux/netfilter/xt_tcpudp.h>
        !            27: #include <linux/netfilter/xt_mark.h>
        !            28: #include <linux/netfilter/xt_MARK.h>
        !            29: #include <linux/netfilter/xt_policy.h>
        !            30: #include <linux/netfilter/xt_CONNMARK.h>
        !            31: 
        !            32: /**
        !            33:  * Add a struct at the current position in the buffer
        !            34:  */
        !            35: #define ADD_STRUCT(pos, st, ...) ({\
        !            36:        typeof(pos) _cur = pos; pos += XT_ALIGN(sizeof(st));\
        !            37:        *(st*)_cur = (st){ __VA_ARGS__ };\
        !            38:        (st*)_cur;\
        !            39: })
        !            40: 
        !            41: typedef struct private_connmark_listener_t private_connmark_listener_t;
        !            42: 
        !            43: /**
        !            44:  * Private data of an connmark_listener_t object.
        !            45:  */
        !            46: struct private_connmark_listener_t {
        !            47: 
        !            48:        /**
        !            49:         * Public connmark_listener_t interface.
        !            50:         */
        !            51:        connmark_listener_t public;
        !            52: };
        !            53: 
        !            54: /**
        !            55:  * Convert an (IPv4) traffic selector to an address and mask
        !            56:  */
        !            57: static bool ts2in(traffic_selector_t *ts,
        !            58:                                  struct in_addr *addr, struct in_addr *mask)
        !            59: {
        !            60:        uint8_t bits;
        !            61:        host_t *net;
        !            62: 
        !            63:        if (ts->get_type(ts) == TS_IPV4_ADDR_RANGE &&
        !            64:                ts->to_subnet(ts, &net, &bits))
        !            65:        {
        !            66:                memcpy(&addr->s_addr, net->get_address(net).ptr, 4);
        !            67:                net->destroy(net);
        !            68:                mask->s_addr = htonl(0xffffffffU << (32 - bits));
        !            69:                return TRUE;
        !            70:        }
        !            71:        return FALSE;
        !            72: }
        !            73: 
        !            74: /**
        !            75:  * Convert an (IPv4) host to an address with mask
        !            76:  */
        !            77: static bool host2in(host_t *host, struct in_addr *addr, struct in_addr *mask)
        !            78: {
        !            79:        if (host->get_family(host) == AF_INET)
        !            80:        {
        !            81:                memcpy(&addr->s_addr, host->get_address(host).ptr, 4);
        !            82:                mask->s_addr = ~0;
        !            83:                return TRUE;
        !            84:        }
        !            85:        return FALSE;
        !            86: }
        !            87: 
        !            88: /**
        !            89:  * Add or remove a rule to/from the specified chain
        !            90:  */
        !            91: static bool manage_rule(struct iptc_handle *ipth, const char *chain,
        !            92:                                                bool add, struct ipt_entry *e)
        !            93: {
        !            94:        if (add)
        !            95:        {
        !            96:                if (!iptc_insert_entry(chain, e, 0, ipth))
        !            97:                {
        !            98:                        DBG1(DBG_CFG, "appending %s rule failed: %s",
        !            99:                                 chain, iptc_strerror(errno));
        !           100:                        return FALSE;
        !           101:                }
        !           102:        }
        !           103:        else
        !           104:        {
        !           105:                u_char matchmask[e->next_offset];
        !           106: 
        !           107:                memset(matchmask, 255, sizeof(matchmask));
        !           108:                if (!iptc_delete_entry(chain, e, matchmask, ipth))
        !           109:                {
        !           110:                        DBG1(DBG_CFG, "deleting %s rule failed: %s",
        !           111:                                 chain, iptc_strerror(errno));
        !           112:                        return FALSE;
        !           113:                }
        !           114:        }
        !           115:        return TRUE;
        !           116: }
        !           117: 
        !           118: /**
        !           119:  * Add rule marking UDP-encapsulated ESP packets to match the correct policy
        !           120:  */
        !           121: static bool manage_pre_esp_in_udp(private_connmark_listener_t *this,
        !           122:                                                                  struct iptc_handle *ipth, bool add,
        !           123:                                                                  u_int mark, uint32_t spi,
        !           124:                                                                  host_t *dst, host_t *src)
        !           125: {
        !           126:        uint16_t match_size     = XT_ALIGN(sizeof(struct ipt_entry_match)) +
        !           127:                                                          XT_ALIGN(sizeof(struct xt_udp));
        !           128:        uint16_t target_offset = XT_ALIGN(sizeof(struct ipt_entry)) + match_size;
        !           129:        uint16_t target_size    = XT_ALIGN(sizeof(struct ipt_entry_target)) +
        !           130:                                                          XT_ALIGN(sizeof(struct xt_mark_tginfo2));
        !           131:        uint16_t entry_size     = target_offset + target_size;
        !           132:        u_char ipt[entry_size], *pos = ipt;
        !           133:        struct ipt_entry *e;
        !           134: 
        !           135:        memset(ipt, 0, sizeof(ipt));
        !           136:        e = ADD_STRUCT(pos, struct ipt_entry,
        !           137:                .target_offset = target_offset,
        !           138:                .next_offset = entry_size,
        !           139:                .ip = {
        !           140:                        .proto = IPPROTO_UDP,
        !           141:                },
        !           142:        );
        !           143:        if (!host2in(dst, &e->ip.dst, &e->ip.dmsk) ||
        !           144:                !host2in(src, &e->ip.src, &e->ip.smsk))
        !           145:        {
        !           146:                return FALSE;
        !           147:        }
        !           148:        ADD_STRUCT(pos, struct ipt_entry_match,
        !           149:                .u = {
        !           150:                        .user = {
        !           151:                                .match_size = match_size,
        !           152:                                .name = "udp",
        !           153:                        },
        !           154:                },
        !           155:        );
        !           156:        ADD_STRUCT(pos, struct xt_udp,
        !           157:                .spts = { src->get_port(src), src->get_port(src) },
        !           158:                .dpts = { dst->get_port(dst), dst->get_port(dst) },
        !           159:        );
        !           160:        ADD_STRUCT(pos, struct ipt_entry_target,
        !           161:                .u = {
        !           162:                        .user = {
        !           163:                                .target_size = target_size,
        !           164:                                .name = "MARK",
        !           165:                                .revision = 2,
        !           166:                        },
        !           167:                },
        !           168:        );
        !           169:        ADD_STRUCT(pos, struct xt_mark_tginfo2,
        !           170:                .mark = mark,
        !           171:                .mask = ~0,
        !           172:        );
        !           173:        return manage_rule(ipth, "PREROUTING", add, e);
        !           174: }
        !           175: 
        !           176: /**
        !           177:  * Add rule marking non-encapsulated ESP packets to match the correct policy
        !           178:  */
        !           179: static bool manage_pre_esp(private_connmark_listener_t *this,
        !           180:                                                   struct iptc_handle *ipth, bool add,
        !           181:                                                   u_int mark, uint32_t spi,
        !           182:                                                   host_t *dst, host_t *src)
        !           183: {
        !           184:        uint16_t match_size     = XT_ALIGN(sizeof(struct ipt_entry_match)) +
        !           185:                                                          XT_ALIGN(sizeof(struct xt_esp));
        !           186:        uint16_t target_offset = XT_ALIGN(sizeof(struct ipt_entry)) + match_size;
        !           187:        uint16_t target_size    = XT_ALIGN(sizeof(struct ipt_entry_target)) +
        !           188:                                                          XT_ALIGN(sizeof(struct xt_mark_tginfo2));
        !           189:        uint16_t entry_size     = target_offset + target_size;
        !           190:        u_char ipt[entry_size], *pos = ipt;
        !           191:        struct ipt_entry *e;
        !           192: 
        !           193:        memset(ipt, 0, sizeof(ipt));
        !           194:        e = ADD_STRUCT(pos, struct ipt_entry,
        !           195:                .target_offset = target_offset,
        !           196:                .next_offset = entry_size,
        !           197:                .ip = {
        !           198:                        .proto = IPPROTO_ESP,
        !           199:                },
        !           200:        );
        !           201:        if (!host2in(dst, &e->ip.dst, &e->ip.dmsk) ||
        !           202:                !host2in(src, &e->ip.src, &e->ip.smsk))
        !           203:        {
        !           204:                return FALSE;
        !           205:        }
        !           206:        ADD_STRUCT(pos, struct ipt_entry_match,
        !           207:                .u = {
        !           208:                        .user = {
        !           209:                                .match_size = match_size,
        !           210:                                .name = "esp",
        !           211:                        },
        !           212:                },
        !           213:        );
        !           214:        ADD_STRUCT(pos, struct xt_esp,
        !           215:                .spis = { htonl(spi), htonl(spi) },
        !           216:        );
        !           217:        ADD_STRUCT(pos, struct ipt_entry_target,
        !           218:                .u = {
        !           219:                        .user = {
        !           220:                                .target_size = target_size,
        !           221:                                .name = "MARK",
        !           222:                                .revision = 2,
        !           223:                        },
        !           224:                },
        !           225:        );
        !           226:        ADD_STRUCT(pos, struct xt_mark_tginfo2,
        !           227:                .mark = mark,
        !           228:                .mask = ~0,
        !           229:        );
        !           230:        return manage_rule(ipth, "PREROUTING", add, e);
        !           231: }
        !           232: 
        !           233: /**
        !           234:  * Add rule marking ESP packets to match the correct policy
        !           235:  */
        !           236: static bool manage_pre(private_connmark_listener_t *this,
        !           237:                                           struct iptc_handle *ipth, bool add,
        !           238:                                           u_int mark, uint32_t spi, bool encap,
        !           239:                                           host_t *dst, host_t *src)
        !           240: {
        !           241:        if (encap)
        !           242:        {
        !           243:                return manage_pre_esp_in_udp(this, ipth, add, mark, spi, dst, src);
        !           244:        }
        !           245:        return manage_pre_esp(this, ipth, add, mark, spi, dst, src);
        !           246: }
        !           247: 
        !           248: /**
        !           249:  * Add inbound rule applying CONNMARK to matching traffic
        !           250:  */
        !           251: static bool manage_in(private_connmark_listener_t *this,
        !           252:                                          struct iptc_handle *ipth, bool add,
        !           253:                                          u_int mark, uint32_t spi,
        !           254:                                          traffic_selector_t *dst, traffic_selector_t *src)
        !           255: {
        !           256:        uint16_t match_size     = XT_ALIGN(sizeof(struct ipt_entry_match)) +
        !           257:                                                          XT_ALIGN(sizeof(struct xt_policy_info));
        !           258:        uint16_t target_offset = XT_ALIGN(sizeof(struct ipt_entry)) + match_size;
        !           259:        uint16_t target_size    = XT_ALIGN(sizeof(struct ipt_entry_target)) +
        !           260:                                                          XT_ALIGN(sizeof(struct xt_connmark_tginfo1));
        !           261:        uint16_t entry_size     = target_offset + target_size;
        !           262:        u_char ipt[entry_size], *pos = ipt;
        !           263:        struct ipt_entry *e;
        !           264: 
        !           265:        memset(ipt, 0, sizeof(ipt));
        !           266:        e = ADD_STRUCT(pos, struct ipt_entry,
        !           267:                .target_offset = target_offset,
        !           268:                .next_offset = entry_size,
        !           269:        );
        !           270:        if (!ts2in(dst, &e->ip.dst, &e->ip.dmsk) ||
        !           271:                !ts2in(src, &e->ip.src, &e->ip.smsk))
        !           272:        {
        !           273:                return FALSE;
        !           274:        }
        !           275:        ADD_STRUCT(pos, struct ipt_entry_match,
        !           276:                .u = {
        !           277:                        .user = {
        !           278:                                .match_size = match_size,
        !           279:                                .name = "policy",
        !           280:                        },
        !           281:                },
        !           282:        );
        !           283:        ADD_STRUCT(pos, struct xt_policy_info,
        !           284:                .pol = {
        !           285:                        {
        !           286:                                .spi = spi,
        !           287:                                .match.spi = 1,
        !           288:                        },
        !           289:                },
        !           290:                .len = 1,
        !           291:                .flags = XT_POLICY_MATCH_IN,
        !           292:        );
        !           293:        ADD_STRUCT(pos, struct ipt_entry_target,
        !           294:                .u = {
        !           295:                        .user = {
        !           296:                                .target_size = target_size,
        !           297:                                .name = "CONNMARK",
        !           298:                                .revision = 1,
        !           299:                        },
        !           300:                },
        !           301:        );
        !           302:        ADD_STRUCT(pos, struct xt_connmark_tginfo1,
        !           303:                .ctmark = mark,
        !           304:                .ctmask = ~0,
        !           305:                .nfmask = ~0,
        !           306:                .mode = XT_CONNMARK_SET,
        !           307:        );
        !           308:        return manage_rule(ipth, "INPUT", add, e);
        !           309: }
        !           310: 
        !           311: /**
        !           312:  * Add outbund rule restoring CONNMARK on matching traffic unless the packet
        !           313:  * already has a mark set
        !           314:  */
        !           315: static bool manage_out(private_connmark_listener_t *this,
        !           316:                                           struct iptc_handle *ipth, bool add,
        !           317:                                           traffic_selector_t *dst, traffic_selector_t *src)
        !           318: {
        !           319:        uint16_t match_size     = XT_ALIGN(sizeof(struct ipt_entry_match)) +
        !           320:                                                          XT_ALIGN(sizeof(struct xt_mark_mtinfo1));
        !           321:        uint16_t target_offset = XT_ALIGN(sizeof(struct ipt_entry)) + match_size;
        !           322:        uint16_t target_size    = XT_ALIGN(sizeof(struct ipt_entry_target)) +
        !           323:                                                          XT_ALIGN(sizeof(struct xt_connmark_tginfo1));
        !           324:        uint16_t entry_size     = target_offset + target_size;
        !           325:        u_char ipt[entry_size], *pos = ipt;
        !           326:        struct ipt_entry *e;
        !           327: 
        !           328:        memset(ipt, 0, sizeof(ipt));
        !           329:        e = ADD_STRUCT(pos, struct ipt_entry,
        !           330:                .target_offset = target_offset,
        !           331:                .next_offset = entry_size,
        !           332:        );
        !           333:        if (!ts2in(dst, &e->ip.dst, &e->ip.dmsk) ||
        !           334:                !ts2in(src, &e->ip.src, &e->ip.smsk))
        !           335:        {
        !           336:                return FALSE;
        !           337:        }
        !           338:        ADD_STRUCT(pos, struct ipt_entry_match,
        !           339:                .u = {
        !           340:                        .user = {
        !           341:                                .match_size = match_size,
        !           342:                                .name = "mark",
        !           343:                                .revision = 1,
        !           344:                        },
        !           345:                },
        !           346:        );
        !           347:        ADD_STRUCT(pos, struct xt_mark_mtinfo1,
        !           348:                .mask = ~0,
        !           349:        );
        !           350:        ADD_STRUCT(pos, struct ipt_entry_target,
        !           351:                .u = {
        !           352:                        .user = {
        !           353:                                .target_size = target_size,
        !           354:                                .name = "CONNMARK",
        !           355:                                .revision = 1,
        !           356:                        },
        !           357:                },
        !           358:        );
        !           359:        ADD_STRUCT(pos, struct xt_connmark_tginfo1,
        !           360:                .ctmask = ~0,
        !           361:                .nfmask = ~0,
        !           362:                .mode = XT_CONNMARK_RESTORE,
        !           363:        );
        !           364:        return manage_rule(ipth, "OUTPUT", add, e);
        !           365: }
        !           366: 
        !           367: /**
        !           368:  * Initialize iptables handle, log error
        !           369:  */
        !           370: static struct iptc_handle* init_handle()
        !           371: {
        !           372:        struct iptc_handle *ipth;
        !           373: 
        !           374:        ipth = iptc_init("mangle");
        !           375:        if (ipth)
        !           376:        {
        !           377:                return ipth;
        !           378:        }
        !           379:        DBG1(DBG_CFG, "initializing iptables failed: %s", iptc_strerror(errno));
        !           380:        return NULL;
        !           381: }
        !           382: 
        !           383: /**
        !           384:  * Commit iptables rules, log error
        !           385:  */
        !           386: static bool commit_handle(struct iptc_handle *ipth)
        !           387: {
        !           388:        if (iptc_commit(ipth))
        !           389:        {
        !           390:                return TRUE;
        !           391:        }
        !           392:        DBG1(DBG_CFG, "forecast iptables commit failed: %s", iptc_strerror(errno));
        !           393:        return FALSE;
        !           394: }
        !           395: 
        !           396: /**
        !           397:  * Add/Remove policies for a CHILD_SA using a iptables handle
        !           398:  */
        !           399: static bool manage_policies(private_connmark_listener_t *this,
        !           400:                                                struct iptc_handle *ipth, host_t *dst, host_t *src,
        !           401:                                                bool encap, child_sa_t *child_sa, bool add)
        !           402: {
        !           403:        traffic_selector_t *local, *remote;
        !           404:        enumerator_t *enumerator;
        !           405:        uint32_t spi;
        !           406:        u_int mark;
        !           407:        bool done = TRUE;
        !           408: 
        !           409:        spi = child_sa->get_spi(child_sa, TRUE);
        !           410:        mark = child_sa->get_mark(child_sa, TRUE).value;
        !           411: 
        !           412:        enumerator = child_sa->create_policy_enumerator(child_sa);
        !           413:        while (enumerator->enumerate(enumerator, &local, &remote))
        !           414:        {
        !           415:                if (!manage_pre(this, ipth, add, mark, spi, encap, dst, src) ||
        !           416:                        !manage_in(this, ipth, add, mark, spi, local, remote) ||
        !           417:                        !manage_out(this, ipth, add, remote, local))
        !           418:                {
        !           419:                        done = FALSE;
        !           420:                        break;
        !           421:                }
        !           422:        }
        !           423:        enumerator->destroy(enumerator);
        !           424: 
        !           425:        return done;
        !           426: }
        !           427: 
        !           428: /**
        !           429:  * Check if rules should be installed for given CHILD_SA
        !           430:  */
        !           431: static bool handle_sa(child_sa_t *child_sa)
        !           432: {
        !           433:        return child_sa->get_mark(child_sa, TRUE).value &&
        !           434:                   child_sa->get_mark(child_sa, FALSE).value &&
        !           435:                   child_sa->get_mode(child_sa) == MODE_TRANSPORT &&
        !           436:                   child_sa->get_protocol(child_sa) == PROTO_ESP;
        !           437: }
        !           438: 
        !           439: METHOD(listener_t, child_updown, bool,
        !           440:        private_connmark_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
        !           441:        bool up)
        !           442: {
        !           443:        struct iptc_handle *ipth;
        !           444:        host_t *dst, *src;
        !           445:        bool encap;
        !           446: 
        !           447:        dst = ike_sa->get_my_host(ike_sa);
        !           448:        src = ike_sa->get_other_host(ike_sa);
        !           449:        encap = child_sa->has_encap(child_sa);
        !           450: 
        !           451:        if (handle_sa(child_sa))
        !           452:        {
        !           453:                ipth = init_handle();
        !           454:                if (ipth)
        !           455:                {
        !           456:                        if (manage_policies(this, ipth, dst, src, encap, child_sa, up))
        !           457:                        {
        !           458:                                commit_handle(ipth);
        !           459:                        }
        !           460:                        iptc_free(ipth);
        !           461:                }
        !           462:        }
        !           463:        return TRUE;
        !           464: }
        !           465: 
        !           466: METHOD(listener_t, child_rekey, bool,
        !           467:        private_connmark_listener_t *this, ike_sa_t *ike_sa,
        !           468:        child_sa_t *old, child_sa_t *new)
        !           469: {
        !           470:        struct iptc_handle *ipth;
        !           471:        host_t *dst, *src;
        !           472:        bool oldencap, newencap;
        !           473: 
        !           474:        dst = ike_sa->get_my_host(ike_sa);
        !           475:        src = ike_sa->get_other_host(ike_sa);
        !           476:        oldencap = old->has_encap(old);
        !           477:        newencap = new->has_encap(new);
        !           478: 
        !           479:        if (handle_sa(old))
        !           480:        {
        !           481:                ipth = init_handle();
        !           482:                if (ipth)
        !           483:                {
        !           484:                        if (manage_policies(this, ipth, dst, src, oldencap, old, FALSE) &&
        !           485:                                manage_policies(this, ipth, dst, src, newencap, new, TRUE))
        !           486:                        {
        !           487:                                commit_handle(ipth);
        !           488:                        }
        !           489:                        iptc_free(ipth);
        !           490:                }
        !           491:        }
        !           492:        return TRUE;
        !           493: }
        !           494: 
        !           495: METHOD(listener_t, ike_update, bool,
        !           496:        private_connmark_listener_t *this, ike_sa_t *ike_sa,
        !           497:        bool local, host_t *new)
        !           498: {
        !           499:        struct iptc_handle *ipth;
        !           500:        enumerator_t *enumerator;
        !           501:        child_sa_t *child_sa;
        !           502:        host_t *dst, *src;
        !           503:        bool oldencap, newencap;
        !           504: 
        !           505:        if (local)
        !           506:        {
        !           507:                dst = new;
        !           508:                src = ike_sa->get_other_host(ike_sa);
        !           509:        }
        !           510:        else
        !           511:        {
        !           512:                dst = ike_sa->get_my_host(ike_sa);
        !           513:                src = new;
        !           514:        }
        !           515:        /* during ike_update(), has_encap() on the CHILD_SA has not yet been
        !           516:         * updated, but shows the old state. */
        !           517:        newencap = ike_sa->has_condition(ike_sa, COND_NAT_ANY);
        !           518: 
        !           519:        enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
        !           520:        while (enumerator->enumerate(enumerator, &child_sa))
        !           521:        {
        !           522:                if (handle_sa(child_sa))
        !           523:                {
        !           524:                        oldencap = child_sa->has_encap(child_sa);
        !           525:                        ipth = init_handle();
        !           526:                        if (ipth)
        !           527:                        {
        !           528:                                if (manage_policies(this, ipth, dst, src, oldencap,
        !           529:                                                                        child_sa, FALSE) &&
        !           530:                                        manage_policies(this, ipth, dst, src, newencap,
        !           531:                                                                        child_sa, TRUE))
        !           532:                                {
        !           533:                                        commit_handle(ipth);
        !           534:                                }
        !           535:                                iptc_free(ipth);
        !           536:                        }
        !           537:                }
        !           538:        }
        !           539:        enumerator->destroy(enumerator);
        !           540: 
        !           541:        return TRUE;
        !           542: }
        !           543: 
        !           544: METHOD(connmark_listener_t, destroy, void,
        !           545:        private_connmark_listener_t *this)
        !           546: {
        !           547:        free(this);
        !           548: }
        !           549: 
        !           550: /**
        !           551:  * See header
        !           552:  */
        !           553: connmark_listener_t *connmark_listener_create()
        !           554: {
        !           555:        private_connmark_listener_t *this;
        !           556: 
        !           557:        INIT(this,
        !           558:                .public = {
        !           559:                        .listener = {
        !           560:                                .ike_update = _ike_update,
        !           561:                                .child_updown = _child_updown,
        !           562:                                .child_rekey = _child_rekey,
        !           563:                        },
        !           564:                        .destroy = _destroy,
        !           565:                },
        !           566:        );
        !           567: 
        !           568:        return &this->public;
        !           569: }

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