Revamp SSH authentication code so that user interaction is more
[u/mdw/putty] / terminal.c
index 904f1b8..d712fc9 100644 (file)
@@ -133,14 +133,13 @@ static void unlineptr(termline *line)
        freeline(line);
 }
 
+#ifdef TERM_CC_DIAGS
 /*
  * Diagnostic function: verify that a termline has a correct
  * combining character structure.
  * 
- * XXX-REMOVE-BEFORE-RELEASE: This is a performance-intensive
- * check. Although it's currently really useful for getting all the
- * bugs out of the new cc stuff, it will want to be absent when we
- * make a proper release.
+ * This is a performance-intensive check, so it's no longer enabled
+ * by default.
  */
 static void cc_check(termline *line)
 {
@@ -185,6 +184,7 @@ static void cc_check(termline *line)
 
     sfree(flags);
 }
+#endif
 
 /*
  * Add a combining character to a character cell.
@@ -231,7 +231,9 @@ static void add_cc(termline *line, int col, unsigned long chr)
     line->chars[newcc].chr = chr;
     line->chars[col].cc_next = newcc - col;
 
-    cc_check(line);                   /* XXX-REMOVE-BEFORE-RELEASE */
+#ifdef TERM_CC_DIAGS
+    cc_check(line);
+#endif
 }
 
 /*
@@ -257,7 +259,9 @@ static void clear_cc(termline *line, int col)
 
     line->chars[origcol].cc_next = 0;
 
-    cc_check(line);                   /* XXX-REMOVE-BEFORE-RELEASE */
+#ifdef TERM_CC_DIAGS
+    cc_check(line);
+#endif
 }
 
 /*
@@ -305,7 +309,9 @@ static void copy_termchar(termline *destline, int x, termchar *src)
        add_cc(destline, x, src->chr);
     }
 
-    cc_check(destline);                       /* XXX-REMOVE-BEFORE-RELEASE */
+#ifdef TERM_CC_DIAGS
+    cc_check(destline);
+#endif
 }
 
 /*
@@ -324,7 +330,9 @@ static void move_termchar(termline *line, termchar *dest, termchar *src)
     /* Ensure the original cell doesn't have a cc list. */
     src->cc_next = 0;
 
-    cc_check(line);                   /* XXX-REMOVE-BEFORE-RELEASE */
+#ifdef TERM_CC_DIAGS
+    cc_check(line);
+#endif
 }
 
 /*
@@ -669,9 +677,9 @@ static unsigned char *compressline(termline *ldata)
      * Diagnostics: ensure that the compressed data really does
      * decompress to the right thing.
      * 
-     * XXX-REMOVE-BEFORE-RELEASE: This is a bit performance-heavy
-     * to be leaving in production code.
+     * This is a bit performance-heavy for production code.
      */
+#ifdef TERM_CC_DIAGS
 #ifndef CHECK_SB_COMPRESSION
     {
        int dused;
@@ -701,6 +709,7 @@ static unsigned char *compressline(termline *ldata)
        freeline(dcl);
     }
 #endif
+#endif /* TERM_CC_DIAGS */
 
     /*
      * Trim the allocated memory so we don't waste any, and return.
@@ -960,7 +969,9 @@ static void resizeline(Terminal *term, termline *line, int cols)
        for (i = oldcols; i < cols; i++)
            line->chars[i] = term->basic_erase_char;
 
-       cc_check(line);                /* XXX-REMOVE-BEFORE-RELEASE */
+#ifdef TERM_CC_DIAGS
+       cc_check(line);
+#endif
     }
 }
 
