| 1 | /* |
| 2 | * ldisc.c: PuTTY line discipline. Sits between the input coming |
| 3 | * from keypresses in the window, and the output channel leading to |
| 4 | * the back end. Implements echo and/or local line editing, |
| 5 | * depending on what's currently configured. |
| 6 | */ |
| 7 | |
| 8 | #include <stdio.h> |
| 9 | #include <ctype.h> |
| 10 | |
| 11 | #include "putty.h" |
| 12 | #include "terminal.h" |
| 13 | #include "ldisc.h" |
| 14 | |
| 15 | #define ECHOING (ldisc->cfg->localecho == FORCE_ON || \ |
| 16 | (ldisc->cfg->localecho == AUTO && \ |
| 17 | (ldisc->back->ldisc(ldisc->backhandle, LD_ECHO) || \ |
| 18 | term_ldisc(ldisc->term, LD_ECHO)))) |
| 19 | #define EDITING (ldisc->cfg->localedit == FORCE_ON || \ |
| 20 | (ldisc->cfg->localedit == AUTO && \ |
| 21 | (ldisc->back->ldisc(ldisc->backhandle, LD_EDIT) || \ |
| 22 | term_ldisc(ldisc->term, LD_EDIT)))) |
| 23 | |
| 24 | static void c_write(Ldisc ldisc, char *buf, int len) |
| 25 | { |
| 26 | from_backend(ldisc->frontend, 0, buf, len); |
| 27 | } |
| 28 | |
| 29 | static int plen(Ldisc ldisc, unsigned char c) |
| 30 | { |
| 31 | if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term))) |
| 32 | return 1; |
| 33 | else if (c < 128) |
| 34 | return 2; /* ^x for some x */ |
| 35 | else |
| 36 | return 4; /* <XY> for hex XY */ |
| 37 | } |
| 38 | |
| 39 | static void pwrite(Ldisc ldisc, unsigned char c) |
| 40 | { |
| 41 | if ((c >= 32 && c <= 126) || (c >= 160 && !in_utf(ldisc->term))) { |
| 42 | c_write(ldisc, (char *)&c, 1); |
| 43 | } else if (c < 128) { |
| 44 | char cc[2]; |
| 45 | cc[1] = (c == 127 ? '?' : c + 0x40); |
| 46 | cc[0] = '^'; |
| 47 | c_write(ldisc, cc, 2); |
| 48 | } else { |
| 49 | char cc[5]; |
| 50 | sprintf(cc, "<%02X>", c); |
| 51 | c_write(ldisc, cc, 4); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | static void bsb(Ldisc ldisc, int n) |
| 56 | { |
| 57 | while (n--) |
| 58 | c_write(ldisc, "\010 \010", 3); |
| 59 | } |
| 60 | |
| 61 | #define CTRL(x) (x^'@') |
| 62 | #define KCTRL(x) ((x^'@') | 0x100) |
| 63 | |
| 64 | void *ldisc_create(Config *mycfg, Terminal *term, |
| 65 | Backend *back, void *backhandle, |
| 66 | void *frontend) |
| 67 | { |
| 68 | Ldisc ldisc = snew(struct ldisc_tag); |
| 69 | |
| 70 | ldisc->buf = NULL; |
| 71 | ldisc->buflen = 0; |
| 72 | ldisc->bufsiz = 0; |
| 73 | ldisc->quotenext = 0; |
| 74 | |
| 75 | ldisc->cfg = mycfg; |
| 76 | ldisc->back = back; |
| 77 | ldisc->backhandle = backhandle; |
| 78 | ldisc->term = term; |
| 79 | ldisc->frontend = frontend; |
| 80 | |
| 81 | /* Link ourselves into the backend and the terminal */ |
| 82 | if (term) |
| 83 | term->ldisc = ldisc; |
| 84 | if (back) |
| 85 | back->provide_ldisc(backhandle, ldisc); |
| 86 | |
| 87 | return ldisc; |
| 88 | } |
| 89 | |
| 90 | void ldisc_free(void *handle) |
| 91 | { |
| 92 | Ldisc ldisc = (Ldisc) handle; |
| 93 | |
| 94 | if (ldisc->term) |
| 95 | ldisc->term->ldisc = NULL; |
| 96 | if (ldisc->back) |
| 97 | ldisc->back->provide_ldisc(ldisc->backhandle, NULL); |
| 98 | if (ldisc->buf) |
| 99 | sfree(ldisc->buf); |
| 100 | sfree(ldisc); |
| 101 | } |
| 102 | |
| 103 | void ldisc_send(void *handle, char *buf, int len, int interactive) |
| 104 | { |
| 105 | Ldisc ldisc = (Ldisc) handle; |
| 106 | int keyflag = 0; |
| 107 | /* |
| 108 | * Called with len=0 when the options change. We must inform |
| 109 | * the front end in case it needs to know. |
| 110 | */ |
| 111 | if (len == 0) { |
| 112 | ldisc_update(ldisc->frontend, ECHOING, EDITING); |
| 113 | return; |
| 114 | } |
| 115 | /* |
| 116 | * Notify the front end that something was pressed, in case |
| 117 | * it's depending on finding out (e.g. keypress termination for |
| 118 | * Close On Exit). |
| 119 | */ |
| 120 | frontend_keypress(ldisc->frontend); |
| 121 | |
| 122 | /* |
| 123 | * Less than zero means null terminated special string. |
| 124 | */ |
| 125 | if (len < 0) { |
| 126 | len = strlen(buf); |
| 127 | keyflag = KCTRL('@'); |
| 128 | } |
| 129 | /* |
| 130 | * Either perform local editing, or just send characters. |
| 131 | */ |
| 132 | if (EDITING) { |
| 133 | while (len--) { |
| 134 | int c; |
| 135 | c = *buf++ + keyflag; |
| 136 | if (!interactive && c == '\r') |
| 137 | c += KCTRL('@'); |
| 138 | switch (ldisc->quotenext ? ' ' : c) { |
| 139 | /* |
| 140 | * ^h/^?: delete one char and output one BSB |
| 141 | * ^w: delete, and output BSBs, to return to last |
| 142 | * space/nonspace boundary |
| 143 | * ^u: delete, and output BSBs, to return to BOL |
| 144 | * ^c: Do a ^u then send a telnet IP |
| 145 | * ^z: Do a ^u then send a telnet SUSP |
| 146 | * ^\: Do a ^u then send a telnet ABORT |
| 147 | * ^r: echo "^R\n" and redraw line |
| 148 | * ^v: quote next char |
| 149 | * ^d: if at BOL, end of file and close connection, |
| 150 | * else send line and reset to BOL |
| 151 | * ^m: send line-plus-\r\n and reset to BOL |
| 152 | */ |
| 153 | case KCTRL('H'): |
| 154 | case KCTRL('?'): /* backspace/delete */ |
| 155 | if (ldisc->buflen > 0) { |
| 156 | if (ECHOING) |
| 157 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); |
| 158 | ldisc->buflen--; |
| 159 | } |
| 160 | break; |
| 161 | case CTRL('W'): /* delete word */ |
| 162 | while (ldisc->buflen > 0) { |
| 163 | if (ECHOING) |
| 164 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); |
| 165 | ldisc->buflen--; |
| 166 | if (ldisc->buflen > 0 && |
| 167 | isspace((unsigned char)ldisc->buf[ldisc->buflen-1]) && |
| 168 | !isspace((unsigned char)ldisc->buf[ldisc->buflen])) |
| 169 | break; |
| 170 | } |
| 171 | break; |
| 172 | case CTRL('U'): /* delete line */ |
| 173 | case CTRL('C'): /* Send IP */ |
| 174 | case CTRL('\\'): /* Quit */ |
| 175 | case CTRL('Z'): /* Suspend */ |
| 176 | while (ldisc->buflen > 0) { |
| 177 | if (ECHOING) |
| 178 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); |
| 179 | ldisc->buflen--; |
| 180 | } |
| 181 | ldisc->back->special(ldisc->backhandle, TS_EL); |
| 182 | /* |
| 183 | * We don't send IP, SUSP or ABORT if the user has |
| 184 | * configured telnet specials off! This breaks |
| 185 | * talkers otherwise. |
| 186 | */ |
| 187 | if (!ldisc->cfg->telnet_keyboard) |
| 188 | goto default_case; |
| 189 | if (c == CTRL('C')) |
| 190 | ldisc->back->special(ldisc->backhandle, TS_IP); |
| 191 | if (c == CTRL('Z')) |
| 192 | ldisc->back->special(ldisc->backhandle, TS_SUSP); |
| 193 | if (c == CTRL('\\')) |
| 194 | ldisc->back->special(ldisc->backhandle, TS_ABORT); |
| 195 | break; |
| 196 | case CTRL('R'): /* redraw line */ |
| 197 | if (ECHOING) { |
| 198 | int i; |
| 199 | c_write(ldisc, "^R\r\n", 4); |
| 200 | for (i = 0; i < ldisc->buflen; i++) |
| 201 | pwrite(ldisc, ldisc->buf[i]); |
| 202 | } |
| 203 | break; |
| 204 | case CTRL('V'): /* quote next char */ |
| 205 | ldisc->quotenext = TRUE; |
| 206 | break; |
| 207 | case CTRL('D'): /* logout or send */ |
| 208 | if (ldisc->buflen == 0) { |
| 209 | ldisc->back->special(ldisc->backhandle, TS_EOF); |
| 210 | } else { |
| 211 | ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); |
| 212 | ldisc->buflen = 0; |
| 213 | } |
| 214 | break; |
| 215 | /* |
| 216 | * This particularly hideous bit of code from RDB |
| 217 | * allows ordinary ^M^J to do the same thing as |
| 218 | * magic-^M when in Raw protocol. The line `case |
| 219 | * KCTRL('M'):' is _inside_ the if block. Thus: |
| 220 | * |
| 221 | * - receiving regular ^M goes straight to the |
| 222 | * default clause and inserts as a literal ^M. |
| 223 | * - receiving regular ^J _not_ directly after a |
| 224 | * literal ^M (or not in Raw protocol) fails the |
| 225 | * if condition, leaps to the bottom of the if, |
| 226 | * and falls through into the default clause |
| 227 | * again. |
| 228 | * - receiving regular ^J just after a literal ^M |
| 229 | * in Raw protocol passes the if condition, |
| 230 | * deletes the literal ^M, and falls through |
| 231 | * into the magic-^M code |
| 232 | * - receiving a magic-^M empties the line buffer, |
| 233 | * signals end-of-line in one of the various |
| 234 | * entertaining ways, and _doesn't_ fall out of |
| 235 | * the bottom of the if and through to the |
| 236 | * default clause because of the break. |
| 237 | */ |
| 238 | case CTRL('J'): |
| 239 | if (ldisc->cfg->protocol == PROT_RAW && |
| 240 | ldisc->buflen > 0 && ldisc->buf[ldisc->buflen - 1] == '\r') { |
| 241 | if (ECHOING) |
| 242 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); |
| 243 | ldisc->buflen--; |
| 244 | /* FALLTHROUGH */ |
| 245 | case KCTRL('M'): /* send with newline */ |
| 246 | if (ldisc->buflen > 0) |
| 247 | ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); |
| 248 | if (ldisc->cfg->protocol == PROT_RAW) |
| 249 | ldisc->back->send(ldisc->backhandle, "\r\n", 2); |
| 250 | else if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline) |
| 251 | ldisc->back->special(ldisc->backhandle, TS_EOL); |
| 252 | else |
| 253 | ldisc->back->send(ldisc->backhandle, "\r", 1); |
| 254 | if (ECHOING) |
| 255 | c_write(ldisc, "\r\n", 2); |
| 256 | ldisc->buflen = 0; |
| 257 | break; |
| 258 | } |
| 259 | /* FALLTHROUGH */ |
| 260 | default: /* get to this label from ^V handler */ |
| 261 | default_case: |
| 262 | if (ldisc->buflen >= ldisc->bufsiz) { |
| 263 | ldisc->bufsiz = ldisc->buflen + 256; |
| 264 | ldisc->buf = sresize(ldisc->buf, ldisc->bufsiz, char); |
| 265 | } |
| 266 | ldisc->buf[ldisc->buflen++] = c; |
| 267 | if (ECHOING) |
| 268 | pwrite(ldisc, (unsigned char) c); |
| 269 | ldisc->quotenext = FALSE; |
| 270 | break; |
| 271 | } |
| 272 | } |
| 273 | } else { |
| 274 | if (ldisc->buflen != 0) { |
| 275 | ldisc->back->send(ldisc->backhandle, ldisc->buf, ldisc->buflen); |
| 276 | while (ldisc->buflen > 0) { |
| 277 | bsb(ldisc, plen(ldisc, ldisc->buf[ldisc->buflen - 1])); |
| 278 | ldisc->buflen--; |
| 279 | } |
| 280 | } |
| 281 | if (len > 0) { |
| 282 | if (ECHOING) |
| 283 | c_write(ldisc, buf, len); |
| 284 | if (keyflag && ldisc->cfg->protocol == PROT_TELNET && len == 1) { |
| 285 | switch (buf[0]) { |
| 286 | case CTRL('M'): |
| 287 | if (ldisc->cfg->protocol == PROT_TELNET && ldisc->cfg->telnet_newline) |
| 288 | ldisc->back->special(ldisc->backhandle, TS_EOL); |
| 289 | else |
| 290 | ldisc->back->send(ldisc->backhandle, "\r", 1); |
| 291 | break; |
| 292 | case CTRL('?'): |
| 293 | case CTRL('H'): |
| 294 | if (ldisc->cfg->telnet_keyboard) { |
| 295 | ldisc->back->special(ldisc->backhandle, TS_EC); |
| 296 | break; |
| 297 | } |
| 298 | case CTRL('C'): |
| 299 | if (ldisc->cfg->telnet_keyboard) { |
| 300 | ldisc->back->special(ldisc->backhandle, TS_IP); |
| 301 | break; |
| 302 | } |
| 303 | case CTRL('Z'): |
| 304 | if (ldisc->cfg->telnet_keyboard) { |
| 305 | ldisc->back->special(ldisc->backhandle, TS_SUSP); |
| 306 | break; |
| 307 | } |
| 308 | |
| 309 | default: |
| 310 | ldisc->back->send(ldisc->backhandle, buf, len); |
| 311 | break; |
| 312 | } |
| 313 | } else |
| 314 | ldisc->back->send(ldisc->backhandle, buf, len); |
| 315 | } |
| 316 | } |
| 317 | } |