| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * sprompt.c |
| 4 | * simple_prompt() routine |
| 5 | * |
| 6 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 7 | * Portions Copyright (c) 1994, Regents of the University of California |
| 8 | * |
| 9 | * |
| 10 | * IDENTIFICATION |
| 11 | * src/port/sprompt.c |
| 12 | * |
| 13 | *------------------------------------------------------------------------- |
| 14 | */ |
| 15 | #include "c.h" |
| 16 | |
| 17 | #ifdef HAVE_TERMIOS_H |
| 18 | #include <termios.h> |
| 19 | #endif |
| 20 | |
| 21 | |
| 22 | /* |
| 23 | * simple_prompt |
| 24 | * |
| 25 | * Generalized function especially intended for reading in usernames and |
| 26 | * passwords interactively. Reads from /dev/tty or stdin/stderr. |
| 27 | * |
| 28 | * prompt: The prompt to print, or NULL if none (automatically localized) |
| 29 | * destination: buffer in which to store result |
| 30 | * destlen: allocated length of destination |
| 31 | * echo: Set to false if you want to hide what is entered (for passwords) |
| 32 | * |
| 33 | * The input (without trailing newline) is returned in the destination buffer, |
| 34 | * with a '\0' appended. |
| 35 | */ |
| 36 | void |
| 37 | simple_prompt(const char *prompt, char *destination, size_t destlen, bool echo) |
| 38 | { |
| 39 | int length; |
| 40 | FILE *termin, |
| 41 | *termout; |
| 42 | |
| 43 | #if defined(HAVE_TERMIOS_H) |
| 44 | struct termios t_orig, |
| 45 | t; |
| 46 | #elif defined(WIN32) |
| 47 | HANDLE t = NULL; |
| 48 | DWORD t_orig = 0; |
| 49 | #endif |
| 50 | |
| 51 | #ifdef WIN32 |
| 52 | |
| 53 | /* |
| 54 | * A Windows console has an "input code page" and an "output code page"; |
| 55 | * these usually match each other, but they rarely match the "Windows ANSI |
| 56 | * code page" defined at system boot and expected of "char *" arguments to |
| 57 | * Windows API functions. The Microsoft CRT write() implementation |
| 58 | * automatically converts text between these code pages when writing to a |
| 59 | * console. To identify such file descriptors, it calls GetConsoleMode() |
| 60 | * on the underlying HANDLE, which in turn requires GENERIC_READ access on |
| 61 | * the HANDLE. Opening termout in mode "w+" allows that detection to |
| 62 | * succeed. Otherwise, write() would not recognize the descriptor as a |
| 63 | * console, and non-ASCII characters would display incorrectly. |
| 64 | * |
| 65 | * XXX fgets() still receives text in the console's input code page. This |
| 66 | * makes non-ASCII credentials unportable. |
| 67 | * |
| 68 | * Unintuitively, we also open termin in mode "w+", even though we only |
| 69 | * read it; that's needed for SetConsoleMode() to succeed. |
| 70 | */ |
| 71 | termin = fopen("CONIN$" , "w+" ); |
| 72 | termout = fopen("CONOUT$" , "w+" ); |
| 73 | #else |
| 74 | |
| 75 | /* |
| 76 | * Do not try to collapse these into one "w+" mode file. Doesn't work on |
| 77 | * some platforms (eg, HPUX 10.20). |
| 78 | */ |
| 79 | termin = fopen("/dev/tty" , "r" ); |
| 80 | termout = fopen("/dev/tty" , "w" ); |
| 81 | #endif |
| 82 | if (!termin || !termout |
| 83 | #ifdef WIN32 |
| 84 | |
| 85 | /* |
| 86 | * Direct console I/O does not work from the MSYS 1.0.10 console. Writes |
| 87 | * reach nowhere user-visible; reads block indefinitely. XXX This affects |
| 88 | * most Windows terminal environments, including rxvt, mintty, Cygwin |
| 89 | * xterm, Cygwin sshd, and PowerShell ISE. Switch to a more-generic test. |
| 90 | */ |
| 91 | || (getenv("OSTYPE" ) && strcmp(getenv("OSTYPE" ), "msys" ) == 0) |
| 92 | #endif |
| 93 | ) |
| 94 | { |
| 95 | if (termin) |
| 96 | fclose(termin); |
| 97 | if (termout) |
| 98 | fclose(termout); |
| 99 | termin = stdin; |
| 100 | termout = stderr; |
| 101 | } |
| 102 | |
| 103 | if (!echo) |
| 104 | { |
| 105 | #if defined(HAVE_TERMIOS_H) |
| 106 | /* disable echo via tcgetattr/tcsetattr */ |
| 107 | tcgetattr(fileno(termin), &t); |
| 108 | t_orig = t; |
| 109 | t.c_lflag &= ~ECHO; |
| 110 | tcsetattr(fileno(termin), TCSAFLUSH, &t); |
| 111 | #elif defined(WIN32) |
| 112 | /* need the file's HANDLE to turn echo off */ |
| 113 | t = (HANDLE) _get_osfhandle(_fileno(termin)); |
| 114 | |
| 115 | /* save the old configuration first */ |
| 116 | GetConsoleMode(t, &t_orig); |
| 117 | |
| 118 | /* set to the new mode */ |
| 119 | SetConsoleMode(t, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); |
| 120 | #endif |
| 121 | } |
| 122 | |
| 123 | if (prompt) |
| 124 | { |
| 125 | fputs(_(prompt), termout); |
| 126 | fflush(termout); |
| 127 | } |
| 128 | |
| 129 | if (fgets(destination, destlen, termin) == NULL) |
| 130 | destination[0] = '\0'; |
| 131 | |
| 132 | length = strlen(destination); |
| 133 | if (length > 0 && destination[length - 1] != '\n') |
| 134 | { |
| 135 | /* eat rest of the line */ |
| 136 | char buf[128]; |
| 137 | int buflen; |
| 138 | |
| 139 | do |
| 140 | { |
| 141 | if (fgets(buf, sizeof(buf), termin) == NULL) |
| 142 | break; |
| 143 | buflen = strlen(buf); |
| 144 | } while (buflen > 0 && buf[buflen - 1] != '\n'); |
| 145 | } |
| 146 | |
| 147 | if (length > 0 && destination[length - 1] == '\n') |
| 148 | /* remove trailing newline */ |
| 149 | destination[length - 1] = '\0'; |
| 150 | |
| 151 | if (!echo) |
| 152 | { |
| 153 | /* restore previous echo behavior, then echo \n */ |
| 154 | #if defined(HAVE_TERMIOS_H) |
| 155 | tcsetattr(fileno(termin), TCSAFLUSH, &t_orig); |
| 156 | fputs("\n" , termout); |
| 157 | fflush(termout); |
| 158 | #elif defined(WIN32) |
| 159 | SetConsoleMode(t, t_orig); |
| 160 | fputs("\n" , termout); |
| 161 | fflush(termout); |
| 162 | #endif |
| 163 | } |
| 164 | |
| 165 | if (termin != stdin) |
| 166 | { |
| 167 | fclose(termin); |
| 168 | fclose(termout); |
| 169 | } |
| 170 | } |
| 171 | |