Coverage Report

Created: 2026-07-15 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libssh/src/ttyopts.c
Line
Count
Source
1
/*
2
 * ttyopts.c - encoding of TTY modes.
3
 *
4
 * This file is part of the SSH Library
5
 *
6
 * Copyright (c) 2023    by Utimaco TS GmbH <oss_committee@utimaco.com>
7
 * Author: Daniel Evers <daniel.evers@utimaco.com>
8
 *
9
 * The SSH Library is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU Lesser General Public License as published by
11
 * the Free Software Foundation; either version 2.1 of the License, or (at your
12
 * option) any later version.
13
 *
14
 * The SSH Library is distributed in the hope that it will be useful, but
15
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
17
 * License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public License
20
 * along with the SSH Library; see the file COPYING.  If not, write to
21
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
22
 * MA 02111-1307, USA.
23
 */
24
25
#include "config.h"
26
27
#include <stdint.h>
28
#include <stdio.h>
29
30
#include <libssh/priv.h>
31
#include <string.h>
32
33
#ifdef HAVE_TERMIOS_H
34
#include <termios.h>
35
#endif
36
37
/** Terminal mode opcodes */
38
enum {
39
    TTY_OP_END = 0,
40
    TTY_OP_VINTR = 1,
41
    TTY_OP_VQUIT = 2,
42
    TTY_OP_VERASE = 3,
43
    TTY_OP_VKILL = 4,
44
    TTY_OP_VEOF = 5,
45
    TTY_OP_VEOL = 6,
46
    TTY_OP_VEOL2 = 7,
47
    TTY_OP_VSTART = 8,
48
    TTY_OP_VSTOP = 9,
49
    TTY_OP_VSUSP = 10,
50
    TTY_OP_VDSUSP = 11,
51
    TTY_OP_VREPRINT = 12,
52
    TTY_OP_VWERASE = 13,
53
    TTY_OP_VLNEXT = 14,
54
    TTY_OP_VFLUSH = 15,
55
    TTY_OP_VSWTC = 16,
56
    TTY_OP_VSTATUS = 17,
57
    TTY_OP_VDISCARD = 18,
58
    TTY_OP_IGNPAR = 30,
59
    TTY_OP_PARMRK = 31,
60
    TTY_OP_INPCK = 32,
61
    TTY_OP_ISTRIP = 33,
62
    TTY_OP_INLCR = 34,
63
    TTY_OP_IGNCR = 35,
64
    TTY_OP_ICRNL = 36,
65
    TTY_OP_IUCLC = 37,
66
    TTY_OP_IXON = 38,
67
    TTY_OP_IXANY = 39,
68
    TTY_OP_IXOFF = 40,
69
    TTY_OP_IMAXBEL = 41,
70
    TTY_OP_IUTF8 = 42,
71
    TTY_OP_ISIG = 50,
72
    TTY_OP_ICANON = 51,
73
    TTY_OP_XCASE = 52,
74
    TTY_OP_ECHO = 53,
75
    TTY_OP_ECHOE = 54,
76
    TTY_OP_ECHOK = 55,
77
    TTY_OP_ECHONL = 56,
78
    TTY_OP_NOFLSH = 57,
79
    TTY_OP_TOSTOP = 58,
80
    TTY_OP_IEXTEN = 59,
81
    TTY_OP_ECHOCTL = 60,
82
    TTY_OP_ECHOKE = 61,
83
    TTY_OP_PENDIN = 62,
84
    TTY_OP_OPOST = 70,
85
    TTY_OP_OLCUC = 71,
86
    TTY_OP_ONLCR = 72,
87
    TTY_OP_OCRNL = 73,
88
    TTY_OP_ONOCR = 74,
89
    TTY_OP_ONLRET = 75,
90
    TTY_OP_CS7 = 90,
91
    TTY_OP_CS8 = 91,
92
    TTY_OP_PARENB = 92,
93
    TTY_OP_PARODD = 93,
94
    TTY_OP_ISPEED = 128,
95
    TTY_OP_OSPEED = 129,
96
};
97
98
/**
99
 * Encodes a single SSH terminal mode option into the buffer.
100
 *
101
 * @param[in]  attr     The mode's opcode value.
102
 *
103
 * @param[in]  value    The mode's value.
104
 *
105
 * @param[out] buf      Destination buffer to encode into.
106
 *
107
 * @param[in]  buflen   The length of the buffer.
108
 *
109
 * @return              number of bytes written to the buffer on success, -1 on
110
 * error.
111
 */