@@ -1835,7 +1846,9 @@ static void scroll(Terminal *term, int topline, int botline, int lines, int sb)
     } else {
        while (lines > 0) {
            line = delpos234(term->screen, topline);
-           cc_check(line);            /* XXX-REMOVE-BEFORE-RELEASE */
+#ifdef TERM_CC_DIAGS
+           cc_check(line);
+#endif
            if (sb && term->savelines > 0) {
                int sblen = count234(term->scrollback);
                /*
@@ -2433,7 +2446,7 @@ static void term_out(Terminal *term)
     unget = -1;
 
     chars = NULL;                     /* placate compiler warnings */
-    while (nchars > 0 || bufchain_size(&term->inbuf) > 0) {
+    while (nchars > 0 || unget != -1 || bufchain_size(&term->inbuf) > 0) {
        if (unget == -1) {
            if (nchars == 0) {
                void *ret;
@@ -2664,22 +2677,16 @@ static void term_out(Terminal *term)
                 */
                compatibility(ANSIMIN);
                if (term->ldisc) {
-                   char abuf[256], *s, *d;
-                   int state = 0;
-                   for (s = term->cfg.answerback, d = abuf; *s; s++) {
-                       if (state) {
-                           if (*s >= 'a' && *s <= 'z')
-                               *d++ = (*s - ('a' - 1));
-                           else if ((*s >= '@' && *s <= '_') ||
-                                    *s == '?' || (*s & 0x80))
-                               *d++ = ('@' ^ *s);
-                           else if (*s == '~')
-                               *d++ = '^';
-                           state = 0;
-                       } else if (*s == '^') {
-                           state = 1;
-                       } else
-                           *d++ = *s;
+                   char abuf[lenof(term->cfg.answerback)], *s, *d;
+                   for (s = term->cfg.answerback, d = abuf; *s;) {
+                       char *n;
+                       char c = ctrlparse(s, &n);
+                       if (n) {
+                           *d++ = c;
+                           s = n;
+                       } else {
+                           *d++ = *s++;
+                       }
                    }
                    lpage_send(term->ldisc, DEFAULT_CODEPAGE,
                               abuf, d - abuf, 0);
@@ -2846,8 +2853,8 @@ static void term_out(Terminal *term)
                        width = 1;
                    if (!width)
                        width = (term->cfg.cjk_ambig_wide ?
-                                wcwidth_cjk((wchar_t) c) :
-                                wcwidth((wchar_t) c));
+                                mk_wcwidth_cjk((wchar_t) c) :
+                                mk_wcwidth((wchar_t) c));
 
                    if (term->wrapnext && term->wrap && width > 0) {
                        cline->lattr |= LATTR_WRAPPED;
@@ -3892,6 +3899,7 @@ static void term_out(Terminal *term)
                            term->curr_attr |= colour;
                            term->default_attr &= ~ATTR_FGMASK;
                            term->default_attr |= colour;
+                           set_erase_char(term);
                        }
                        break;
                      case ANSI('G', '='):      /* set normal background */
@@ -3905,6 +3913,7 @@ static void term_out(Terminal *term)
                            term->curr_attr |= colour;
                            term->default_attr &= ~ATTR_BGMASK;
                            term->default_attr |= colour;
+                           set_erase_char(term);
                        }
                        break;
                      case ANSI('L', '='):
@@ -6248,6 +6257,24 @@ int term_data(Terminal *term, int is_stderr, const char *data, int len)
     return 0;
 }
 
+/*
+ * Write untrusted data to the terminal.
+ * The only control character that should be honoured is \n (which
+ * will behave as a CRLF).
+ */
+int term_data_untrusted(Terminal *term, const char *data, int len)
+{
+    int i;
+    /* FIXME: more sophisticated checking? */
+    for (i = 0; i < len; i++) {
+       if (data[i] == '\n')
+           term_data(term, 1, "\r\n", 2);
+       else if (data[i] & 0x60)
+           term_data(term, 1, data + i, 1);
+    }
+    return 0; /* assumes that term_data() always returns 0 */
+}
+
 void term_provide_logctx(Terminal *term, void *logctx)
 {
     term->logctx = logctx;
@@ -6258,3 +6285,142 @@ void term_set_focus(Terminal *term, int has_focus)
     term->has_focus = has_focus;
     term_schedule_cblink(term);
 }
+
+/*
+ * Provide "auto" settings for remote tty modes, suitable for an
+ * application with a terminal window.
+ */
+char *term_get_ttymode(Terminal *term, const char *mode)
+{
+    char *val = NULL;
+    if (strcmp(mode, "ERASE") == 0) {
+       val = term->cfg.bksp_is_delete ? "^?" : "^H";
+    }
+    /* FIXME: perhaps we should set ONLCR based on cfg.lfhascr as well? */
+    return dupstr(val);
+}
+
+struct term_userpass_state {
+    size_t curr_prompt;
+    int done_prompt;   /* printed out prompt yet? */
+    size_t pos;                /* cursor position */
+};
+
+/*
+ * Process some terminal data in the course of username/password
+ * input.
+ */
+int term_get_userpass_input(Terminal *term, prompts_t *p,
+                           unsigned char *in, int inlen)
+{
+    struct term_userpass_state *s = (struct term_userpass_state *)p->data;
+    if (!s) {
+       /*
+        * First call. Set some stuff up.
+        */
+       p->data = s = snew(struct term_userpass_state);
+       s->curr_prompt = 0;
+       s->done_prompt = 0;
+       /* We only print the `name' caption if we have to... */
+       if (p->name_reqd && p->name) {
+           size_t l = strlen(p->name);
+           term_data_untrusted(term, p->name, l);
+           if (p->name[l-1] != '\n')
+               term_data_untrusted(term, "\n", 1);
+       }
+       /* ...but we always print any `instruction'. */
+       if (p->instruction) {
+           size_t l = strlen(p->instruction);
+           term_data_untrusted(term, p->instruction, l);
+           if (p->instruction[l-1] != '\n')
+               term_data_untrusted(term, "\n", 1);
+       }
+       /*
+        * Zero all the results, in case we abort half-way through.
+        */
+       {
+           int i;
+           for (i = 0; i < p->n_prompts; i++)
+               memset(p->prompts[i]->result, 0, p->prompts[i]->result_len);
+       }
+    }
+
+    while (s->curr_prompt < p->n_prompts) {
+
+       prompt_t *pr = p->prompts[s->curr_prompt];
+       int finished_prompt = 0;
+
+       if (!s->done_prompt) {
+           term_data_untrusted(term, pr->prompt, strlen(pr->prompt));
+           s->done_prompt = 1;
+           s->pos = 0;
+       }
+
+       /* Breaking out here ensures that the prompt is printed even
+        * if we're now waiting for user data. */
+       if (!in || !inlen) break;
+
+       /* FIXME: should we be using local-line-editing code instead? */
+       while (!finished_prompt && inlen) {
+           char c = *in++;
+           inlen--;
+           switch (c) {
+             case 10:
+             case 13:
+               term_data(term, 0, "\r\n", 2);
+               pr->result[s->pos] = '\0';
+               pr->result[pr->result_len - 1] = '\0';
+               /* go to next prompt, if any */
+               s->curr_prompt++;
+               s->done_prompt = 0;
+               finished_prompt = 1; /* break out */
+               break;
+             case 8:
+             case 127:
+               if (s->pos > 0) {
+                   if (pr->echo)
+                       term_data(term, 0, "\b \b", 3);
+                   s->pos--;
+               }
+               break;
+             case 21:
+             case 27:
+               while (s->pos > 0) {
+                   if (pr->echo)
+                       term_data(term, 0, "\b \b", 3);
+                   s->pos--;
+               }
+               break;
+             case 3:
+             case 4:
+               /* Immediate abort. */
+               term_data(term, 0, "\r\n", 2);
+               sfree(s);
+               return 0; /* user abort */
+             default:
+               /*
+                * This simplistic check for printability is disabled
+                * when we're doing password input, because some people
+                * have control characters in their passwords.
+                */
+               if ((!pr->echo ||
+                    (c >= ' ' && c <= '~') ||
+                    ((unsigned char) c >= 160))
+                   && s->pos < pr->result_len - 1) {
+                   pr->result[s->pos++] = c;
+                   if (pr->echo)
+                       term_data(term, 0, &c, 1);
+               }
+               break;
+           }
+       }
+       
+    }
+
+    if (s->curr_prompt < p->n_prompts) {
+       return -1; /* more data required */
+    } else {
+       sfree(s);
+       return +1; /* all done */
+    }
+}