Annotation of embedaddon/miniupnpd/minissdp.c, revision 1.1.1.1

1.1       misho       1: /* $Id: minissdp.c,v 1.19 2010/09/21 15:31:02 nanard Exp $ */
                      2: /* MiniUPnP project
                      3:  * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
                      4:  * (c) 2006-2010 Thomas Bernard
                      5:  * This software is subject to the conditions detailed
                      6:  * in the LICENCE file provided within the distribution */
                      7: 
                      8: #include <stdio.h>
                      9: #include <string.h>
                     10: #include <unistd.h>
                     11: #include <sys/socket.h>
                     12: #include <sys/un.h>
                     13: #include <netinet/in.h>
                     14: #include <arpa/inet.h>
                     15: #include <syslog.h>
                     16: #include "config.h"
                     17: #include "upnpdescstrings.h"
                     18: #include "miniupnpdpath.h"
                     19: #include "upnphttp.h"
                     20: #include "upnpglobalvars.h"
                     21: #include "minissdp.h"
                     22: #include "codelength.h"
                     23: 
                     24: /* SSDP ip/port */
                     25: #define SSDP_PORT (1900)
                     26: /* Prototypes */
                     27: void ProcessSSDPData(int s, char *bufr, struct sockaddr_in sendername, int n, unsigned short port) ;
                     28: 
                     29: 
                     30: #define SSDP_MCAST_ADDR ("239.255.255.250")
                     31: 
                     32: static int
                     33: AddMulticastMembership(int s, in_addr_t ifaddr)
                     34: {
                     35:        struct ip_mreq imr;     /* Ip multicast membership */
                     36: 
                     37:     /* setting up imr structure */
                     38:     imr.imr_multiaddr.s_addr = inet_addr(SSDP_MCAST_ADDR);
                     39:     /*imr.imr_interface.s_addr = htonl(INADDR_ANY);*/
                     40:     imr.imr_interface.s_addr = ifaddr; /*inet_addr(ifaddr);*/
                     41:        
                     42:        if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&imr, sizeof(struct ip_mreq)) < 0)
                     43:        {
                     44:         syslog(LOG_ERR, "setsockopt(udp, IP_ADD_MEMBERSHIP): %m");
                     45:                return -1;
                     46:     }
                     47: 
                     48:        return 0;
                     49: }
                     50: 
                     51: /* Open and configure the socket listening for 
                     52:  * SSDP udp packets sent on 239.255.255.250 port 1900 */
                     53: int
                     54: OpenAndConfSSDPReceiveSocket()
                     55: {
                     56:        int s;
                     57:        int i;
                     58:        int j = 1;
                     59:        struct sockaddr_in sockname;
                     60:        
                     61:        if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
                     62:        {
                     63:                syslog(LOG_ERR, "socket(udp): %m");
                     64:                return -1;
                     65:        }       
                     66:        
                     67:        memset(&sockname, 0, sizeof(struct sockaddr_in));
                     68:     sockname.sin_family = AF_INET;
                     69:     sockname.sin_port = htons(SSDP_PORT);
                     70:        /* NOTE : it seems it doesnt work when binding on the specific address */
                     71:     /*sockname.sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR);*/
                     72:     sockname.sin_addr.s_addr = htonl(INADDR_ANY);
                     73:     /*sockname.sin_addr.s_addr = inet_addr(ifaddr);*/
                     74: 
                     75:        if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j)) < 0)
                     76:        {
                     77:                syslog(LOG_WARNING, "setsockopt(udp, SO_REUSEADDR): %m");
                     78:        }
                     79: 
                     80: 
                     81:     if(bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
                     82:        {
                     83:                syslog(LOG_ERR, "bind(udp): %m");
                     84:                close(s);
                     85:                return -1;
                     86:     }
                     87: 
                     88:        i = n_lan_addr;
                     89:        while(i>0)
                     90:        {
                     91:                i--;
                     92:                if(AddMulticastMembership(s, lan_addr[i].addr.s_addr) < 0)
                     93:                {
                     94:                        syslog(LOG_WARNING,
                     95:                               "Failed to add multicast membership for address %s", 
                     96:                               lan_addr[i].str );
                     97:                }
                     98:        }
                     99: 
                    100:        return s;
                    101: }
                    102: 
                    103: /* open the UDP socket used to send SSDP notifications to
                    104:  * the multicast group reserved for them */
                    105: static int
                    106: OpenAndConfSSDPNotifySocket(in_addr_t addr)
                    107: {
                    108:        int s;
                    109:        unsigned char loopchar = 0;
                    110:        int bcast = 1;
                    111:        struct in_addr mc_if;
                    112:        struct sockaddr_in sockname;
                    113:        
                    114:        if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
                    115:        {
                    116:                syslog(LOG_ERR, "socket(udp_notify): %m");
                    117:                return -1;
                    118:        }
                    119: 
                    120:        mc_if.s_addr = addr;    /*inet_addr(addr);*/
                    121: 
                    122:        if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopchar, sizeof(loopchar)) < 0)
                    123:        {
                    124:                syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_LOOP): %m");
                    125:                close(s);
                    126:                return -1;
                    127:        }
                    128: 
                    129:        if(setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&mc_if, sizeof(mc_if)) < 0)
                    130:        {
                    131:                syslog(LOG_ERR, "setsockopt(udp_notify, IP_MULTICAST_IF): %m");
                    132:                close(s);
                    133:                return -1;
                    134:        }
                    135:        
                    136:        if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) < 0)
                    137:        {
                    138:                syslog(LOG_ERR, "setsockopt(udp_notify, SO_BROADCAST): %m");
                    139:                close(s);
                    140:                return -1;
                    141:        }
                    142: 
                    143:        memset(&sockname, 0, sizeof(struct sockaddr_in));
                    144:     sockname.sin_family = AF_INET;
                    145:     sockname.sin_addr.s_addr = addr;   /*inet_addr(addr);*/
                    146: 
                    147:     if (bind(s, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)) < 0)
                    148:        {
                    149:                syslog(LOG_ERR, "bind(udp_notify): %m");
                    150:                close(s);
                    151:                return -1;
                    152:     }
                    153: 
                    154:        return s;
                    155: }
                    156: 
                    157: int
                    158: OpenAndConfSSDPNotifySockets(int * sockets)
                    159: /*OpenAndConfSSDPNotifySockets(int * sockets,
                    160:                              struct lan_addr_s * lan_addr, int n_lan_addr)*/
                    161: {
                    162:        int i, j;
                    163:        for(i=0; i<n_lan_addr; i++)
                    164:        {
                    165:                sockets[i] = OpenAndConfSSDPNotifySocket(lan_addr[i].addr.s_addr);
                    166:                if(sockets[i] < 0)
                    167:                {
                    168:                        for(j=0; j<i; j++)
                    169:                        {
                    170:                                close(sockets[j]);
                    171:                                sockets[j] = -1;
                    172:                        }
                    173:                        return -1;
                    174:                }
                    175:        }
                    176:        return 0;
                    177: }
                    178: 
                    179: /*
                    180:  * response from a LiveBox (Wanadoo)
                    181: HTTP/1.1 200 OK
                    182: CACHE-CONTROL: max-age=1800
                    183: DATE: Thu, 01 Jan 1970 04:03:23 GMT
                    184: EXT:
                    185: LOCATION: http://192.168.0.1:49152/gatedesc.xml
                    186: SERVER: Linux/2.4.17, UPnP/1.0, Intel SDK for UPnP devices /1.2
                    187: ST: upnp:rootdevice
                    188: USN: uuid:75802409-bccb-40e7-8e6c-fa095ecce13e::upnp:rootdevice
                    189: 
                    190:  * response from a Linksys 802.11b :
                    191: HTTP/1.1 200 OK
                    192: Cache-Control:max-age=120
                    193: Location:http://192.168.5.1:5678/rootDesc.xml
                    194: Server:NT/5.0 UPnP/1.0
                    195: ST:upnp:rootdevice
                    196: USN:uuid:upnp-InternetGatewayDevice-1_0-0090a2777777::upnp:rootdevice
                    197: EXT:
                    198:  */
                    199: 
                    200: /* not really an SSDP "announce" as it is the response
                    201:  * to a SSDP "M-SEARCH" */
                    202: static void
                    203: SendSSDPAnnounce2(int s, struct sockaddr_in sockname,
                    204:                   const char * st, int st_len, const char * suffix,
                    205:                   const char * host, unsigned short port)
                    206: {
                    207:        int l, n;
                    208:        char buf[512];
                    209:        /* 
                    210:         * follow guideline from document "UPnP Device Architecture 1.0"
                    211:         * uppercase is recommended.
                    212:         * DATE: is recommended
                    213:         * SERVER: OS/ver UPnP/1.0 miniupnpd/1.0
                    214:         * - check what to put in the 'Cache-Control' header 
                    215:         * */
                    216:        l = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
                    217:                "CACHE-CONTROL: max-age=120\r\n"
                    218:                /*"DATE: ...\r\n"*/
                    219:                "ST: %.*s%s\r\n"
                    220:                "USN: %s::%.*s%s\r\n"
                    221:                "EXT:\r\n"
                    222:                "SERVER: " MINIUPNPD_SERVER_STRING "\r\n"
                    223:                "LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n"
                    224:                "\r\n",
                    225:                st_len, st, suffix,
                    226:                uuidvalue, st_len, st, suffix,
                    227:                host, (unsigned int)port);
                    228:        n = sendto(s, buf, l, 0,
                    229:                   (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
                    230:        syslog(LOG_INFO, "SSDP Announce %d bytes to %s:%d ST: %.*s",n,
                    231:                        inet_ntoa(sockname.sin_addr),
                    232:                ntohs(sockname.sin_port),
                    233:                l, buf);
                    234:        if(n < 0)
                    235:        {
                    236:                syslog(LOG_ERR, "sendto(udp): %m");
                    237:        }
                    238: }
                    239: 
                    240: static const char * const known_service_types[] =
                    241: {
                    242:        "upnp:rootdevice",
                    243:        "urn:schemas-upnp-org:device:InternetGatewayDevice:",
                    244:        "urn:schemas-upnp-org:device:WANConnectionDevice:",
                    245:        "urn:schemas-upnp-org:device:WANDevice:",
                    246:        "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:",
                    247:        "urn:schemas-upnp-org:service:WANIPConnection:",
                    248:        "urn:schemas-upnp-org:service:WANPPPConnection:",
                    249:        "urn:schemas-upnp-org:service:Layer3Forwarding:",
                    250:        0
                    251: };
                    252: 
                    253: static void
                    254: SendSSDPNotifies(int s, const char * host, unsigned short port,
                    255:                  unsigned int lifetime)
                    256: {
                    257:        struct sockaddr_in sockname;
                    258:        int l, n, i=0;
                    259:        char bufr[512];
                    260: 
                    261:        memset(&sockname, 0, sizeof(struct sockaddr_in));
                    262:        sockname.sin_family = AF_INET;
                    263:        sockname.sin_port = htons(SSDP_PORT);
                    264:        sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
                    265: 
                    266:        while(known_service_types[i])
                    267:        {
                    268:                l = snprintf(bufr, sizeof(bufr), 
                    269:                                "NOTIFY * HTTP/1.1\r\n"
                    270:                                "HOST:%s:%d\r\n"
                    271:                                "Cache-Control:max-age=%u\r\n"
                    272:                                "Location:http://%s:%d" ROOTDESC_PATH"\r\n"
                    273:                                /*"Server:miniupnpd/1.0 UPnP/1.0\r\n"*/
                    274:                                "Server: " MINIUPNPD_SERVER_STRING "\r\n"
                    275:                                "NT:%s%s\r\n"
                    276:                                "USN:%s::%s%s\r\n"
                    277:                                "NTS:ssdp:alive\r\n"
                    278:                                "\r\n",
                    279:                                SSDP_MCAST_ADDR, SSDP_PORT,
                    280:                                lifetime,
                    281:                                host, port,
                    282:                                known_service_types[i], (i==0?"":"1"),
                    283:                                uuidvalue, known_service_types[i], (i==0?"":"1") );
                    284:                if(l>=sizeof(bufr))
                    285:                {
                    286:                        syslog(LOG_WARNING, "SendSSDPNotifies(): truncated output");
                    287:                        l = sizeof(bufr);
                    288:                }
                    289:                n = sendto(s, bufr, l, 0,
                    290:                        (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
                    291:                if(n < 0)
                    292:                {
                    293:                        syslog(LOG_ERR, "sendto(udp_notify=%d, %s): %m", s, host);
                    294:                }
                    295:                i++;
                    296:        }
                    297: }
                    298: 
                    299: void
                    300: SendSSDPNotifies2(int * sockets,
                    301:                   unsigned short port,
                    302:                   unsigned int lifetime)
                    303: /*SendSSDPNotifies2(int * sockets, struct lan_addr_s * lan_addr, int n_lan_addr,
                    304:                   unsigned short port,
                    305:                   unsigned int lifetime)*/
                    306: {
                    307:        int i;
                    308:        for(i=0; i<n_lan_addr; i++)
                    309:        {
                    310:                SendSSDPNotifies(sockets[i], lan_addr[i].str, port, lifetime);
                    311:        }
                    312: }
                    313: 
                    314: /* ProcessSSDPRequest()
                    315:  * process SSDP M-SEARCH requests and responds to them */
                    316: void
                    317: ProcessSSDPRequest(int s, unsigned short port)
                    318: {
                    319:        int n;
                    320:        char bufr[1500];
                    321:        socklen_t len_r;
                    322:        struct sockaddr_in sendername;
                    323:        len_r = sizeof(struct sockaddr_in);
                    324: 
                    325:        n = recvfrom(s, bufr, sizeof(bufr), 0,
                    326:                     (struct sockaddr *)&sendername, &len_r);
                    327:        if(n < 0)
                    328:        {
                    329:                syslog(LOG_ERR, "recvfrom(udp): %m");
                    330:                return;
                    331:        }
                    332:        ProcessSSDPData(s, bufr, sendername, n, port);
                    333: 
                    334: }
                    335: 
                    336: void ProcessSSDPData(int s, char *bufr, struct sockaddr_in sendername, int n, unsigned short port) {
                    337:        int i, l;
                    338:        int lan_addr_index = 0;
                    339:        char * st = 0;
                    340:        int st_len = 0;
                    341: 
                    342: 
                    343:        if(memcmp(bufr, "NOTIFY", 6) == 0)
                    344:        {
                    345:                /* ignore NOTIFY packets. We could log the sender and device type */
                    346:                return;
                    347:        }
                    348:        else if(memcmp(bufr, "M-SEARCH", 8) == 0)
                    349:        {
                    350:                i = 0;
                    351:                while(i < n)
                    352:                {
                    353:                        while((i < n - 1) && (bufr[i] != '\r' || bufr[i+1] != '\n'))
                    354:                                i++;
                    355:                        i += 2;
                    356:                        if((i < n - 3) && (strncasecmp(bufr+i, "st:", 3) == 0))
                    357:                        {
                    358:                                st = bufr+i+3;
                    359:                                st_len = 0;
                    360:                                while((*st == ' ' || *st == '\t') && (st < bufr + n))
                    361:                                        st++;
                    362:                                while(st[st_len]!='\r' && st[st_len]!='\n'
                    363:                                     && (st + st_len < bufr + n))
                    364:                                        st_len++;
                    365:                                /*syslog(LOG_INFO, "ST: %.*s", st_len, st);*/
                    366:                                /*j = 0;*/
                    367:                                /*while(bufr[i+j]!='\r') j++;*/
                    368:                                /*syslog(LOG_INFO, "%.*s", j, bufr+i);*/
                    369:                        }
                    370:                }
                    371:                /*syslog(LOG_INFO, "SSDP M-SEARCH packet received from %s:%d",
                    372:                   inet_ntoa(sendername.sin_addr),
                    373:                   ntohs(sendername.sin_port) );*/
                    374:                if(st && (st_len > 0))
                    375:                {
                    376:                        /* TODO : doesnt answer at once but wait for a random time */
                    377:                        syslog(LOG_INFO, "SSDP M-SEARCH from %s:%d ST: %.*s",
                    378:                           inet_ntoa(sendername.sin_addr),
                    379:                           ntohs(sendername.sin_port),
                    380:                                   st_len, st);
                    381:                        /* find in which sub network the client is */
                    382:                        for(i = 0; i<n_lan_addr; i++)
                    383:                        {
                    384:                                if( (sendername.sin_addr.s_addr & lan_addr[i].mask.s_addr)
                    385:                                   == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
                    386:                                {
                    387:                                        lan_addr_index = i;
                    388:                                        break;
                    389:                                }
                    390:                        }
                    391:                        /* Responds to request with a device as ST header */
                    392:                        for(i = 0; known_service_types[i]; i++)
                    393:                        {
                    394:                                l = (int)strlen(known_service_types[i]);
                    395:                                if(l<=st_len && (0 == memcmp(st, known_service_types[i], l)))
                    396:                                {
                    397:                                        syslog(LOG_INFO, "Single search found");
                    398:                                        SendSSDPAnnounce2(s, sendername,
                    399:                                                          st, st_len, "",
                    400:                                                          lan_addr[lan_addr_index].str, port);
                    401:                                        break;
                    402:                                }
                    403:                        }
                    404:                        /* Responds to request with ST: ssdp:all */
                    405:                        /* strlen("ssdp:all") == 8 */
                    406:                        if(st_len==8 && (0 == memcmp(st, "ssdp:all", 8)))
                    407:                        {
                    408:                                syslog(LOG_INFO, "ssdp:all found");
                    409:                                for(i=0; known_service_types[i]; i++)
                    410:                                {
                    411:                                        l = (int)strlen(known_service_types[i]);
                    412:                                        SendSSDPAnnounce2(s, sendername,
                    413:                                                          known_service_types[i], l, i==0?"":"1",
                    414:                                                          lan_addr[lan_addr_index].str, port);
                    415:                                }
                    416:                        }
                    417:                        /* responds to request by UUID value */
                    418:                        l = (int)strlen(uuidvalue);
                    419:                        if(l==st_len && (0 == memcmp(st, uuidvalue, l)))
                    420:                        {
                    421:                                syslog(LOG_INFO, "ssdp:uuid found");
                    422:                                SendSSDPAnnounce2(s, sendername, st, st_len, "",
                    423:                                                  lan_addr[lan_addr_index].str, port);
                    424:                        }
                    425:                }
                    426:                else
                    427:                {
                    428:                        syslog(LOG_INFO, "Invalid SSDP M-SEARCH from %s:%d",
                    429:                           inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
                    430:                }
                    431:        }
                    432:        else
                    433:        {
                    434:                syslog(LOG_NOTICE, "Unknown udp packet received from %s:%d",
                    435:                       inet_ntoa(sendername.sin_addr), ntohs(sendername.sin_port));
                    436:        }
                    437: }
                    438: 
                    439: /* This will broadcast ssdp:byebye notifications to inform 
                    440:  * the network that UPnP is going down. */
                    441: int
                    442: SendSSDPGoodbye(int * sockets, int n_sockets)
                    443: {
                    444:        struct sockaddr_in sockname;
                    445:        int n, l;
                    446:        int i, j;
                    447:        char bufr[512];
                    448: 
                    449:     memset(&sockname, 0, sizeof(struct sockaddr_in));
                    450:     sockname.sin_family = AF_INET;
                    451:     sockname.sin_port = htons(SSDP_PORT);
                    452:     sockname.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
                    453: 
                    454:        for(j=0; j<n_sockets; j++)
                    455:        {
                    456:            for(i=0; known_service_types[i]; i++)
                    457:            {
                    458:                l = snprintf(bufr, sizeof(bufr),
                    459:                         "NOTIFY * HTTP/1.1\r\n"
                    460:                         "HOST:%s:%d\r\n"
                    461:                         "NT:%s%s\r\n"
                    462:                         "USN:%s::%s%s\r\n"
                    463:                         "NTS:ssdp:byebye\r\n"
                    464:                         "\r\n",
                    465:                         SSDP_MCAST_ADDR, SSDP_PORT,
                    466:                                         known_service_types[i], (i==0?"":"1"),
                    467:                         uuidvalue, known_service_types[i], (i==0?"":"1"));
                    468:                n = sendto(sockets[j], bufr, l, 0,
                    469:                           (struct sockaddr *)&sockname, sizeof(struct sockaddr_in) );
                    470:                        if(n < 0)
                    471:                        {
                    472:                                syslog(LOG_ERR, "SendSSDPGoodbye: sendto(udp_shutdown=%d): %m",
                    473:                                       sockets[j]);
                    474:                                return -1;
                    475:                        }
                    476:        }
                    477:        }
                    478:        return 0;
                    479: }
                    480: 
                    481: /* SubmitServicesToMiniSSDPD() :
                    482:  * register services offered by MiniUPnPd to a running instance of
                    483:  * MiniSSDPd */
                    484: int
                    485: SubmitServicesToMiniSSDPD(const char * host, unsigned short port) {
                    486:        struct sockaddr_un addr;
                    487:        int s;
                    488:        unsigned char buffer[2048];
                    489:        char strbuf[256];
                    490:        unsigned char * p;
                    491:        int i, l;
                    492: 
                    493:        s = socket(AF_UNIX, SOCK_STREAM, 0);
                    494:        if(s < 0) {
                    495:                syslog(LOG_ERR, "socket(unix): %m");
                    496:                return -1;
                    497:        }
                    498:        addr.sun_family = AF_UNIX;
                    499:        strncpy(addr.sun_path, minissdpdsocketpath, sizeof(addr.sun_path));
                    500:        if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
                    501:                syslog(LOG_ERR, "connect(\"%s\"): %m", minissdpdsocketpath);
                    502:                return -1;
                    503:        }
                    504:        for(i = 0; known_service_types[i]; i++) {
                    505:                buffer[0] = 4;
                    506:                p = buffer + 1;
                    507:                l = (int)strlen(known_service_types[i]);
                    508:                if(i > 0)
                    509:                        l++;
                    510:                CODELENGTH(l, p);
                    511:                memcpy(p, known_service_types[i], l);
                    512:                if(i > 0)
                    513:                        p[l-1] = '1';
                    514:                p += l;
                    515:                l = snprintf(strbuf, sizeof(strbuf), "%s::%s%s", 
                    516:                             uuidvalue, known_service_types[i], (i==0)?"":"1");
                    517:                CODELENGTH(l, p);
                    518:                memcpy(p, strbuf, l);
                    519:                p += l;
                    520:                l = (int)strlen(MINIUPNPD_SERVER_STRING);
                    521:                CODELENGTH(l, p);
                    522:                memcpy(p, MINIUPNPD_SERVER_STRING, l);
                    523:                p += l;
                    524:                l = snprintf(strbuf, sizeof(strbuf), "http://%s:%u" ROOTDESC_PATH,
                    525:                             host, (unsigned int)port);
                    526:                CODELENGTH(l, p);
                    527:                memcpy(p, strbuf, l);
                    528:                p += l;
                    529:                if(write(s, buffer, p - buffer) < 0) {
                    530:                        syslog(LOG_ERR, "write(): %m");
                    531:                        return -1;
                    532:                }
                    533:        }
                    534:        close(s);
                    535:        return 0;
                    536: }
                    537: 

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