1192595Sdes/* $OpenBSD: ttymodes.c,v 1.29 2008/11/02 00:16:16 stevesk Exp $ */
257429Smarkm/*
357429Smarkm * Author: Tatu Ylonen <ylo@cs.hut.fi>
457429Smarkm * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
557429Smarkm *                    All rights reserved
665668Skris *
765668Skris * As far as I am concerned, the code I have written for this software
865668Skris * can be used freely for any purpose.  Any derived versions of this
965668Skris * software must be clearly marked as such, and if the derived work is
1065668Skris * incompatible with the protocol description in the RFC file, it must be
1165668Skris * called by a name other than "ssh" or "Secure Shell".
1257429Smarkm */
1357429Smarkm
1476259Sgreen/*
1576259Sgreen * SSH2 tty modes support by Kevin Steves.
1676259Sgreen * Copyright (c) 2001 Kevin Steves.  All rights reserved.
1776259Sgreen *
1876259Sgreen * Redistribution and use in source and binary forms, with or without
1976259Sgreen * modification, are permitted provided that the following conditions
2076259Sgreen * are met:
2176259Sgreen * 1. Redistributions of source code must retain the above copyright
2276259Sgreen *    notice, this list of conditions and the following disclaimer.
2376259Sgreen * 2. Redistributions in binary form must reproduce the above copyright
2476259Sgreen *    notice, this list of conditions and the following disclaimer in the
2576259Sgreen *    documentation and/or other materials provided with the distribution.
2676259Sgreen *
2776259Sgreen * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2876259Sgreen * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2976259Sgreen * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
3076259Sgreen * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
3176259Sgreen * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
3276259Sgreen * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3376259Sgreen * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3476259Sgreen * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3576259Sgreen * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3676259Sgreen * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3776259Sgreen */
3876259Sgreen
3976259Sgreen/*
4076259Sgreen * Encoding and decoding of terminal modes in a portable way.
4176259Sgreen * Much of the format is defined in ttymodes.h; it is included multiple times
4276259Sgreen * into this file with the appropriate macro definitions to generate the
4376259Sgreen * suitable code.
4476259Sgreen */
4576259Sgreen
4657429Smarkm#include "includes.h"
4757429Smarkm
48162852Sdes#include <sys/types.h>
49162852Sdes
50162852Sdes#include <errno.h>
51162852Sdes#include <string.h>
52162852Sdes#include <termios.h>
53162852Sdes#include <stdarg.h>
54162852Sdes
5557429Smarkm#include "packet.h"
5676259Sgreen#include "log.h"
5776259Sgreen#include "ssh1.h"
5876259Sgreen#include "compat.h"
5976259Sgreen#include "buffer.h"
6057429Smarkm
6176259Sgreen#define TTY_OP_END		0
6276259Sgreen/*
6376259Sgreen * uint32 (u_int) follows speed in SSH1 and SSH2
6476259Sgreen */
6576259Sgreen#define TTY_OP_ISPEED_PROTO1	192
6676259Sgreen#define TTY_OP_OSPEED_PROTO1	193
6776259Sgreen#define TTY_OP_ISPEED_PROTO2	128
6876259Sgreen#define TTY_OP_OSPEED_PROTO2	129
6957429Smarkm
7057429Smarkm/*
7157429Smarkm * Converts POSIX speed_t to a baud rate.  The values of the
7257429Smarkm * constants for speed_t are not themselves portable.
7357429Smarkm */
7460573Skrisstatic int
7557429Smarkmspeed_to_baud(speed_t speed)
7657429Smarkm{
7757429Smarkm	switch (speed) {
7857429Smarkm	case B0:
7957429Smarkm		return 0;
8057429Smarkm	case B50:
8157429Smarkm		return 50;
8257429Smarkm	case B75:
8357429Smarkm		return 75;
8457429Smarkm	case B110:
8557429Smarkm		return 110;
8657429Smarkm	case B134:
8757429Smarkm		return 134;
8857429Smarkm	case B150:
8957429Smarkm		return 150;
9057429Smarkm	case B200:
9157429Smarkm		return 200;
9257429Smarkm	case B300:
9357429Smarkm		return 300;
9457429Smarkm	case B600:
9557429Smarkm		return 600;
9657429Smarkm	case B1200:
9757429Smarkm		return 1200;
9857429Smarkm	case B1800:
9957429Smarkm		return 1800;
10057429Smarkm	case B2400:
10157429Smarkm		return 2400;
10257429Smarkm	case B4800:
10357429Smarkm		return 4800;
10457429Smarkm	case B9600:
10557429Smarkm		return 9600;
10657429Smarkm
10757429Smarkm#ifdef B19200
10857429Smarkm	case B19200:
10957429Smarkm		return 19200;
11057429Smarkm#else /* B19200 */
11157429Smarkm#ifdef EXTA
11257429Smarkm	case EXTA:
11357429Smarkm		return 19200;
11457429Smarkm#endif /* EXTA */
11557429Smarkm#endif /* B19200 */
11657429Smarkm
11757429Smarkm#ifdef B38400
11857429Smarkm	case B38400:
11957429Smarkm		return 38400;
12057429Smarkm#else /* B38400 */
12157429Smarkm#ifdef EXTB
12257429Smarkm	case EXTB:
12357429Smarkm		return 38400;
12457429Smarkm#endif /* EXTB */
12557429Smarkm#endif /* B38400 */
12657429Smarkm
12757429Smarkm#ifdef B7200
12857429Smarkm	case B7200:
12957429Smarkm		return 7200;
13057429Smarkm#endif /* B7200 */
13157429Smarkm#ifdef B14400
13257429Smarkm	case B14400:
13357429Smarkm		return 14400;
13457429Smarkm#endif /* B14400 */
13557429Smarkm#ifdef B28800
13657429Smarkm	case B28800:
13757429Smarkm		return 28800;
13857429Smarkm#endif /* B28800 */
13957429Smarkm#ifdef B57600
14057429Smarkm	case B57600:
14157429Smarkm		return 57600;
14257429Smarkm#endif /* B57600 */
14357429Smarkm#ifdef B76800
14457429Smarkm	case B76800:
14557429Smarkm		return 76800;
14657429Smarkm#endif /* B76800 */
14757429Smarkm#ifdef B115200
14857429Smarkm	case B115200:
14957429Smarkm		return 115200;
15057429Smarkm#endif /* B115200 */
15157429Smarkm#ifdef B230400
15257429Smarkm	case B230400:
15357429Smarkm		return 230400;
15457429Smarkm#endif /* B230400 */
15557429Smarkm	default:
15657429Smarkm		return 9600;
15757429Smarkm	}
15857429Smarkm}
15957429Smarkm
16057429Smarkm/*
16157429Smarkm * Converts a numeric baud rate to a POSIX speed_t.
16257429Smarkm */
16360573Skrisstatic speed_t
16457429Smarkmbaud_to_speed(int baud)
16557429Smarkm{
16657429Smarkm	switch (baud) {
16776259Sgreen	case 0:
16857429Smarkm		return B0;
16957429Smarkm	case 50:
17057429Smarkm		return B50;
17157429Smarkm	case 75:
17257429Smarkm		return B75;
17357429Smarkm	case 110:
17457429Smarkm		return B110;
17557429Smarkm	case 134:
17657429Smarkm		return B134;
17757429Smarkm	case 150:
17857429Smarkm		return B150;
17957429Smarkm	case 200:
18057429Smarkm		return B200;
18157429Smarkm	case 300:
18257429Smarkm		return B300;
18357429Smarkm	case 600:
18457429Smarkm		return B600;
18557429Smarkm	case 1200:
18657429Smarkm		return B1200;
18757429Smarkm	case 1800:
18857429Smarkm		return B1800;
18957429Smarkm	case 2400:
19057429Smarkm		return B2400;
19157429Smarkm	case 4800:
19257429Smarkm		return B4800;
19357429Smarkm	case 9600:
19457429Smarkm		return B9600;
19557429Smarkm
19657429Smarkm#ifdef B19200
19757429Smarkm	case 19200:
19857429Smarkm		return B19200;
19957429Smarkm#else /* B19200 */
20057429Smarkm#ifdef EXTA
20157429Smarkm	case 19200:
20257429Smarkm		return EXTA;
20357429Smarkm#endif /* EXTA */
20457429Smarkm#endif /* B19200 */
20557429Smarkm
20657429Smarkm#ifdef B38400
20757429Smarkm	case 38400:
20857429Smarkm		return B38400;
20957429Smarkm#else /* B38400 */
21057429Smarkm#ifdef EXTB
21157429Smarkm	case 38400:
21257429Smarkm		return EXTB;
21357429Smarkm#endif /* EXTB */
21457429Smarkm#endif /* B38400 */
21557429Smarkm
21657429Smarkm#ifdef B7200
21757429Smarkm	case 7200:
21857429Smarkm		return B7200;
21957429Smarkm#endif /* B7200 */
22057429Smarkm#ifdef B14400
22157429Smarkm	case 14400:
22257429Smarkm		return B14400;
22357429Smarkm#endif /* B14400 */
22457429Smarkm#ifdef B28800
22557429Smarkm	case 28800:
22657429Smarkm		return B28800;
22757429Smarkm#endif /* B28800 */
22857429Smarkm#ifdef B57600
22957429Smarkm	case 57600:
23057429Smarkm		return B57600;
23157429Smarkm#endif /* B57600 */
23257429Smarkm#ifdef B76800
23357429Smarkm	case 76800:
23457429Smarkm		return B76800;
23557429Smarkm#endif /* B76800 */
23657429Smarkm#ifdef B115200
23757429Smarkm	case 115200:
23857429Smarkm		return B115200;
23957429Smarkm#endif /* B115200 */
24057429Smarkm#ifdef B230400
24157429Smarkm	case 230400:
24257429Smarkm		return B230400;
24357429Smarkm#endif /* B230400 */
24457429Smarkm	default:
24557429Smarkm		return B9600;
24657429Smarkm	}
24757429Smarkm}
24857429Smarkm
24957429Smarkm/*
250149749Sdes * Encode a special character into SSH line format.
251149749Sdes */
252149749Sdesstatic u_int
253149749Sdesspecial_char_encode(cc_t c)
254149749Sdes{
255149749Sdes#ifdef _POSIX_VDISABLE
256149749Sdes	if (c == _POSIX_VDISABLE)
257149749Sdes		return 255;
258149749Sdes#endif /* _POSIX_VDISABLE */
259149749Sdes	return c;
260149749Sdes}
261149749Sdes
262149749Sdes/*
263149749Sdes * Decode a special character from SSH line format.
264149749Sdes */
265149749Sdesstatic cc_t
266149749Sdesspecial_char_decode(u_int c)
267149749Sdes{
268149749Sdes#ifdef _POSIX_VDISABLE
269149749Sdes	if (c == 255)
270149749Sdes		return _POSIX_VDISABLE;
271149749Sdes#endif /* _POSIX_VDISABLE */
272149749Sdes	return c;
273149749Sdes}
274149749Sdes
275149749Sdes/*
27657429Smarkm * Encodes terminal modes for the terminal referenced by fd
27776259Sgreen * or tiop in a portable manner, and appends the modes to a packet
27857429Smarkm * being constructed.
27957429Smarkm */
28060573Skrisvoid
28176259Sgreentty_make_modes(int fd, struct termios *tiop)
28257429Smarkm{
28357429Smarkm	struct termios tio;
28457429Smarkm	int baud;
28576259Sgreen	Buffer buf;
28676259Sgreen	int tty_op_ospeed, tty_op_ispeed;
28776259Sgreen	void (*put_arg)(Buffer *, u_int);
28857429Smarkm
28976259Sgreen	buffer_init(&buf);
29076259Sgreen	if (compat20) {
29176259Sgreen		tty_op_ospeed = TTY_OP_OSPEED_PROTO2;
29276259Sgreen		tty_op_ispeed = TTY_OP_ISPEED_PROTO2;
29376259Sgreen		put_arg = buffer_put_int;
29476259Sgreen	} else {
29576259Sgreen		tty_op_ospeed = TTY_OP_OSPEED_PROTO1;
29676259Sgreen		tty_op_ispeed = TTY_OP_ISPEED_PROTO1;
29776259Sgreen		put_arg = (void (*)(Buffer *, u_int)) buffer_put_char;
29857429Smarkm	}
29976259Sgreen
30076259Sgreen	if (tiop == NULL) {
301181111Sdes		if (fd == -1) {
302181111Sdes			debug("tty_make_modes: no fd or tio");
303181111Sdes			goto end;
304181111Sdes		}
30576259Sgreen		if (tcgetattr(fd, &tio) == -1) {
306124208Sdes			logit("tcgetattr: %.100s", strerror(errno));
30776259Sgreen			goto end;
30876259Sgreen		}
30976259Sgreen	} else
31076259Sgreen		tio = *tiop;
31176259Sgreen
31257429Smarkm	/* Store input and output baud rates. */
31357429Smarkm	baud = speed_to_baud(cfgetospeed(&tio));
31476259Sgreen	buffer_put_char(&buf, tty_op_ospeed);
31576259Sgreen	buffer_put_int(&buf, baud);
31657429Smarkm	baud = speed_to_baud(cfgetispeed(&tio));
31776259Sgreen	buffer_put_char(&buf, tty_op_ispeed);
31876259Sgreen	buffer_put_int(&buf, baud);
31957429Smarkm
32057429Smarkm	/* Store values of mode flags. */
32157429Smarkm#define TTYCHAR(NAME, OP) \
32276259Sgreen	buffer_put_char(&buf, OP); \
323149749Sdes	put_arg(&buf, special_char_encode(tio.c_cc[NAME]));
32476259Sgreen
32557429Smarkm#define TTYMODE(NAME, FIELD, OP) \
32676259Sgreen	buffer_put_char(&buf, OP); \
32776259Sgreen	put_arg(&buf, ((tio.FIELD & NAME) != 0));
32857429Smarkm
32957429Smarkm#include "ttymodes.h"
33057429Smarkm
33157429Smarkm#undef TTYCHAR
33257429Smarkm#undef TTYMODE
33357429Smarkm
33476259Sgreenend:
33557429Smarkm	/* Mark end of mode data. */
33676259Sgreen	buffer_put_char(&buf, TTY_OP_END);
33776259Sgreen	if (compat20)
33876259Sgreen		packet_put_string(buffer_ptr(&buf), buffer_len(&buf));
33976259Sgreen	else
34076259Sgreen		packet_put_raw(buffer_ptr(&buf), buffer_len(&buf));
34176259Sgreen	buffer_free(&buf);
34257429Smarkm}
34357429Smarkm
34457429Smarkm/*
34557429Smarkm * Decodes terminal modes for the terminal referenced by fd in a portable
34657429Smarkm * manner from a packet being read.
34757429Smarkm */
34860573Skrisvoid
34957429Smarkmtty_parse_modes(int fd, int *n_bytes_ptr)
35057429Smarkm{
35157429Smarkm	struct termios tio;
35257429Smarkm	int opcode, baud;
35357429Smarkm	int n_bytes = 0;
35457429Smarkm	int failure = 0;
35576259Sgreen	u_int (*get_arg)(void);
356181111Sdes	int arg_size;
35757429Smarkm
35876259Sgreen	if (compat20) {
35976259Sgreen		*n_bytes_ptr = packet_get_int();
36076259Sgreen		if (*n_bytes_ptr == 0)
36176259Sgreen			return;
36276259Sgreen		get_arg = packet_get_int;
36376259Sgreen		arg_size = 4;
36476259Sgreen	} else {
36576259Sgreen		get_arg = packet_get_char;
36676259Sgreen		arg_size = 1;
36776259Sgreen	}
36876259Sgreen
36957429Smarkm	/*
37057429Smarkm	 * Get old attributes for the terminal.  We will modify these
37157429Smarkm	 * flags. I am hoping that if there are any machine-specific
37257429Smarkm	 * modes, they will initially have reasonable values.
37357429Smarkm	 */
37476259Sgreen	if (tcgetattr(fd, &tio) == -1) {
375124208Sdes		logit("tcgetattr: %.100s", strerror(errno));
37657429Smarkm		failure = -1;
37776259Sgreen	}
37857429Smarkm
37957429Smarkm	for (;;) {
38057429Smarkm		n_bytes += 1;
38157429Smarkm		opcode = packet_get_char();
38257429Smarkm		switch (opcode) {
38357429Smarkm		case TTY_OP_END:
38457429Smarkm			goto set;
38557429Smarkm
38676259Sgreen		/* XXX: future conflict possible */
38776259Sgreen		case TTY_OP_ISPEED_PROTO1:
38876259Sgreen		case TTY_OP_ISPEED_PROTO2:
38957429Smarkm			n_bytes += 4;
39057429Smarkm			baud = packet_get_int();
391162852Sdes			if (failure != -1 &&
392162852Sdes			    cfsetispeed(&tio, baud_to_speed(baud)) == -1)
39357429Smarkm				error("cfsetispeed failed for %d", baud);
39457429Smarkm			break;
39557429Smarkm
39676259Sgreen		/* XXX: future conflict possible */
39776259Sgreen		case TTY_OP_OSPEED_PROTO1:
39876259Sgreen		case TTY_OP_OSPEED_PROTO2:
39957429Smarkm			n_bytes += 4;
40057429Smarkm			baud = packet_get_int();
401162852Sdes			if (failure != -1 &&
402162852Sdes			    cfsetospeed(&tio, baud_to_speed(baud)) == -1)
40357429Smarkm				error("cfsetospeed failed for %d", baud);
40457429Smarkm			break;
40557429Smarkm
40676259Sgreen#define TTYCHAR(NAME, OP) \
40776259Sgreen	case OP: \
40876259Sgreen	  n_bytes += arg_size; \
409149749Sdes	  tio.c_cc[NAME] = special_char_decode(get_arg()); \
41057429Smarkm	  break;
41176259Sgreen#define TTYMODE(NAME, FIELD, OP) \
41276259Sgreen	case OP: \
41376259Sgreen	  n_bytes += arg_size; \
414181111Sdes	  if (get_arg()) \
41576259Sgreen	    tio.FIELD |= NAME; \
41676259Sgreen	  else \
41776259Sgreen	    tio.FIELD &= ~NAME;	\
41857429Smarkm	  break;
41957429Smarkm
42057429Smarkm#include "ttymodes.h"
42157429Smarkm
42257429Smarkm#undef TTYCHAR
42357429Smarkm#undef TTYMODE
42457429Smarkm
42557429Smarkm		default:
42657429Smarkm			debug("Ignoring unsupported tty mode opcode %d (0x%x)",
42792555Sdes			    opcode, opcode);
42876259Sgreen			if (!compat20) {
42976259Sgreen				/*
43076259Sgreen				 * SSH1:
43176259Sgreen				 * Opcodes 1 to 127 are defined to have
43276259Sgreen				 * a one-byte argument.
43398675Sdes				 * Opcodes 128 to 159 are defined to have
43498675Sdes				 * an integer argument.
43598675Sdes				 */
43676259Sgreen				if (opcode > 0 && opcode < 128) {
43776259Sgreen					n_bytes += 1;
43876259Sgreen					(void) packet_get_char();
43976259Sgreen					break;
44076259Sgreen				} else if (opcode >= 128 && opcode < 160) {
44198675Sdes					n_bytes += 4;
44298675Sdes					(void) packet_get_int();
44398675Sdes					break;
44476259Sgreen				} else {
44576259Sgreen					/*
44676259Sgreen					 * It is a truly undefined opcode (160 to 255).
44776259Sgreen					 * We have no idea about its arguments.  So we
448162852Sdes					 * must stop parsing.  Note that some data
449162852Sdes					 * may be left in the packet; hopefully there
450162852Sdes					 * is nothing more coming after the mode data.
45176259Sgreen					 */
452162852Sdes					logit("parse_tty_modes: unknown opcode %d",
453162852Sdes					    opcode);
45476259Sgreen					goto set;
45598675Sdes				}
45657429Smarkm			} else {
45757429Smarkm				/*
45876259Sgreen				 * SSH2:
45976259Sgreen				 * Opcodes 1 to 159 are defined to have
46076259Sgreen				 * a uint32 argument.
46176259Sgreen				 * Opcodes 160 to 255 are undefined and
46276259Sgreen				 * cause parsing to stop.
46357429Smarkm				 */
46476259Sgreen				if (opcode > 0 && opcode < 160) {
46557429Smarkm					n_bytes += 4;
46657429Smarkm					(void) packet_get_int();
46757429Smarkm					break;
46876259Sgreen				} else {
469162852Sdes					logit("parse_tty_modes: unknown opcode %d",
470162852Sdes					    opcode);
47176259Sgreen					goto set;
47257429Smarkm				}
47398675Sdes			}
47457429Smarkm		}
47557429Smarkm	}
47657429Smarkm
47757429Smarkmset:
47857429Smarkm	if (*n_bytes_ptr != n_bytes) {
47957429Smarkm		*n_bytes_ptr = n_bytes;
480124208Sdes		logit("parse_tty_modes: n_bytes_ptr != n_bytes: %d %d",
48176259Sgreen		    *n_bytes_ptr, n_bytes);
48257429Smarkm		return;		/* Don't process bytes passed */
48357429Smarkm	}
48457429Smarkm	if (failure == -1)
48576259Sgreen		return;		/* Packet parsed ok but tcgetattr() failed */
48657429Smarkm
48757429Smarkm	/* Set the new modes for the terminal. */
48876259Sgreen	if (tcsetattr(fd, TCSANOW, &tio) == -1)
489124208Sdes		logit("Setting tty modes failed: %.100s", strerror(errno));
49057429Smarkm}
491