155714Skris/*-
255714Skris * Copyright (c) 2002-2007 Sam Leffler, Errno Consulting
355714Skris * All rights reserved.
455714Skris *
555714Skris * Redistribution and use in source and binary forms, with or without
655714Skris * modification, are permitted provided that the following conditions
755714Skris * are met:
8280304Sjkim * 1. Redistributions of source code must retain the above copyright
955714Skris *    notice, this list of conditions and the following disclaimer,
1055714Skris *    without modification.
1155714Skris * 2. Redistributions in binary form must reproduce at minimum a disclaimer
1255714Skris *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
1355714Skris *    redistribution must be conditioned upon including a substantially
1455714Skris *    similar Disclaimer requirement for further binary redistribution.
15280304Sjkim *
1655714Skris * NO WARRANTY
1755714Skris * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1855714Skris * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1955714Skris * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
2055714Skris * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
2155714Skris * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22280304Sjkim * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2355714Skris * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2455714Skris * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
2555714Skris * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2655714Skris * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
2755714Skris * THE POSSIBILITY OF SUCH DAMAGES.
2855714Skris *
2955714Skris * $FreeBSD$
3055714Skris */
3155714Skris
3255714Skris/*
3355714Skris * Monitor 802.11 events using a routing socket.
3455714Skris * Code liberaly swiped from route(8).
3555714Skris */
3655714Skris#include <sys/param.h>
37280304Sjkim#include <sys/file.h>
3855714Skris#include <sys/socket.h>
3955714Skris#include <sys/ioctl.h>
40280304Sjkim#include <sys/sysctl.h>
4155714Skris#include <sys/types.h>
4255714Skris
4355714Skris#include <net/if.h>
4455714Skris#include <net/route.h>
4555714Skris#include <net/if_dl.h>
4655714Skris#include <netinet/in.h>
4755714Skris#include <netinet/if_ether.h>
4855714Skris#include <netatalk/at.h>
4955714Skris#ifdef __NetBSD__
5055714Skris#include <net80211/ieee80211_netbsd.h>
5155714Skris#elif __FreeBSD__
52280304Sjkim#include <net80211/ieee80211_freebsd.h>
5355714Skris#else
5455714Skris#error	"No support for your operating system!"
5555714Skris#endif
5655714Skris#include <arpa/inet.h>
5755714Skris#include <netdb.h>
5855714Skris
5955714Skris#include <ctype.h>
60101613Snectar#include <err.h>
6155714Skris#include <errno.h>
6255714Skris#include <paths.h>
6355714Skris#include <stdio.h>
6455714Skris#include <stdlib.h>
65280304Sjkim#include <string.h>
66298999Sjkim#include <sysexits.h>
6755714Skris#include <unistd.h>
68280304Sjkim#include <ifaddrs.h>
6955714Skris
70160814Ssimon/* XXX */
71280304Sjkimenum ieee80211_notify_cac_event {
72280304Sjkim	IEEE80211_NOTIFY_CAC_START  = 0, /* CAC timer started */
73280304Sjkim	IEEE80211_NOTIFY_CAC_STOP   = 1, /* CAC intentionally stopped */
74280304Sjkim	IEEE80211_NOTIFY_CAC_RADAR  = 2, /* CAC stopped due to radar detectio */
75280304Sjkim	IEEE80211_NOTIFY_CAC_EXPIRE = 3, /* CAC expired w/o radar */
76280304Sjkim};
77280304Sjkim
78280304Sjkimstatic	void print_rtmsg(struct rt_msghdr *rtm, int msglen);
79280304Sjkim
80280304Sjkimint	nflag = 0;
81280304Sjkim
82280304Sjkimint
8355714Skrismain(int argc, char *argv[])
84160814Ssimon{
85280304Sjkim	int n, s;
86280304Sjkim	char msg[2048];
87280304Sjkim
8855714Skris	s = socket(PF_ROUTE, SOCK_RAW, 0);
89160814Ssimon	if (s < 0)
90280304Sjkim		err(EX_OSERR, "socket");
91280304Sjkim	for(;;) {
92280304Sjkim		n = read(s, msg, 2048);
93160814Ssimon		print_rtmsg((struct rt_msghdr *)msg, n);
94160814Ssimon	}
95280304Sjkim	return 0;
96280304Sjkim}
97280304Sjkim
98280304Sjkimstatic void
99280304Sjkimbprintf(FILE *fp, int b, char *s)
100280304Sjkim{
101280304Sjkim	int i;
10255714Skris	int gotsome = 0;
103280304Sjkim
104280304Sjkim	if (b == 0)
105280304Sjkim		return;
106280304Sjkim	while ((i = *s++) != 0) {
107280304Sjkim		if (b & (1 << (i-1))) {
108280304Sjkim			if (gotsome == 0)
109280304Sjkim				i = '<';
110280304Sjkim			else
111280304Sjkim				i = ',';
112280304Sjkim			(void) putc(i, fp);
113280304Sjkim			gotsome = 1;
114280304Sjkim			for (; (i = *s) > 32; s++)
115280304Sjkim				(void) putc(i, fp);
116280304Sjkim		} else
117280304Sjkim			while (*s > 32)
118280304Sjkim				s++;
119280304Sjkim	}
120280304Sjkim	if (gotsome)
121280304Sjkim		putc('>', fp);
122280304Sjkim}
123280304Sjkim
124280304Sjkimchar metricnames[] =
125280304Sjkim"\011pksent\010rttvar\7rtt\6ssthresh\5sendpipe\4recvpipe\3expire\2hopcount"
126280304Sjkim"\1mtu";
127280304Sjkimchar routeflags[] =
128280304Sjkim"\1UP\2GATEWAY\3HOST\4REJECT\5DYNAMIC\6MODIFIED\7DONE\010MASK_PRESENT"
129280304Sjkim"\011CLONING\012XRESOLVE\013LLINFO\014STATIC\015BLACKHOLE\016b016"
130280304Sjkim"\017PROTO2\020PROTO1\021PRCLONING\022WASCLONED\023PROTO3\024CHAINDELETE"
131280304Sjkim"\025PINNED\026LOCAL\027BROADCAST\030MULTICAST";
132280304Sjkimchar ifnetflags[] =
133280304Sjkim"\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5PTP\6b6\7RUNNING\010NOARP"
134298999Sjkim"\011PPROMISC\012ALLMULTI\013OACTIVE\014SIMPLEX\015LINK0\016LINK1"
135280304Sjkim"\017LINK2\020MULTICAST";
13655714Skrischar addrnames[] =
137280304Sjkim"\1DST\2GATEWAY\3NETMASK\4GENMASK\5IFP\6IFA\7AUTHOR\010BRD";
138280304Sjkim
139269686Sjkimstatic const char *
14055714Skrisroutename(struct sockaddr *sa)
141280304Sjkim{
142280304Sjkim	char *cp;
143280304Sjkim	static char line[MAXHOSTNAMELEN + 1];
14455714Skris	struct hostent *hp;
14555714Skris	static char domain[MAXHOSTNAMELEN + 1];
146280304Sjkim	static int first = 1, n;
147280304Sjkim
148280304Sjkim	if (first) {
149280304Sjkim		first = 0;
150280304Sjkim		if (gethostname(domain, MAXHOSTNAMELEN) == 0 &&
151280304Sjkim		    (cp = strchr(domain, '.'))) {
152280304Sjkim			domain[MAXHOSTNAMELEN] = '\0';
153280304Sjkim			(void) strcpy(domain, cp + 1);
154280304Sjkim		} else
155280304Sjkim			domain[0] = 0;
156280304Sjkim	}
157280304Sjkim
158280304Sjkim	if (sa->sa_len == 0)
159280304Sjkim		strcpy(line, "default");
16055714Skris	else switch (sa->sa_family) {
161280304Sjkim
162298999Sjkim	case AF_INET:
163280304Sjkim	    {	struct in_addr in;
164280304Sjkim		in = ((struct sockaddr_in *)sa)->sin_addr;
165280304Sjkim
166298999Sjkim		cp = 0;
16755714Skris		if (in.s_addr == INADDR_ANY || sa->sa_len < 4)
168280304Sjkim			cp = "default";
169298999Sjkim		if (cp == 0 && !nflag) {
170280304Sjkim			hp = gethostbyaddr((char *)&in, sizeof (struct in_addr),
171280304Sjkim				AF_INET);
172280304Sjkim			if (hp) {
173280304Sjkim				if ((cp = strchr(hp->h_name, '.')) &&
174280304Sjkim				    !strcmp(cp + 1, domain))
175280304Sjkim					*cp = 0;
176280304Sjkim				cp = hp->h_name;
177280304Sjkim			}
178298999Sjkim		}
179280304Sjkim		if (cp) {
180280304Sjkim			strncpy(line, cp, sizeof(line) - 1);
181280304Sjkim			line[sizeof(line) - 1] = '\0';
182280304Sjkim		} else
183280304Sjkim			(void) sprintf(line, "%s", inet_ntoa(in));
184280304Sjkim		break;
185280304Sjkim	    }
186280304Sjkim
187280304Sjkim#ifdef INET6
188280304Sjkim	case AF_INET6:
189280304Sjkim	{
190280304Sjkim		struct sockaddr_in6 sin6; /* use static var for safety */
191298999Sjkim		int niflags = 0;
192280304Sjkim#ifdef NI_WITHSCOPEID
19355714Skris		niflags = NI_WITHSCOPEID;
194280304Sjkim#endif
195280304Sjkim
196280304Sjkim		memset(&sin6, 0, sizeof(sin6));
19755714Skris		memcpy(&sin6, sa, sa->sa_len);
198280304Sjkim		sin6.sin6_len = sizeof(struct sockaddr_in6);
199280304Sjkim		sin6.sin6_family = AF_INET6;
200280304Sjkim#ifdef __KAME__
201280304Sjkim		if (sa->sa_len == sizeof(struct sockaddr_in6) &&
20255714Skris		    (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr) ||
203280304Sjkim		     IN6_IS_ADDR_MC_LINKLOCAL(&sin6.sin6_addr)) &&
204280304Sjkim		    sin6.sin6_scope_id == 0) {
205280304Sjkim			sin6.sin6_scope_id =
206280304Sjkim			    ntohs(*(u_int16_t *)&sin6.sin6_addr.s6_addr[2]);
207280304Sjkim			sin6.sin6_addr.s6_addr[2] = 0;
208280304Sjkim			sin6.sin6_addr.s6_addr[3] = 0;
209280304Sjkim		}
210280304Sjkim#endif
211280304Sjkim		if (nflag)
212280304Sjkim			niflags |= NI_NUMERICHOST;
213280304Sjkim		if (getnameinfo((struct sockaddr *)&sin6, sin6.sin6_len,
214280304Sjkim		    line, sizeof(line), NULL, 0, niflags) != 0)
215280304Sjkim			strncpy(line, "invalid", sizeof(line));
216280304Sjkim
217280304Sjkim		return(line);
218280304Sjkim	}
219280304Sjkim#endif
220280304Sjkim
221280304Sjkim	case AF_LINK:
222280304Sjkim		return (link_ntoa((struct sockaddr_dl *)sa));
223280304Sjkim
224280304Sjkim	default:
225280304Sjkim	    {	u_short *s = (u_short *)sa;
22655714Skris		u_short *slim = s + ((sa->sa_len + 1) >> 1);
227160814Ssimon		char *cp = line + sprintf(line, "(%d)", sa->sa_family);
228280304Sjkim		char *cpe = line + sizeof(line);
229280304Sjkim
230280304Sjkim		while (++s < slim && cp < cpe) /* start with sa->sa_data */
231280304Sjkim			if ((n = snprintf(cp, cpe - cp, " %x", *s)) > 0)
232280304Sjkim				cp += n;
233280304Sjkim			else
234280304Sjkim				*cp = '\0';
235160814Ssimon		break;
23655714Skris	    }
237280304Sjkim	}
238280304Sjkim	return (line);
239280304Sjkim}
240280304Sjkim
241280304Sjkim#ifndef SA_SIZE
242280304Sjkim/*
243280304Sjkim * This macro returns the size of a struct sockaddr when passed
244280304Sjkim * through a routing socket. Basically we round up sa_len to
245280304Sjkim * a multiple of sizeof(long), with a minimum of sizeof(long).
246280304Sjkim * The check for a NULL pointer is just a convenience, probably never used.
247280304Sjkim * The case sa_len == 0 should only apply to empty structures.
248280304Sjkim */
249280304Sjkim#define SA_SIZE(sa)						\
250280304Sjkim    (  (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ?	\
251280304Sjkim	sizeof(long)		:				\
252280304Sjkim	1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1) ) )
253280304Sjkim#endif
254280304Sjkim
255280304Sjkimstatic void
25655714Skrispmsg_addrs(char *cp, int addrs)
25755714Skris{
258280304Sjkim	struct sockaddr *sa;
259306196Sjkim	int i;
260306196Sjkim
261306196Sjkim	if (addrs == 0) {
262280304Sjkim		(void) putchar('\n');
263280304Sjkim		return;
264280304Sjkim	}
265280304Sjkim	printf("\nsockaddrs: ");
266280304Sjkim	bprintf(stdout, addrs, addrnames);
267280304Sjkim	putchar('\n');
268306196Sjkim	for (i = 1; i; i <<= 1)
269306196Sjkim		if (i & addrs) {
270306196Sjkim			sa = (struct sockaddr *)cp;
271306196Sjkim			printf(" %s", routename(sa));
272306196Sjkim			cp += SA_SIZE(sa);
273306196Sjkim		}
274306196Sjkim	putchar('\n');
275306196Sjkim}
276306196Sjkim
277306196Sjkimstatic const char *
278280304Sjkimether_sprintf(const uint8_t mac[6])
279280304Sjkim{
280306196Sjkim	static char buf[32];
281306196Sjkim
282306196Sjkim	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
283280304Sjkim		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
28455714Skris	return buf;
285160814Ssimon}
286280304Sjkim
287280304Sjkimstatic void
288280304Sjkimprint_rtmsg(struct rt_msghdr *rtm, int msglen)
289280304Sjkim{
290280304Sjkim	struct if_msghdr *ifm;
291280304Sjkim	struct if_announcemsghdr *ifan;
292280304Sjkim	time_t now = time(NULL);
293280304Sjkim	char *cnow = ctime(&now);
294280304Sjkim
295280304Sjkim	if (rtm->rtm_version != RTM_VERSION) {
296280304Sjkim		(void) printf("routing message version %d not understood\n",
297280304Sjkim		    rtm->rtm_version);
298280304Sjkim		return;
29955714Skris	}
300160814Ssimon	switch (rtm->rtm_type) {
301280304Sjkim	case RTM_IFINFO:
302280304Sjkim		ifm = (struct if_msghdr *)rtm;
303280304Sjkim		printf("%.19s RTM_IFINFO: if# %d, ",
30455714Skris			cnow, ifm->ifm_index);
305160814Ssimon		switch (ifm->ifm_data.ifi_link_state) {
306280304Sjkim		case LINK_STATE_DOWN:
307280304Sjkim			printf("link: down, flags:");
308280304Sjkim			break;
309160814Ssimon		case LINK_STATE_UP:
310160814Ssimon			printf("link: up, flags:");
311280304Sjkim			break;
312280304Sjkim		default:
313160814Ssimon			printf("link: unknown<%d>, flags:",
314280304Sjkim			    ifm->ifm_data.ifi_link_state);
315280304Sjkim			break;
316280304Sjkim		}
317280304Sjkim		bprintf(stdout, ifm->ifm_flags, ifnetflags);
318280304Sjkim		pmsg_addrs((char *)(ifm + 1), ifm->ifm_addrs);
319280304Sjkim		fflush(stdout);
320280304Sjkim		break;
321280304Sjkim	case RTM_IFANNOUNCE:
322280304Sjkim		ifan = (struct if_announcemsghdr *)rtm;
323280304Sjkim		printf("%.19s RTM_IFANNOUNCE: if# %d, what: ",
324280304Sjkim			cnow, ifan->ifan_index);
325280304Sjkim		switch (ifan->ifan_what) {
326280304Sjkim		case IFAN_ARRIVAL:
327280304Sjkim			printf("arrival");
328280304Sjkim			break;
329280304Sjkim		case IFAN_DEPARTURE:
330280304Sjkim			printf("departure");
331306196Sjkim			break;
332280304Sjkim		default:
333280304Sjkim			printf("#%d", ifan->ifan_what);
334280304Sjkim			break;
33555714Skris		}
336238405Sjkim		printf("\n");
337280304Sjkim		fflush(stdout);
338280304Sjkim		break;
339280304Sjkim	case RTM_IEEE80211:
340280304Sjkim#define	V(type)	((struct type *)(&ifan[1]))
341280304Sjkim		ifan = (struct if_announcemsghdr *)rtm;
342280304Sjkim		printf("%.19s RTM_IEEE80211: if# %d, ", cnow, ifan->ifan_index);
343280304Sjkim		switch (ifan->ifan_what) {
344280304Sjkim		case RTM_IEEE80211_ASSOC:
345280304Sjkim			printf("associate with %s",
346238405Sjkim			    ether_sprintf(V(ieee80211_join_event)->iev_addr));
347238405Sjkim			break;
348280304Sjkim		case RTM_IEEE80211_REASSOC:
349280304Sjkim			printf("reassociate with %s",
350280304Sjkim			    ether_sprintf(V(ieee80211_join_event)->iev_addr));
351280304Sjkim			break;
352280304Sjkim		case RTM_IEEE80211_DISASSOC:
353280304Sjkim			printf("disassociate");
354280304Sjkim			break;
355280304Sjkim		case RTM_IEEE80211_JOIN:
356280304Sjkim		case RTM_IEEE80211_REJOIN:
357280304Sjkim			printf("%s station %sjoin",
358280304Sjkim			    ether_sprintf(V(ieee80211_join_event)->iev_addr),
359280304Sjkim			    ifan->ifan_what == RTM_IEEE80211_REJOIN ? "re" : ""
360280304Sjkim			);
36155714Skris			break;
36255714Skris		case RTM_IEEE80211_LEAVE:
363280304Sjkim			printf("%s station leave",
364280304Sjkim			    ether_sprintf(V(ieee80211_leave_event)->iev_addr));
365280304Sjkim			break;
36655714Skris		case RTM_IEEE80211_SCAN:
367280304Sjkim			printf("scan complete");
368280304Sjkim			break;
369280304Sjkim		case RTM_IEEE80211_REPLAY:
370280304Sjkim			printf("replay failure: src %s "
371280304Sjkim			    , ether_sprintf(V(ieee80211_replay_event)->iev_src)
372280304Sjkim			);
373306196Sjkim			printf("dst %s cipher %u keyix %u keyrsc %llu rsc %llu"
374280304Sjkim			    , ether_sprintf(V(ieee80211_replay_event)->iev_dst)
375280304Sjkim			    , V(ieee80211_replay_event)->iev_cipher
376280304Sjkim			    , V(ieee80211_replay_event)->iev_keyix
377280304Sjkim			    , V(ieee80211_replay_event)->iev_keyrsc
378280304Sjkim			    , V(ieee80211_replay_event)->iev_rsc
37955714Skris			);
380280304Sjkim			break;
381280304Sjkim		case RTM_IEEE80211_MICHAEL:
382280304Sjkim			printf("michael failure: src %s "
383280304Sjkim			    , ether_sprintf(V(ieee80211_michael_event)->iev_src)
384280304Sjkim			);
385280304Sjkim			printf("dst %s cipher %u keyix %u"
386280304Sjkim			    , ether_sprintf(V(ieee80211_michael_event)->iev_dst)
387280304Sjkim			    , V(ieee80211_michael_event)->iev_cipher
388280304Sjkim			    , V(ieee80211_michael_event)->iev_keyix
389280304Sjkim			);
390280304Sjkim			break;
391280304Sjkim		case RTM_IEEE80211_WDS:
392280304Sjkim			printf("%s wds discovery",
393280304Sjkim			    ether_sprintf(V(ieee80211_wds_event)->iev_addr));
39455714Skris			break;
395194206Ssimon		case RTM_IEEE80211_CSA:
396280304Sjkim			printf("channel switch announcement: channel %u (%u MHz flags 0x%x) mode %d count %d"
397280304Sjkim			    , V(ieee80211_csa_event)->iev_ieee
398280304Sjkim			    , V(ieee80211_csa_event)->iev_freq
399280304Sjkim			    , V(ieee80211_csa_event)->iev_flags
400280304Sjkim			    , V(ieee80211_csa_event)->iev_mode
401280304Sjkim			    , V(ieee80211_csa_event)->iev_count
402194206Ssimon			);
40355714Skris			break;
404280304Sjkim		case RTM_IEEE80211_CAC:
405280304Sjkim			printf("channel availability check "
406280304Sjkim			    "(channel %u, %u MHz flags 0x%x) "
40755714Skris			    , V(ieee80211_cac_event)->iev_ieee
40855714Skris			    , V(ieee80211_cac_event)->iev_freq
409280304Sjkim			    , V(ieee80211_cac_event)->iev_flags
410280304Sjkim			);
41155714Skris			switch (V(ieee80211_cac_event)->iev_type) {
412280304Sjkim			case IEEE80211_NOTIFY_CAC_START:
413280304Sjkim				printf("start timer");
414280304Sjkim				break;
415280304Sjkim			case IEEE80211_NOTIFY_CAC_STOP:
416280304Sjkim				printf("stop timer");
417280304Sjkim				break;
418280304Sjkim			case IEEE80211_NOTIFY_CAC_EXPIRE:
419280304Sjkim				printf("timer expired");
420280304Sjkim				break;
421280304Sjkim			case IEEE80211_NOTIFY_CAC_RADAR:
422280304Sjkim				printf("radar detected");
42355714Skris				break;
42455714Skris			default:
425280304Sjkim				printf("unknown type %d",
426280304Sjkim				   V(ieee80211_cac_event)->iev_type);
427280304Sjkim				break;
428280304Sjkim			}
429280304Sjkim			break;
430280304Sjkim		case RTM_IEEE80211_DEAUTH:
431280304Sjkim			printf("%s wds deauth",
43255714Skris			    ether_sprintf(V(ieee80211_deauth_event)->iev_addr));
433280304Sjkim			break;
434280304Sjkim		case RTM_IEEE80211_AUTH:
435280304Sjkim			printf("%s node authenticate",
436280304Sjkim			    ether_sprintf(V(ieee80211_auth_event)->iev_addr));
437280304Sjkim			break;
438280304Sjkim		case RTM_IEEE80211_COUNTRY:
439280304Sjkim			printf("%s adopt country code '%c%c'",
440238405Sjkim			    ether_sprintf(V(ieee80211_country_event)->iev_addr),
441280304Sjkim			    V(ieee80211_country_event)->iev_cc[0],
442280304Sjkim			    V(ieee80211_country_event)->iev_cc[1]);
44355714Skris			break;
444280304Sjkim		case RTM_IEEE80211_RADIO:
445280304Sjkim			printf("radio %s",
446280304Sjkim			    V(ieee80211_radio_event)->iev_state ? "ON" : "OFF");
447280304Sjkim			break;
448280304Sjkim		default:
449280304Sjkim			printf("what: #%d", ifan->ifan_what);
450280304Sjkim			break;
451280304Sjkim		}
452280304Sjkim		printf("\n");
453280304Sjkim		fflush(stdout);
45455714Skris		break;
455160814Ssimon#undef V
456280304Sjkim	}
457280304Sjkim}
45855714Skris