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