112
static int
113
encode_termios_opt(unsigned char opcode,
114
                   uint32_t value,
115
                   unsigned char *buf,
116
                   size_t buflen)
117
0
{
118
0
    int offset = 0;
119
120
    /* always need 5 bytes */
121
0
    if (buflen < 5) {
122
0
        return -1;
123
0
    }
124
125
    /* 1 byte opcode */
126
0
    buf[offset++] = opcode;
127
128
    /* 4 bytes value (big endian) */
129
0
    value = htonl(value);
130
0
    memcpy(buf + offset, &value, sizeof(value));
131
0
    offset += sizeof(value);
132
133
0
    return offset;
134
0
}
135
136
#ifdef HAVE_TERMIOS_H
137
/** Converts a baudrate constant (Bxxxx) to a numeric value. */
138
static int
139
baud2speed(int baudrate)
140
0
{
141
0
    switch (baudrate) {
142
0
    default:
143
0
    case B0:
144
0
        return 0;
145
0
    case B50:
146
0
        return 50;
147
0
    case B75:
148
0
        return 75;
149
0
    case B110:
150
0
        return 110;
151
0
    case B134:
152
0
        return 134;
153
0
    case B150:
154
0
        return 150;
155
0
    case B200:
156
0
        return 200;
157
0
    case B300:
158
0
        return 300;
159
0
    case B600:
160
0
        return 600;
161
0
    case B1200:
162
0
        return 1200;
163
0
    case B1800:
164
0
        return 1800;
165
0
    case B2400:
166
0
        return 2400;
167
0
    case B4800:
168
0
        return 4800;
169
0
    case B9600:
170
0
        return 9600;
171
0
    case B19200:
172
0
        return 19200;
173
0
    case B38400:
174
0
        return 38400;
175
0
#ifdef B57600
176
0
    case B57600:
177
0
        return 57600;
178
0
#endif
179
0
#ifdef B115200
180
0
    case B115200:
181
0
        return 115200;
182
0
#endif
183
0
#ifdef B230400
184
0
    case B230400:
185
0
        return 230400;
186
0
#endif
187
0
    }
188
0
}
189
190
/**
191
 * Encodes all terminal options from the given \c termios structure
192
 * into the buffer.
193
 *
194
 * @param[in]  attr     The terminal options to encode.
195
 *
196
 * @param[out] buf      Modes will be encoded into this buffer.
197
 *
198
 * @param[in]  buflen   The length of the buffer.
199
 *
200
 * @return              number of bytes in the buffer on success, -1 on error.
201
 */
202
static int
203
encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen)
204
0
{
205
0
    unsigned int offset = 0;
206
0
    int rc;
207
208
0
#define SSH_ENCODE_OPT(code, value)                                      \
209
0
    rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \
210
0
    if (rc < 0) {                                                        \
211
0
        return rc;                                                       \
212
0
    } else {                                                             \
213
0
        offset += rc;                                                    \
214
0
    }
215
216
0
#define SSH_ENCODE_INPUT_OPT(opt) \
217
0
    SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_iflag & opt) ? 1 : 0)
218
0
    SSH_ENCODE_INPUT_OPT(IGNPAR)
219
0
    SSH_ENCODE_INPUT_OPT(PARMRK)
220
0
    SSH_ENCODE_INPUT_OPT(INPCK)
221
0
    SSH_ENCODE_INPUT_OPT(ISTRIP)
222
0
    SSH_ENCODE_INPUT_OPT(INLCR)
223
0
    SSH_ENCODE_INPUT_OPT(IGNCR)
224
0
    SSH_ENCODE_INPUT_OPT(ICRNL)
225
0
#ifdef IUCLC
226
0
    SSH_ENCODE_INPUT_OPT(IUCLC)
227
0
#endif
228
0
    SSH_ENCODE_INPUT_OPT(IXON)
229
0
    SSH_ENCODE_INPUT_OPT(IXANY)
230
0
    SSH_ENCODE_INPUT_OPT(IXOFF)
231
0
#ifdef IMAXBEL
232
0
    SSH_ENCODE_INPUT_OPT(IMAXBEL)
233
0
#endif
234
0
#ifdef IUTF8
235
0
    SSH_ENCODE_INPUT_OPT(IUTF8)
