main.c revision 66907
1/*-
2 * Copyright (c) 1980, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static const char copyright[] =
36"@(#) Copyright (c) 1980, 1993\n\
37	The Regents of the University of California.  All rights reserved.\n";
38#endif /* not lint */
39
40#ifndef lint
41#if 0
42static char sccsid[] = "@(#)from: main.c	8.1 (Berkeley) 6/20/93";
43#endif
44static const char rcsid[] =
45  "$FreeBSD: head/libexec/getty/main.c 66907 2000-10-10 01:50:26Z wollman $";
46#endif /* not lint */
47
48#include <sys/param.h>
49#include <sys/stat.h>
50#include <sys/ioctl.h>
51#include <sys/resource.h>
52#include <sys/ttydefaults.h>
53#include <sys/utsname.h>
54
55#include <ctype.h>
56#include <errno.h>
57#include <fcntl.h>
58#include <locale.h>
59#include <libutil.h>
60#include <signal.h>
61#include <setjmp.h>
62#include <signal.h>
63#include <stdlib.h>
64#include <string.h>
65#include <syslog.h>
66#include <termios.h>
67#include <time.h>
68#include <unistd.h>
69
70#include "gettytab.h"
71#include "pathnames.h"
72#include "extern.h"
73
74/*
75 * Set the amount of running time that getty should accumulate
76 * before deciding that something is wrong and exit.
77 */
78#define GETTY_TIMEOUT	60 /* seconds */
79
80#undef CTRL
81#define CTRL(x)  (x&037)
82
83/* defines for auto detection of incoming PPP calls (->PAP/CHAP) */
84
85#define PPP_FRAME           0x7e  /* PPP Framing character */
86#define PPP_STATION         0xff  /* "All Station" character */
87#define PPP_ESCAPE          0x7d  /* Escape Character */
88#define PPP_CONTROL         0x03  /* PPP Control Field */
89#define PPP_CONTROL_ESCAPED 0x23  /* PPP Control Field, escaped */
90#define PPP_LCP_HI          0xc0  /* LCP protocol - high byte */
91#define PPP_LCP_LOW         0x21  /* LCP protocol - low byte */
92
93struct termios tmode, omode;
94
95int crmod, digit, lower, upper;
96
97char	hostname[MAXHOSTNAMELEN];
98char	name[MAXLOGNAME*3];
99char	dev[] = _PATH_DEV;
100char	ttyn[32];
101
102#define	OBUFSIZ		128
103#define	TABBUFSIZ	512
104
105char	defent[TABBUFSIZ];
106char	tabent[TABBUFSIZ];
107
108char	*env[128];
109
110char partab[] = {
111	0001,0201,0201,0001,0201,0001,0001,0201,
112	0202,0004,0003,0205,0005,0206,0201,0001,
113	0201,0001,0001,0201,0001,0201,0201,0001,
114	0001,0201,0201,0001,0201,0001,0001,0201,
115	0200,0000,0000,0200,0000,0200,0200,0000,
116	0000,0200,0200,0000,0200,0000,0000,0200,
117	0000,0200,0200,0000,0200,0000,0000,0200,
118	0200,0000,0000,0200,0000,0200,0200,0000,
119	0200,0000,0000,0200,0000,0200,0200,0000,
120	0000,0200,0200,0000,0200,0000,0000,0200,
121	0000,0200,0200,0000,0200,0000,0000,0200,
122	0200,0000,0000,0200,0000,0200,0200,0000,
123	0000,0200,0200,0000,0200,0000,0000,0200,
124	0200,0000,0000,0200,0000,0200,0200,0000,
125	0200,0000,0000,0200,0000,0200,0200,0000,
126	0000,0200,0200,0000,0200,0000,0000,0201
127};
128
129#define	ERASE	tmode.c_cc[VERASE]
130#define	KILL	tmode.c_cc[VKILL]
131#define	EOT	tmode.c_cc[VEOF]
132
133#define	puts	Gputs
134
135static void	dingdong __P((int));
136static int	getname __P((void));
137static void	interrupt __P((int));
138static void	oflush __P((void));
139static void	prompt __P((void));
140static void	putchr __P((int));
141static void	putf __P((const char *));
142static void	putpad __P((const char *));
143static void	puts __P((const char *));
144static void	timeoverrun __P((int));
145static char	*getline __P((int));
146static void	setttymode __P((const char *, int));
147static void	setdefttymode __P((const char *));
148static int	opentty __P((const char *, int));
149
150int		main __P((int, char **));
151
152jmp_buf timeout;
153
154static void
155dingdong(signo)
156	int signo;
157{
158	alarm(0);
159	longjmp(timeout, 1);
160}
161
162jmp_buf	intrupt;
163
164static void
165interrupt(signo)
166	int signo;
167{
168	longjmp(intrupt, 1);
169}
170
171/*
172 * Action to take when getty is running too long.
173 */
174static void
175timeoverrun(signo)
176	int signo;
177{
178
179	syslog(LOG_ERR, "getty exiting due to excessive running time");
180	exit(1);
181}
182
183int
184main(argc, argv)
185	int argc;
186	char **argv;
187{
188	extern	char **environ;
189	const char *tname;
190	int first_sleep = 1, first_time = 1;
191	struct rlimit limit;
192	int rval;
193
194	signal(SIGINT, SIG_IGN);
195	signal(SIGQUIT, SIG_IGN);
196
197	openlog("getty", LOG_ODELAY|LOG_CONS|LOG_PID, LOG_AUTH);
198	gethostname(hostname, sizeof(hostname) - 1);
199	hostname[sizeof(hostname) - 1] = '\0';
200	if (hostname[0] == '\0')
201		strcpy(hostname, "Amnesiac");
202
203	/*
204	 * Limit running time to deal with broken or dead lines.
205	 */
206	(void)signal(SIGXCPU, timeoverrun);
207	limit.rlim_max = RLIM_INFINITY;
208	limit.rlim_cur = GETTY_TIMEOUT;
209	(void)setrlimit(RLIMIT_CPU, &limit);
210
211	gettable("default", defent);
212	gendefaults();
213	tname = "default";
214	if (argc > 1)
215		tname = argv[1];
216
217	/*
218	 * The following is a work around for vhangup interactions
219	 * which cause great problems getting window systems started.
220	 * If the tty line is "-", we do the old style getty presuming
221	 * that the file descriptors are already set up for us.
222	 * J. Gettys - MIT Project Athena.
223	 */
224	if (argc <= 2 || strcmp(argv[2], "-") == 0)
225	    strcpy(ttyn, ttyname(STDIN_FILENO));
226	else {
227	    strcpy(ttyn, dev);
228	    strncat(ttyn, argv[2], sizeof(ttyn)-sizeof(dev));
229	    if (strcmp(argv[0], "+") != 0) {
230		chown(ttyn, 0, 0);
231		chmod(ttyn, 0600);
232		revoke(ttyn);
233
234		gettable(tname, tabent);
235
236		/* Init modem sequence has been specified
237		 */
238		if (IC) {
239			if (!opentty(ttyn, O_RDWR|O_NONBLOCK))
240				exit(1);
241			setdefttymode(tname);
242			if (getty_chat(IC, CT, DC) > 0) {
243				syslog(LOG_ERR, "modem init problem on %s", ttyn);
244				(void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode);
245				exit(1);
246			}
247		}
248
249		if (AC) {
250			int i, rfds;
251			struct timeval timeout;
252
253			if (!opentty(ttyn, O_RDWR|O_NONBLOCK))
254				exit(1);
255        		setdefttymode(tname);
256        		rfds = 1 << 0;	/* FD_SET */
257        		timeout.tv_sec = RT;
258        		timeout.tv_usec = 0;
259        		i = select(32, (fd_set*)&rfds, (fd_set*)NULL,
260        			       (fd_set*)NULL, RT ? &timeout : NULL);
261        		if (i < 0) {
262				syslog(LOG_ERR, "select %s: %m", ttyn);
263			} else if (i == 0) {
264				syslog(LOG_NOTICE, "recycle tty %s", ttyn);
265				(void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode);
266				exit(0);  /* recycle for init */
267			}
268			i = getty_chat(AC, CT, DC);
269			if (i > 0) {
270				syslog(LOG_ERR, "modem answer problem on %s", ttyn);
271				(void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode);
272				exit(1);
273			}
274		} else { /* maybe blocking open */
275			if (!opentty(ttyn, O_RDWR | (NC ? O_NONBLOCK : 0 )))
276				exit(1);
277		}
278	    }
279	}
280
281	/* Start with default tty settings */
282	if (tcgetattr(STDIN_FILENO, &tmode) < 0) {
283		syslog(LOG_ERR, "tcgetattr %s: %m", ttyn);
284		exit(1);
285	}
286	/*
287	 * Don't rely on the driver too much, and initialize crucial
288	 * things according to <sys/ttydefaults.h>.  Avoid clobbering
289	 * the c_cc[] settings however, the console drivers might wish
290	 * to leave their idea of the preferred VERASE key value
291	 * there.
292	 */
293	tmode.c_iflag = TTYDEF_IFLAG;
294	tmode.c_oflag = TTYDEF_OFLAG;
295	tmode.c_lflag = TTYDEF_LFLAG;
296	tmode.c_cflag = TTYDEF_CFLAG;
297	tmode.c_cflag |= (NC ? CLOCAL : 0);
298	omode = tmode;
299
300	for (;;) {
301
302		/*
303		 * if a delay was specified then sleep for that
304		 * number of seconds before writing the initial prompt
305		 */
306		if (first_sleep && DE) {
307		    sleep(DE);
308		    /* remove any noise */
309		    (void)tcflush(STDIN_FILENO, TCIOFLUSH);
310		}
311		first_sleep = 0;
312
313		setttymode(tname, 0);
314		if (AB) {
315			tname = autobaud();
316			continue;
317		}
318		if (PS) {
319			tname = portselector();
320			continue;
321		}
322		if (CL && *CL)
323			putpad(CL);
324		edithost(HE);
325
326		/* if this is the first time through this, and an
327		   issue file has been given, then send it */
328		if (first_time && IF) {
329			int fd;
330
331			if ((fd = open(IF, O_RDONLY)) != -1) {
332				char * cp;
333
334				while ((cp = getline(fd)) != NULL) {
335					  putf(cp);
336				}
337				close(fd);
338			}
339		}
340		first_time = 0;
341
342		if (IM && *IM)
343			putf(IM);
344		if (setjmp(timeout)) {
345			cfsetispeed(&tmode, B0);
346			cfsetospeed(&tmode, B0);
347			(void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode);
348			exit(1);
349		}
350		if (TO) {
351			signal(SIGALRM, dingdong);
352			alarm(TO);
353		}
354		if (AL) {
355			const char *p = AL;
356			char *q = name;
357			int n = sizeof name;
358
359			while (*p && q < &name[sizeof name - 1]) {
360				if (isupper(*p))
361					upper = 1;
362				else if (islower(*p))
363					lower = 1;
364				else if (isdigit(*p))
365					digit++;
366				*q++ = *p++;
367			}
368		} else
369			rval = getname();
370		if (rval == 2) {
371			oflush();
372			alarm(0);
373			limit.rlim_max = RLIM_INFINITY;
374			limit.rlim_cur = RLIM_INFINITY;
375			(void)setrlimit(RLIMIT_CPU, &limit);
376			execle(PP, "ppplogin", ttyn, (char *) 0, env);
377			syslog(LOG_ERR, "%s: %m", PP);
378			exit(1);
379		} else if (rval || AL) {
380			register int i;
381
382			oflush();
383			alarm(0);
384			signal(SIGALRM, SIG_DFL);
385			if (name[0] == '-') {
386				puts("user names may not start with '-'.");
387				continue;
388			}
389			if (!(upper || lower || digit))
390				continue;
391			set_flags(2);
392			if (crmod) {
393				tmode.c_iflag |= ICRNL;
394				tmode.c_oflag |= ONLCR;
395			}
396#if REALLY_OLD_TTYS
397			if (upper || UC)
398				tmode.sg_flags |= LCASE;
399			if (lower || LC)
400				tmode.sg_flags &= ~LCASE;
401#endif
402			if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) {
403				syslog(LOG_ERR, "tcsetattr %s: %m", ttyn);
404				exit(1);
405			}
406			signal(SIGINT, SIG_DFL);
407			for (i = 0; environ[i] != (char *)0; i++)
408				env[i] = environ[i];
409			makeenv(&env[i]);
410
411			limit.rlim_max = RLIM_INFINITY;
412			limit.rlim_cur = RLIM_INFINITY;
413			(void)setrlimit(RLIMIT_CPU, &limit);
414			execle(LO, "login", AL ? "-fp" : "-p", name,
415			    (char *) 0, env);
416			syslog(LOG_ERR, "%s: %m", LO);
417			exit(1);
418		}
419		alarm(0);
420		signal(SIGALRM, SIG_DFL);
421		signal(SIGINT, SIG_IGN);
422		if (NX && *NX)
423			tname = NX;
424	}
425}
426
427static int
428opentty(const char *ttyn, int flags)
429{
430	int i, j = 0;
431	int failopenlogged = 0;
432
433	while (j < 10 && (i = open(ttyn, flags)) == -1)
434	{
435		if (((j % 10) == 0) && (errno != ENXIO || !failopenlogged)) {
436			syslog(LOG_ERR, "open %s: %m", ttyn);
437			failopenlogged = 1;
438		}
439		j++;
440		sleep(60);
441	}
442	if (i == -1) {
443		syslog(LOG_ERR, "open %s: %m", ttyn);
444		return 0;
445	}
446	else {
447		login_tty(i);
448		return 1;
449	}
450}
451
452static void
453setdefttymode(tname)
454	const char * tname;
455{
456	if (tcgetattr(STDIN_FILENO, &tmode) < 0) {
457		syslog(LOG_ERR, "tcgetattr %s: %m", ttyn);
458		exit(1);
459	}
460	tmode.c_iflag = TTYDEF_IFLAG;
461        tmode.c_oflag = TTYDEF_OFLAG;
462        tmode.c_lflag = TTYDEF_LFLAG;
463        tmode.c_cflag = TTYDEF_CFLAG;
464        omode = tmode;
465	setttymode(tname, 1);
466}
467
468static void
469setttymode(tname, raw)
470	const char * tname;
471	int raw;
472{
473	int off = 0;
474
475	gettable(tname, tabent);
476	if (OPset || EPset || APset)
477		APset++, OPset++, EPset++;
478	setdefaults();
479	(void)tcflush(STDIN_FILENO, TCIOFLUSH);	/* clear out the crap */
480	ioctl(STDIN_FILENO, FIONBIO, &off);	/* turn off non-blocking mode */
481	ioctl(STDIN_FILENO, FIOASYNC, &off);	/* ditto for async mode */
482
483	if (IS)
484		cfsetispeed(&tmode, speed(IS));
485	else if (SP)
486		cfsetispeed(&tmode, speed(SP));
487	if (OS)
488		cfsetospeed(&tmode, speed(OS));
489	else if (SP)
490		cfsetospeed(&tmode, speed(SP));
491	set_flags(0);
492	setchars();
493	if (raw)
494		cfmakeraw(&tmode);
495	if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) {
496		syslog(LOG_ERR, "tcsetattr %s: %m", ttyn);
497		exit(1);
498	}
499}
500
501
502static int
503getname()
504{
505	register int c;
506	register char *np;
507	unsigned char cs;
508	int ppp_state = 0;
509	int ppp_connection = 0;
510
511	/*
512	 * Interrupt may happen if we use CBREAK mode
513	 */
514	if (setjmp(intrupt)) {
515		signal(SIGINT, SIG_IGN);
516		return (0);
517	}
518	signal(SIGINT, interrupt);
519	set_flags(1);
520	prompt();
521	oflush();
522	if (PF > 0) {
523		sleep(PF);
524		PF = 0;
525	}
526	if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) {
527		syslog(LOG_ERR, "%s: %m", ttyn);
528		exit(1);
529	}
530	crmod = digit = lower = upper = 0;
531	np = name;
532	for (;;) {
533		oflush();
534		if (read(STDIN_FILENO, &cs, 1) <= 0)
535			exit(0);
536		if ((c = cs&0177) == 0)
537			return (0);
538
539		/* PPP detection state machine..
540		   Look for sequences:
541		   PPP_FRAME, PPP_STATION, PPP_ESCAPE, PPP_CONTROL_ESCAPED or
542		   PPP_FRAME, PPP_STATION, PPP_CONTROL (deviant from RFC)
543		   See RFC1662.
544		   Derived from code from Michael Hancock, <michaelh@cet.co.jp>
545		   and Erik 'PPP' Olson, <eriko@wrq.com>
546		 */
547
548		if (PP && (cs == PPP_FRAME)) {
549			ppp_state = 1;
550		} else if (ppp_state == 1 && cs == PPP_STATION) {
551			ppp_state = 2;
552		} else if (ppp_state == 2 && cs == PPP_ESCAPE) {
553			ppp_state = 3;
554		} else if ((ppp_state == 2 && cs == PPP_CONTROL)
555			|| (ppp_state == 3 && cs == PPP_CONTROL_ESCAPED)) {
556			ppp_state = 4;
557		} else if (ppp_state == 4 && cs == PPP_LCP_HI) {
558			ppp_state = 5;
559		} else if (ppp_state == 5 && cs == PPP_LCP_LOW) {
560			ppp_connection = 1;
561			break;
562		} else {
563			ppp_state = 0;
564		}
565
566		if (c == EOT || c == CTRL('d'))
567			exit(1);
568		if (c == '\r' || c == '\n' || np >= &name[sizeof name-1]) {
569			putf("\r\n");
570			break;
571		}
572		if (islower(c))
573			lower = 1;
574		else if (isupper(c))
575			upper = 1;
576		else if (c == ERASE || c == '\b' || c == 0177) {
577			if (np > name) {
578				np--;
579				if (cfgetospeed(&tmode) >= 1200)
580					puts("\b \b");
581				else
582					putchr(cs);
583			}
584			continue;
585		} else if (c == KILL || c == CTRL('u')) {
586			putchr('\r');
587			if (cfgetospeed(&tmode) < 1200)
588				putchr('\n');
589			/* this is the way they do it down under ... */
590			else if (np > name)
591				puts("                                     \r");
592			prompt();
593			np = name;
594			continue;
595		} else if (isdigit(c))
596			digit++;
597		if (IG && (c <= ' ' || c > 0176))
598			continue;
599		*np++ = c;
600		putchr(cs);
601	}
602	signal(SIGINT, SIG_IGN);
603	*np = 0;
604	if (c == '\r')
605		crmod = 1;
606	if ((upper && !lower && !LC) || UC)
607		for (np = name; *np; np++)
608			if (isupper(*np))
609				*np = tolower(*np);
610	return (1 + ppp_connection);
611}
612
613static void
614putpad(s)
615	register const char *s;
616{
617	register pad = 0;
618	speed_t ospeed = cfgetospeed(&tmode);
619
620	if (isdigit(*s)) {
621		while (isdigit(*s)) {
622			pad *= 10;
623			pad += *s++ - '0';
624		}
625		pad *= 10;
626		if (*s == '.' && isdigit(s[1])) {
627			pad += s[1] - '0';
628			s += 2;
629		}
630	}
631
632	puts(s);
633	/*
634	 * If no delay needed, or output speed is
635	 * not comprehensible, then don't try to delay.
636	 */
637	if (pad == 0 || ospeed <= 0)
638		return;
639
640	/*
641	 * Round up by a half a character frame, and then do the delay.
642	 * Too bad there are no user program accessible programmed delays.
643	 * Transmitting pad characters slows many terminals down and also
644	 * loads the system.
645	 */
646	pad = (pad * ospeed + 50000) / 100000;
647	while (pad--)
648		putchr(*PC);
649}
650
651static void
652puts(s)
653	register const char *s;
654{
655	while (*s)
656		putchr(*s++);
657}
658
659char	outbuf[OBUFSIZ];
660int	obufcnt = 0;
661
662static void
663putchr(cc)
664	int cc;
665{
666	char c;
667
668	c = cc;
669	if (!NP) {
670		c |= partab[c&0177] & 0200;
671		if (OP)
672			c ^= 0200;
673	}
674	if (!UB) {
675		outbuf[obufcnt++] = c;
676		if (obufcnt >= OBUFSIZ)
677			oflush();
678	} else
679		write(STDOUT_FILENO, &c, 1);
680}
681
682static void
683oflush()
684{
685	if (obufcnt)
686		write(STDOUT_FILENO, outbuf, obufcnt);
687	obufcnt = 0;
688}
689
690static void
691prompt()
692{
693
694	putf(LM);
695	if (CO)
696		putchr('\n');
697}
698
699
700static char *
701getline(fd)
702	int fd;
703{
704	int i = 0;
705	static char linebuf[512];
706
707	/*
708	 * This is certainly slow, but it avoids having to include
709	 * stdio.h unnecessarily. Issue files should be small anyway.
710	 */
711	while (i < (sizeof linebuf - 3) && read(fd, linebuf+i, 1)==1) {
712		if (linebuf[i] == '\n') {
713			/* Don't rely on newline mode, assume raw */
714			linebuf[i++] = '\r';
715			linebuf[i++] = '\n';
716			linebuf[i] = '\0';
717			return linebuf;
718		}
719		++i;
720	}
721	linebuf[i] = '\0';
722	return i ? linebuf : 0;
723}
724
725static void
726putf(cp)
727	register const char *cp;
728{
729	extern char editedhost[];
730	time_t t;
731	char *slash, db[100];
732
733	static struct utsname kerninfo;
734
735	if (!*kerninfo.sysname)
736		uname(&kerninfo);
737
738	while (*cp) {
739		if (*cp != '%') {
740			putchr(*cp++);
741			continue;
742		}
743		switch (*++cp) {
744
745		case 't':
746			slash = strrchr(ttyn, '/');
747			if (slash == (char *) 0)
748				puts(ttyn);
749			else
750				puts(&slash[1]);
751			break;
752
753		case 'h':
754			puts(editedhost);
755			break;
756
757		case 'd': {
758			t = (time_t)0;
759			(void)time(&t);
760			if (Lo)
761				(void)setlocale(LC_TIME, Lo);
762			(void)strftime(db, sizeof(db), "%+", localtime(&t));
763			puts(db);
764			break;
765
766		case 's':
767			puts(kerninfo.sysname);
768			break;
769
770		case 'm':
771			puts(kerninfo.machine);
772			break;
773
774		case 'r':
775			puts(kerninfo.release);
776			break;
777
778		case 'v':
779			puts(kerninfo.version);
780			break;
781		}
782
783		case '%':
784			putchr('%');
785			break;
786		}
787		cp++;
788	}
789}
790