1251881Speter/*
2251881Speter * prompt.c -- ask the user for authentication information.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <apr_lib.h>
31251881Speter#include <apr_poll.h>
32251881Speter#include <apr_portable.h>
33251881Speter
34251881Speter#include "svn_cmdline.h"
35251881Speter#include "svn_ctype.h"
36251881Speter#include "svn_string.h"
37251881Speter#include "svn_auth.h"
38251881Speter#include "svn_error.h"
39251881Speter#include "svn_path.h"
40251881Speter
41251881Speter#include "private/svn_cmdline_private.h"
42251881Speter#include "svn_private_config.h"
43251881Speter
44251881Speter#ifdef WIN32
45251881Speter#include <conio.h>
46251881Speter#elif defined(HAVE_TERMIOS_H)
47251881Speter#include <signal.h>
48251881Speter#include <termios.h>
49251881Speter#endif
50251881Speter
51251881Speter
52251881Speter
53251881Speter/* Descriptor of an open terminal */
54251881Spetertypedef struct terminal_handle_t terminal_handle_t;
55251881Speterstruct terminal_handle_t
56251881Speter{
57251881Speter  apr_file_t *infd;              /* input file handle */
58251881Speter  apr_file_t *outfd;             /* output file handle */
59251881Speter  svn_boolean_t noecho;          /* terminal echo was turned off */
60251881Speter  svn_boolean_t close_handles;   /* close handles when closing the terminal */
61251881Speter  apr_pool_t *pool;              /* pool associated with the file handles */
62251881Speter
63251881Speter#ifdef HAVE_TERMIOS_H
64251881Speter  svn_boolean_t restore_state;   /* terminal state was changed */
65251881Speter  apr_os_file_t osinfd;          /* OS-specific handle for infd */
66251881Speter  struct termios attr;           /* saved terminal attributes */
67251881Speter#endif
68251881Speter};
69251881Speter
70251881Speter/* Initialize safe state of terminal_handle_t. */
71251881Speterstatic void
72251881Speterterminal_handle_init(terminal_handle_t *terminal,
73251881Speter                     apr_file_t *infd, apr_file_t *outfd,
74251881Speter                     svn_boolean_t noecho, svn_boolean_t close_handles,
75251881Speter                     apr_pool_t *pool)
76251881Speter{
77251881Speter  memset(terminal, 0, sizeof(*terminal));
78251881Speter  terminal->infd = infd;
79251881Speter  terminal->outfd = outfd;
80251881Speter  terminal->noecho = noecho;
81251881Speter  terminal->close_handles = close_handles;
82251881Speter  terminal->pool = pool;
83251881Speter}
84251881Speter
85251881Speter/*
86251881Speter * Common pool cleanup handler for terminal_handle_t. Closes TERMINAL.
87251881Speter * If CLOSE_HANDLES is TRUE, close the terminal file handles.
88251881Speter * If RESTORE_STATE is TRUE, restores the TERMIOS flags of the terminal.
89251881Speter */
90251881Speterstatic apr_status_t
91251881Speterterminal_cleanup_handler(terminal_handle_t *terminal,
92251881Speter                         svn_boolean_t close_handles,
93251881Speter                         svn_boolean_t restore_state)
94251881Speter{
95251881Speter  apr_status_t status = APR_SUCCESS;
96251881Speter
97251881Speter#ifdef HAVE_TERMIOS_H
98251881Speter  /* Restore terminal state flags. */
99251881Speter  if (restore_state && terminal->restore_state)
100251881Speter    tcsetattr(terminal->osinfd, TCSANOW, &terminal->attr);
101251881Speter#endif
102251881Speter
103251881Speter  /* Close terminal handles. */
104251881Speter  if (close_handles && terminal->close_handles)
105251881Speter    {
106251881Speter      apr_file_t *const infd = terminal->infd;
107251881Speter      apr_file_t *const outfd = terminal->outfd;
108251881Speter
109251881Speter      if (infd)
110251881Speter        {
111251881Speter          terminal->infd = NULL;
112251881Speter          status = apr_file_close(infd);
113251881Speter        }
114251881Speter
115251881Speter      if (!status && outfd && outfd != infd)
116251881Speter        {
117251881Speter          terminal->outfd = NULL;
118251881Speter          status = apr_file_close(terminal->outfd);
119251881Speter        }
120251881Speter    }
121251881Speter  return status;
122251881Speter}
123251881Speter
124251881Speter/* Normal pool cleanup for a terminal. */
125251881Speterstatic apr_status_t terminal_plain_cleanup(void *baton)
126251881Speter{
127251881Speter  return terminal_cleanup_handler(baton, FALSE, TRUE);
128251881Speter}
129251881Speter
130251881Speter/* Child pool cleanup for a terminal -- does not restore echo state. */
131251881Speterstatic apr_status_t terminal_child_cleanup(void *baton)
132251881Speter{
133251881Speter  return terminal_cleanup_handler(baton, FALSE, FALSE);
134251881Speter}
135251881Speter
136251881Speter/* Explicitly close the terminal, removing its cleanup handlers. */
137251881Speterstatic svn_error_t *
138251881Speterterminal_close(terminal_handle_t *terminal)
139251881Speter{
140251881Speter  apr_status_t status;
141251881Speter
142251881Speter  /* apr_pool_cleanup_kill() removes both normal and child cleanup */
143251881Speter  apr_pool_cleanup_kill(terminal->pool, terminal, terminal_plain_cleanup);
144251881Speter
145251881Speter  status = terminal_cleanup_handler(terminal, TRUE, TRUE);
146251881Speter  if (status)
147251881Speter    return svn_error_create(status, NULL, _("Can't close terminal"));
148251881Speter  return SVN_NO_ERROR;
149251881Speter}
150251881Speter
151251881Speter/* Allocate and open *TERMINAL. If NOECHO is TRUE, try to turn off
152251881Speter   terminal echo.  Use POOL for all allocations.*/
153251881Speterstatic svn_error_t *
154251881Speterterminal_open(terminal_handle_t **terminal, svn_boolean_t noecho,
155251881Speter              apr_pool_t *pool)
156251881Speter{
157251881Speter  apr_status_t status;
158251881Speter
159251881Speter#ifdef WIN32
160251881Speter  /* On Windows, we'll use the console API directly if the process has
161251881Speter     a console attached; otherwise we'll just use stdin and stderr. */
162251881Speter  const HANDLE conin = CreateFileW(L"CONIN$", GENERIC_READ,
163251881Speter                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
164251881Speter                                   NULL, OPEN_EXISTING,
165251881Speter                                   FILE_ATTRIBUTE_NORMAL, NULL);
166251881Speter  *terminal = apr_palloc(pool, sizeof(terminal_handle_t));
167251881Speter  if (conin != INVALID_HANDLE_VALUE)
168251881Speter    {
169251881Speter      /* The process has a console. */
170251881Speter      CloseHandle(conin);
171251881Speter      terminal_handle_init(*terminal, NULL, NULL, noecho, FALSE, NULL);
172251881Speter      return SVN_NO_ERROR;
173251881Speter    }
174251881Speter#else  /* !WIN32 */
175251881Speter  /* Without evidence to the contrary, we'll assume this is *nix and
176251881Speter     try to open /dev/tty. If that fails, we'll use stdin for input
177251881Speter     and stderr for prompting. */
178251881Speter  apr_file_t *tmpfd;
179251881Speter  status = apr_file_open(&tmpfd, "/dev/tty",
180289180Speter                         APR_FOPEN_READ | APR_FOPEN_WRITE,
181251881Speter                         APR_OS_DEFAULT, pool);
182251881Speter  *terminal = apr_palloc(pool, sizeof(terminal_handle_t));
183251881Speter  if (!status)
184251881Speter    {
185251881Speter      /* We have a terminal handle that we can use for input and output. */
186251881Speter      terminal_handle_init(*terminal, tmpfd, tmpfd, FALSE, TRUE, pool);
187251881Speter    }
188251881Speter#endif /* !WIN32 */
189251881Speter  else
190251881Speter    {
191251881Speter      /* There is no terminal. Sigh. */
192251881Speter      apr_file_t *infd;
193251881Speter      apr_file_t *outfd;
194251881Speter
195251881Speter      status = apr_file_open_stdin(&infd, pool);
196251881Speter      if (status)
197251881Speter        return svn_error_wrap_apr(status, _("Can't open stdin"));
198251881Speter      status = apr_file_open_stderr(&outfd, pool);
199251881Speter      if (status)
200251881Speter        return svn_error_wrap_apr(status, _("Can't open stderr"));
201251881Speter      terminal_handle_init(*terminal, infd, outfd, FALSE, FALSE, pool);
202251881Speter    }
203251881Speter
204251881Speter#ifdef HAVE_TERMIOS_H
205251881Speter  /* Set terminal state */
206251881Speter  if (0 == apr_os_file_get(&(*terminal)->osinfd, (*terminal)->infd))
207251881Speter    {
208251881Speter      if (0 == tcgetattr((*terminal)->osinfd, &(*terminal)->attr))
209251881Speter        {
210251881Speter          struct termios attr = (*terminal)->attr;
211251881Speter          /* Turn off signal handling and canonical input mode */
212251881Speter          attr.c_lflag &= ~(ISIG | ICANON);
213251881Speter          attr.c_cc[VMIN] = 1;          /* Read one byte at a time */
214251881Speter          attr.c_cc[VTIME] = 0;         /* No timeout, wait indefinitely */
215251881Speter          attr.c_lflag &= ~(ECHO);      /* Turn off echo */
216251881Speter          if (0 == tcsetattr((*terminal)->osinfd, TCSAFLUSH, &attr))
217251881Speter            {
218251881Speter              (*terminal)->noecho = noecho;
219251881Speter              (*terminal)->restore_state = TRUE;
220251881Speter            }
221251881Speter        }
222251881Speter    }
223251881Speter#endif /* HAVE_TERMIOS_H */
224251881Speter
225251881Speter  /* Register pool cleanup to close handles and restore echo state. */
226251881Speter  apr_pool_cleanup_register((*terminal)->pool, *terminal,
227251881Speter                            terminal_plain_cleanup,
228251881Speter                            terminal_child_cleanup);
229251881Speter  return SVN_NO_ERROR;
230251881Speter}
231251881Speter
232251881Speter/* Write a null-terminated STRING to TERMINAL.
233251881Speter   Use POOL for allocations related to converting STRING from UTF-8. */
234251881Speterstatic svn_error_t *
235251881Speterterminal_puts(const char *string, terminal_handle_t *terminal,
236251881Speter              apr_pool_t *pool)
237251881Speter{
238251881Speter  svn_error_t *err;
239251881Speter  const char *converted;
240251881Speter
241251881Speter  err = svn_cmdline_cstring_from_utf8(&converted, string, pool);
242251881Speter  if (err)
243251881Speter    {
244251881Speter      svn_error_clear(err);
245251881Speter      converted = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
246251881Speter    }
247251881Speter
248251881Speter#ifdef WIN32
249251881Speter  if (!terminal->outfd)
250251881Speter    {
251251881Speter      /* See terminal_open; we're using Console I/O. */
252251881Speter      _cputs(converted);
253251881Speter      return SVN_NO_ERROR;
254251881Speter    }
255251881Speter#endif
256251881Speter
257289180Speter  SVN_ERR(svn_io_file_write_full(terminal->outfd, converted,
258289180Speter                                 strlen(converted), NULL, pool));
259289180Speter
260289180Speter  return svn_error_trace(svn_io_file_flush(terminal->outfd, pool));
261251881Speter}
262251881Speter
263251881Speter/* These codes can be returned from terminal_getc instead of a character. */
264251881Speter#define TERMINAL_NONE  0x80000               /* no character read, retry */
265251881Speter#define TERMINAL_DEL   (TERMINAL_NONE + 1)   /* the input was a deleteion */
266251881Speter#define TERMINAL_EOL   (TERMINAL_NONE + 2)   /* end of input/end of line */
267251881Speter#define TERMINAL_EOF   (TERMINAL_NONE + 3)   /* end of file during input */
268251881Speter
269251881Speter/* Helper for terminal_getc: writes CH to OUTFD as a control char. */
270251881Speter#ifndef WIN32
271251881Speterstatic void
272251881Speterecho_control_char(char ch, apr_file_t *outfd)
273251881Speter{
274251881Speter  if (svn_ctype_iscntrl(ch))
275251881Speter    {
276251881Speter      const char substitute = (ch < 32? '@' + ch : '?');
277251881Speter      apr_file_putc('^', outfd);
278251881Speter      apr_file_putc(substitute, outfd);
279251881Speter    }
280251881Speter  else if (svn_ctype_isprint(ch))
281251881Speter    {
282251881Speter      /* Pass printable characters unchanged. */
283251881Speter      apr_file_putc(ch, outfd);
284251881Speter    }
285251881Speter  else
286251881Speter    {
287251881Speter      /* Everything else is strange. */
288251881Speter      apr_file_putc('^', outfd);
289251881Speter      apr_file_putc('!', outfd);
290251881Speter    }
291251881Speter}
292251881Speter#endif /* WIN32 */
293251881Speter
294251881Speter/* Read one character or control code from TERMINAL, returning it in CODE.
295251881Speter   if CAN_ERASE and the input was a deletion, emit codes to erase the
296251881Speter   last character displayed on the terminal.
297251881Speter   Use POOL for all allocations. */
298251881Speterstatic svn_error_t *
299251881Speterterminal_getc(int *code, terminal_handle_t *terminal,
300251881Speter              svn_boolean_t can_erase, apr_pool_t *pool)
301251881Speter{
302251881Speter  const svn_boolean_t echo = !terminal->noecho;
303251881Speter  apr_status_t status = APR_SUCCESS;
304251881Speter  char ch;
305251881Speter
306251881Speter#ifdef WIN32
307251881Speter  if (!terminal->infd)
308251881Speter    {
309251881Speter      /* See terminal_open; we're using Console I/O. */
310251881Speter
311251881Speter      /*  The following was hoisted from APR's getpass for Windows. */
312251881Speter      int concode = _getch();
313251881Speter      switch (concode)
314251881Speter        {
315251881Speter        case '\r':                      /* end-of-line */
316251881Speter          *code = TERMINAL_EOL;
317251881Speter          if (echo)
318251881Speter            _cputs("\r\n");
319251881Speter          break;
320251881Speter
321251881Speter        case EOF:                       /* end-of-file */
322251881Speter        case 26:                        /* Ctrl+Z */
323251881Speter          *code = TERMINAL_EOF;
324251881Speter          if (echo)
325251881Speter            _cputs((concode == EOF ? "[EOF]\r\n" : "^Z\r\n"));
326251881Speter          break;
327251881Speter
328251881Speter        case 3:                         /* Ctrl+C, Ctrl+Break */
329251881Speter          /* _getch() bypasses Ctrl+C but not Ctrl+Break detection! */
330251881Speter          if (echo)
331251881Speter            _cputs("^C\r\n");
332251881Speter          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
333251881Speter
334251881Speter        case 0:                         /* Function code prefix */
335251881Speter        case 0xE0:
336251881Speter          concode = (concode << 4) | _getch();
337251881Speter          /* Catch {DELETE}, {<--}, Num{DEL} and Num{<--} */
338251881Speter          if (concode == 0xE53 || concode == 0xE4B
339251881Speter              || concode == 0x053 || concode == 0x04B)
340251881Speter            {
341251881Speter              *code = TERMINAL_DEL;
342251881Speter              if (can_erase)
343251881Speter                _cputs("\b \b");
344251881Speter            }
345251881Speter          else
346251881Speter            {
347251881Speter              *code = TERMINAL_NONE;
348251881Speter              _putch('\a');
349251881Speter            }
350251881Speter          break;
351251881Speter
352251881Speter        case '\b':                      /* BS */
353251881Speter        case 127:                       /* DEL */
354251881Speter          *code = TERMINAL_DEL;
355251881Speter          if (can_erase)
356251881Speter            _cputs("\b \b");
357251881Speter          break;
358251881Speter
359251881Speter        default:
360251881Speter          if (!apr_iscntrl(concode))
361251881Speter            {
362251881Speter              *code = (int)(unsigned char)concode;
363251881Speter              _putch(echo ? concode : '*');
364251881Speter            }
365251881Speter          else
366251881Speter            {
367251881Speter              *code = TERMINAL_NONE;
368251881Speter              _putch('\a');
369251881Speter            }
370251881Speter        }
371251881Speter      return SVN_NO_ERROR;
372251881Speter    }
373251881Speter#elif defined(HAVE_TERMIOS_H)
374251881Speter  if (terminal->restore_state)
375251881Speter    {
376251881Speter      /* We're using a bytewise-immediate termios input */
377251881Speter      const struct termios *const attr = &terminal->attr;
378251881Speter
379251881Speter      status = apr_file_getc(&ch, terminal->infd);
380251881Speter      if (status)
381251881Speter        return svn_error_wrap_apr(status, _("Can't read from terminal"));
382251881Speter
383251881Speter      if (ch == attr->c_cc[VINTR] || ch == attr->c_cc[VQUIT])
384251881Speter        {
385251881Speter          /* Break */
386251881Speter          echo_control_char(ch, terminal->outfd);
387251881Speter          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
388251881Speter        }
389251881Speter      else if (ch == '\r' || ch == '\n' || ch == attr->c_cc[VEOL])
390251881Speter        {
391251881Speter          /* Newline */
392251881Speter          *code = TERMINAL_EOL;
393251881Speter          apr_file_putc('\n', terminal->outfd);
394251881Speter        }
395251881Speter      else if (ch == '\b' || ch == attr->c_cc[VERASE])
396251881Speter        {
397251881Speter          /* Delete */
398251881Speter          *code = TERMINAL_DEL;
399251881Speter          if (can_erase)
400251881Speter            {
401251881Speter              apr_file_putc('\b', terminal->outfd);
402251881Speter              apr_file_putc(' ', terminal->outfd);
403251881Speter              apr_file_putc('\b', terminal->outfd);
404251881Speter            }
405251881Speter        }
406251881Speter      else if (ch == attr->c_cc[VEOF])
407251881Speter        {
408251881Speter          /* End of input */
409251881Speter          *code = TERMINAL_EOF;
410251881Speter          echo_control_char(ch, terminal->outfd);
411251881Speter        }
412251881Speter      else if (ch == attr->c_cc[VSUSP])
413251881Speter        {
414251881Speter          /* Suspend */
415251881Speter          *code = TERMINAL_NONE;
416251881Speter          kill(0, SIGTSTP);
417251881Speter        }
418251881Speter      else if (!apr_iscntrl(ch))
419251881Speter        {
420251881Speter          /* Normal character */
421251881Speter          *code = (int)(unsigned char)ch;
422251881Speter          apr_file_putc((echo ? ch : '*'), terminal->outfd);
423251881Speter        }
424251881Speter      else
425251881Speter        {
426251881Speter          /* Ignored character */
427251881Speter          *code = TERMINAL_NONE;
428251881Speter          apr_file_putc('\a', terminal->outfd);
429251881Speter        }
430251881Speter      return SVN_NO_ERROR;
431251881Speter    }
432251881Speter#endif /* HAVE_TERMIOS_H */
433251881Speter
434251881Speter  /* Fall back to plain stream-based I/O. */
435251881Speter#ifndef WIN32
436251881Speter  /* Wait for input on termin. This code is based on
437251881Speter     apr_wait_for_io_or_timeout().
438251881Speter     Note that this will return an EINTR on a signal. */
439251881Speter  {
440251881Speter    apr_pollfd_t pollset;
441251881Speter    int n;
442251881Speter
443251881Speter    pollset.desc_type = APR_POLL_FILE;
444251881Speter    pollset.desc.f = terminal->infd;
445251881Speter    pollset.p = pool;
446251881Speter    pollset.reqevents = APR_POLLIN;
447251881Speter
448251881Speter    status = apr_poll(&pollset, 1, &n, -1);
449251881Speter
450251881Speter    if (n == 1 && pollset.rtnevents & APR_POLLIN)
451251881Speter      status = APR_SUCCESS;
452251881Speter  }
453251881Speter#endif /* !WIN32 */
454251881Speter
455251881Speter  if (!status)
456251881Speter    status = apr_file_getc(&ch, terminal->infd);
457251881Speter  if (APR_STATUS_IS_EINTR(status))
458251881Speter    {
459251881Speter      *code = TERMINAL_NONE;
460251881Speter      return SVN_NO_ERROR;
461251881Speter    }
462251881Speter  else if (APR_STATUS_IS_EOF(status))
463251881Speter    {
464251881Speter      *code = TERMINAL_EOF;
465251881Speter      return SVN_NO_ERROR;
466251881Speter    }
467251881Speter  else if (status)
468251881Speter    return svn_error_wrap_apr(status, _("Can't read from terminal"));
469251881Speter
470251881Speter  *code = (int)(unsigned char)ch;
471251881Speter  return SVN_NO_ERROR;
472251881Speter}
473251881Speter
474251881Speter
475251881Speter/* Set @a *result to the result of prompting the user with @a
476251881Speter * prompt_msg.  Use @ *pb to get the cancel_func and cancel_baton.
477251881Speter * Do not call the cancel_func if @a *pb is NULL.
478251881Speter * Allocate @a *result in @a pool.
479251881Speter *
480251881Speter * If @a hide is true, then try to avoid displaying the user's input.
481251881Speter */
482251881Speterstatic svn_error_t *
483251881Speterprompt(const char **result,
484251881Speter       const char *prompt_msg,
485251881Speter       svn_boolean_t hide,
486251881Speter       svn_cmdline_prompt_baton2_t *pb,
487251881Speter       apr_pool_t *pool)
488251881Speter{
489251881Speter  /* XXX: If this functions ever starts using members of *pb
490251881Speter   * which were not included in svn_cmdline_prompt_baton_t,
491251881Speter   * we need to update svn_cmdline_prompt_user2 and its callers. */
492251881Speter
493251881Speter  svn_boolean_t saw_first_half_of_eol = FALSE;
494251881Speter  svn_stringbuf_t *strbuf = svn_stringbuf_create_empty(pool);
495251881Speter  terminal_handle_t *terminal;
496251881Speter  int code;
497251881Speter  char c;
498251881Speter
499251881Speter  SVN_ERR(terminal_open(&terminal, hide, pool));
500251881Speter  SVN_ERR(terminal_puts(prompt_msg, terminal, pool));
501251881Speter
502251881Speter  while (1)
503251881Speter    {
504251881Speter      SVN_ERR(terminal_getc(&code, terminal, (strbuf->len > 0), pool));
505251881Speter
506251881Speter      /* Check for cancellation after a character has been read, some
507251881Speter         input processing modes may eat ^C and we'll only notice a
508251881Speter         cancellation signal after characters have been read --
509251881Speter         sometimes even after a newline. */
510251881Speter      if (pb)
511251881Speter        SVN_ERR(pb->cancel_func(pb->cancel_baton));
512251881Speter
513251881Speter      switch (code)
514251881Speter        {
515251881Speter        case TERMINAL_NONE:
516251881Speter          /* Nothing useful happened; retry. */
517251881Speter          continue;
518251881Speter
519251881Speter        case TERMINAL_DEL:
520251881Speter          /* Delete the last input character. terminal_getc takes care
521251881Speter             of erasing the feedback from the terminal, if applicable. */
522251881Speter          svn_stringbuf_chop(strbuf, 1);
523251881Speter          continue;
524251881Speter
525251881Speter        case TERMINAL_EOL:
526251881Speter          /* End-of-line means end of input. Trick the EOL-detection code
527251881Speter             below to stop reading. */
528251881Speter          saw_first_half_of_eol = TRUE;
529251881Speter          c = APR_EOL_STR[1];   /* Could be \0 but still stops reading. */
530251881Speter          break;
531251881Speter
532251881Speter        case TERMINAL_EOF:
533251881Speter          return svn_error_create(
534251881Speter              APR_EOF,
535251881Speter              terminal_close(terminal),
536251881Speter              _("End of file while reading from terminal"));
537251881Speter
538251881Speter        default:
539251881Speter          /* Convert the returned code back to the character. */
540251881Speter          c = (char)code;
541251881Speter        }
542251881Speter
543251881Speter      if (saw_first_half_of_eol)
544251881Speter        {
545251881Speter          if (c == APR_EOL_STR[1])
546251881Speter            break;
547251881Speter          else
548251881Speter            saw_first_half_of_eol = FALSE;
549251881Speter        }
550251881Speter      else if (c == APR_EOL_STR[0])
551251881Speter        {
552251881Speter          /* GCC might complain here: "warning: will never be executed"
553251881Speter           * That's fine. This is a compile-time check for "\r\n\0" */
554251881Speter          if (sizeof(APR_EOL_STR) == 3)
555251881Speter            {
556251881Speter              saw_first_half_of_eol = TRUE;
557251881Speter              continue;
558251881Speter            }
559251881Speter          else if (sizeof(APR_EOL_STR) == 2)
560251881Speter            break;
561251881Speter          else
562251881Speter            /* ### APR_EOL_STR holds more than two chars?  Who
563251881Speter               ever heard of such a thing? */
564251881Speter            SVN_ERR_MALFUNCTION();
565251881Speter        }
566251881Speter
567251881Speter      svn_stringbuf_appendbyte(strbuf, c);
568251881Speter    }
569251881Speter
570251881Speter  if (terminal->noecho)
571251881Speter    {
572251881Speter      /* If terminal echo was turned off, make sure future output
573251881Speter         to the terminal starts on a new line, as expected. */
574251881Speter      SVN_ERR(terminal_puts(APR_EOL_STR, terminal, pool));
575251881Speter    }
576251881Speter  SVN_ERR(terminal_close(terminal));
577251881Speter
578251881Speter  return svn_cmdline_cstring_to_utf8(result, strbuf->data, pool);
579251881Speter}
580251881Speter
581251881Speter
582251881Speter
583251881Speter/** Prompt functions for auth providers. **/
584251881Speter
585251881Speter/* Helper function for auth provider prompters: mention the
586251881Speter * authentication @a realm on stderr, in a manner appropriate for
587251881Speter * preceding a prompt; or if @a realm is null, then do nothing.
588251881Speter */
589251881Speterstatic svn_error_t *
590251881Spetermaybe_print_realm(const char *realm, apr_pool_t *pool)
591251881Speter{
592251881Speter  if (realm)
593251881Speter    {
594251881Speter      terminal_handle_t *terminal;
595251881Speter      SVN_ERR(terminal_open(&terminal, FALSE, pool));
596251881Speter      SVN_ERR(terminal_puts(
597251881Speter                  apr_psprintf(pool,
598251881Speter                               _("Authentication realm: %s\n"), realm),
599251881Speter                  terminal, pool));
600251881Speter      SVN_ERR(terminal_close(terminal));
601251881Speter    }
602251881Speter
603251881Speter  return SVN_NO_ERROR;
604251881Speter}
605251881Speter
606251881Speter
607251881Speter/* This implements 'svn_auth_simple_prompt_func_t'. */
608251881Spetersvn_error_t *
609251881Spetersvn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t **cred_p,
610251881Speter                               void *baton,
611251881Speter                               const char *realm,
612251881Speter                               const char *username,
613251881Speter                               svn_boolean_t may_save,
614251881Speter                               apr_pool_t *pool)
615251881Speter{
616251881Speter  svn_auth_cred_simple_t *ret = apr_pcalloc(pool, sizeof(*ret));
617251881Speter  const char *pass_prompt;
618251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
619251881Speter
620251881Speter  SVN_ERR(maybe_print_realm(realm, pool));
621251881Speter
622251881Speter  if (username)
623251881Speter    ret->username = apr_pstrdup(pool, username);
624251881Speter  else
625251881Speter    SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
626251881Speter
627251881Speter  pass_prompt = apr_psprintf(pool, _("Password for '%s': "), ret->username);
628251881Speter  SVN_ERR(prompt(&(ret->password), pass_prompt, TRUE, pb, pool));
629251881Speter  ret->may_save = may_save;
630251881Speter  *cred_p = ret;
631251881Speter  return SVN_NO_ERROR;
632251881Speter}
633251881Speter
634251881Speter
635251881Speter/* This implements 'svn_auth_username_prompt_func_t'. */
636251881Spetersvn_error_t *
637251881Spetersvn_cmdline_auth_username_prompt(svn_auth_cred_username_t **cred_p,
638251881Speter                                 void *baton,
639251881Speter                                 const char *realm,
640251881Speter                                 svn_boolean_t may_save,
641251881Speter                                 apr_pool_t *pool)
642251881Speter{
643251881Speter  svn_auth_cred_username_t *ret = apr_pcalloc(pool, sizeof(*ret));
644251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
645251881Speter
646251881Speter  SVN_ERR(maybe_print_realm(realm, pool));
647251881Speter
648251881Speter  SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
649251881Speter  ret->may_save = may_save;
650251881Speter  *cred_p = ret;
651251881Speter  return SVN_NO_ERROR;
652251881Speter}
653251881Speter
654251881Speter
655251881Speter/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. */
656251881Spetersvn_error_t *
657251881Spetersvn_cmdline_auth_ssl_server_trust_prompt
658251881Speter  (svn_auth_cred_ssl_server_trust_t **cred_p,
659251881Speter   void *baton,
660251881Speter   const char *realm,
661251881Speter   apr_uint32_t failures,
662251881Speter   const svn_auth_ssl_server_cert_info_t *cert_info,
663251881Speter   svn_boolean_t may_save,
664251881Speter   apr_pool_t *pool)
665251881Speter{
666251881Speter  const char *choice;
667251881Speter  svn_stringbuf_t *msg;
668251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
669251881Speter  svn_stringbuf_t *buf = svn_stringbuf_createf
670251881Speter    (pool, _("Error validating server certificate for '%s':\n"), realm);
671251881Speter
672251881Speter  if (failures & SVN_AUTH_SSL_UNKNOWNCA)
673251881Speter    {
674251881Speter      svn_stringbuf_appendcstr
675251881Speter        (buf,
676251881Speter         _(" - The certificate is not issued by a trusted authority. Use the\n"
677251881Speter           "   fingerprint to validate the certificate manually!\n"));
678251881Speter    }
679251881Speter
680251881Speter  if (failures & SVN_AUTH_SSL_CNMISMATCH)
681251881Speter    {
682251881Speter      svn_stringbuf_appendcstr
683251881Speter        (buf, _(" - The certificate hostname does not match.\n"));
684251881Speter    }
685251881Speter
686251881Speter  if (failures & SVN_AUTH_SSL_NOTYETVALID)
687251881Speter    {
688251881Speter      svn_stringbuf_appendcstr
689251881Speter        (buf, _(" - The certificate is not yet valid.\n"));
690251881Speter    }
691251881Speter
692251881Speter  if (failures & SVN_AUTH_SSL_EXPIRED)
693251881Speter    {
694251881Speter      svn_stringbuf_appendcstr
695251881Speter        (buf, _(" - The certificate has expired.\n"));
696251881Speter    }
697251881Speter
698251881Speter  if (failures & SVN_AUTH_SSL_OTHER)
699251881Speter    {
700251881Speter      svn_stringbuf_appendcstr
701251881Speter        (buf, _(" - The certificate has an unknown error.\n"));
702251881Speter    }
703251881Speter
704251881Speter  msg = svn_stringbuf_createf
705251881Speter    (pool,
706251881Speter     _("Certificate information:\n"
707251881Speter       " - Hostname: %s\n"
708251881Speter       " - Valid: from %s until %s\n"
709251881Speter       " - Issuer: %s\n"
710251881Speter       " - Fingerprint: %s\n"),
711251881Speter     cert_info->hostname,
712251881Speter     cert_info->valid_from,
713251881Speter     cert_info->valid_until,
714251881Speter     cert_info->issuer_dname,
715251881Speter     cert_info->fingerprint);
716251881Speter  svn_stringbuf_appendstr(buf, msg);
717251881Speter
718251881Speter  if (may_save)
719251881Speter    {
720251881Speter      svn_stringbuf_appendcstr
721251881Speter        (buf, _("(R)eject, accept (t)emporarily or accept (p)ermanently? "));
722251881Speter    }
723251881Speter  else
724251881Speter    {
725251881Speter      svn_stringbuf_appendcstr(buf, _("(R)eject or accept (t)emporarily? "));
726251881Speter    }
727251881Speter  SVN_ERR(prompt(&choice, buf->data, FALSE, pb, pool));
728251881Speter
729251881Speter  if (choice[0] == 't' || choice[0] == 'T')
730251881Speter    {
731251881Speter      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
732251881Speter      (*cred_p)->may_save = FALSE;
733251881Speter      (*cred_p)->accepted_failures = failures;
734251881Speter    }
735251881Speter  else if (may_save && (choice[0] == 'p' || choice[0] == 'P'))
736251881Speter    {
737251881Speter      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
738251881Speter      (*cred_p)->may_save = TRUE;
739251881Speter      (*cred_p)->accepted_failures = failures;
740251881Speter    }
741251881Speter  else
742251881Speter    {
743251881Speter      *cred_p = NULL;
744251881Speter    }
745251881Speter
746251881Speter  return SVN_NO_ERROR;
747251881Speter}
748251881Speter
749251881Speter
750251881Speter/* This implements 'svn_auth_ssl_client_cert_prompt_func_t'. */
751251881Spetersvn_error_t *
752251881Spetersvn_cmdline_auth_ssl_client_cert_prompt
753251881Speter  (svn_auth_cred_ssl_client_cert_t **cred_p,
754251881Speter   void *baton,
755251881Speter   const char *realm,
756251881Speter   svn_boolean_t may_save,
757251881Speter   apr_pool_t *pool)
758251881Speter{
759251881Speter  svn_auth_cred_ssl_client_cert_t *cred = NULL;
760251881Speter  const char *cert_file = NULL;
761251881Speter  const char *abs_cert_file = NULL;
762251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
763251881Speter
764251881Speter  SVN_ERR(maybe_print_realm(realm, pool));
765251881Speter  SVN_ERR(prompt(&cert_file, _("Client certificate filename: "),
766251881Speter                 FALSE, pb, pool));
767251881Speter  SVN_ERR(svn_dirent_get_absolute(&abs_cert_file, cert_file, pool));
768251881Speter
769251881Speter  cred = apr_palloc(pool, sizeof(*cred));
770251881Speter  cred->cert_file = abs_cert_file;
771251881Speter  cred->may_save = may_save;
772251881Speter  *cred_p = cred;
773251881Speter
774251881Speter  return SVN_NO_ERROR;
775251881Speter}
776251881Speter
777251881Speter
778251881Speter/* This implements 'svn_auth_ssl_client_cert_pw_prompt_func_t'. */
779251881Spetersvn_error_t *
780251881Spetersvn_cmdline_auth_ssl_client_cert_pw_prompt
781251881Speter  (svn_auth_cred_ssl_client_cert_pw_t **cred_p,
782251881Speter   void *baton,
783251881Speter   const char *realm,
784251881Speter   svn_boolean_t may_save,
785251881Speter   apr_pool_t *pool)
786251881Speter{
787251881Speter  svn_auth_cred_ssl_client_cert_pw_t *cred = NULL;
788251881Speter  const char *result;
789251881Speter  const char *text = apr_psprintf(pool, _("Passphrase for '%s': "), realm);
790251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
791251881Speter
792251881Speter  SVN_ERR(prompt(&result, text, TRUE, pb, pool));
793251881Speter
794251881Speter  cred = apr_pcalloc(pool, sizeof(*cred));
795251881Speter  cred->password = result;
796251881Speter  cred->may_save = may_save;
797251881Speter  *cred_p = cred;
798251881Speter
799251881Speter  return SVN_NO_ERROR;
800251881Speter}
801251881Speter
802251881Speter/* This is a helper for plaintext prompt functions. */
803251881Speterstatic svn_error_t *
804251881Speterplaintext_prompt_helper(svn_boolean_t *may_save_plaintext,
805251881Speter                        const char *realmstring,
806251881Speter                        const char *prompt_string,
807251881Speter                        const char *prompt_text,
808251881Speter                        void *baton,
809251881Speter                        apr_pool_t *pool)
810251881Speter{
811251881Speter  const char *answer = NULL;
812251881Speter  svn_boolean_t answered = FALSE;
813251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
814251881Speter  const char *config_path = NULL;
815251881Speter  terminal_handle_t *terminal;
816251881Speter
817362181Sdim  *may_save_plaintext = FALSE; /* de facto API promise */
818362181Sdim
819251881Speter  if (pb)
820251881Speter    SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir,
821251881Speter                                            SVN_CONFIG_CATEGORY_SERVERS, pool));
822251881Speter
823251881Speter  SVN_ERR(terminal_open(&terminal, FALSE, pool));
824251881Speter  SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text,
825251881Speter                                     realmstring, config_path),
826251881Speter                        terminal, pool));
827251881Speter  SVN_ERR(terminal_close(terminal));
828251881Speter
829251881Speter  do
830251881Speter    {
831362181Sdim      SVN_ERR(prompt(&answer, prompt_string, FALSE, pb, pool));
832251881Speter      if (apr_strnatcasecmp(answer, _("yes")) == 0 ||
833251881Speter          apr_strnatcasecmp(answer, _("y")) == 0)
834251881Speter        {
835251881Speter          *may_save_plaintext = TRUE;
836251881Speter          answered = TRUE;
837251881Speter        }
838251881Speter      else if (apr_strnatcasecmp(answer, _("no")) == 0 ||
839251881Speter               apr_strnatcasecmp(answer, _("n")) == 0)
840251881Speter        {
841251881Speter          *may_save_plaintext = FALSE;
842251881Speter          answered = TRUE;
843251881Speter        }
844251881Speter      else
845251881Speter          prompt_string = _("Please type 'yes' or 'no': ");
846251881Speter    }
847251881Speter  while (! answered);
848251881Speter
849251881Speter  return SVN_NO_ERROR;
850251881Speter}
851251881Speter
852251881Speter/* This implements 'svn_auth_plaintext_prompt_func_t'. */
853251881Spetersvn_error_t *
854251881Spetersvn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext,
855251881Speter                                  const char *realmstring,
856251881Speter                                  void *baton,
857251881Speter                                  apr_pool_t *pool)
858251881Speter{
859251881Speter  const char *prompt_string = _("Store password unencrypted (yes/no)? ");
860251881Speter  const char *prompt_text =
861251881Speter  _("\n-----------------------------------------------------------------------"
862251881Speter    "\nATTENTION!  Your password for authentication realm:\n"
863251881Speter    "\n"
864251881Speter    "   %s\n"
865251881Speter    "\n"
866251881Speter    "can only be stored to disk unencrypted!  You are advised to configure\n"
867251881Speter    "your system so that Subversion can store passwords encrypted, if\n"
868251881Speter    "possible.  See the documentation for details.\n"
869251881Speter    "\n"
870251881Speter    "You can avoid future appearances of this warning by setting the value\n"
871251881Speter    "of the 'store-plaintext-passwords' option to either 'yes' or 'no' in\n"
872251881Speter    "'%s'.\n"
873251881Speter    "-----------------------------------------------------------------------\n"
874251881Speter    );
875251881Speter
876251881Speter  return plaintext_prompt_helper(may_save_plaintext, realmstring,
877251881Speter                                 prompt_string, prompt_text, baton,
878251881Speter                                 pool);
879251881Speter}
880251881Speter
881251881Speter/* This implements 'svn_auth_plaintext_passphrase_prompt_func_t'. */
882251881Spetersvn_error_t *
883251881Spetersvn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext,
884251881Speter                                             const char *realmstring,
885251881Speter                                             void *baton,
886251881Speter                                             apr_pool_t *pool)
887251881Speter{
888251881Speter  const char *prompt_string = _("Store passphrase unencrypted (yes/no)? ");
889251881Speter  const char *prompt_text =
890251881Speter  _("\n-----------------------------------------------------------------------\n"
891251881Speter    "ATTENTION!  Your passphrase for client certificate:\n"
892251881Speter    "\n"
893251881Speter    "   %s\n"
894251881Speter    "\n"
895251881Speter    "can only be stored to disk unencrypted!  You are advised to configure\n"
896251881Speter    "your system so that Subversion can store passphrase encrypted, if\n"
897251881Speter    "possible.  See the documentation for details.\n"
898251881Speter    "\n"
899251881Speter    "You can avoid future appearances of this warning by setting the value\n"
900251881Speter    "of the 'store-ssl-client-cert-pp-plaintext' option to either 'yes' or\n"
901251881Speter    "'no' in '%s'.\n"
902251881Speter    "-----------------------------------------------------------------------\n"
903251881Speter    );
904251881Speter
905251881Speter  return plaintext_prompt_helper(may_save_plaintext, realmstring,
906251881Speter                                 prompt_string, prompt_text, baton,
907251881Speter                                 pool);
908251881Speter}
909251881Speter
910251881Speter
911251881Speter/** Generic prompting. **/
912251881Speter
913251881Spetersvn_error_t *
914251881Spetersvn_cmdline_prompt_user2(const char **result,
915251881Speter                         const char *prompt_str,
916251881Speter                         svn_cmdline_prompt_baton_t *baton,
917251881Speter                         apr_pool_t *pool)
918251881Speter{
919251881Speter  /* XXX: We know prompt doesn't use the new members
920251881Speter   * of svn_cmdline_prompt_baton2_t. */
921251881Speter  return prompt(result, prompt_str, FALSE /* don't hide input */,
922251881Speter                (svn_cmdline_prompt_baton2_t *)baton, pool);
923251881Speter}
924251881Speter
925251881Speter/* This implements 'svn_auth_gnome_keyring_unlock_prompt_func_t'. */
926251881Spetersvn_error_t *
927251881Spetersvn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password,
928251881Speter                                              const char *keyring_name,
929251881Speter                                              void *baton,
930251881Speter                                              apr_pool_t *pool)
931251881Speter{
932251881Speter  const char *password;
933251881Speter  const char *pass_prompt;
934251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
935251881Speter
936251881Speter  pass_prompt = apr_psprintf(pool, _("Password for '%s' GNOME keyring: "),
937251881Speter                             keyring_name);
938251881Speter  SVN_ERR(prompt(&password, pass_prompt, TRUE, pb, pool));
939251881Speter  *keyring_password = apr_pstrdup(pool, password);
940251881Speter  return SVN_NO_ERROR;
941251881Speter}
942