236
0
#endif
237
0
#undef SSH_ENCODE_INPUT_OPT
238
239
0
#define SSH_ENCODE_OUTPUT_OPT(opt) \
240
0
    SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_oflag & opt) ? 1 : 0)
241
0
    SSH_ENCODE_OUTPUT_OPT(OPOST)
242
0
#ifdef OLCUC
243
0
    SSH_ENCODE_OUTPUT_OPT(OLCUC)
244
0
#endif
245
0
    SSH_ENCODE_OUTPUT_OPT(ONLCR)
246
0
    SSH_ENCODE_OUTPUT_OPT(OCRNL)
247
0
    SSH_ENCODE_OUTPUT_OPT(ONOCR)
248
0
    SSH_ENCODE_OUTPUT_OPT(ONLRET)
249
0
#undef SSH_ENCODE_OUTPUT_OPT
250
251
0
#define SSH_ENCODE_CONTROL_OPT(opt) \
252
0
    SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_cflag & opt) ? 1 : 0)
253
0
    SSH_ENCODE_CONTROL_OPT(CS7)
254
0
    SSH_ENCODE_CONTROL_OPT(CS8)
255
0
    SSH_ENCODE_CONTROL_OPT(PARENB)
256
0
    SSH_ENCODE_CONTROL_OPT(PARODD)
257
0
#undef SSH_ENCODE_CONTROL_OPT
258
259
0
#define SSH_ENCODE_LOCAL_OPT(opt) \
260
0
    SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_lflag & opt) ? 1 : 0)
261
0
    SSH_ENCODE_LOCAL_OPT(ISIG)
262
0
    SSH_ENCODE_LOCAL_OPT(ICANON)
263
0
#ifdef XCASE
264
0
    SSH_ENCODE_LOCAL_OPT(XCASE)
265
0
#endif
266
0
    SSH_ENCODE_LOCAL_OPT(ECHO)
267
0
    SSH_ENCODE_LOCAL_OPT(ECHOE)
268
0
    SSH_ENCODE_LOCAL_OPT(ECHOK)
269
0
    SSH_ENCODE_LOCAL_OPT(ECHONL)
270
0
    SSH_ENCODE_LOCAL_OPT(NOFLSH)
271
0
    SSH_ENCODE_LOCAL_OPT(TOSTOP)
272
0
    SSH_ENCODE_LOCAL_OPT(IEXTEN)
273
0
#ifdef ECHOCTL
274
0
    SSH_ENCODE_LOCAL_OPT(ECHOCTL)
275
0
#endif
276
0
#ifdef ECHOKE
277
0
    SSH_ENCODE_LOCAL_OPT(ECHOKE)
278
0
#endif
279
0
#ifdef PENDIN
280
0
    SSH_ENCODE_LOCAL_OPT(PENDIN)
281
0
#endif
282
0
#undef SSH_ENCODE_LOCAL_OPT
283
284
0
#define SSH_ENCODE_CC_OPT(opt) SSH_ENCODE_OPT(TTY_OP_##opt, attr->c_cc[opt])
285
0
    SSH_ENCODE_CC_OPT(VINTR)
286
0
    SSH_ENCODE_CC_OPT(VQUIT)
287
0
    SSH_ENCODE_CC_OPT(VERASE)
288
0
    SSH_ENCODE_CC_OPT(VKILL)
289
0
    SSH_ENCODE_CC_OPT(VEOF)
290
0
    SSH_ENCODE_CC_OPT(VEOL)
291
0
#ifdef VEOL2
292
0
    SSH_ENCODE_CC_OPT(VEOL2)
293
0
#endif
294
0
    SSH_ENCODE_CC_OPT(VSTART)
295
0
    SSH_ENCODE_CC_OPT(VSTOP)
296
0
    SSH_ENCODE_CC_OPT(VSUSP)
297
#ifdef VDSUSP
298
    SSH_ENCODE_CC_OPT(VDSUSP)
299
#endif
300
0
#ifdef VREPRINT
301
0
    SSH_ENCODE_CC_OPT(VREPRINT)
302
0
#endif
303
0
#ifdef VWERASE
304
0
    SSH_ENCODE_CC_OPT(VWERASE)
305
0
#endif
306
0
#ifdef VLNEXT
307
0
    SSH_ENCODE_CC_OPT(VLNEXT)
308
0
#endif
309
#ifdef VFLUSH
310
    SSH_ENCODE_CC_OPT(VFLUSH)
