xref: /openbsd/usr.sbin/npppctl/npppctl.c (revision 882428cd)
1 /*	$OpenBSD: npppctl.c,v 1.15 2024/11/21 13:43:10 claudio Exp $	*/
2 
3 /*
4  * Copyright (c) 2012 Internet Initiative Japan Inc.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include <sys/queue.h>
19 #include <sys/socket.h>
20 #include <sys/un.h>
21 #include <sys/uio.h>
22 #include <net/if.h>
23 #include <net/if_dl.h>
24 #include <netinet/in.h>
25 #include <arpa/inet.h>
26 
27 #include <errno.h>
28 #include <netdb.h>
29 #include <stdbool.h>
30 #include <stddef.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <imsg.h>
35 
36 #include <unistd.h>
37 #include <err.h>
38 
39 #include "parser.h"
40 #include "npppd_ctl.h"
41 
42 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
43 
44 #ifndef nitems
45 #define nitems(_x)	(sizeof(_x) / sizeof(_x[0]))
46 #endif
47 
48 #define	NMAX_DISCONNECT		2048
49 
50 static void         usage (void);
51 static void         show_clear_session (struct parse_result *, FILE *);
52 static void         monitor_session (struct parse_result *, FILE *);
53 static void         clear_session (u_int[], int, int, FILE *);
54 static void         fprint_who_brief (int, struct npppd_who *, FILE *);
55 static void         fprint_who_packets (int, struct npppd_who *, FILE *);
56 static void         fprint_who_all (int, struct npppd_who *, FILE *);
57 static const char  *peerstr (struct sockaddr *, char *, int);
58 static const char  *humanize_duration (uint32_t, char *, int);
59 static const char  *humanize_bytes (double, char *, int);
60 static bool         filter_match(struct parse_result *, struct npppd_who *);
61 static int          imsg_wait_command_completion (void);
62 
63 static int             nflag = 0;
64 static struct imsgbuf  ctl_ibuf;
65 static struct imsg     ctl_imsg;
66 
67 static void
usage(void)68 usage(void)
69 {
70 	extern char		*__progname;
71 
72 	fprintf(stderr,
73 	    "usage: %s [-n] [-s socket] command [arg ...]\n", __progname);
74 }
75 
76 int
main(int argc,char * argv[])77 main(int argc, char *argv[])
78 {
79 	int			 ch, ctlsock = -1;
80 	struct parse_result	*result;
81 	struct sockaddr_un	 sun;
82 	const char		*npppd_ctlpath = NPPPD_SOCKET;
83 
84 	while ((ch = getopt(argc, argv, "ns:")) != -1)
85 		switch (ch) {
86 		case 'n':
87 			nflag = 1;
88 			break;
89 		case 's':
90 			npppd_ctlpath = optarg;
91 			break;
92 		default:
93 			usage();
94 			exit(EXIT_FAILURE);
95 		}
96 
97 	argc -= optind;
98 	argv += optind;
99 
100 	if ((result = parse(argc, argv)) == NULL)
101 		exit(EXIT_FAILURE);
102 
103 	if ((ctlsock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
104 		err(EXIT_FAILURE, "socket");
105 	memset(&sun, 0, sizeof(sun));
106 	sun.sun_family = AF_UNIX;
107 	strlcpy(sun.sun_path, npppd_ctlpath, sizeof(sun.sun_path));
108 	if (connect(ctlsock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
109 		err(EXIT_FAILURE, "connect");
110 
111 	if (imsgbuf_init(&ctl_ibuf, ctlsock) == -1)
112 		err(EXIT_FAILURE, "imsgbuf_init");
113 
114 	switch (result->action) {
115 	case SESSION_BRIEF:
116 	case SESSION_PKTS:
117 	case SESSION_ALL:
118 		show_clear_session(result, stdout);
119 		break;
120 	case CLEAR_SESSION:
121 		if (!result->has_ppp_id)
122 			show_clear_session(result, stdout);
123 		else {
124 			u_int ids[1];
125 			ids[0] = result->ppp_id;
126 			clear_session(ids, 1, 1, stdout);
127 		}
128 		break;
129 	case MONITOR_SESSION:
130 		monitor_session(result, stdout);
131 		break;
132 	case NONE:
133 		break;
134 	}
135 
136 	exit(EXIT_SUCCESS);
137 }
138 
139 static void
show_clear_session(struct parse_result * result,FILE * out)140 show_clear_session(struct parse_result *result, FILE *out)
141 {
142 	int                    i, n, ppp_id_idx;
143 	struct npppd_who_list *res;
144 	u_int                  ppp_id[NMAX_DISCONNECT];
145 
146 	if (imsg_compose(&ctl_ibuf, IMSG_CTL_WHO, 0, 0, -1, NULL, 0) == -1)
147 		err(EXIT_FAILURE, "failed to compose a message\n");
148 	if (imsg_wait_command_completion() < 0)
149 		errx(EXIT_FAILURE, "failed to get response");
150 	if (ctl_imsg.hdr.type != IMSG_CTL_OK)
151 		errx(EXIT_FAILURE, "command was fail");
152 	n = ppp_id_idx = 0;
153 	while (imsg_wait_command_completion() == IMSG_PPP_START) {
154 		res = (struct npppd_who_list *)ctl_imsg.data;
155 		if (ctl_imsg.hdr.len - IMSG_HEADER_SIZE <
156 		    offsetof(struct npppd_who_list,
157 			    entry[res->entry_count])) {
158 			errx(1, "response size %d is too small for "
159 			    "the entry count %d",
160 			    (int)(ctl_imsg.hdr.len - IMSG_HEADER_SIZE),
161 			    res->entry_count);
162 		}
163 		for (i = 0; i < res->entry_count; i++, n++) {
164 			switch (result->action) {
165 			case SESSION_BRIEF:
166 				fprint_who_brief(n, &res->entry[i], out);
167 				break;
168 			case SESSION_PKTS:
169 				fprint_who_packets(n, &res->entry[i], out);
170 				break;
171 			case SESSION_ALL:
172 				if (filter_match(result, &res->entry[i]))
173 					fprint_who_all(n, &res->entry[i], out);
174 				break;
175 			case CLEAR_SESSION:
176 				if (filter_match(result, &res->entry[i])) {
177 					if (ppp_id_idx < nitems(ppp_id))
178 						ppp_id[ppp_id_idx] =
179 						    res->entry[i].ppp_id;
180 					ppp_id_idx++;
181 				}
182 				break;
183 			default:
184 				warnx("must not reached here");
185 				abort();
186 			}
187 		}
188 		if (!res->more_data)
189 			break;
190 	}
191 	if (result->action == CLEAR_SESSION) {
192 		if (ppp_id_idx > nitems(ppp_id))
193 			warnx(
194 			    "Disconnection for %d sessions has been requested, "
195 			    "but cannot disconnect only %d sessions because of "
196 			    "the implementation limit.",
197 			    ppp_id_idx, (int)nitems(ppp_id));
198 		clear_session(ppp_id, MINIMUM(ppp_id_idx, nitems(ppp_id)),
199 			ppp_id_idx, out);
200 	}
201 }
202 
203 const char *bar =
204 "------------------------------------------------------------------------\n";
205 static void
monitor_session(struct parse_result * result,FILE * out)206 monitor_session(struct parse_result *result, FILE *out)
207 {
208 	int                    i, n;
209 	struct npppd_who_list *res;
210 
211 	if (imsg_compose(&ctl_ibuf, IMSG_CTL_MONITOR, 0, 0, -1, NULL, 0) == -1)
212 		err(EXIT_FAILURE, "failed to compose a message");
213 	if (imsg_wait_command_completion() < 0)
214 		errx(EXIT_FAILURE, "failed to get response");
215 	if (ctl_imsg.hdr.type != IMSG_CTL_OK)
216 		errx(EXIT_FAILURE, "command was fail");
217 	do {
218 		if (imsg_wait_command_completion() < 0)
219 			break;
220 		n = 0;
221 		if (ctl_imsg.hdr.type == IMSG_PPP_START ||
222 		    ctl_imsg.hdr.type == IMSG_PPP_STOP) {
223 			res = (struct npppd_who_list *)ctl_imsg.data;
224 			for (i = 0; i < res->entry_count; i++) {
225 				if (!filter_match(result, &res->entry[i]))
226 					continue;
227 				if (n == 0)
228 					fprintf(out, "PPP %s\n%s",
229 					    (ctl_imsg.hdr.type ==
230 						    IMSG_PPP_START)
231 						    ? "Started"
232 						    : "Stopped", bar);
233 				fprint_who_all(n++, &res->entry[i], out);
234 			}
235 			if (n > 0)
236 				fputs(bar, out);
237 		} else {
238 			warnx("received unknown message type = %d",
239 			    ctl_imsg.hdr.type);
240 			break;
241 		}
242 	} while (true);
243 
244 	return;
245 }
246 
247 static void
fprint_who_brief(int i,struct npppd_who * w,FILE * out)248 fprint_who_brief(int i, struct npppd_who *w, FILE *out)
249 {
250 	char buf[BUFSIZ];
251 
252 	if (i == 0)
253 		fputs(
254 "Ppp Id     Assigned IPv4   Username             Proto Tunnel From\n"
255 "---------- --------------- -------------------- ----- ------------------------"
256 "-\n",
257 		    out);
258 	fprintf(out, "%10u %-15s %-20s %-5s %s\n", w->ppp_id,
259 	    inet_ntoa(w->framed_ip_address), w->username, w->tunnel_proto,
260 	    peerstr((struct sockaddr *)&w->tunnel_peer, buf, sizeof(buf)));
261 }
262 
263 static void
fprint_who_packets(int i,struct npppd_who * w,FILE * out)264 fprint_who_packets(int i, struct npppd_who *w, FILE *out)
265 {
266 	if (i == 0)
267 		fputs(
268 "Ppd Id     Username             In(Kbytes/pkts/errs)    Out(Kbytes/pkts/errs)"
269 "\n"
270 "---------- -------------------- ----------------------- ----------------------"
271 "-\n",
272 		    out);
273 	fprintf(out, "%10u %-20s %9.1f %7u %5u %9.1f %7u %5u\n", w->ppp_id,
274 	    w->username,
275 	    (double)w->ibytes/1024, w->ipackets, w->ierrors,
276 	    (double)w->obytes/1024, w->opackets, w->oerrors);
277 }
278 
279 static void
fprint_who_all(int i,struct npppd_who * w,FILE * out)280 fprint_who_all(int i, struct npppd_who *w, FILE *out)
281 {
282 	struct tm  tm;
283 	char       ibytes_buf[48], obytes_buf[48], peer_buf[48], time_buf[48];
284 	char       dur_buf[48];
285 
286 	localtime_r(&w->time, &tm);
287 	strftime(time_buf, sizeof(time_buf), "%Y/%m/%d %T", &tm);
288 	if (i != 0)
289 		fputs("\n", out);
290 
291 	fprintf(out,
292 	    "Ppp Id = %u\n"
293 	    "          Ppp Id                  : %u\n"
294 	    "          Username                : %s\n"
295 	    "          Realm Name              : %s\n"
296 	    "          Concentrated Interface  : %s\n"
297 	    "          Assigned IPv4 Address   : %s\n"
298 	    "          MRU                     : %u\n"
299 	    "          Tunnel Protocol         : %s\n"
300 	    "          Tunnel From             : %s\n"
301 	    "          Start Time              : %s\n"
302 	    "          Elapsed Time            : %lu sec %s\n"
303 	    "          Input Bytes             : %llu%s\n"
304 	    "          Input Packets           : %lu\n"
305 	    "          Input Errors            : %lu (%.1f%%)\n"
306 	    "          Output Bytes            : %llu%s\n"
307 	    "          Output Packets          : %lu\n"
308 	    "          Output Errors           : %lu (%.1f%%)\n",
309 	    w->ppp_id, w->ppp_id, w->username, w->rlmname, w->ifname,
310 	    inet_ntoa(w->framed_ip_address), (u_int)w->mru, w->tunnel_proto,
311 	    peerstr((struct sockaddr *)&w->tunnel_peer, peer_buf,
312 		sizeof(peer_buf)), time_buf,
313 	    (unsigned long)w->duration_sec,
314 	    humanize_duration(w->duration_sec, dur_buf, sizeof(dur_buf)),
315 	    (unsigned long long)w->ibytes,
316 	    humanize_bytes((double)w->ibytes, ibytes_buf, sizeof(ibytes_buf)),
317 	    (unsigned long)w->ipackets,
318 	    (unsigned long)w->ierrors,
319 	    ((w->ipackets + w->ierrors) <= 0)
320 		? 0.0 : (100.0 * w->ierrors) / (w->ierrors + w->ipackets),
321 	    (unsigned long long)w->obytes,
322 	    humanize_bytes((double)w->obytes, obytes_buf, sizeof(obytes_buf)),
323 	    (unsigned long)w->opackets,
324 	    (unsigned long)w->oerrors,
325 	    ((w->opackets + w->oerrors) <= 0)
326 		? 0.0 : (100.0 * w->oerrors) / (w->oerrors + w->opackets));
327 }
328 
329 /***********************************************************************
330  * clear session
331  ***********************************************************************/
332 static void
clear_session(u_int ppp_id[],int ppp_id_count,int total,FILE * out)333 clear_session(u_int ppp_id[], int ppp_id_count, int total, FILE *out)
334 {
335 	int                               succ, fail, i, n, nmax;
336 	struct iovec                      iov[2];
337 	struct npppd_disconnect_request   req;
338 	struct npppd_disconnect_response *res;
339 
340 	succ = fail = 0;
341 	if (ppp_id_count > 0) {
342 		nmax = (MAX_IMSGSIZE - IMSG_HEADER_SIZE -
343 		    offsetof(struct npppd_disconnect_request, ppp_id[0])) /
344 		    sizeof(u_int);
345 		for (i = 0; i < ppp_id_count; i += n) {
346 			n = MINIMUM(nmax, ppp_id_count - i);
347 			req.count = n;
348 			iov[0].iov_base = &req;
349 			iov[0].iov_len = offsetof(
350 			    struct npppd_disconnect_request, ppp_id[0]);
351 			iov[1].iov_base = &ppp_id[i];
352 			iov[1].iov_len = sizeof(u_int) * n;
353 
354 			if (imsg_composev(&ctl_ibuf, IMSG_CTL_DISCONNECT, 0, 0,
355 			    -1, iov, 2) == -1)
356 				err(EXIT_FAILURE,
357 				    "Failed to compose a message");
358 			if (imsg_wait_command_completion() < 0)
359 				errx(EXIT_FAILURE, "failed to get response");
360 			if (ctl_imsg.hdr.type != IMSG_CTL_OK)
361 				errx(EXIT_FAILURE,
362 				    "Command was fail: msg type = %d",
363 				    ctl_imsg.hdr.type);
364 			if (ctl_imsg.hdr.len - IMSG_HEADER_SIZE <
365 			    sizeof(struct npppd_disconnect_response))
366 				err(EXIT_FAILURE, "response is corrupted");
367 			res = (struct npppd_disconnect_response *)ctl_imsg.data;
368 			succ += res->count;
369 		}
370 		fail = total - succ;
371 	}
372 	if (succ > 0)
373 		fprintf(out, "Successfully disconnected %d session%s.\n",
374 		    succ, (succ > 1)? "s" : "");
375 	if (fail > 0)
376 		fprintf(out, "Failed to disconnect %d session%s.\n",
377 		    fail, (fail > 1)? "s" : "");
378 	if (succ == 0 && fail == 0)
379 		fprintf(out, "No session to disconnect.\n");
380 }
381 
382 /***********************************************************************
383  * common functions
384  ***********************************************************************/
385 static bool
filter_match(struct parse_result * result,struct npppd_who * who)386 filter_match(struct parse_result *result, struct npppd_who *who)
387 {
388 	if (result->has_ppp_id && result->ppp_id != who->ppp_id)
389 		return (false);
390 
391 	switch (result->address.ss_family) {
392 	case AF_INET:
393 		if (((struct sockaddr_in *)&result->address)->sin_addr.
394 		    s_addr != who->framed_ip_address.s_addr)
395 			return (false);
396 		break;
397 	case AF_INET6:
398 		/* npppd doesn't support IPv6 yet */
399 		return (false);
400 	}
401 
402 	if (result->interface != NULL &&
403 	    strcmp(result->interface, who->ifname) != 0)
404 		return (false);
405 
406 	if (result->protocol != PROTO_UNSPEC &&
407 	    result->protocol != parse_protocol(who->tunnel_proto) )
408 		return (false);
409 
410 	if (result->realm != NULL && strcmp(result->realm, who->rlmname) != 0)
411 		return (false);
412 
413 	if (result->username != NULL &&
414 	    strcmp(result->username, who->username) != 0)
415 		return (false);
416 
417 	return (true);
418 }
419 
420 static const char *
peerstr(struct sockaddr * sa,char * buf,int lbuf)421 peerstr(struct sockaddr *sa, char *buf, int lbuf)
422 {
423 	int   niflags, hasserv;
424 	char  hoststr[NI_MAXHOST], servstr[NI_MAXSERV];
425 
426 	niflags = hasserv = 0;
427 	if (nflag)
428 		niflags |= NI_NUMERICHOST;
429 	if (sa->sa_family == AF_INET || sa->sa_family ==AF_INET6) {
430 		hasserv = 1;
431 		niflags |= NI_NUMERICSERV;
432 	}
433 
434 	if (sa->sa_family == AF_LINK)
435 		snprintf(hoststr, sizeof(hoststr),
436 		    "%02x:%02x:%02x:%02x:%02x:%02x",
437 		    LLADDR((struct sockaddr_dl *)sa)[0] & 0xff,
438 		    LLADDR((struct sockaddr_dl *)sa)[1] & 0xff,
439 		    LLADDR((struct sockaddr_dl *)sa)[2] & 0xff,
440 		    LLADDR((struct sockaddr_dl *)sa)[3] & 0xff,
441 		    LLADDR((struct sockaddr_dl *)sa)[4] & 0xff,
442 		    LLADDR((struct sockaddr_dl *)sa)[5] & 0xff);
443 	else
444 		getnameinfo(sa, sa->sa_len, hoststr, sizeof(hoststr), servstr,
445 		    sizeof(servstr), niflags);
446 
447 	strlcpy(buf, hoststr, lbuf);
448 	if (hasserv) {
449 		strlcat(buf, ":", lbuf);
450 		strlcat(buf, servstr, lbuf);
451 	}
452 
453 	return (buf);
454 }
455 
456 static const char *
humanize_duration(uint32_t sec,char * buf,int lbuf)457 humanize_duration(uint32_t sec, char *buf, int lbuf)
458 {
459 	char  fbuf[128];
460 	int   hour, min;
461 
462 	hour = sec / (60 * 60);
463 	min = sec / 60;
464 	min %= 60;
465 
466 	if (lbuf <= 0)
467 		return (buf);
468 	buf[0] = '\0';
469 	if (hour || min) {
470 		strlcat(buf, "(", lbuf);
471 		if (hour) {
472 			snprintf(fbuf, sizeof(fbuf),
473 			    "%d hour%s", hour, (hour > 1)? "s" : "");
474 			strlcat(buf, fbuf, lbuf);
475 		}
476 		if (hour && min)
477 			strlcat(buf, " and ", lbuf);
478 		if (min) {
479 			snprintf(fbuf, sizeof(fbuf),
480 			    "%d minute%s", min, (min > 1)? "s" : "");
481 			strlcat(buf, fbuf, lbuf);
482 		}
483 		strlcat(buf, ")", lbuf);
484 	}
485 
486 	return (buf);
487 }
488 
489 static const char *
humanize_bytes(double val,char * buf,int lbuf)490 humanize_bytes(double val, char *buf, int lbuf)
491 {
492 	if (lbuf <= 0)
493 		return (buf);
494 
495 	if (val >= 1000 * 1024 * 1024)
496 		snprintf(buf, lbuf, " (%.1f GB)",
497 		    (double)val / (1024 * 1024 * 1024));
498 	else if (val >= 1000 * 1024)
499 		snprintf(buf, lbuf, " (%.1f MB)", (double)val / (1024 * 1024));
500 	else if (val >= 1000)
501 		snprintf(buf, lbuf, " (%.1f KB)", (double)val / 1024);
502 	else
503 		buf[0] = '\0';
504 
505 	return (buf);
506 }
507 
508 static int
imsg_wait_command_completion(void)509 imsg_wait_command_completion(void)
510 {
511 	int  n;
512 
513 	if (imsgbuf_flush(&ctl_ibuf) == -1)
514 		return (-1);
515 	do {
516 		if ((n = imsg_get(&ctl_ibuf, &ctl_imsg)) == -1)
517 			return (-1);
518 		if (n != 0)
519 			break;
520 		if (imsgbuf_read(&ctl_ibuf) != 1)
521 			return (-1);
522 	} while (1);
523 
524 	return (ctl_imsg.hdr.type);
525 }
526