1: /* $Id: upnpcommands.c,v 1.1.1.1 2023/09/27 11:25:11 misho Exp $ */
2: /* vim: tabstop=4 shiftwidth=4 noexpandtab
3: * Project : miniupnp
4: * Author : Thomas Bernard
5: * Copyright (c) 2005-2018 Thomas Bernard
6: * This software is subject to the conditions detailed in the
7: * LICENCE file provided in this distribution.
8: * */
9: #include <stdlib.h>
10: #include <stdio.h>
11: #include <string.h>
12: #include "upnpcommands.h"
13: #include "miniupnpc.h"
14: #include "portlistingparse.h"
15: #include "upnpreplyparse.h"
16:
17: static UNSIGNED_INTEGER
18: my_atoui(const char * s)
19: {
20: return s ? ((UNSIGNED_INTEGER)STRTOUI(s, NULL, 0)) : 0;
21: }
22:
23: /*
24: * */
25: MINIUPNP_LIBSPEC UNSIGNED_INTEGER
26: UPNP_GetTotalBytesSent(const char * controlURL,
27: const char * servicetype)
28: {
29: struct NameValueParserData pdata;
30: char * buffer;
31: int bufsize;
32: unsigned int r = 0;
33: char * p;
34: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
35: "GetTotalBytesSent", 0, &bufsize))) {
36: return (UNSIGNED_INTEGER)UPNPCOMMAND_HTTP_ERROR;
37: }
38: ParseNameValue(buffer, bufsize, &pdata);
39: /*DisplayNameValueList(buffer, bufsize);*/
40: free(buffer);
41: p = GetValueFromNameValueList(&pdata, "NewTotalBytesSent");
42: r = my_atoui(p);
43: ClearNameValueList(&pdata);
44: return r;
45: }
46:
47: /*
48: * */
49: MINIUPNP_LIBSPEC UNSIGNED_INTEGER
50: UPNP_GetTotalBytesReceived(const char * controlURL,
51: const char * servicetype)
52: {
53: struct NameValueParserData pdata;
54: char * buffer;
55: int bufsize;
56: unsigned int r = 0;
57: char * p;
58: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
59: "GetTotalBytesReceived", 0, &bufsize))) {
60: return (UNSIGNED_INTEGER)UPNPCOMMAND_HTTP_ERROR;
61: }
62: ParseNameValue(buffer, bufsize, &pdata);
63: /*DisplayNameValueList(buffer, bufsize);*/
64: free(buffer);
65: p = GetValueFromNameValueList(&pdata, "NewTotalBytesReceived");
66: r = my_atoui(p);
67: ClearNameValueList(&pdata);
68: return r;
69: }
70:
71: /*
72: * */
73: MINIUPNP_LIBSPEC UNSIGNED_INTEGER
74: UPNP_GetTotalPacketsSent(const char * controlURL,
75: const char * servicetype)
76: {
77: struct NameValueParserData pdata;
78: char * buffer;
79: int bufsize;
80: unsigned int r = 0;
81: char * p;
82: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
83: "GetTotalPacketsSent", 0, &bufsize))) {
84: return (UNSIGNED_INTEGER)UPNPCOMMAND_HTTP_ERROR;
85: }
86: ParseNameValue(buffer, bufsize, &pdata);
87: /*DisplayNameValueList(buffer, bufsize);*/
88: free(buffer);
89: p = GetValueFromNameValueList(&pdata, "NewTotalPacketsSent");
90: r = my_atoui(p);
91: ClearNameValueList(&pdata);
92: return r;
93: }
94:
95: /*
96: * */
97: MINIUPNP_LIBSPEC UNSIGNED_INTEGER
98: UPNP_GetTotalPacketsReceived(const char * controlURL,
99: const char * servicetype)
100: {
101: struct NameValueParserData pdata;
102: char * buffer;
103: int bufsize;
104: unsigned int r = 0;
105: char * p;
106: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
107: "GetTotalPacketsReceived", 0, &bufsize))) {
108: return (UNSIGNED_INTEGER)UPNPCOMMAND_HTTP_ERROR;
109: }
110: ParseNameValue(buffer, bufsize, &pdata);
111: /*DisplayNameValueList(buffer, bufsize);*/
112: free(buffer);
113: p = GetValueFromNameValueList(&pdata, "NewTotalPacketsReceived");
114: r = my_atoui(p);
115: ClearNameValueList(&pdata);
116: return r;
117: }
118:
119: /* UPNP_GetStatusInfo() call the corresponding UPNP method
120: * returns the current status and uptime */
121: MINIUPNP_LIBSPEC int
122: UPNP_GetStatusInfo(const char * controlURL,
123: const char * servicetype,
124: char * status,
125: unsigned int * uptime,
126: char * lastconnerror)
127: {
128: struct NameValueParserData pdata;
129: char * buffer;
130: int bufsize;
131: char * p;
132: char * up;
133: char * err;
134: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
135:
136: if(!status && !uptime)
137: return UPNPCOMMAND_INVALID_ARGS;
138:
139: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
140: "GetStatusInfo", 0, &bufsize))) {
141: return UPNPCOMMAND_HTTP_ERROR;
142: }
143: ParseNameValue(buffer, bufsize, &pdata);
144: /*DisplayNameValueList(buffer, bufsize);*/
145: free(buffer);
146: up = GetValueFromNameValueList(&pdata, "NewUptime");
147: p = GetValueFromNameValueList(&pdata, "NewConnectionStatus");
148: err = GetValueFromNameValueList(&pdata, "NewLastConnectionError");
149: if(p && up)
150: ret = UPNPCOMMAND_SUCCESS;
151:
152: if(status) {
153: if(p){
154: strncpy(status, p, 64 );
155: status[63] = '\0';
156: }else
157: status[0]= '\0';
158: }
159:
160: if(uptime) {
161: if(up)
162: sscanf(up,"%u",uptime);
163: else
164: *uptime = 0;
165: }
166:
167: if(lastconnerror) {
168: if(err) {
169: strncpy(lastconnerror, err, 64 );
170: lastconnerror[63] = '\0';
171: } else
172: lastconnerror[0] = '\0';
173: }
174:
175: p = GetValueFromNameValueList(&pdata, "errorCode");
176: if(p) {
177: ret = UPNPCOMMAND_UNKNOWN_ERROR;
178: sscanf(p, "%d", &ret);
179: }
180: ClearNameValueList(&pdata);
181: return ret;
182: }
183:
184: /* UPNP_GetConnectionTypeInfo() call the corresponding UPNP method
185: * returns the connection type */
186: MINIUPNP_LIBSPEC int
187: UPNP_GetConnectionTypeInfo(const char * controlURL,
188: const char * servicetype,
189: char * connectionType)
190: {
191: struct NameValueParserData pdata;
192: char * buffer;
193: int bufsize;
194: char * p;
195: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
196:
197: if(!connectionType)
198: return UPNPCOMMAND_INVALID_ARGS;
199:
200: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
201: "GetConnectionTypeInfo", 0, &bufsize))) {
202: return UPNPCOMMAND_HTTP_ERROR;
203: }
204: ParseNameValue(buffer, bufsize, &pdata);
205: free(buffer);
206: p = GetValueFromNameValueList(&pdata, "NewConnectionType");
207: /*p = GetValueFromNameValueList(&pdata, "NewPossibleConnectionTypes");*/
208: /* PossibleConnectionTypes will have several values.... */
209: if(p) {
210: strncpy(connectionType, p, 64 );
211: connectionType[63] = '\0';
212: ret = UPNPCOMMAND_SUCCESS;
213: } else
214: connectionType[0] = '\0';
215: p = GetValueFromNameValueList(&pdata, "errorCode");
216: if(p) {
217: ret = UPNPCOMMAND_UNKNOWN_ERROR;
218: sscanf(p, "%d", &ret);
219: }
220: ClearNameValueList(&pdata);
221: return ret;
222: }
223:
224: /* UPNP_GetLinkLayerMaxBitRate() call the corresponding UPNP method.
225: * Returns 2 values: Downloadlink bandwidth and Uplink bandwidth.
226: * One of the values can be null
227: * Note : GetLinkLayerMaxBitRates belongs to WANPPPConnection:1 only
228: * We can use the GetCommonLinkProperties from WANCommonInterfaceConfig:1 */
229: MINIUPNP_LIBSPEC int
230: UPNP_GetLinkLayerMaxBitRates(const char * controlURL,
231: const char * servicetype,
232: unsigned int * bitrateDown,
233: unsigned int * bitrateUp)
234: {
235: struct NameValueParserData pdata;
236: char * buffer;
237: int bufsize;
238: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
239: char * down;
240: char * up;
241: char * p;
242:
243: if(!bitrateDown && !bitrateUp)
244: return UPNPCOMMAND_INVALID_ARGS;
245:
246: /* shouldn't we use GetCommonLinkProperties ? */
247: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
248: "GetCommonLinkProperties", 0, &bufsize))) {
249: /*"GetLinkLayerMaxBitRates", 0, &bufsize);*/
250: return UPNPCOMMAND_HTTP_ERROR;
251: }
252: /*DisplayNameValueList(buffer, bufsize);*/
253: ParseNameValue(buffer, bufsize, &pdata);
254: free(buffer);
255: /*down = GetValueFromNameValueList(&pdata, "NewDownstreamMaxBitRate");*/
256: /*up = GetValueFromNameValueList(&pdata, "NewUpstreamMaxBitRate");*/
257: down = GetValueFromNameValueList(&pdata, "NewLayer1DownstreamMaxBitRate");
258: up = GetValueFromNameValueList(&pdata, "NewLayer1UpstreamMaxBitRate");
259: /*GetValueFromNameValueList(&pdata, "NewWANAccessType");*/
260: /*GetValueFromNameValueList(&pdata, "NewPhysicalLinkStatus");*/
261: if(down && up)
262: ret = UPNPCOMMAND_SUCCESS;
263:
264: if(bitrateDown) {
265: if(down)
266: sscanf(down,"%u",bitrateDown);
267: else
268: *bitrateDown = 0;
269: }
270:
271: if(bitrateUp) {
272: if(up)
273: sscanf(up,"%u",bitrateUp);
274: else
275: *bitrateUp = 0;
276: }
277: p = GetValueFromNameValueList(&pdata, "errorCode");
278: if(p) {
279: ret = UPNPCOMMAND_UNKNOWN_ERROR;
280: sscanf(p, "%d", &ret);
281: }
282: ClearNameValueList(&pdata);
283: return ret;
284: }
285:
286:
287: /* UPNP_GetExternalIPAddress() call the corresponding UPNP method.
288: * if the third arg is not null the value is copied to it.
289: * at least 16 bytes must be available
290: *
291: * Return values :
292: * 0 : SUCCESS
293: * NON ZERO : ERROR Either an UPnP error code or an unknown error.
294: *
295: * 402 Invalid Args - See UPnP Device Architecture section on Control.
296: * 501 Action Failed - See UPnP Device Architecture section on Control.
297: */
298: MINIUPNP_LIBSPEC int
299: UPNP_GetExternalIPAddress(const char * controlURL,
300: const char * servicetype,
301: char * extIpAdd)
302: {
303: struct NameValueParserData pdata;
304: char * buffer;
305: int bufsize;
306: char * p;
307: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
308:
309: if(!extIpAdd || !controlURL || !servicetype)
310: return UPNPCOMMAND_INVALID_ARGS;
311:
312: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
313: "GetExternalIPAddress", 0, &bufsize))) {
314: return UPNPCOMMAND_HTTP_ERROR;
315: }
316: /*DisplayNameValueList(buffer, bufsize);*/
317: ParseNameValue(buffer, bufsize, &pdata);
318: free(buffer);
319: /*printf("external ip = %s\n", GetValueFromNameValueList(&pdata, "NewExternalIPAddress") );*/
320: p = GetValueFromNameValueList(&pdata, "NewExternalIPAddress");
321: if(p) {
322: strncpy(extIpAdd, p, 16 );
323: extIpAdd[15] = '\0';
324: ret = UPNPCOMMAND_SUCCESS;
325: } else
326: extIpAdd[0] = '\0';
327:
328: p = GetValueFromNameValueList(&pdata, "errorCode");
329: if(p) {
330: ret = UPNPCOMMAND_UNKNOWN_ERROR;
331: sscanf(p, "%d", &ret);
332: }
333:
334: ClearNameValueList(&pdata);
335: return ret;
336: }
337:
338: MINIUPNP_LIBSPEC int
339: UPNP_AddPortMapping(const char * controlURL, const char * servicetype,
340: const char * extPort,
341: const char * inPort,
342: const char * inClient,
343: const char * desc,
344: const char * proto,
345: const char * remoteHost,
346: const char * leaseDuration)
347: {
348: struct UPNParg * AddPortMappingArgs;
349: char * buffer;
350: int bufsize;
351: struct NameValueParserData pdata;
352: const char * resVal;
353: int ret;
354:
355: if(!inPort || !inClient || !proto || !extPort)
356: return UPNPCOMMAND_INVALID_ARGS;
357:
358: AddPortMappingArgs = calloc(9, sizeof(struct UPNParg));
359: if(AddPortMappingArgs == NULL)
360: return UPNPCOMMAND_MEM_ALLOC_ERROR;
361: AddPortMappingArgs[0].elt = "NewRemoteHost";
362: AddPortMappingArgs[0].val = remoteHost;
363: AddPortMappingArgs[1].elt = "NewExternalPort";
364: AddPortMappingArgs[1].val = extPort;
365: AddPortMappingArgs[2].elt = "NewProtocol";
366: AddPortMappingArgs[2].val = proto;
367: AddPortMappingArgs[3].elt = "NewInternalPort";
368: AddPortMappingArgs[3].val = inPort;
369: AddPortMappingArgs[4].elt = "NewInternalClient";
370: AddPortMappingArgs[4].val = inClient;
371: AddPortMappingArgs[5].elt = "NewEnabled";
372: AddPortMappingArgs[5].val = "1";
373: AddPortMappingArgs[6].elt = "NewPortMappingDescription";
374: AddPortMappingArgs[6].val = desc?desc:"libminiupnpc";
375: AddPortMappingArgs[7].elt = "NewLeaseDuration";
376: AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0";
377: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
378: "AddPortMapping", AddPortMappingArgs,
379: &bufsize);
380: free(AddPortMappingArgs);
381: if(!buffer) {
382: return UPNPCOMMAND_HTTP_ERROR;
383: }
384: /*DisplayNameValueList(buffer, bufsize);*/
385: /*buffer[bufsize] = '\0';*/
386: /*puts(buffer);*/
387: ParseNameValue(buffer, bufsize, &pdata);
388: free(buffer);
389: resVal = GetValueFromNameValueList(&pdata, "errorCode");
390: if(resVal) {
391: /*printf("AddPortMapping errorCode = '%s'\n", resVal); */
392: ret = UPNPCOMMAND_UNKNOWN_ERROR;
393: sscanf(resVal, "%d", &ret);
394: } else {
395: ret = UPNPCOMMAND_SUCCESS;
396: }
397: ClearNameValueList(&pdata);
398: return ret;
399: }
400:
401: MINIUPNP_LIBSPEC int
402: UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype,
403: const char * extPort,
404: const char * inPort,
405: const char * inClient,
406: const char * desc,
407: const char * proto,
408: const char * remoteHost,
409: const char * leaseDuration,
410: char * reservedPort)
411: {
412: struct UPNParg * AddPortMappingArgs;
413: char * buffer;
414: int bufsize;
415: struct NameValueParserData pdata;
416: const char * resVal;
417: int ret;
418:
419: if(!inPort || !inClient || !proto || !extPort)
420: return UPNPCOMMAND_INVALID_ARGS;
421:
422: AddPortMappingArgs = calloc(9, sizeof(struct UPNParg));
423: if(AddPortMappingArgs == NULL)
424: return UPNPCOMMAND_MEM_ALLOC_ERROR;
425: AddPortMappingArgs[0].elt = "NewRemoteHost";
426: AddPortMappingArgs[0].val = remoteHost;
427: AddPortMappingArgs[1].elt = "NewExternalPort";
428: AddPortMappingArgs[1].val = extPort;
429: AddPortMappingArgs[2].elt = "NewProtocol";
430: AddPortMappingArgs[2].val = proto;
431: AddPortMappingArgs[3].elt = "NewInternalPort";
432: AddPortMappingArgs[3].val = inPort;
433: AddPortMappingArgs[4].elt = "NewInternalClient";
434: AddPortMappingArgs[4].val = inClient;
435: AddPortMappingArgs[5].elt = "NewEnabled";
436: AddPortMappingArgs[5].val = "1";
437: AddPortMappingArgs[6].elt = "NewPortMappingDescription";
438: AddPortMappingArgs[6].val = desc?desc:"libminiupnpc";
439: AddPortMappingArgs[7].elt = "NewLeaseDuration";
440: AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0";
441: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
442: "AddAnyPortMapping", AddPortMappingArgs,
443: &bufsize);
444: free(AddPortMappingArgs);
445: if(!buffer) {
446: return UPNPCOMMAND_HTTP_ERROR;
447: }
448: ParseNameValue(buffer, bufsize, &pdata);
449: free(buffer);
450: resVal = GetValueFromNameValueList(&pdata, "errorCode");
451: if(resVal) {
452: ret = UPNPCOMMAND_UNKNOWN_ERROR;
453: sscanf(resVal, "%d", &ret);
454: } else {
455: char *p;
456:
457: p = GetValueFromNameValueList(&pdata, "NewReservedPort");
458: if(p) {
459: strncpy(reservedPort, p, 6);
460: reservedPort[5] = '\0';
461: ret = UPNPCOMMAND_SUCCESS;
462: } else {
463: ret = UPNPCOMMAND_INVALID_RESPONSE;
464: }
465: }
466: ClearNameValueList(&pdata);
467: return ret;
468: }
469:
470: MINIUPNP_LIBSPEC int
471: UPNP_DeletePortMapping(const char * controlURL, const char * servicetype,
472: const char * extPort, const char * proto,
473: const char * remoteHost)
474: {
475: /*struct NameValueParserData pdata;*/
476: struct UPNParg * DeletePortMappingArgs;
477: char * buffer;
478: int bufsize;
479: struct NameValueParserData pdata;
480: const char * resVal;
481: int ret;
482:
483: if(!extPort || !proto)
484: return UPNPCOMMAND_INVALID_ARGS;
485:
486: DeletePortMappingArgs = calloc(4, sizeof(struct UPNParg));
487: if(DeletePortMappingArgs == NULL)
488: return UPNPCOMMAND_MEM_ALLOC_ERROR;
489: DeletePortMappingArgs[0].elt = "NewRemoteHost";
490: DeletePortMappingArgs[0].val = remoteHost;
491: DeletePortMappingArgs[1].elt = "NewExternalPort";
492: DeletePortMappingArgs[1].val = extPort;
493: DeletePortMappingArgs[2].elt = "NewProtocol";
494: DeletePortMappingArgs[2].val = proto;
495: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
496: "DeletePortMapping",
497: DeletePortMappingArgs, &bufsize);
498: free(DeletePortMappingArgs);
499: if(!buffer) {
500: return UPNPCOMMAND_HTTP_ERROR;
501: }
502: /*DisplayNameValueList(buffer, bufsize);*/
503: ParseNameValue(buffer, bufsize, &pdata);
504: free(buffer);
505: resVal = GetValueFromNameValueList(&pdata, "errorCode");
506: if(resVal) {
507: ret = UPNPCOMMAND_UNKNOWN_ERROR;
508: sscanf(resVal, "%d", &ret);
509: } else {
510: ret = UPNPCOMMAND_SUCCESS;
511: }
512: ClearNameValueList(&pdata);
513: return ret;
514: }
515:
516: MINIUPNP_LIBSPEC int
517: UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype,
518: const char * extPortStart, const char * extPortEnd,
519: const char * proto,
520: const char * manage)
521: {
522: struct UPNParg * DeletePortMappingArgs;
523: char * buffer;
524: int bufsize;
525: struct NameValueParserData pdata;
526: const char * resVal;
527: int ret;
528:
529: if(!extPortStart || !extPortEnd || !proto || !manage)
530: return UPNPCOMMAND_INVALID_ARGS;
531:
532: DeletePortMappingArgs = calloc(5, sizeof(struct UPNParg));
533: if(DeletePortMappingArgs == NULL)
534: return UPNPCOMMAND_MEM_ALLOC_ERROR;
535: DeletePortMappingArgs[0].elt = "NewStartPort";
536: DeletePortMappingArgs[0].val = extPortStart;
537: DeletePortMappingArgs[1].elt = "NewEndPort";
538: DeletePortMappingArgs[1].val = extPortEnd;
539: DeletePortMappingArgs[2].elt = "NewProtocol";
540: DeletePortMappingArgs[2].val = proto;
541: DeletePortMappingArgs[3].elt = "NewManage";
542: DeletePortMappingArgs[3].val = manage;
543:
544: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
545: "DeletePortMappingRange",
546: DeletePortMappingArgs, &bufsize);
547: free(DeletePortMappingArgs);
548: if(!buffer) {
549: return UPNPCOMMAND_HTTP_ERROR;
550: }
551: ParseNameValue(buffer, bufsize, &pdata);
552: free(buffer);
553: resVal = GetValueFromNameValueList(&pdata, "errorCode");
554: if(resVal) {
555: ret = UPNPCOMMAND_UNKNOWN_ERROR;
556: sscanf(resVal, "%d", &ret);
557: } else {
558: ret = UPNPCOMMAND_SUCCESS;
559: }
560: ClearNameValueList(&pdata);
561: return ret;
562: }
563:
564: MINIUPNP_LIBSPEC int
565: UPNP_GetGenericPortMappingEntry(const char * controlURL,
566: const char * servicetype,
567: const char * index,
568: char * extPort,
569: char * intClient,
570: char * intPort,
571: char * protocol,
572: char * desc,
573: char * enabled,
574: char * rHost,
575: char * duration)
576: {
577: struct NameValueParserData pdata;
578: struct UPNParg * GetPortMappingArgs;
579: char * buffer;
580: int bufsize;
581: char * p;
582: int r = UPNPCOMMAND_UNKNOWN_ERROR;
583: if(!index)
584: return UPNPCOMMAND_INVALID_ARGS;
585: intClient[0] = '\0';
586: intPort[0] = '\0';
587: GetPortMappingArgs = calloc(2, sizeof(struct UPNParg));
588: if(GetPortMappingArgs == NULL)
589: return UPNPCOMMAND_MEM_ALLOC_ERROR;
590: GetPortMappingArgs[0].elt = "NewPortMappingIndex";
591: GetPortMappingArgs[0].val = index;
592: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
593: "GetGenericPortMappingEntry",
594: GetPortMappingArgs, &bufsize);
595: free(GetPortMappingArgs);
596: if(!buffer) {
597: return UPNPCOMMAND_HTTP_ERROR;
598: }
599: ParseNameValue(buffer, bufsize, &pdata);
600: free(buffer);
601:
602: p = GetValueFromNameValueList(&pdata, "NewRemoteHost");
603: if(p && rHost)
604: {
605: strncpy(rHost, p, 64);
606: rHost[63] = '\0';
607: }
608: p = GetValueFromNameValueList(&pdata, "NewExternalPort");
609: if(p && extPort)
610: {
611: strncpy(extPort, p, 6);
612: extPort[5] = '\0';
613: r = UPNPCOMMAND_SUCCESS;
614: }
615: p = GetValueFromNameValueList(&pdata, "NewProtocol");
616: if(p && protocol)
617: {
618: strncpy(protocol, p, 4);
619: protocol[3] = '\0';
620: }
621: p = GetValueFromNameValueList(&pdata, "NewInternalClient");
622: if(p)
623: {
624: strncpy(intClient, p, 16);
625: intClient[15] = '\0';
626: r = 0;
627: }
628: p = GetValueFromNameValueList(&pdata, "NewInternalPort");
629: if(p)
630: {
631: strncpy(intPort, p, 6);
632: intPort[5] = '\0';
633: }
634: p = GetValueFromNameValueList(&pdata, "NewEnabled");
635: if(p && enabled)
636: {
637: strncpy(enabled, p, 4);
638: enabled[3] = '\0';
639: }
640: p = GetValueFromNameValueList(&pdata, "NewPortMappingDescription");
641: if(p && desc)
642: {
643: strncpy(desc, p, 80);
644: desc[79] = '\0';
645: }
646: p = GetValueFromNameValueList(&pdata, "NewLeaseDuration");
647: if(p && duration)
648: {
649: strncpy(duration, p, 16);
650: duration[15] = '\0';
651: }
652: p = GetValueFromNameValueList(&pdata, "errorCode");
653: if(p) {
654: r = UPNPCOMMAND_UNKNOWN_ERROR;
655: sscanf(p, "%d", &r);
656: }
657: ClearNameValueList(&pdata);
658: return r;
659: }
660:
661: MINIUPNP_LIBSPEC int
662: UPNP_GetPortMappingNumberOfEntries(const char * controlURL,
663: const char * servicetype,
664: unsigned int * numEntries)
665: {
666: struct NameValueParserData pdata;
667: char * buffer;
668: int bufsize;
669: char* p;
670: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
671: if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype,
672: "GetPortMappingNumberOfEntries", 0,
673: &bufsize))) {
674: return UPNPCOMMAND_HTTP_ERROR;
675: }
676: #ifdef DEBUG
677: DisplayNameValueList(buffer, bufsize);
678: #endif
679: ParseNameValue(buffer, bufsize, &pdata);
680: free(buffer);
681:
682: p = GetValueFromNameValueList(&pdata, "NewPortMappingNumberOfEntries");
683: if(numEntries && p) {
684: *numEntries = 0;
685: sscanf(p, "%u", numEntries);
686: ret = UPNPCOMMAND_SUCCESS;
687: }
688:
689: p = GetValueFromNameValueList(&pdata, "errorCode");
690: if(p) {
691: ret = UPNPCOMMAND_UNKNOWN_ERROR;
692: sscanf(p, "%d", &ret);
693: }
694:
695: ClearNameValueList(&pdata);
696: return ret;
697: }
698:
699: /* UPNP_GetSpecificPortMappingEntry retrieves an existing port mapping
700: * the result is returned in the intClient and intPort strings
701: * please provide 16 and 6 bytes of data */
702: MINIUPNP_LIBSPEC int
703: UPNP_GetSpecificPortMappingEntry(const char * controlURL,
704: const char * servicetype,
705: const char * extPort,
706: const char * proto,
707: const char * remoteHost,
708: char * intClient,
709: char * intPort,
710: char * desc,
711: char * enabled,
712: char * leaseDuration)
713: {
714: struct NameValueParserData pdata;
715: struct UPNParg * GetPortMappingArgs;
716: char * buffer;
717: int bufsize;
718: char * p;
719: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
720:
721: if(!intPort || !intClient || !extPort || !proto)
722: return UPNPCOMMAND_INVALID_ARGS;
723:
724: GetPortMappingArgs = calloc(4, sizeof(struct UPNParg));
725: if(GetPortMappingArgs == NULL)
726: return UPNPCOMMAND_MEM_ALLOC_ERROR;
727: GetPortMappingArgs[0].elt = "NewRemoteHost";
728: GetPortMappingArgs[0].val = remoteHost;
729: GetPortMappingArgs[1].elt = "NewExternalPort";
730: GetPortMappingArgs[1].val = extPort;
731: GetPortMappingArgs[2].elt = "NewProtocol";
732: GetPortMappingArgs[2].val = proto;
733: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
734: "GetSpecificPortMappingEntry",
735: GetPortMappingArgs, &bufsize);
736: free(GetPortMappingArgs);
737: if(!buffer) {
738: return UPNPCOMMAND_HTTP_ERROR;
739: }
740: /*DisplayNameValueList(buffer, bufsize);*/
741: ParseNameValue(buffer, bufsize, &pdata);
742: free(buffer);
743:
744: p = GetValueFromNameValueList(&pdata, "NewInternalClient");
745: if(p) {
746: strncpy(intClient, p, 16);
747: intClient[15] = '\0';
748: ret = UPNPCOMMAND_SUCCESS;
749: } else
750: intClient[0] = '\0';
751:
752: p = GetValueFromNameValueList(&pdata, "NewInternalPort");
753: if(p) {
754: strncpy(intPort, p, 6);
755: intPort[5] = '\0';
756: } else
757: intPort[0] = '\0';
758:
759: p = GetValueFromNameValueList(&pdata, "NewEnabled");
760: if(p && enabled) {
761: strncpy(enabled, p, 4);
762: enabled[3] = '\0';
763: }
764:
765: p = GetValueFromNameValueList(&pdata, "NewPortMappingDescription");
766: if(p && desc) {
767: strncpy(desc, p, 80);
768: desc[79] = '\0';
769: }
770:
771: p = GetValueFromNameValueList(&pdata, "NewLeaseDuration");
772: if(p && leaseDuration)
773: {
774: strncpy(leaseDuration, p, 16);
775: leaseDuration[15] = '\0';
776: }
777:
778: p = GetValueFromNameValueList(&pdata, "errorCode");
779: if(p) {
780: ret = UPNPCOMMAND_UNKNOWN_ERROR;
781: sscanf(p, "%d", &ret);
782: }
783:
784: ClearNameValueList(&pdata);
785: return ret;
786: }
787:
788: /* UPNP_GetListOfPortMappings()
789: *
790: * Possible UPNP Error codes :
791: * 606 Action not Authorized
792: * 730 PortMappingNotFound - no port mapping is found in the specified range.
793: * 733 InconsistantParameters - NewStartPort and NewEndPort values are not
794: * consistent.
795: */
796: MINIUPNP_LIBSPEC int
797: UPNP_GetListOfPortMappings(const char * controlURL,
798: const char * servicetype,
799: const char * startPort,
800: const char * endPort,
801: const char * protocol,
802: const char * numberOfPorts,
803: struct PortMappingParserData * data)
804: {
805: struct NameValueParserData pdata;
806: struct UPNParg * GetListOfPortMappingsArgs;
807: const char * p;
808: char * buffer;
809: int bufsize;
810: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
811:
812: if(!startPort || !endPort || !protocol)
813: return UPNPCOMMAND_INVALID_ARGS;
814:
815: GetListOfPortMappingsArgs = calloc(6, sizeof(struct UPNParg));
816: if(GetListOfPortMappingsArgs == NULL)
817: return UPNPCOMMAND_MEM_ALLOC_ERROR;
818: GetListOfPortMappingsArgs[0].elt = "NewStartPort";
819: GetListOfPortMappingsArgs[0].val = startPort;
820: GetListOfPortMappingsArgs[1].elt = "NewEndPort";
821: GetListOfPortMappingsArgs[1].val = endPort;
822: GetListOfPortMappingsArgs[2].elt = "NewProtocol";
823: GetListOfPortMappingsArgs[2].val = protocol;
824: GetListOfPortMappingsArgs[3].elt = "NewManage";
825: GetListOfPortMappingsArgs[3].val = "1";
826: GetListOfPortMappingsArgs[4].elt = "NewNumberOfPorts";
827: GetListOfPortMappingsArgs[4].val = numberOfPorts?numberOfPorts:"1000";
828:
829: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
830: "GetListOfPortMappings",
831: GetListOfPortMappingsArgs, &bufsize);
832: free(GetListOfPortMappingsArgs);
833: if(!buffer) {
834: return UPNPCOMMAND_HTTP_ERROR;
835: }
836:
837: /*DisplayNameValueList(buffer, bufsize);*/
838: ParseNameValue(buffer, bufsize, &pdata);
839: free(buffer);
840:
841: /*p = GetValueFromNameValueList(&pdata, "NewPortListing");*/
842: /*if(p) {
843: printf("NewPortListing : %s\n", p);
844: }*/
845: /*printf("NewPortListing(%d chars) : %s\n",
846: pdata.portListingLength, pdata.portListing);*/
847: if(pdata.portListing)
848: {
849: /*struct PortMapping * pm;
850: int i = 0;*/
851: ParsePortListing(pdata.portListing, pdata.portListingLength,
852: data);
853: ret = UPNPCOMMAND_SUCCESS;
854: /*
855: for(pm = data->head.lh_first; pm != NULL; pm = pm->entries.le_next)
856: {
857: printf("%2d %s %5hu->%s:%-5hu '%s' '%s'\n",
858: i, pm->protocol, pm->externalPort, pm->internalClient,
859: pm->internalPort,
860: pm->description, pm->remoteHost);
861: i++;
862: }
863: */
864: /*FreePortListing(&data);*/
865: }
866:
867: p = GetValueFromNameValueList(&pdata, "errorCode");
868: if(p) {
869: ret = UPNPCOMMAND_UNKNOWN_ERROR;
870: sscanf(p, "%d", &ret);
871: }
872: ClearNameValueList(&pdata);
873:
874: /*printf("%.*s", bufsize, buffer);*/
875:
876: return ret;
877: }
878:
879: /* IGD:2, functions for service WANIPv6FirewallControl:1 */
880: MINIUPNP_LIBSPEC int
881: UPNP_GetFirewallStatus(const char * controlURL,
882: const char * servicetype,
883: int * firewallEnabled,
884: int * inboundPinholeAllowed)
885: {
886: struct NameValueParserData pdata;
887: char * buffer;
888: int bufsize;
889: char * fe, *ipa, *p;
890: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
891:
892: if(!firewallEnabled || !inboundPinholeAllowed)
893: return UPNPCOMMAND_INVALID_ARGS;
894:
895: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
896: "GetFirewallStatus", 0, &bufsize);
897: if(!buffer) {
898: return UPNPCOMMAND_HTTP_ERROR;
899: }
900: ParseNameValue(buffer, bufsize, &pdata);
901: free(buffer);
902: fe = GetValueFromNameValueList(&pdata, "FirewallEnabled");
903: ipa = GetValueFromNameValueList(&pdata, "InboundPinholeAllowed");
904: if(ipa && fe)
905: ret = UPNPCOMMAND_SUCCESS;
906: if(fe)
907: *firewallEnabled = my_atoui(fe);
908: /*else
909: *firewallEnabled = 0;*/
910: if(ipa)
911: *inboundPinholeAllowed = my_atoui(ipa);
912: /*else
913: *inboundPinholeAllowed = 0;*/
914: p = GetValueFromNameValueList(&pdata, "errorCode");
915: if(p)
916: {
917: ret = UPNPCOMMAND_UNKNOWN_ERROR;
918: sscanf(p, "%d", &ret);
919: }
920: ClearNameValueList(&pdata);
921: return ret;
922: }
923:
924: MINIUPNP_LIBSPEC int
925: UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype,
926: const char * remoteHost,
927: const char * remotePort,
928: const char * intClient,
929: const char * intPort,
930: const char * proto,
931: int * opTimeout)
932: {
933: struct UPNParg * GetOutboundPinholeTimeoutArgs;
934: char * buffer;
935: int bufsize;
936: struct NameValueParserData pdata;
937: const char * resVal;
938: int ret;
939:
940: if(!intPort || !intClient || !proto || !remotePort || !remoteHost)
941: return UPNPCOMMAND_INVALID_ARGS;
942:
943: GetOutboundPinholeTimeoutArgs = calloc(6, sizeof(struct UPNParg));
944: if(GetOutboundPinholeTimeoutArgs == NULL)
945: return UPNPCOMMAND_MEM_ALLOC_ERROR;
946: GetOutboundPinholeTimeoutArgs[0].elt = "RemoteHost";
947: GetOutboundPinholeTimeoutArgs[0].val = remoteHost;
948: GetOutboundPinholeTimeoutArgs[1].elt = "RemotePort";
949: GetOutboundPinholeTimeoutArgs[1].val = remotePort;
950: GetOutboundPinholeTimeoutArgs[2].elt = "Protocol";
951: GetOutboundPinholeTimeoutArgs[2].val = proto;
952: GetOutboundPinholeTimeoutArgs[3].elt = "InternalPort";
953: GetOutboundPinholeTimeoutArgs[3].val = intPort;
954: GetOutboundPinholeTimeoutArgs[4].elt = "InternalClient";
955: GetOutboundPinholeTimeoutArgs[4].val = intClient;
956: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
957: "GetOutboundPinholeTimeout", GetOutboundPinholeTimeoutArgs, &bufsize);
958: free(GetOutboundPinholeTimeoutArgs);
959: if(!buffer)
960: return UPNPCOMMAND_HTTP_ERROR;
961: ParseNameValue(buffer, bufsize, &pdata);
962: free(buffer);
963: resVal = GetValueFromNameValueList(&pdata, "errorCode");
964: if(resVal)
965: {
966: ret = UPNPCOMMAND_UNKNOWN_ERROR;
967: sscanf(resVal, "%d", &ret);
968: }
969: else
970: {
971: const char * p = GetValueFromNameValueList(&pdata, "OutboundPinholeTimeout");
972: if(p)
973: *opTimeout = my_atoui(p);
974: ret = UPNPCOMMAND_SUCCESS;
975: }
976: ClearNameValueList(&pdata);
977: return ret;
978: }
979:
980: MINIUPNP_LIBSPEC int
981: UPNP_AddPinhole(const char * controlURL, const char * servicetype,
982: const char * remoteHost,
983: const char * remotePort,
984: const char * intClient,
985: const char * intPort,
986: const char * proto,
987: const char * leaseTime,
988: char * uniqueID)
989: {
990: struct UPNParg * AddPinholeArgs;
991: char * buffer;
992: int bufsize;
993: struct NameValueParserData pdata;
994: const char * resVal;
995: char * p;
996: int ret;
997:
998: if(!intPort || !intClient || !proto || !remoteHost || !remotePort || !leaseTime)
999: return UPNPCOMMAND_INVALID_ARGS;
1000:
1001: AddPinholeArgs = calloc(7, sizeof(struct UPNParg));
1002: if(AddPinholeArgs == NULL)
1003: return UPNPCOMMAND_MEM_ALLOC_ERROR;
1004: /* RemoteHost can be wilcarded */
1005: if(strncmp(remoteHost, "empty", 5)==0)
1006: {
1007: AddPinholeArgs[0].elt = "RemoteHost";
1008: AddPinholeArgs[0].val = "";
1009: }
1010: else
1011: {
1012: AddPinholeArgs[0].elt = "RemoteHost";
1013: AddPinholeArgs[0].val = remoteHost;
1014: }
1015: AddPinholeArgs[1].elt = "RemotePort";
1016: AddPinholeArgs[1].val = remotePort;
1017: AddPinholeArgs[2].elt = "Protocol";
1018: AddPinholeArgs[2].val = proto;
1019: AddPinholeArgs[3].elt = "InternalPort";
1020: AddPinholeArgs[3].val = intPort;
1021: if(strncmp(intClient, "empty", 5)==0)
1022: {
1023: AddPinholeArgs[4].elt = "InternalClient";
1024: AddPinholeArgs[4].val = "";
1025: }
1026: else
1027: {
1028: AddPinholeArgs[4].elt = "InternalClient";
1029: AddPinholeArgs[4].val = intClient;
1030: }
1031: AddPinholeArgs[5].elt = "LeaseTime";
1032: AddPinholeArgs[5].val = leaseTime;
1033: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
1034: "AddPinhole", AddPinholeArgs, &bufsize);
1035: free(AddPinholeArgs);
1036: if(!buffer)
1037: return UPNPCOMMAND_HTTP_ERROR;
1038: ParseNameValue(buffer, bufsize, &pdata);
1039: free(buffer);
1040: p = GetValueFromNameValueList(&pdata, "UniqueID");
1041: if(p)
1042: {
1043: strncpy(uniqueID, p, 8);
1044: uniqueID[7] = '\0';
1045: }
1046: resVal = GetValueFromNameValueList(&pdata, "errorCode");
1047: if(resVal)
1048: {
1049: /*printf("AddPortMapping errorCode = '%s'\n", resVal);*/
1050: ret = UPNPCOMMAND_UNKNOWN_ERROR;
1051: sscanf(resVal, "%d", &ret);
1052: }
1053: else
1054: {
1055: ret = UPNPCOMMAND_SUCCESS;
1056: }
1057: ClearNameValueList(&pdata);
1058: return ret;
1059: }
1060:
1061: MINIUPNP_LIBSPEC int
1062: UPNP_UpdatePinhole(const char * controlURL, const char * servicetype,
1063: const char * uniqueID,
1064: const char * leaseTime)
1065: {
1066: struct UPNParg * UpdatePinholeArgs;
1067: char * buffer;
1068: int bufsize;
1069: struct NameValueParserData pdata;
1070: const char * resVal;
1071: int ret;
1072:
1073: if(!uniqueID || !leaseTime)
1074: return UPNPCOMMAND_INVALID_ARGS;
1075:
1076: UpdatePinholeArgs = calloc(3, sizeof(struct UPNParg));
1077: if(UpdatePinholeArgs == NULL)
1078: return UPNPCOMMAND_MEM_ALLOC_ERROR;
1079: UpdatePinholeArgs[0].elt = "UniqueID";
1080: UpdatePinholeArgs[0].val = uniqueID;
1081: UpdatePinholeArgs[1].elt = "NewLeaseTime";
1082: UpdatePinholeArgs[1].val = leaseTime;
1083: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
1084: "UpdatePinhole", UpdatePinholeArgs, &bufsize);
1085: free(UpdatePinholeArgs);
1086: if(!buffer)
1087: return UPNPCOMMAND_HTTP_ERROR;
1088: ParseNameValue(buffer, bufsize, &pdata);
1089: free(buffer);
1090: resVal = GetValueFromNameValueList(&pdata, "errorCode");
1091: if(resVal)
1092: {
1093: /*printf("AddPortMapping errorCode = '%s'\n", resVal); */
1094: ret = UPNPCOMMAND_UNKNOWN_ERROR;
1095: sscanf(resVal, "%d", &ret);
1096: }
1097: else
1098: {
1099: ret = UPNPCOMMAND_SUCCESS;
1100: }
1101: ClearNameValueList(&pdata);
1102: return ret;
1103: }
1104:
1105: MINIUPNP_LIBSPEC int
1106: UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID)
1107: {
1108: /*struct NameValueParserData pdata;*/
1109: struct UPNParg * DeletePinholeArgs;
1110: char * buffer;
1111: int bufsize;
1112: struct NameValueParserData pdata;
1113: const char * resVal;
1114: int ret;
1115:
1116: if(!uniqueID)
1117: return UPNPCOMMAND_INVALID_ARGS;
1118:
1119: DeletePinholeArgs = calloc(2, sizeof(struct UPNParg));
1120: if(DeletePinholeArgs == NULL)
1121: return UPNPCOMMAND_MEM_ALLOC_ERROR;
1122: DeletePinholeArgs[0].elt = "UniqueID";
1123: DeletePinholeArgs[0].val = uniqueID;
1124: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
1125: "DeletePinhole", DeletePinholeArgs, &bufsize);
1126: free(DeletePinholeArgs);
1127: if(!buffer)
1128: return UPNPCOMMAND_HTTP_ERROR;
1129: /*DisplayNameValueList(buffer, bufsize);*/
1130: ParseNameValue(buffer, bufsize, &pdata);
1131: free(buffer);
1132: resVal = GetValueFromNameValueList(&pdata, "errorCode");
1133: if(resVal)
1134: {
1135: ret = UPNPCOMMAND_UNKNOWN_ERROR;
1136: sscanf(resVal, "%d", &ret);
1137: }
1138: else
1139: {
1140: ret = UPNPCOMMAND_SUCCESS;
1141: }
1142: ClearNameValueList(&pdata);
1143: return ret;
1144: }
1145:
1146: MINIUPNP_LIBSPEC int
1147: UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype,
1148: const char * uniqueID, int * isWorking)
1149: {
1150: struct NameValueParserData pdata;
1151: struct UPNParg * CheckPinholeWorkingArgs;
1152: char * buffer;
1153: int bufsize;
1154: char * p;
1155: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
1156:
1157: if(!uniqueID)
1158: return UPNPCOMMAND_INVALID_ARGS;
1159:
1160: CheckPinholeWorkingArgs = calloc(4, sizeof(struct UPNParg));
1161: if(CheckPinholeWorkingArgs == NULL)
1162: return UPNPCOMMAND_MEM_ALLOC_ERROR;
1163: CheckPinholeWorkingArgs[0].elt = "UniqueID";
1164: CheckPinholeWorkingArgs[0].val = uniqueID;
1165: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
1166: "CheckPinholeWorking", CheckPinholeWorkingArgs, &bufsize);
1167: free(CheckPinholeWorkingArgs);
1168: if(!buffer)
1169: {
1170: return UPNPCOMMAND_HTTP_ERROR;
1171: }
1172: ParseNameValue(buffer, bufsize, &pdata);
1173: free(buffer);
1174:
1175: p = GetValueFromNameValueList(&pdata, "IsWorking");
1176: if(p)
1177: {
1178: *isWorking=my_atoui(p);
1179: ret = UPNPCOMMAND_SUCCESS;
1180: }
1181: else
1182: *isWorking = 0;
1183:
1184: p = GetValueFromNameValueList(&pdata, "errorCode");
1185: if(p)
1186: {
1187: ret = UPNPCOMMAND_UNKNOWN_ERROR;
1188: sscanf(p, "%d", &ret);
1189: }
1190:
1191: ClearNameValueList(&pdata);
1192: return ret;
1193: }
1194:
1195: MINIUPNP_LIBSPEC int
1196: UPNP_GetPinholePackets(const char * controlURL, const char * servicetype,
1197: const char * uniqueID, int * packets)
1198: {
1199: struct NameValueParserData pdata;
1200: struct UPNParg * GetPinholePacketsArgs;
1201: char * buffer;
1202: int bufsize;
1203: char * p;
1204: int ret = UPNPCOMMAND_UNKNOWN_ERROR;
1205:
1206: if(!uniqueID)
1207: return UPNPCOMMAND_INVALID_ARGS;
1208:
1209: GetPinholePacketsArgs = calloc(4, sizeof(struct UPNParg));
1210: if(GetPinholePacketsArgs == NULL)
1211: return UPNPCOMMAND_MEM_ALLOC_ERROR;
1212: GetPinholePacketsArgs[0].elt = "UniqueID";
1213: GetPinholePacketsArgs[0].val = uniqueID;
1214: buffer = simpleUPnPcommand(-1, controlURL, servicetype,
1215: "GetPinholePackets", GetPinholePacketsArgs, &bufsize);
1216: free(GetPinholePacketsArgs);
1217: if(!buffer)
1218: return UPNPCOMMAND_HTTP_ERROR;
1219: ParseNameValue(buffer, bufsize, &pdata);
1220: free(buffer);
1221:
1222: p = GetValueFromNameValueList(&pdata, "PinholePackets");
1223: if(p)
1224: {
1225: *packets=my_atoui(p);
1226: ret = UPNPCOMMAND_SUCCESS;
1227: }
1228:
1229: p = GetValueFromNameValueList(&pdata, "errorCode");
1230: if(p)
1231: {
1232: ret = UPNPCOMMAND_UNKNOWN_ERROR;
1233: sscanf(p, "%d", &ret);
1234: }
1235:
1236: ClearNameValueList(&pdata);
1237: return ret;
1238: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>