311
#endif
312
0
#ifdef VSWTC
313
0
    SSH_ENCODE_CC_OPT(VSWTC)
314
0
#endif
315
#ifdef VSTATUS
316
    SSH_ENCODE_CC_OPT(VSTATUS)
317
#endif
318
0
#ifdef VDISCARD
319
0
    SSH_ENCODE_CC_OPT(VDISCARD)
320
0
#endif
321
0
#undef SSH_ENCODE_CC_OPT
322
323
0
    SSH_ENCODE_OPT(TTY_OP_ISPEED, baud2speed(cfgetispeed(attr)))
324
0
    SSH_ENCODE_OPT(TTY_OP_OSPEED, baud2speed(cfgetospeed(attr)))
325
0
#undef SSH_ENCODE_OPT
326
327
    /* end of options */
328
0
    if (buflen > offset) {
329
0
        buf[offset++] = TTY_OP_END;
330
0
    } else {
331
0
        return -1;
332
0
    }
333
334
0
    return (int)offset;
335
0
}
336
#endif
337
338
/**
339
 * Encodes a set of default options to ensure "sane" PTY behavior.
340
 * This function intentionally doesn't use the \c termios structure
341
 * to allow it to work on Windows as well.
342
 *
343
 * The "sane" default set is derived from the `stty sane`, but iutf8 support is
344
 * added on top of that.
345
 *
346
 * @param[out] buf      Modes will be encoded into this buffer.
347
 *
348
 * @param[in]  buflen   The length of the buffer.
349
 *
350
 * @return              number of bytes in the buffer on success, -1 on error.
351
 */
