mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-24 16:52:24 +00:00
Add a per-line 'trusted' status in Terminal.
This indicates that a line contains trusted information (originated by PuTTY) or untrusted (from the server). Trusted lines are prefixed by a three-column signature consisting of the trust sigil (i.e. PuTTY icon) and a separating space. To protect against a server using escape sequences to move the cursor back up to a trusted line and overwrite its contents, any attempt to write to a termline is preceded by a call to check_trust_status(), which clears the line completely if the terminal's current trust status is different from the previous state of that line. In the terminal data structures, the trust sigil is represented by 0xDFFE (an otherwise unused value, because it's in the surrogate space). For bidi purposes I've arranged to treat that value as direction-neutral, so that it will appear on the right if a terminal line needs it to. (Not that that's currently likely to happen, with PuTTY not being properly localised, but it's a bit of futureproofing.) The bidi system is also where I actually insert the trust sigil: the _logical_ terminal data structures don't include it. term_bidi_line was a convenient place to add it, because that function was already transforming a logical terminal line into a physical one in a way that also generates a logical<->physical mapping table for handling mouse clicks and cursor positioning; so that function now adds the trust sigil as well as running the bidi algorithm. (A knock-on effect of _that_ is that the log<->phys position map now has to have a value for 'no correspondence', because if the user does click on the trust sigil, there's no logical terminal position corresponding to that. So the map can now contain the special value BIDI_CHAR_INDEX_NONE, and anyone looking things up in it has to be prepared to receive that as an answer.) Of course, this terminal-data transformation can't be kept _wholly_ within term_bidi_line, because unlike proper bidi, it actually reduces the number of visible columns on the line. So the wrapping code (during glyph display and also copy and paste) has to take account of the trusted status and use it to ignore the last 3 columns of the line. This is probably not done absolutely perfectly, but then, it doesn't need to be - trusted lines will be filled with well-controlled data generated from the SSH code, which won't be doing every trick in the book with escape sequences. Only untrusted terminal lines will be using all the terminal's capabilities, and they don't have this sigil getting in the way.
This commit is contained in:
parent
2a5d8e05e8
commit
9c367eba4c
@ -865,7 +865,8 @@ static unsigned char getType(int ch)
|
||||
{0x4e00, 0x9fa5, L},
|
||||
{0xa000, 0xa48c, L},
|
||||
{0xac00, 0xd7a3, L},
|
||||
{0xd800, 0xfa2d, L},
|
||||
{0xd800, 0xdff7, L},
|
||||
{0xe000, 0xfa2d, L},
|
||||
{0xfa30, 0xfa6a, L},
|
||||
{0xfb00, 0xfb06, L},
|
||||
{0xfb13, 0xfb17, L},
|
||||
|
2
putty.h
2
putty.h
@ -1627,6 +1627,7 @@ void term_provide_logctx(Terminal *term, LogContext *logctx);
|
||||
void term_set_focus(Terminal *term, bool has_focus);
|
||||
char *term_get_ttymode(Terminal *term, const char *mode);
|
||||
int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input);
|
||||
void term_set_trust_status(Terminal *term, SeatTrustStatus status);
|
||||
|
||||
typedef enum SmallKeypadKey {
|
||||
SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN,
|
||||
@ -1977,6 +1978,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
|
||||
/*
|
||||
* Exports from minibidi.c.
|
||||
*/
|
||||
#define BIDI_CHAR_INDEX_NONE ((unsigned short)-1)
|
||||
typedef struct bidi_char {
|
||||
unsigned int origwc, wc;
|
||||
unsigned short index, nchars;
|
||||
|
164
terminal.c
164
terminal.c
@ -126,6 +126,7 @@ static termline *newtermline(Terminal *term, int cols, bool bce)
|
||||
line->chars[j] = (bce ? term->erase_char : term->basic_erase_char);
|
||||
line->cols = line->size = cols;
|
||||
line->lattr = LATTR_NORM;
|
||||
line->trusted = false;
|
||||
line->temporary = false;
|
||||
line->cc_free = 0;
|
||||
|
||||
@ -707,10 +708,11 @@ static compressed_scrollback_line *compressline(termline *ldata)
|
||||
}
|
||||
|
||||
/*
|
||||
* Next store the lattrs; same principle.
|
||||
* Next store the lattrs; same principle. We add one extra bit to
|
||||
* this to indicate the trust state of the line.
|
||||
*/
|
||||
{
|
||||
int n = ldata->lattr;
|
||||
int n = ldata->lattr | (ldata->trusted ? 0x10000 : 0);
|
||||
while (n >= 128) {
|
||||
put_byte(b, (unsigned char)((n & 0x7F) | 0x80));
|
||||
n >>= 7;
|
||||
@ -954,12 +956,14 @@ static termline *decompressline(compressed_scrollback_line *line)
|
||||
/*
|
||||
* Now read in the lattr.
|
||||
*/
|
||||
ldata->lattr = shift = 0;
|
||||
int lattr = shift = 0;
|
||||
do {
|
||||
byte = get_byte(bs);
|
||||
ldata->lattr |= (byte & 0x7F) << shift;
|
||||
lattr |= (byte & 0x7F) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
ldata->lattr = lattr & 0xFFFF;
|
||||
ldata->trusted = (lattr & 0x10000) != 0;
|
||||
|
||||
/*
|
||||
* Now we read in each of the RLE streams in turn.
|
||||
@ -1748,6 +1752,8 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win)
|
||||
|
||||
term->last_graphic_char = 0;
|
||||
|
||||
term->trusted = true;
|
||||
|
||||
return term;
|
||||
}
|
||||
|
||||
@ -1804,6 +1810,14 @@ void term_free(Terminal *term)
|
||||
sfree(term);
|
||||
}
|
||||
|
||||
void term_set_trust_status(Terminal *term, SeatTrustStatus status)
|
||||
{
|
||||
/* We don't need to distinguish STS_UNTRUSTED from STS_SESSION,
|
||||
* because the terminal's method of communicating trust can flip
|
||||
* back and forth between trusted and untrusted easily. */
|
||||
term->trusted = (status == STS_TRUSTED);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up the terminal for a given size.
|
||||
*/
|
||||
@ -2152,6 +2166,20 @@ static void clear_line(Terminal *term, termline *line)
|
||||
line->lattr = LATTR_NORM;
|
||||
}
|
||||
|
||||
static void check_trust_status(Terminal *term, termline *line)
|
||||
{
|
||||
if (line->trusted != term->trusted) {
|
||||
/*
|
||||
* If we're displaying trusted output on a previously
|
||||
* untrusted line, or vice versa, we need to switch the
|
||||
* 'trusted' attribute on this terminal line, and also clear
|
||||
* all its previous contents.
|
||||
*/
|
||||
clear_line(term, line);
|
||||
line->trusted = term->trusted;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Scroll the screen. (`lines' is +ve for scrolling forward, -ve
|
||||
* for backward.) `sb' is true if the scrolling is permitted to
|
||||
@ -2240,6 +2268,7 @@ static void scroll(Terminal *term, int topline, int botline,
|
||||
}
|
||||
resizeline(term, line, term->cols);
|
||||
clear_line(term, line);
|
||||
check_trust_status(term, line);
|
||||
addpos234(term->screen, line, botline);
|
||||
|
||||
/*
|
||||
@ -2380,6 +2409,7 @@ static void check_boundary(Terminal *term, int x, int y)
|
||||
return;
|
||||
|
||||
ldata = scrlineptr(y);
|
||||
check_trust_status(term, ldata);
|
||||
check_line_size(term, ldata);
|
||||
if (x == term->cols) {
|
||||
ldata->lattr &= ~LATTR_WRAPPED2;
|
||||
@ -2450,6 +2480,7 @@ static void erase_lots(Terminal *term,
|
||||
scroll(term, 0, scrolllines - 1, scrolllines, true);
|
||||
} else {
|
||||
termline *ldata = scrlineptr(start.y);
|
||||
check_trust_status(term, ldata);
|
||||
while (poslt(start, end)) {
|
||||
check_line_size(term, ldata);
|
||||
if (start.x == term->cols) {
|
||||
@ -2462,6 +2493,7 @@ static void erase_lots(Terminal *term,
|
||||
}
|
||||
if (incpos(start) && start.y < term->rows) {
|
||||
ldata = scrlineptr(start.y);
|
||||
check_trust_status(term, ldata);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2530,6 +2562,7 @@ static void insch(Terminal *term, int n)
|
||||
if (dir < 0)
|
||||
check_boundary(term, term->curs.x + n, term->curs.y);
|
||||
ldata = scrlineptr(term->curs.y);
|
||||
check_trust_status(term, ldata);
|
||||
if (dir < 0) {
|
||||
for (j = 0; j < m; j++)
|
||||
move_termchar(ldata,
|
||||
@ -2803,12 +2836,18 @@ static void term_display_graphic_char(Terminal *term, unsigned long c)
|
||||
(c & CSET_MASK) == 0) && term->logctx)
|
||||
logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
|
||||
|
||||
check_trust_status(term, cline);
|
||||
|
||||
int linecols = term->cols;
|
||||
if (cline->trusted)
|
||||
linecols -= TRUST_SIGIL_WIDTH;
|
||||
|
||||
/*
|
||||
* Preliminary check: if the terminal is only one character cell
|
||||
* wide, then we cannot display any double-width character at all.
|
||||
* Substitute single-width REPLACEMENT CHARACTER instead.
|
||||
*/
|
||||
if (width == 2 && term->cols < 2) {
|
||||
if (width == 2 && linecols < 2) {
|
||||
width = 1;
|
||||
c = 0xFFFD;
|
||||
}
|
||||
@ -2831,7 +2870,7 @@ static void term_display_graphic_char(Terminal *term, unsigned long c)
|
||||
*/
|
||||
check_boundary(term, term->curs.x, term->curs.y);
|
||||
check_boundary(term, term->curs.x+2, term->curs.y);
|
||||
if (term->curs.x == term->cols-1) {
|
||||
if (term->curs.x >= linecols-1) {
|
||||
copy_termchar(cline, term->curs.x,
|
||||
&term->erase_char);
|
||||
cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2;
|
||||
@ -2902,8 +2941,8 @@ static void term_display_graphic_char(Terminal *term, unsigned long c)
|
||||
return;
|
||||
}
|
||||
term->curs.x++;
|
||||
if (term->curs.x == term->cols) {
|
||||
term->curs.x--;
|
||||
if (term->curs.x >= linecols) {
|
||||
term->curs.x = linecols - 1;
|
||||
term->wrapnext = true;
|
||||
if (term->wrap && term->vt52_mode) {
|
||||
cline->lattr |= LATTR_WRAPPED;
|
||||
@ -3514,6 +3553,7 @@ static void term_out(Terminal *term)
|
||||
}
|
||||
ldata = scrlineptr(term->curs.y);
|
||||
check_line_size(term, ldata);
|
||||
check_trust_status(term, ldata);
|
||||
ldata->lattr = nlattr;
|
||||
}
|
||||
break;
|
||||
@ -4297,6 +4337,7 @@ static void term_out(Terminal *term)
|
||||
int p = term->curs.x;
|
||||
termline *cline = scrlineptr(term->curs.y);
|
||||
|
||||
check_trust_status(term, cline);
|
||||
if (n > term->cols - term->curs.x)
|
||||
n = term->cols - term->curs.x;
|
||||
cursplus = term->curs;
|
||||
@ -4919,7 +4960,7 @@ static void parse_optionalrgb(optionalrgb *out, unsigned *values)
|
||||
* fed to the algorithm on each line of the display.
|
||||
*/
|
||||
static bool term_bidi_cache_hit(Terminal *term, int line,
|
||||
termchar *lbefore, int width)
|
||||
termchar *lbefore, int width, bool trusted)
|
||||
{
|
||||
int i;
|
||||
|
||||
@ -4935,6 +4976,9 @@ static bool term_bidi_cache_hit(Terminal *term, int line,
|
||||
if (term->pre_bidi_cache[line].width != width)
|
||||
return false; /* line is wrong width */
|
||||
|
||||
if (term->pre_bidi_cache[line].trusted != trusted)
|
||||
return false; /* line has wrong trust state */
|
||||
|
||||
for (i = 0; i < width; i++)
|
||||
if (!termchars_equal(term->pre_bidi_cache[line].chars+i, lbefore+i))
|
||||
return false; /* line doesn't match cache */
|
||||
@ -4944,7 +4988,7 @@ static bool term_bidi_cache_hit(Terminal *term, int line,
|
||||
|
||||
static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
|
||||
termchar *lafter, bidi_char *wcTo,
|
||||
int width, int size)
|
||||
int width, int size, bool trusted)
|
||||
{
|
||||
size_t i, j;
|
||||
|
||||
@ -4959,6 +5003,8 @@ static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
|
||||
term->post_bidi_cache[j].chars = NULL;
|
||||
term->pre_bidi_cache[j].width =
|
||||
term->post_bidi_cache[j].width = -1;
|
||||
term->pre_bidi_cache[j].trusted = false;
|
||||
term->post_bidi_cache[j].trusted = false;
|
||||
term->pre_bidi_cache[j].forward =
|
||||
term->post_bidi_cache[j].forward = NULL;
|
||||
term->pre_bidi_cache[j].backward =
|
||||
@ -4973,8 +5019,10 @@ static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
|
||||
sfree(term->post_bidi_cache[line].backward);
|
||||
|
||||
term->pre_bidi_cache[line].width = width;
|
||||
term->pre_bidi_cache[line].trusted = trusted;
|
||||
term->pre_bidi_cache[line].chars = snewn(size, termchar);
|
||||
term->post_bidi_cache[line].width = width;
|
||||
term->post_bidi_cache[line].trusted = trusted;
|
||||
term->post_bidi_cache[line].chars = snewn(size, termchar);
|
||||
term->post_bidi_cache[line].forward = snewn(width, int);
|
||||
term->post_bidi_cache[line].backward = snewn(width, int);
|
||||
@ -4987,11 +5035,13 @@ static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
|
||||
for (i = j = 0; j < width; j += wcTo[i].nchars, i++) {
|
||||
int p = wcTo[i].index;
|
||||
|
||||
assert(0 <= p && p < width);
|
||||
if (p != BIDI_CHAR_INDEX_NONE) {
|
||||
assert(0 <= p && p < width);
|
||||
|
||||
for (int x = 0; x < wcTo[i].nchars; x++) {
|
||||
term->post_bidi_cache[line].backward[j+x] = p+x;
|
||||
term->post_bidi_cache[line].forward[p+x] = j+x;
|
||||
for (int x = 0; x < wcTo[i].nchars; x++) {
|
||||
term->post_bidi_cache[line].backward[j+x] = p+x;
|
||||
term->post_bidi_cache[line].forward[p+x] = j+x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5011,9 +5061,11 @@ static termchar *term_bidi_line(Terminal *term, struct termline *ldata,
|
||||
int it;
|
||||
|
||||
/* Do Arabic shaping and bidi. */
|
||||
if(!term->bidi || !term->arabicshaping) {
|
||||
if (!term->bidi || !term->arabicshaping ||
|
||||
(ldata->trusted && term->cols > TRUST_SIGIL_WIDTH)) {
|
||||
|
||||
if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols)) {
|
||||
if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols,
|
||||
ldata->trusted)) {
|
||||
|
||||
if (term->wcFromTo_size < term->cols) {
|
||||
term->wcFromTo_size = term->cols;
|
||||
@ -5055,6 +5107,19 @@ static termchar *term_bidi_line(Terminal *term, struct termline *ldata,
|
||||
term->wcFrom[it].nchars = 1;
|
||||
}
|
||||
|
||||
if (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH) {
|
||||
memmove(
|
||||
term->wcFrom + TRUST_SIGIL_WIDTH, term->wcFrom,
|
||||
(term->cols - TRUST_SIGIL_WIDTH) * sizeof(*term->wcFrom));
|
||||
for (it = 0; it < TRUST_SIGIL_WIDTH; it++) {
|
||||
term->wcFrom[it].origwc = term->wcFrom[it].wc =
|
||||
(it == 0 ? TRUST_SIGIL_CHAR :
|
||||
it == 1 ? UCSWIDE : ' ');
|
||||
term->wcFrom[it].index = BIDI_CHAR_INDEX_NONE;
|
||||
term->wcFrom[it].nchars = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int nbc = 0;
|
||||
for (it = 0; it < term->cols; it++) {
|
||||
term->wcFrom[nbc] = term->wcFrom[it];
|
||||
@ -5088,22 +5153,27 @@ static termchar *term_bidi_line(Terminal *term, struct termline *ldata,
|
||||
for (it=0; it<nbc; it++) {
|
||||
int ipos = term->wcTo[it].index;
|
||||
for (int j = 0; j < term->wcTo[it].nchars; j++) {
|
||||
term->ltemp[opos] = ldata->chars[ipos];
|
||||
if (term->ltemp[opos].cc_next)
|
||||
term->ltemp[opos].cc_next -= opos - ipos;
|
||||
|
||||
if (j > 0)
|
||||
term->ltemp[opos].chr = UCSWIDE;
|
||||
else if (term->wcTo[it].origwc != term->wcTo[it].wc)
|
||||
term->ltemp[opos].chr = term->wcTo[it].wc;
|
||||
if (ipos != BIDI_CHAR_INDEX_NONE) {
|
||||
term->ltemp[opos] = ldata->chars[ipos];
|
||||
if (term->ltemp[opos].cc_next)
|
||||
term->ltemp[opos].cc_next -= opos - ipos;
|
||||
|
||||
if (j > 0)
|
||||
term->ltemp[opos].chr = UCSWIDE;
|
||||
else if (term->wcTo[it].origwc != term->wcTo[it].wc)
|
||||
term->ltemp[opos].chr = term->wcTo[it].wc;
|
||||
} else {
|
||||
term->ltemp[opos] = term->basic_erase_char;
|
||||
term->ltemp[opos].chr =
|
||||
j > 0 ? UCSWIDE : term->wcTo[it].origwc;
|
||||
}
|
||||
opos++;
|
||||
}
|
||||
}
|
||||
assert(opos == term->cols);
|
||||
term_bidi_cache_store(term, scr_y, ldata->chars,
|
||||
term->ltemp, term->wcTo,
|
||||
term->cols, ldata->size);
|
||||
term->cols, ldata->size, ldata->trusted);
|
||||
|
||||
lchars = term->ltemp;
|
||||
} else {
|
||||
@ -5120,10 +5190,21 @@ static void do_paint_draw(Terminal *term, termline *ldata, int x, int y,
|
||||
wchar_t *ch, int ccount,
|
||||
unsigned long attr, truecolour tc)
|
||||
{
|
||||
win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc);
|
||||
if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
|
||||
win_draw_cursor(term->win, x, y, ch, ccount,
|
||||
attr, ldata->lattr, tc);
|
||||
if (ch[0] == TRUST_SIGIL_CHAR) {
|
||||
assert(ldata->trusted);
|
||||
assert(ccount == 1);
|
||||
assert(attr & ATTR_WIDE);
|
||||
wchar_t tch[2];
|
||||
tch[0] = tch[1] = L' ';
|
||||
win_draw_text(term->win, x, y, tch, 2, term->basic_erase_char.attr,
|
||||
ldata->lattr, term->basic_erase_char.truecolour);
|
||||
win_draw_trust_sigil(term->win, x, y);
|
||||
} else {
|
||||
win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc);
|
||||
if (attr & (TATTR_ACTCURS | TATTR_PASCURS))
|
||||
win_draw_cursor(term->win, x, y, ch, ccount,
|
||||
attr, ldata->lattr, tc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -5421,6 +5502,14 @@ static void do_paint(Terminal *term)
|
||||
(j > 0 && d[-1].cc_next != 0))
|
||||
break_run = true;
|
||||
|
||||
/*
|
||||
* Break on both sides of a trust sigil.
|
||||
*/
|
||||
if (d->chr == TRUST_SIGIL_CHAR ||
|
||||
(j >= 2 && d[-1].chr == UCSWIDE &&
|
||||
d[-2].chr == TRUST_SIGIL_CHAR))
|
||||
break_run = true;
|
||||
|
||||
if (!term->ucsdata->dbcs_screenfont && !dirty_line) {
|
||||
if (term->disptext[i]->chars[j].chr == tchar &&
|
||||
(term->disptext[i]->chars[j].attr &~ DATTR_MASK) == tattr)
|
||||
@ -5689,9 +5778,17 @@ static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel,
|
||||
decpos(nlpos);
|
||||
if (poslt(nlpos, bottom))
|
||||
nl = true;
|
||||
} else if (ldata->lattr & LATTR_WRAPPED2) {
|
||||
/* Ignore the last char on the line in a WRAPPED2 line. */
|
||||
decpos(nlpos);
|
||||
} else {
|
||||
if (ldata->trusted) {
|
||||
/* A wrapped line with a trust sigil on it terminates
|
||||
* a few characters earlier. */
|
||||
nlpos.x = (nlpos.x < TRUST_SIGIL_WIDTH ? 0 :
|
||||
nlpos.x - TRUST_SIGIL_WIDTH);
|
||||
}
|
||||
if (ldata->lattr & LATTR_WRAPPED2) {
|
||||
/* Ignore the last char on the line in a WRAPPED2 line. */
|
||||
decpos(nlpos);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -5999,6 +6096,9 @@ static int wordtype(Terminal *term, int uc)
|
||||
static int line_cols(Terminal *term, termline *ldata)
|
||||
{
|
||||
int cols = term->cols;
|
||||
if (ldata->trusted) {
|
||||
cols -= TRUST_SIGIL_WIDTH;
|
||||
}
|
||||
if (ldata->lattr & LATTR_WRAPPED2)
|
||||
cols--;
|
||||
if (cols < 0)
|
||||
|
11
terminal.h
11
terminal.h
@ -16,6 +16,9 @@ struct beeptime {
|
||||
unsigned long ticks;
|
||||
};
|
||||
|
||||
#define TRUST_SIGIL_WIDTH 3
|
||||
#define TRUST_SIGIL_CHAR 0xDFFE
|
||||
|
||||
typedef struct {
|
||||
int y, x;
|
||||
} pos;
|
||||
@ -55,10 +58,12 @@ struct termline {
|
||||
bool temporary; /* true if decompressed from scrollback */
|
||||
int cc_free; /* offset to first cc in free list */
|
||||
struct termchar *chars;
|
||||
bool trusted;
|
||||
};
|
||||
|
||||
struct bidi_cache_entry {
|
||||
int width;
|
||||
bool trusted;
|
||||
struct termchar *chars;
|
||||
int *forward, *backward; /* the permutations of line positions */
|
||||
};
|
||||
@ -277,6 +282,12 @@ struct terminal_tag {
|
||||
struct bidi_cache_entry *pre_bidi_cache, *post_bidi_cache;
|
||||
size_t bidi_cache_size;
|
||||
|
||||
/*
|
||||
* Current trust state, used to annotate every line of the
|
||||
* terminal that a graphic character is output to.
|
||||
*/
|
||||
bool trusted;
|
||||
|
||||
/*
|
||||
* We copy a bunch of stuff out of the Conf structure into local
|
||||
* fields in the Terminal structure, to avoid the repeated
|
||||
|
Loading…
Reference in New Issue
Block a user