1 /*
2  * $Id$
3  *
4  * Copyright (c) 2008, 2009, 2010
5  *      Sten Spans <sten@blinkenlights.nl>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include "common.h"
21 #include "util.h"
22 #include "proto/protos.h"
23 #include "cli.h"
24 #include <sys/file.h>
25 #include <sys/un.h>
26 #include <netdb.h>
27 #include <termios.h>
28 
29 extern struct proto protos[];
30 int status = EXIT_SUCCESS;
31 static void usage() __noreturn;
32 
33 static struct mode modes[] = {
34   { &cli_header, &cli_write, NULL },
35   { NULL, NULL, NULL },
36   { NULL, &batch_write, NULL },
37   { &debug_header, &debug_write, &debug_close },
38 #if HAVE_EVHTTP_H
39   { &http_connect, &http_request, &http_dispatch },
40 #endif /* HAVE_EVHTTP_H */
41 };
42 
43 #define MODE_CLI    0
44 #define MODE_PRINT  1
45 #define MODE_BATCH  2
46 #define MODE_DEBUG  3
47 #define MODE_HTTP   4
48 
49 #define TERM_DEFAULT 80
50 static int host_width = 20;
51 static int port_width = 10;
52 
53 #if HAVE_EVHTTP_H
54 char *http_host = NULL;
55 char *http_path = NULL;
56 char *hostname = NULL;
57 short http_port = 0;
58 
59 struct evhttp_connection *evcon = NULL;
60 struct evhttp_request *lreq = NULL;
61 #endif /* HAVE_EVHTTP_H */
62 
63 __noreturn
cli_main(int argc,char * argv[])64 void cli_main(int argc, char *argv[]) {
65     int ch, i;
66     uint8_t proto = 0, mode = MODE_CLI;
67     uint32_t *indexes = NULL;
68     struct sockaddr_un usock = {};
69     int fd = -1;
70     time_t now;
71     struct parent_msg *msg;
72     uint16_t holdtime;
73 
74     options = 0;
75 
76     while ((ch = getopt(argc, argv, "LCEFNbdfp:ovh")) != -1) {
77 	switch(ch) {
78 	    case 'L':
79 		proto |= (1 << PROTO_LLDP);
80 		break;
81 	    case 'C':
82 		proto |= (1 << PROTO_CDP);
83 		break;
84 	    case 'E':
85 		proto |= (1 << PROTO_EDP);
86 		break;
87 	    case 'F':
88 		proto |= (1 << PROTO_FDP);
89 		break;
90 	    case 'N':
91 		proto |= (1 << PROTO_NDP);
92 		break;
93 	    case 'b':
94 		mode = MODE_BATCH;
95 		break;
96 	    case 'd':
97 		mode = MODE_DEBUG;
98 		break;
99 	    case 'f':
100 		mode = MODE_PRINT;
101 		break;
102 #if HAVE_EVHTTP_H
103 	    case 'p':
104 		if (http_host)
105 		    usage();
106 		mode = MODE_HTTP;
107 		if (strncmp(optarg, "http://", 7) == 0)
108 		    optarg += 7;
109 		char *host = my_strdup(optarg);
110 		char *path = strchr(host, '/');
111 		if (path) {
112 		    http_path = my_strdup(path);
113 		    *path = '\0';
114 		} else {
115 		    http_path = my_strdup("/");
116 		}
117 		http_host = my_strdup(host);
118 		free(host);
119 		break;
120 #endif /* HAVE_EVHTTP_H */
121 	    case 'o':
122 		options |= OPT_ONCE;
123 		break;
124 	    case 'v':
125 		loglevel++;
126 		break;
127 	    default:
128 		usage();
129 	}
130     }
131 
132     argc -= optind;
133     argv += optind;
134 
135     // default to all protocols
136     if (!proto)
137 	proto = UINT8_MAX;
138 
139     if (argc) {
140 	indexes = my_calloc(argc, sizeof(msg->index));
141 	for (i = 0; i < argc; i++) {
142 	    indexes[i] = if_nametoindex(argv[i]);
143 	    if (!indexes[i])
144 		usage();
145 	}
146     }
147 
148     // open socket connection
149     fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
150     // XXX: make do with a stream and hope for the best
151     if ((fd == -1) && (errno == EPROTONOSUPPORT))
152 	fd = my_socket(AF_UNIX, SOCK_STREAM, 0);
153     if (fd == -1)
154 	my_fatale("failed to create socket");
155 
156     usock.sun_family = AF_UNIX;
157     strlcpy(usock.sun_path, PACKAGE_SOCKET, sizeof(usock.sun_path));
158 
159     if (connect(fd, (struct sockaddr *)&usock, sizeof(usock)) == -1) {
160 	if (errno == EACCES)
161 	    my_fatal("please add yourself to the " PACKAGE_USER " group");
162 	else if (errno == ECONNREFUSED)
163 	    my_fatal("please enable receive mode before using " PACKAGE_CLI);
164 	else
165 	    my_fatale("failed to open " PACKAGE_SOCKET);
166     }
167     if ((now = time(NULL)) == (time_t)-1)
168 	my_fatale("failed to fetch time");
169 
170     if (modes[mode].init)
171 	modes[mode].init();
172 
173     msg = my_malloc(PARENT_MSG_SIZ);
174 
175     while (read(fd, msg, PARENT_MSG_MAX) == PARENT_MSG_MAX) {
176 
177 	if (msg->proto >= PROTO_MAX)
178 	    continue;
179 	if ((msg->len < (ETHER_MIN_LEN - ETHER_VLAN_ENCAP_LEN)) ||
180 	    (msg->len > ETHER_MAX_LEN))
181 	    continue;
182 
183 	// skip unwanted interfaces
184 	if (indexes) {
185 	    for (i = 0; i < argc; i++) {
186 		if (indexes[i] == msg->index)
187 		    break;
188 	    }
189 	    if (i == argc)
190 		continue;
191 	}
192 
193 	// skip unwanted protocols
194 	if (!(proto & (1 << msg->proto)))
195 	    continue;
196 
197 	// decode packet
198 	msg->decode = DECODE_STR;
199 	if (mode == MODE_PRINT)
200 	    msg->decode = DECODE_PRINT;
201 
202 	if (protos[msg->proto].decode(msg) == 0) {
203 	    peer_free(msg->peer);
204 	    continue;
205 	}
206 	// skip expired packets
207 	if (msg->ttl < (now - msg->received))
208 	    continue;
209 
210 	holdtime = msg->ttl - (now - msg->received);
211 
212 	if (modes[mode].write)
213 	    modes[mode].write(msg, holdtime);
214 
215 	peer_free(msg->peer);
216 	memset(msg, 0, PARENT_MSG_SIZ);
217 
218 	if (options & OPT_ONCE)
219 	    goto out;
220 
221 	if (mode == MODE_PRINT)
222 	    printf("\n");
223     }
224 
225 out:
226     if (modes[mode].dispatch)
227 	modes[mode].dispatch();
228 
229     free(msg);
230     exit(status);
231 }
232 
swapchr(char * str,const int c,const int d)233 static inline void swapchr(char *str, const int c, const int d) {
234     if (!str)
235 	return;
236 
237     while ((str = strchr(str, c)) != NULL) {
238 	*str = d;
239 	 str++;
240     }
241 }
242 
243 #define STR(x)	(x) ? x : ""
244 
batch_write(struct parent_msg * msg,const uint16_t holdtime)245 void batch_write(struct parent_msg *msg, const uint16_t holdtime) {
246     char *peer_host = msg->peer[PEER_HOSTNAME];
247     char *peer_portname = msg->peer[PEER_PORTNAME];
248     char *peer_portdescr = msg->peer[PEER_PORTDESCR];
249     char *cap = msg->peer[PEER_CAP];
250     static unsigned int count = 0;
251 
252     swapchr(peer_host, '\'', '\"');
253     swapchr(peer_portname, '\'', '\"');
254     swapchr(peer_portdescr, '\'', '\"');
255 
256     printf("INTERFACE_%u='%s'\n", count, STR(msg->name));
257     printf("HOSTNAME_%u='%s'\n", count, STR(peer_host));
258     printf("PORTNAME_%u='%s'\n", count, STR(peer_portname));
259     printf("PORTDESCR_%u='%s'\n", count, STR(peer_portdescr));
260     printf("PROTOCOL_%u='%s'\n", count, protos[msg->proto].name);
261     printf("ADDR_INET4_%u='%s'\n", count, STR(msg->peer[PEER_ADDR_INET4]));
262     printf("ADDR_INET6_%u='%s'\n", count, STR(msg->peer[PEER_ADDR_INET6]));
263     printf("ADDR_802_%u='%s'\n", count, STR(msg->peer[PEER_ADDR_802]));
264     printf("VLAN_ID_%u='%s'\n", count, STR(msg->peer[PEER_VLAN_ID]));
265     printf("CAPABILITIES_%u='%s'\n", count, STR(cap));
266     printf("TTL_%u='%" PRIu16 "'\n", count, msg->ttl);
267     printf("HOLDTIME_%u='%" PRIu16 "'\n", count, holdtime);
268 
269     count++;
270 }
271 
cli_header()272 void cli_header() {
273     int twidth = TERM_DEFAULT;
274     struct winsize ws = {};
275 
276     // Try to fetch the terminal width
277     if (ioctl(0, TIOCGWINSZ, &ws) == 0)
278 	twidth = ws.ws_col;
279 
280     twidth -= TERM_DEFAULT;
281     if (twidth > 0) {
282 	if (twidth > 20) {
283 	    host_width += 10;
284 	    port_width = twidth;
285 	} else {
286 	    twidth /= 2;
287 	    host_width += twidth;
288 	    port_width += twidth;
289 	}
290     }
291 
292     printf("Capability Codes:\n"
293 	"\tr - Repeater, B - Bridge, H - Host, R - Router, S - Switch,\n"
294 	"\tW - WLAN Access Point, C - DOCSIS Device, T - Telephone, "
295 	"O - Other\n\n");
296     printf("%-*s Local Intf    Proto   "
297 	"Hold-time    Capability    Port ID\n", host_width, "Device ID");
298 }
299 
cli_write(struct parent_msg * msg,const uint16_t holdtime)300 void cli_write(struct parent_msg *msg, const uint16_t holdtime) {
301     char *peer_host = msg->peer[PEER_HOSTNAME];
302     char *peer_portname = msg->peer[PEER_PORTNAME];
303     char *peer_portdescr = msg->peer[PEER_PORTDESCR];
304     char *peer_suffix = NULL;
305     char *cap = msg->peer[PEER_CAP];
306 
307     // shorten
308     if (peer_host && (strlen(peer_host) > host_width))
309 	peer_host[strcspn(peer_host, ".")] = '\0';
310 
311     peer_suffix = peer_portname;
312     if (!peer_suffix)
313 	peer_suffix = peer_portdescr;
314     if (peer_suffix)
315 	portname_abbr(peer_suffix);
316 
317     printf("%-*.*s %-13.13s %-7.7s %-12" PRIu16 " %-13.13s %-*.*s\n",
318 	host_width, host_width, STR(peer_host), STR(msg->name), protos[msg->proto].name,
319 	holdtime, STR(cap), port_width, port_width, STR(peer_suffix));
320 }
321 
debug_header()322 void debug_header() {
323     my_pcap_init(STDOUT_FILENO);
324 }
325 
debug_write(struct parent_msg * msg,const uint16_t holdtime)326 void debug_write(struct parent_msg *msg, const uint16_t holdtime) {
327     my_pcap_write(msg);
328 }
329 
debug_close()330 void debug_close() {
331     my_pcap_close();
332 }
333 
334 #if HAVE_EVHTTP_H
http_connect()335 void http_connect() {
336     struct servent *sp;
337     struct hostent *hp;
338 
339     if (!http_port) {
340 	if ((sp = getservbyname("http", "tcp")) == NULL)
341 	    my_fatal("HTTP port not found");
342 	http_port = ntohs(sp->s_port);
343     }
344 
345     // initalize the event library
346     event_init();
347 
348     evcon = evhttp_connection_new(http_host, http_port);
349     if (evcon == NULL)
350         my_fatal("HTTP connection failed");
351 
352     hostname = my_malloc(_POSIX_HOST_NAME_MAX);
353     if (gethostname(hostname, _POSIX_HOST_NAME_MAX) == -1)
354 	my_fatale("gethostname failed");
355     if (((hp = gethostbyname(hostname)) != NULL) &&
356 	(strcmp(hp->h_name, "localhost") != 0))
357 	strlcpy(hostname, hp->h_name, _POSIX_HOST_NAME_MAX);
358 }
359 
http_request(struct parent_msg * msg,const uint16_t holdtime)360 void http_request(struct parent_msg *msg, const uint16_t holdtime) {
361     int ret;
362     char *peer_host, *peer_port, *data;
363     char *cap = msg->peer[PEER_CAP];
364     struct evhttp_request *req = NULL;
365 
366     // url-encode the received strings
367     peer_host = evhttp_encode_uri(STR(msg->peer[PEER_HOSTNAME]));
368     peer_port = evhttp_encode_uri(STR(msg->peer[PEER_PORTNAME]));
369 
370     ret = asprintf(&data,
371 	"hostname=%s&interface=%s&peer_hostname=%s&peer_portname=%s&"
372 	"protocol=%s&capabilities=%s&ttl=%" PRIu16 "&holdtime=%" PRIu16
373 	"\r\n\r\n",
374 	hostname, STR(msg->name), peer_host, peer_port,
375 	protos[msg->proto].name, STR(cap), msg->ttl, holdtime);
376     if (ret == -1)
377 	my_fatal("asprintf failed");
378 
379     free(peer_host);
380     free(peer_port);
381 
382     req = evhttp_request_new(http_reply, NULL);
383     if (ret == -1)
384 	my_fatal("failed to allocate HTTP request");
385 
386     evhttp_add_header(req->output_headers, "Host", http_host);
387     evhttp_add_header(req->output_headers, "User-Agent",
388 		PACKAGE_CLI "/" PACKAGE_VERSION);
389     evhttp_add_header(req->output_headers, "Content-Type",
390 		"application/x-www-form-urlencoded");
391     evbuffer_add(req->output_buffer, data, strlen(data));
392 
393     if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, http_path) == -1)
394 	my_fatal("failed to create HTTP request");
395     lreq = req;
396 
397     free(data);
398 }
399 
http_reply(struct evhttp_request * req,void * arg)400 void http_reply(struct evhttp_request *req, void *arg) {
401     if ((req == NULL) || (req->response_code == 0))
402 	my_fatal("HTTP request failed");
403 
404     if (req->response_code < HTTP_BADREQUEST)
405 	return;
406 
407     my_log(CRIT, "HTTP error %d received", req->response_code);
408     status = EXIT_FAILURE;
409 }
410 
http_dispatch()411 void http_dispatch() {
412     // auto-close the connection after the last request
413     if (lreq)
414 	evhttp_add_header(lreq->output_headers, "Connection", "close");
415 
416     event_dispatch();
417     evhttp_connection_free(evcon);
418 }
419 #endif /* HAVE_EVHTTP_H */
420 
421 __noreturn
usage()422 static void usage() {
423     extern char *__progname;
424 
425     fprintf(stderr, PACKAGE_NAME " version " PACKAGE_VERSION "\n"
426 	"Usage: %s [-LCEFN] [INTERFACE] [INTERFACE]\n"
427 	    "\t-L = Print LLDP\n"
428 	    "\t-C = Print CDP\n"
429 	    "\t-E = Print EDP\n"
430 	    "\t-F = Print FDP\n"
431 	    "\t-N = Print NDP\n"
432 	    "\t-b = Print scriptable output\n"
433 	    "\t-d = Dump pcap-compatible packets to stdout\n"
434 	    "\t-f = Print full decode\n"
435 	    "\t-o = Decode only one packet\n"
436 #if HAVE_EVHTTP_H
437 	    "\t-p <url> = Post decode to url\n"
438 #endif /* HAVE_EVHTTP_H */
439 	    "\t-v = Increase logging verbosity\n"
440 	    "\t-h = Print this message\n",
441 	    __progname);
442 
443     exit(EXIT_FAILURE);
444 }
445 
446