352
static int
353
encode_default_opts(unsigned char *buf, size_t buflen)
354
0
{
355
0
    unsigned int offset = 0;
356
0
    int rc;
357
358
0
#define SSH_ENCODE_OPT(code, value)                                      \
359
0
    rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \
360
0
    if (rc < 0) {                                                        \
361
0
        return rc;                                                       \
362
0
    } else {                                                             \
363
0
        offset += rc;                                                    \
364
0
    }
365
366
0
    SSH_ENCODE_OPT(TTY_OP_VINTR, 003)
367
0
    SSH_ENCODE_OPT(TTY_OP_VQUIT, 034)
368
0
    SSH_ENCODE_OPT(TTY_OP_VERASE, 0177)
369
0
    SSH_ENCODE_OPT(TTY_OP_VKILL, 025)
370
0
    SSH_ENCODE_OPT(TTY_OP_VEOF, 004)
371
0
    SSH_ENCODE_OPT(TTY_OP_VEOL, 0)
372
0
    SSH_ENCODE_OPT(TTY_OP_VEOL2, 0)
373
0
    SSH_ENCODE_OPT(TTY_OP_VSTART, 021)
374
0
    SSH_ENCODE_OPT(TTY_OP_VSTOP, 023)
375
0
    SSH_ENCODE_OPT(TTY_OP_VSUSP, 032)
376
0
    SSH_ENCODE_OPT(TTY_OP_VDSUSP, 031)
377
0
    SSH_ENCODE_OPT(TTY_OP_VREPRINT, 022)
378
0
    SSH_ENCODE_OPT(TTY_OP_VWERASE, 027)
379
0
    SSH_ENCODE_OPT(TTY_OP_VLNEXT, 026)
380
0
    SSH_ENCODE_OPT(TTY_OP_VDISCARD, 017)
381
0
    SSH_ENCODE_OPT(TTY_OP_IGNPAR, 0)
382
0
    SSH_ENCODE_OPT(TTY_OP_PARMRK, 0)
383
0
    SSH_ENCODE_OPT(TTY_OP_INPCK, 0)
384
0
    SSH_ENCODE_OPT(TTY_OP_ISTRIP, 0)
385
0
    SSH_ENCODE_OPT(TTY_OP_INLCR, 0)
386
0
    SSH_ENCODE_OPT(TTY_OP_IGNCR, 0)
387
0
    SSH_ENCODE_OPT(TTY_OP_ICRNL, 1)
388
0
    SSH_ENCODE_OPT(TTY_OP_IUCLC, 0)
389
0
    SSH_ENCODE_OPT(TTY_OP_IXON, 1)
390
0
    SSH_ENCODE_OPT(TTY_OP_IXANY, 0)
391
0
    SSH_ENCODE_OPT(TTY_OP_IXOFF, 0)
392
0
    SSH_ENCODE_OPT(TTY_OP_IMAXBEL, 0)
393
0
    SSH_ENCODE_OPT(TTY_OP_IUTF8, 1)
394
0
    SSH_ENCODE_OPT(TTY_OP_ISIG, 1)
395
0
    SSH_ENCODE_OPT(TTY_OP_ICANON, 1)
396
0
    SSH_ENCODE_OPT(TTY_OP_XCASE, 0)
397
0
    SSH_ENCODE_OPT(TTY_OP_ECHO, 1)
398
0
    SSH_ENCODE_OPT(TTY_OP_ECHOE, 1)
399
0
    SSH_ENCODE_OPT(TTY_OP_ECHOK, 1)
400
0
    SSH_ENCODE_OPT(TTY_OP_ECHONL, 0)
401
0
    SSH_ENCODE_OPT(TTY_OP_NOFLSH, 0)
402
0
    SSH_ENCODE_OPT(TTY_OP_TOSTOP, 0)
403
0
    SSH_ENCODE_OPT(TTY_OP_IEXTEN, 1)
404
0
    SSH_ENCODE_OPT(TTY_OP_ECHOCTL, 1)
405
0
    SSH_ENCODE_OPT(TTY_OP_ECHOKE, 1)
406
0
    SSH_ENCODE_OPT(TTY_OP_PENDIN, 0)
407
0
    SSH_ENCODE_OPT(TTY_OP_OPOST, 1)
408
0
    SSH_ENCODE_OPT(TTY_OP_OLCUC, 0)
409
0
    SSH_ENCODE_OPT(TTY_OP_ONLCR, 1)
410
0
    SSH_ENCODE_OPT(TTY_OP_OCRNL, 0)
411
0
    SSH_ENCODE_OPT(TTY_OP_ONOCR, 0)
412
0
    SSH_ENCODE_OPT(TTY_OP_ONLRET, 0)
413
0
    SSH_ENCODE_OPT(TTY_OP_CS7, 1)
414
0
    SSH_ENCODE_OPT(TTY_OP_CS8, 1)
415
0
    SSH_ENCODE_OPT(TTY_OP_PARENB, 0)
416
0
    SSH_ENCODE_OPT(TTY_OP_PARODD, 0)
417
0
    SSH_ENCODE_OPT(TTY_OP_ISPEED, 38400);
418
0
    SSH_ENCODE_OPT(TTY_OP_OSPEED, 38400);
419
420
0
#undef SSH_ENCODE_OPT
421
422
    /* end of options */
423
0
    if (buflen > offset) {
424
0
        buf[offset++] = TTY_OP_END;
425
0
    } else {
426
0
        return -1;
427
0
    }
428
429
0
    return (int)offset;
430
0
}
431
432
/**
433
 * @ingroup libssh_misc
434
 *
435
 * @brief Encode the current TTY options as SSH modes.
436
 *
437
 * Call this function to determine the settings of the process' TTY and
438
 * encode them as SSH Terminal Modes according to RFC 4254 section 8.
439
 *
440
 * If STDIN isn't connected to a TTY, this function fills the buffer with
441
 * "sane" default modes.
442
 *
443
 * The encoded modes can be passed to \c ssh_channel_request_pty_size_modes .
444
 *
445
 * @code
446
 *   unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE];
447
 *   encode_current_tty_opts(modes_buf, sizeof(modes_buf));
448
 * @endcode
449
 *
450
 *
451
 * @param[out] buf      Modes will be encoded into this buffer.
452
 *
453
 * @param[in]  buflen   The length of the buffer.
454
 *
455
 * @return              number of bytes in the buffer on success, -1 on error.
456
 */
457
int
458
encode_current_tty_opts(unsigned char *buf, size_t buflen)
459
0
{
460
0
#ifdef HAVE_TERMIOS_H
461
0
    struct termios attr;
462
0
    ZERO_STRUCT(attr);
463
464
0
    if (isatty(STDIN_FILENO)) {
465
        /* get local terminal attributes */
466
0
        if (tcgetattr(STDIN_FILENO, &attr) < 0) {
467
0
            perror("tcgetattr");
468
0
            return -1;
469
0
        }
470
0
        return encode_termios_opts(&attr, buf, buflen);
471
0
    }
472
0
#endif
473
474
    /* use "sane" default attributes */
475
0
    return encode_default_opts(buf, buflen);
476
0
}