Hi,

an earlier version of the following patch has been is snaps for two
weeks, and no fallout was reported.  I'm now asking for OKs for the
version appended below.

I got plenty of feedback from martijn@ which led to some improvements
that seem significant enough to me to repost:

 1) For window sizes smaller than 35 column positions,
    truncate the progress meter string before handing
    it from refresh_progress_meter() to atomicio()
    in progressmeter.c, line 223.

 2) In utf8.c, dont forget to include <wchar.h>.

 3) In vfmprintf(), file utf8.c, cast sizeof() to (int) before
    comparing it against an int variable.  That's OK in this
    case because it's actually a constant defined a few lines above.

 4) Call the utility functions *mprintf() rather than mbs*printf()
    for closer analogy with *wprintf() and because that's less of
    a tongue twister.

Martijn also pointed out that while this patch improves safe and
sound handling of UTF-8 and US-ASCII locales and - on other operating
systems supporting them - ASCII-compatible locales like ISO_LATIN-*,
it does not make sftp(1) and scp(1) work with arbitrary locales,
not even on operating systems supporting them, for two reasons:

 1. In arbitrary locales, you cannot just print ASCII characters to
    stdout and stderr but sftp(1) and scp(1) do that all over the
    place in many source files.  For example, UTF-16 encodes ASCII
    characters as two-byte sequences, so if you plainly print two
    ASCII characters, what you will see on screen instead is one
    bogus, probably quite unusual Unicode charater.

 2. In arbitrary locales, multibyte strings are not terminated by
    NUL *bytes*, but by NUL *multibyte characters* that may span
    multiple bytes.  Conversely, a NUL byte may occur in the middle
    of a multibyte string.  For example, if you translate the
    US-ASCII string "abc\0" (explicitly showing the terminating NUL
    here, which you of course wouldn't do in C code) into UTF-16BE,
    it becomes "\0a\0b\0c\0\0", whereas "abc\0" in the UTF-16BE
    locale is an unterminated multibyte character string that is
    likely to provoke buffer overruns.

In a private discussion with stsp@, bentley@, and czarkoff@, we
came to the conclusion that there is no reasonable way to support
non-ASCII compatible multibyte locales in OpenSSH.  The only viable
ways we could think of require using either iconv(3) or gettext(3),
and even then it would remain very fragile for the reasons i recently
discussed on undeadly:  You cannot assume that the encoding of
filenames on a remote system agrees with the locale of the local
user's terminal.  And even if you could, using iconv(3) or gettext(3)
in OpenSSH is obviously unacceptable.  This is supposed to be
security software and not bloatware.

Yours,
  Ingo


P.S.
For easier reference, i'm leaving my previous rationale in place,
amended with respect to the function nomenclature:

> in various situations, including directory listings, progress and
> diagnostic messages, the sftp(1) and scp(1) programs print untrusted
> strings to the local terminal, often strings that were received
> from a remote system over the wire, often containing whatever random
> filenames on the remote system may contain, or in general whatever
> the remote server sends.  That can screw up terminal state, for
> example hang the local terminal, change key bindings, change fonts,
> encodings, windows titles and many other terminal properties.  With
> xterm(1), i did not find any vulnerabilities that could execute
> arbitrary code injected remotely.  It is unclear what other terminals
> and terminal emulators may do.
> 
> Here are a few examples of file names you can use for testing:
> 
>   touch `printf "bel\a."`
>   touch `printf "esc\033[4munder"`
>   touch `printf "wt\033]0;rogue_title\a."`
> 
> Try listing the directory containing such files in sftp(1) interactive
> mode remotely or downloading them with sftp(1) or scp(1), in
> particular using wildcards.  Typically, filename display silently
> lacks some of the bytes contained in the filename, but the terminal
> executes control codes instead (like beeping, changing fonts,
> changing titles, or whatever the controls do).
> 
> The following patch sanitizes such strings before sending them to the
> terminal.  I'm posting it here for wider review because deraadt@
> suggested that public feedback is needed, in particular from people
> who know a bit about multibyte character handling.
> 
> Note that the problems are not related to the following areas:
>  - Portable OpenSSH.  They also affect OpenBSD-current to OpenBSD-current
>    connections.
>  - Line editing with libedit / editline(3).  They also affect
>    non-interactive mode.
>  - Multibyte characters.  They also occur when all relevant locales
>    are set to C/POSIX and only seven-bit bytes are sent.
>  - Character display widths.  The patch below does not attempt
>    to fix output columnation but only output corruption involving
>    terminal control codes.  While some of the new functions are
>    already instrumented for width calculation (it doesn't complicate
>    the code since wcwidth(3) validates printability of a character
>    just like iswprint(3) does and isn't any harder to use), the
>    calling code doesn't use that information yet, except in the
>    progressmeter, where is it easily used without complicating the
>    code.
> 
> Some general remarks:
   * The function names in utf8.c are formed in analogy to the wprintf(3)
     family of functions, only with "m" (multibyte character) rather than
     "w" (wide character).  They follow the
>    spirit of the standard functions as closely as they can while still
>    providing the additional sanitation required.  See the comments in
>    utf8.c for details.  The general style of the functions resembles
>    the style we developed for our ls(1) and similar programs, but as
>    we already experienced, not two such programs needed exactly the
>    same helper functions so far, but all have subtly different needs.
>  * For a few of the strings printed with the new sanitizing functions,
>    it may not be possible that they contain remote data.  But even
>    local data may pose risks, for example when root copies around
>    user home directories for backup or migration purposes.  So i'd
>    prefer to err to the side of caution.
>  * At first, i intended to do this in two steps:  First implement
>    safe handling of US-ASCII, then in a second step add multibyte
>    character handling.  But unfortunately, that doesn't work.
>    US-ASCII sanitation would break display of filenames containing
>    multibyte characters, and even though there isn't much explicit
>    code in the programs to handle such characters yet, they partially
>    work because most of the code just didn't care in the past what
>    bytes in file names represented and just passed everything through
>    verbatim, which is exactly where the problems come from, but
>    OpenSSH developers pointed out to me that some users may rely
>    on that, so they don't want to break the existing partial multibyte
>    support, not even to improve ASCII terminal sanitation.  So,
>    unfortunately, introducing the first bits of explicit multibyte
>    support and introducing the first bits of terminal sanitation
>    must be done in a single, rather big step.
>  * Even this patch isn't perfect yet.  It still allows printing
>    '\r' and '\n' to the terminal at too many places, for example
>    when remote filenames contain these characters.  But the patch
>    is already big, so fixing that should probably be kept seperate.
>  * As noted in my recent undeadly posting, protecting the terminal
>    is only possible if either (a) the terminal is UTF-8 *and* the
>    remote side is either UTF-8 or US-ASCII, or (b) the *user*
>    makes sure that the local locale(1) agrees with the remote one.
>    If the remote side uses a non-UTF-8 and non-ASCII locale *or*
>    you don't set a matching locale(1) before starting sftp(1) or
>    scp(1), these programs can still screw up your terminal even
>    with this patch, and i don't think there is any way to protect
>    you from that.
>  * On non-OpenBSD systems, users may legitimately set both the
>    remote and the local locale(1) to the same encoding that is
>    neither UTF-8 nor US-ASCII, in the worst case a state-dependent
>    SHIFT encoding, and i do want to protect users doing that.
>    However, in state dependent locales, one must NEVER attempt to
>    recover from encoding errors because those invalidate state
>    information, so to continue parsing would be utterly unsafe.
>    Given an arbitrary locale on an arbitrary operating system,
>    figuring out whether it is safely possible to recover from a
>    particular error would be very complicated and fragile, which
>    would be inadequate for security software like OpenSSH.
>    Consequently, whenever encoding errors or non-printable characters
>    are encountered in any non-UTF-8, non-ASCII locale, the code
>    immediately aborts parsing and truncates the string to be displayed
>    at the point of the first error.
> 
> Some comments on specific hunks of the patch:
>  * progressmeter.c @@ -117,14 +119,14 @@ char buf
>    The larger buffer will always be large enough on OpenBSD.  On
>    systems with state-dependent encodings, if strings switch state
>    back and forth a lot, the buffer may get exhausted before the
>    display width limit is reached, so it is important to always
>    check the buffer size before adding more bytes to prevent buffer
>    overruns.
>  * progressmeter.c @@ -155,17 +157,16 @@ buf[0]
>    buf[0] = '\r' must be set up front.  mbssnprintf() cannot accept
>    '\r' in the mode with a width limit because that character does
>    not have a width.
>  * progressmeter.c @@ -173,18 +174,18 @@ and subsequent
>    win_size can no longer be used for overflow checks.
>    Using sizeof(buf) seems safer anyway.
>  * scp.c @@ -362,6 +364,8 @@ setlocale(LC_CTYPE, "");
>    Adding #include <locale.h> and setlocale(LC_CTYPE, "") is
>    necessary.  Running in POSIX mode when the terminal is set
>    to an non-UTF-8 multibyte locale is NOT safe.


Index: progressmeter.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/progressmeter.c,v
retrieving revision 1.42
diff -u -p -r1.42 progressmeter.c
--- progressmeter.c     2 Mar 2016 22:42:40 -0000       1.42
+++ progressmeter.c     3 Apr 2016 20:44:07 -0000
@@ -29,6 +29,7 @@
 
 #include <errno.h>
 #include <signal.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 #include <time.h>
@@ -37,6 +38,7 @@
 #include "progressmeter.h"
 #include "atomicio.h"
 #include "misc.h"
+#include "utf8.h"
 
 #define DEFAULT_WINSIZE 80
 #define MAX_WINSIZE 512
@@ -117,14 +119,14 @@ format_size(char *buf, int size, off_t b
 void
 refresh_progress_meter(void)
 {
-       char buf[MAX_WINSIZE + 1];
+       char buf[MAX_WINSIZE * 4 + 1];
        off_t transferred;
        double elapsed, now;
        int percent;
        off_t bytes_left;
        int cur_speed;
        int hours, minutes, seconds;
-       int i, len;
+       size_t i;
        int file_len;
 
        transferred = *counter - (cur_pos ? cur_pos : start_pos);
@@ -155,17 +157,16 @@ refresh_progress_meter(void)
                bytes_per_second = cur_speed;
 
        /* filename */
-       buf[0] = '\0';
+       buf[0] = '\r';
+       buf[1] = '\0';
        file_len = win_size - 35;
        if (file_len > 0) {
-               len = snprintf(buf, file_len + 1, "\r%s", file);
-               if (len < 0)
-                       len = 0;
-               if (len >= file_len + 1)
-                       len = file_len;
-               for (i = len; i < file_len; i++)
-                       buf[i] = ' ';
-               buf[file_len] = '\0';
+               (void) snmprintf(buf + 1, sizeof(buf) - 1 - 35,
+                   &file_len, "%s", file);
+               i = strlen(buf);
+               while (file_len++ < win_size - 35 && i + 1 < sizeof(buf))
+                       buf[i++] = ' ';
+               buf[i] = '\0';
        }
 
        /* percent of transfer done */
@@ -173,18 +174,18 @@ refresh_progress_meter(void)
                percent = ((float)cur_pos / end_pos) * 100;
        else
                percent = 100;
-       snprintf(buf + strlen(buf), win_size - strlen(buf),
+       snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
            " %3d%% ", percent);
 
        /* amount transferred */
-       format_size(buf + strlen(buf), win_size - strlen(buf),
+       format_size(buf + strlen(buf), sizeof(buf) - strlen(buf),
            cur_pos);
-       strlcat(buf, " ", win_size);
+       strlcat(buf, " ", sizeof(buf));
 
        /* bandwidth usage */
-       format_rate(buf + strlen(buf), win_size - strlen(buf),
+       format_rate(buf + strlen(buf), sizeof(buf) - strlen(buf),
            (off_t)bytes_per_second);
-       strlcat(buf, "/s ", win_size);
+       strlcat(buf, "/s ", sizeof(buf));
 
        /* ETA */
        if (!transferred)
@@ -193,9 +194,9 @@ refresh_progress_meter(void)
                stalled = 0;
 
        if (stalled >= STALL_TIME)
-               strlcat(buf, "- stalled -", win_size);
+               strlcat(buf, "- stalled -", sizeof(buf));
        else if (bytes_per_second == 0 && bytes_left)
-               strlcat(buf, "  --:-- ETA", win_size);
+               strlcat(buf, "  --:-- ETA", sizeof(buf));
        else {
                if (bytes_left > 0)
                        seconds = bytes_left / bytes_per_second;
@@ -208,19 +209,21 @@ refresh_progress_meter(void)
                seconds -= minutes * 60;
 
                if (hours != 0)
-                       snprintf(buf + strlen(buf), win_size - strlen(buf),
+                       snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
                            "%d:%02d:%02d", hours, minutes, seconds);
                else
-                       snprintf(buf + strlen(buf), win_size - strlen(buf),
+                       snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
                            "  %02d:%02d", minutes, seconds);
 
                if (bytes_left > 0)
-                       strlcat(buf, " ETA", win_size);
+                       strlcat(buf, " ETA", sizeof(buf));
                else
-                       strlcat(buf, "    ", win_size);
+                       strlcat(buf, "    ", sizeof(buf));
        }
+       if (win_size < 35)
+               buf[win_size] = '\0';
 
-       atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1);
+       atomicio(vwrite, STDOUT_FILENO, buf, strlen(buf));
        last_update = now;
 }
 
Index: scp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/scp.c,v
retrieving revision 1.185
diff -u -p -r1.185 scp.c
--- scp.c       2 Mar 2016 22:43:52 -0000       1.185
+++ scp.c       3 Apr 2016 20:44:08 -0000
@@ -83,6 +83,7 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <locale.h>
 #include <pwd.h>
 #include <signal.h>
 #include <stdarg.h>
@@ -100,6 +101,7 @@
 #include "log.h"
 #include "misc.h"
 #include "progressmeter.h"
+#include "utf8.h"
 
 #define COPY_BUFLEN    16384
 
@@ -175,7 +177,7 @@ do_local_cmd(arglist *a)
        if (verbose_mode) {
                fprintf(stderr, "Executing:");
                for (i = 0; i < a->num; i++)
-                       fprintf(stderr, " %s", a->list[i]);
+                       fmprintf(stderr, " %s", a->list[i]);
                fprintf(stderr, "\n");
        }
        if ((pid = fork()) == -1)
@@ -216,7 +218,7 @@ do_cmd(char *host, char *remuser, char *
        int pin[2], pout[2], reserved[2];
 
        if (verbose_mode)
-               fprintf(stderr,
+               fmprintf(stderr,
                    "Executing: program %s host %s, user %s, command %s\n",
                    ssh_program, host,
                    remuser ? remuser : "(unspecified)", cmd);
@@ -291,7 +293,7 @@ do_cmd2(char *host, char *remuser, char 
        int status;
 
        if (verbose_mode)
-               fprintf(stderr,
+               fmprintf(stderr,
                    "Executing: 2nd program %s host %s, user %s, command %s\n",
                    ssh_program, host,
                    remuser ? remuser : "(unspecified)", cmd);
@@ -362,6 +364,8 @@ main(int argc, char **argv)
        /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
        sanitise_stdfd();
 
+       setlocale(LC_CTYPE, "");
+
        /* Copy argv, because we modify it */
        newargv = xcalloc(MAX(argc + 1, 1), sizeof(*newargv));
        for (n = 0; n < argc; n++)
@@ -789,9 +793,8 @@ syserr:                     run_err("%s: %s", name, strerr
                snprintf(buf, sizeof buf, "C%04o %lld %s\n",
                    (u_int) (stb.st_mode & FILEMODEMASK),
                    (long long)stb.st_size, last);
-               if (verbose_mode) {
-                       fprintf(stderr, "Sending file modes: %s", buf);
-               }
+               if (verbose_mode)
+                       fmprintf(stderr, "Sending file modes: %s", buf);
                (void) atomicio(vwrite, remout, buf, strlen(buf));
                if (response() < 0)
                        goto next;
@@ -868,7 +871,7 @@ rsource(char *name, struct stat *statp)
        (void) snprintf(path, sizeof path, "D%04o %d %.1024s\n",
            (u_int) (statp->st_mode & FILEMODEMASK), 0, last);
        if (verbose_mode)
-               fprintf(stderr, "Entering directory: %s", path);
+               fmprintf(stderr, "Entering directory: %s", path);
        (void) atomicio(vwrite, remout, path, strlen(path));
        if (response() < 0) {
                closedir(dirp);
@@ -908,7 +911,7 @@ sink(int argc, char **argv)
        off_t size, statbytes;
        unsigned long long ull;
        int setimes, targisdir, wrerrno = 0;
-       char ch, *cp, *np, *targ, *why, *vect[1], buf[2048];
+       char ch, *cp, *np, *targ, *why, *vect[1], buf[2048], visbuf[2048];
        struct timeval tv[2];
 
 #define        atime   tv[0]
@@ -943,12 +946,15 @@ sink(int argc, char **argv)
                } while (cp < &buf[sizeof(buf) - 1] && ch != '\n');
                *cp = 0;
                if (verbose_mode)
-                       fprintf(stderr, "Sink: %s", buf);
+                       fmprintf(stderr, "Sink: %s", buf);
 
                if (buf[0] == '\01' || buf[0] == '\02') {
-                       if (iamremote == 0)
+                       if (iamremote == 0) {
+                               (void) snmprintf(visbuf, sizeof(visbuf),
+                                   NULL, "%s", buf + 1);
                                (void) atomicio(vwrite, STDERR_FILENO,
-                                   buf + 1, strlen(buf + 1));
+                                   visbuf, strlen(visbuf));
+                       }
                        if (buf[0] == '\02')
                                exit(1);
                        ++errs;
@@ -1183,7 +1189,7 @@ screwup:
 int
 response(void)
 {
-       char ch, *cp, resp, rbuf[2048];
+       char ch, *cp, resp, rbuf[2048], visbuf[2048];
 
        if (atomicio(read, remin, &resp, sizeof(resp)) != sizeof(resp))
                lostconn(0);
@@ -1203,8 +1209,13 @@ response(void)
                        *cp++ = ch;
                } while (cp < &rbuf[sizeof(rbuf) - 1] && ch != '\n');
 
-               if (!iamremote)
-                       (void) atomicio(vwrite, STDERR_FILENO, rbuf, cp - rbuf);
+               if (!iamremote) {
+                       cp[-1] = '\0';
+                       (void) snmprintf(visbuf, sizeof(visbuf),
+                           NULL, "%s\n", rbuf);
+                       (void) atomicio(vwrite, STDERR_FILENO,
+                           visbuf, strlen(visbuf));
+               }
                ++errs;
                if (resp == 1)
                        return (-1);
@@ -1242,7 +1253,7 @@ run_err(const char *fmt,...)
 
        if (!iamremote) {
                va_start(ap, fmt);
-               vfprintf(stderr, fmt, ap);
+               vfmprintf(stderr, fmt, ap);
                va_end(ap);
                fprintf(stderr, "\n");
        }
@@ -1288,7 +1299,7 @@ okname(char *cp0)
        } while (*++cp);
        return (1);
 
-bad:   fprintf(stderr, "%s: invalid user name\n", cp0);
+bad:   fmprintf(stderr, "%s: invalid user name\n", cp0);
        return (0);
 }
 
Index: sftp-client.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp-client.c,v
retrieving revision 1.121
diff -u -p -r1.121 sftp-client.c
--- sftp-client.c       11 Feb 2016 02:21:34 -0000      1.121
+++ sftp-client.c       3 Apr 2016 20:44:08 -0000
@@ -46,6 +46,7 @@
 #include "atomicio.h"
 #include "progressmeter.h"
 #include "misc.h"
+#include "utf8.h"
 
 #include "sftp.h"
 #include "sftp-common.h"
@@ -604,7 +605,7 @@ do_lsreaddir(struct sftp_conn *conn, con
                        }
 
                        if (print_flag)
-                               printf("%s\n", longname);
+                               mprintf("%s\n", longname);
 
                        /*
                         * Directory entries should never contain '/'
@@ -1450,7 +1451,7 @@ download_dir_internal(struct sftp_conn *
                return -1;
        }
        if (print_flag)
-               printf("Retrieving %s\n", src);
+               mprintf("Retrieving %s\n", src);
 
        if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
                mode = dirattrib->perm & 01777;
@@ -1782,7 +1783,7 @@ upload_dir_internal(struct sftp_conn *co
                return -1;
        }
        if (print_flag)
-               printf("Entering %s\n", src);
+               mprintf("Entering %s\n", src);
 
        attrib_clear(&a);
        stat_to_attrib(&sb, &a);
Index: sftp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp.c,v
retrieving revision 1.172
diff -u -p -r1.172 sftp.c
--- sftp.c      15 Feb 2016 09:47:49 -0000      1.172
+++ sftp.c      3 Apr 2016 20:44:09 -0000
@@ -31,6 +31,7 @@
 #include <libgen.h>
 #include <locale.h>
 #include <signal.h>
+#include <stdarg.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -43,6 +44,7 @@
 #include "log.h"
 #include "pathnames.h"
 #include "misc.h"
+#include "utf8.h"
 
 #include "sftp.h"
 #include "ssherr.h"
@@ -622,9 +624,11 @@ process_get(struct sftp_conn *conn, char
 
                resume |= global_aflag;
                if (!quiet && resume)
-                       printf("Resuming %s to %s\n", g.gl_pathv[i], abs_dst);
+                       mprintf("Resuming %s to %s\n",
+                           g.gl_pathv[i], abs_dst);
                else if (!quiet && !resume)
-                       printf("Fetching %s to %s\n", g.gl_pathv[i], abs_dst);
+                       mprintf("Fetching %s to %s\n",
+                           g.gl_pathv[i], abs_dst);
                if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
                        if (download_dir(conn, g.gl_pathv[i], abs_dst, NULL,
                            pflag || global_pflag, 1, resume,
@@ -713,10 +717,11 @@ process_put(struct sftp_conn *conn, char
 
                 resume |= global_aflag;
                if (!quiet && resume)
-                       printf("Resuming upload of %s to %s\n", g.gl_pathv[i], 
-                               abs_dst);
+                       mprintf("Resuming upload of %s to %s\n",
+                           g.gl_pathv[i], abs_dst);
                else if (!quiet && !resume)
-                       printf("Uploading %s to %s\n", g.gl_pathv[i], abs_dst);
+                       mprintf("Uploading %s to %s\n",
+                           g.gl_pathv[i], abs_dst);
                if (pathname_is_dir(g.gl_pathv[i]) && (rflag || global_rflag)) {
                        if (upload_dir(conn, g.gl_pathv[i], abs_dst,
                            pflag || global_pflag, 1, resume,
@@ -817,12 +822,12 @@ do_ls_dir(struct sftp_conn *conn, char *
                                attrib_to_stat(&d[n]->a, &sb);
                                lname = ls_file(fname, &sb, 1,
                                    (lflag & LS_SI_UNITS));
-                               printf("%s\n", lname);
+                               mprintf("%s\n", lname);
                                free(lname);
                        } else
-                               printf("%s\n", d[n]->longname);
+                               mprintf("%s\n", d[n]->longname);
                } else {
-                       printf("%-*s", colspace, fname);
+                       mprintf("%-*s", colspace, fname);
                        if (c >= columns) {
                                printf("\n");
                                c = 1;
@@ -903,10 +908,10 @@ do_globbed_ls(struct sftp_conn *conn, ch
                        }
                        lname = ls_file(fname, g.gl_statv[i], 1,
                            (lflag & LS_SI_UNITS));
-                       printf("%s\n", lname);
+                       mprintf("%s\n", lname);
                        free(lname);
                } else {
-                       printf("%-*s", colspace, fname);
+                       mprintf("%-*s", colspace, fname);
                        if (c >= columns) {
                                printf("\n");
                                c = 1;
@@ -1434,7 +1439,7 @@ parse_dispatch_command(struct sftp_conn 
                remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
                for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
                        if (!quiet)
-                               printf("Removing %s\n", g.gl_pathv[i]);
+                               mprintf("Removing %s\n", g.gl_pathv[i]);
                        err = do_rm(conn, g.gl_pathv[i]);
                        if (err != 0 && err_abort)
                                break;
@@ -1534,7 +1539,8 @@ parse_dispatch_command(struct sftp_conn 
                remote_glob(conn, path1, GLOB_NOCHECK, NULL, &g);
                for (i = 0; g.gl_pathv[i] && !interrupted; i++) {
                        if (!quiet)
-                               printf("Changing mode on %s\n", g.gl_pathv[i]);
+                               mprintf("Changing mode on %s\n",
+                                   g.gl_pathv[i]);
                        err = do_setstat(conn, g.gl_pathv[i], &a);
                        if (err != 0 && err_abort)
                                break;
@@ -1564,12 +1570,12 @@ parse_dispatch_command(struct sftp_conn 
                        aa->flags &= SSH2_FILEXFER_ATTR_UIDGID;
                        if (cmdnum == I_CHOWN) {
                                if (!quiet)
-                                       printf("Changing owner on %s\n",
+                                       mprintf("Changing owner on %s\n",
                                            g.gl_pathv[i]);
                                aa->uid = n_arg;
                        } else {
                                if (!quiet)
-                                       printf("Changing group on %s\n",
+                                       mprintf("Changing group on %s\n",
                                            g.gl_pathv[i]);
                                aa->gid = n_arg;
                        }
@@ -1579,7 +1585,7 @@ parse_dispatch_command(struct sftp_conn 
                }
                break;
        case I_PWD:
-               printf("Remote working directory: %s\n", *pwd);
+               mprintf("Remote working directory: %s\n", *pwd);
                break;
        case I_LPWD:
                if (!getcwd(path_buf, sizeof(path_buf))) {
@@ -1587,7 +1593,7 @@ parse_dispatch_command(struct sftp_conn 
                        err = -1;
                        break;
                }
-               printf("Local working directory: %s\n", path_buf);
+               mprintf("Local working directory: %s\n", path_buf);
                break;
        case I_QUIT:
                /* Processed below */
@@ -1655,7 +1661,7 @@ complete_display(char **list, u_int len)
        for (y = 0; list[y]; y++) {
                llen = strlen(list[y]);
                tmp = llen > len ? list[y] + len : "";
-               printf("%-*s", colspace, tmp);
+               mprintf("%-*s", colspace, tmp);
                if (m >= columns) {
                        printf("\n");
                        m = 1;
@@ -2036,7 +2042,7 @@ interactive_loop(struct sftp_conn *conn,
 
                if (remote_is_dir(conn, dir) && file2 == NULL) {
                        if (!quiet)
-                               printf("Changing to: %s\n", dir);
+                               mprintf("Changing to: %s\n", dir);
                        snprintf(cmd, sizeof cmd, "cd \"%s\"", dir);
                        if (parse_dispatch_command(conn, cmd,
                            &remote_path, 1) != 0) {
@@ -2082,7 +2088,7 @@ interactive_loop(struct sftp_conn *conn,
                                break;
                        }
                        if (!interactive) { /* Echo command */
-                               printf("sftp> %s", cmd);
+                               mprintf("sftp> %s", cmd);
                                if (strlen(cmd) > 0 &&
                                    cmd[strlen(cmd) - 1] != '\n')
                                        printf("\n");
Index: utf8.c
===================================================================
RCS file: utf8.c
diff -N utf8.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ utf8.c      3 Apr 2016 20:44:09 -0000
@@ -0,0 +1,168 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Utility functions for multibyte-character handling,
+ * in particular to sanitize untrusted strings for terminal output.
+ */
+
+#include <sys/types.h>
+#include <langinfo.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "utf8.h"
+
+static int      dangerous_locale(void);
+static int      vsnmprintf(char *, size_t, int *, const char *, va_list);
+
+
+/*
+ * For US-ASCII and UTF-8 encodings, we can safely recover from
+ * encoding errors and from non-printable characters.  For any
+ * other encodings, err to the side of caution and abort parsing:
+ * For state-dependent encodings, recovery is impossible.
+ * For arbitrary encodings, replacement of non-printable
+ * characters would be non-trivial and too fragile.
+ */
+
+static int
+dangerous_locale(void) {
+       char    *loc;
+
+       loc = nl_langinfo(CODESET);
+       return strcmp(loc, "US-ASCII") && strcmp(loc, "UTF-8");
+}
+
+/*
+ * The following two functions limit the number of bytes written,
+ * including the terminating '\0', to sz.  Unless wp is NULL,
+ * they limit the number of display columns occupied to *wp.
+ * Whichever is reached first terminates the output string.
+ * To stay close to the standard interfaces, they return the number of
+ * non-NUL bytes that would have been written if both were unlimited.
+ * If wp is NULL, newline, carriage return, and tab are allowed;
+ * otherwise, the actual number of columns occupied by what was
+ * written is returned in *wp.
+ */
+
+static int
+vsnmprintf(char *str, size_t sz, int *wp, const char *fmt, va_list ap)
+{
+       char    *sp;
+       wchar_t  wc;
+       int      len, width, total_width, max_width, ret;
+
+       if ((ret = vsnprintf(str, sz, fmt, ap)) <= 0)
+               *str = '\0';
+
+       total_width = 0;
+       max_width = wp == NULL ? INT_MAX : *wp;
+       for (sp = str; *sp != '\0'; sp += len) {
+               if ((len = mbtowc(&wc, sp, MB_CUR_MAX)) == -1) {
+                       (void)mbtowc(NULL, NULL, MB_CUR_MAX);
+                       if (dangerous_locale()) {
+                               *sp = '\0';
+                               break;
+                       }
+                       *sp = '?';
+                       len = 1;
+                       width = 1;
+               } else if (wp == NULL &&
+                   (wc == L'\n' || wc == L'\r' || wc == L'\t')) {
+                       /*
+                        * Don't use width uninitialized;
+                        * the actual value doesn't matter.
+                        */
+                       width = 0;
+               } else if ((width = wcwidth(wc)) == -1) {
+                       if (dangerous_locale()) {
+                               *sp = '\0';
+                               break;
+                       }
+                       memset(sp, '?', len);
+                       width = len;
+               }
+               if (total_width + width > max_width) {
+                       *sp = '\0';
+                       break;
+               }
+               total_width += width;
+       }
+       if (wp != NULL)
+               *wp = total_width;
+       return ret;
+}
+
+int
+snmprintf(char *str, size_t sz, int *wp, const char *fmt, ...)
+{
+       va_list  ap;
+       int      ret;
+       
+       va_start(ap, fmt);
+       ret = vsnmprintf(str, sz, wp, fmt, ap);
+       va_end(ap);
+       return ret;
+}
+
+/*
+ * To stay close to the standard interfaces, the following functions
+ * return the number of non-NUL bytes written.
+ * Truncation detection is not needed and not supported.
+ */
+
+int
+vfmprintf(FILE *stream, const char *fmt, va_list ap)
+{
+       char     msgbuf[2048];
+       int      ret;
+
+       ret = vsnmprintf(msgbuf, sizeof(msgbuf), NULL, fmt, ap);
+       if (ret >= (int)sizeof(msgbuf))
+               ret = sizeof(msgbuf) - 1;
+       fputs(msgbuf, stream);
+       return ret;
+}
+
+int
+fmprintf(FILE *stream, const char *fmt, ...)
+{
+       va_list  ap;
+       int      ret;
+
+       va_start(ap, fmt);
+       ret = vfmprintf(stream, fmt, ap);
+       va_end(ap);
+       return ret;
+}
+
+int
+mprintf(const char *fmt, ...)
+{
+       va_list  ap;
+       int      ret;
+
+       va_start(ap, fmt);
+       ret = vfmprintf(stdout, fmt, ap);
+       va_end(ap);
+       return ret;
+}
Index: utf8.h
===================================================================
RCS file: utf8.h
diff -N utf8.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ utf8.h      3 Apr 2016 20:44:09 -0000
@@ -0,0 +1,24 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+int     mprintf(const char *, ...)
+            __attribute__((format(printf, 1, 2)));
+int     fmprintf(FILE *, const char *, ...)
+            __attribute__((format(printf, 2, 3)));
+int     vfmprintf(FILE *, const char *, va_list);
+int     snmprintf(char *, size_t, int *, const char *, ...)
+            __attribute__((format(printf, 4, 5)));
Index: lib/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/ssh/lib/Makefile,v
retrieving revision 1.84
diff -u -p -r1.84 Makefile
--- lib/Makefile        14 Jan 2016 16:17:40 -0000      1.84
+++ lib/Makefile        3 Apr 2016 20:44:09 -0000
@@ -28,7 +28,7 @@ SRCS= ${LIB_SRCS} \
        cleanup.c compat.c crc32.c deattack.c fatal.c \
        hostfile.c log.c match.c nchan.c packet.c opacket.c readpass.c \
        ttymodes.c xmalloc.c atomicio.c \
-       key.c dispatch.c kex.c mac.c uidswap.c uuencode.c misc.c \
+       key.c dispatch.c kex.c mac.c uidswap.c uuencode.c misc.c utf8.c \
        msg.c progressmeter.c dns.c \
        monitor_fdpass.c addrmatch.c \
        smult_curve25519_ref.c \

Reply via email to