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",
180251881Speter                         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  apr_status_t status;
240251881Speter  const char *converted;
241251881Speter
242251881Speter  err = svn_cmdline_cstring_from_utf8(&converted, string, pool);
243251881Speter  if (err)
244251881Speter    {
245251881Speter      svn_error_clear(err);
246251881Speter      converted = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
247251881Speter    }
248251881Speter
249251881Speter#ifdef WIN32
250251881Speter  if (!terminal->outfd)
251251881Speter    {
252251881Speter      /* See terminal_open; we're using Console I/O. */
253251881Speter      _cputs(converted);
254251881Speter      return SVN_NO_ERROR;
255251881Speter    }
256251881Speter#endif
257251881Speter
258251881Speter  status = apr_file_write_full(terminal->outfd, converted,
259251881Speter                               strlen(converted), NULL);
260251881Speter  if (!status)
261251881Speter    status = apr_file_flush(terminal->outfd);
262251881Speter  if (status)
263251881Speter    return svn_error_wrap_apr(status, _("Can't write to terminal"));
264251881Speter  return SVN_NO_ERROR;
265251881Speter}
266251881Speter
267251881Speter/* These codes can be returned from terminal_getc instead of a character. */
268251881Speter#define TERMINAL_NONE  0x80000               /* no character read, retry */
269251881Speter#define TERMINAL_DEL   (TERMINAL_NONE + 1)   /* the input was a deleteion */
270251881Speter#define TERMINAL_EOL   (TERMINAL_NONE + 2)   /* end of input/end of line */
271251881Speter#define TERMINAL_EOF   (TERMINAL_NONE + 3)   /* end of file during input */
272251881Speter
273251881Speter/* Helper for terminal_getc: writes CH to OUTFD as a control char. */
274251881Speter#ifndef WIN32
275251881Speterstatic void
276251881Speterecho_control_char(char ch, apr_file_t *outfd)
277251881Speter{
278251881Speter  if (svn_ctype_iscntrl(ch))
279251881Speter    {
280251881Speter      const char substitute = (ch < 32? '@' + ch : '?');
281251881Speter      apr_file_putc('^', outfd);
282251881Speter      apr_file_putc(substitute, outfd);
283251881Speter    }
284251881Speter  else if (svn_ctype_isprint(ch))
285251881Speter    {
286251881Speter      /* Pass printable characters unchanged. */
287251881Speter      apr_file_putc(ch, outfd);
288251881Speter    }
289251881Speter  else
290251881Speter    {
291251881Speter      /* Everything else is strange. */
292251881Speter      apr_file_putc('^', outfd);
293251881Speter      apr_file_putc('!', outfd);
294251881Speter    }
295251881Speter}
296251881Speter#endif /* WIN32 */
297251881Speter
298251881Speter/* Read one character or control code from TERMINAL, returning it in CODE.
299251881Speter   if CAN_ERASE and the input was a deletion, emit codes to erase the
300251881Speter   last character displayed on the terminal.
301251881Speter   Use POOL for all allocations. */
302251881Speterstatic svn_error_t *
303251881Speterterminal_getc(int *code, terminal_handle_t *terminal,
304251881Speter              svn_boolean_t can_erase, apr_pool_t *pool)
305251881Speter{
306251881Speter  const svn_boolean_t echo = !terminal->noecho;
307251881Speter  apr_status_t status = APR_SUCCESS;
308251881Speter  char ch;
309251881Speter
310251881Speter#ifdef WIN32
311251881Speter  if (!terminal->infd)
312251881Speter    {
313251881Speter      /* See terminal_open; we're using Console I/O. */
314251881Speter
315251881Speter      /*  The following was hoisted from APR's getpass for Windows. */
316251881Speter      int concode = _getch();
317251881Speter      switch (concode)
318251881Speter        {
319251881Speter        case '\r':                      /* end-of-line */
320251881Speter          *code = TERMINAL_EOL;
321251881Speter          if (echo)
322251881Speter            _cputs("\r\n");
323251881Speter          break;
324251881Speter
325251881Speter        case EOF:                       /* end-of-file */
326251881Speter        case 26:                        /* Ctrl+Z */
327251881Speter          *code = TERMINAL_EOF;
328251881Speter          if (echo)
329251881Speter            _cputs((concode == EOF ? "[EOF]\r\n" : "^Z\r\n"));
330251881Speter          break;
331251881Speter
332251881Speter        case 3:                         /* Ctrl+C, Ctrl+Break */
333251881Speter          /* _getch() bypasses Ctrl+C but not Ctrl+Break detection! */
334251881Speter          if (echo)
335251881Speter            _cputs("^C\r\n");
336251881Speter          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
337251881Speter
338251881Speter        case 0:                         /* Function code prefix */
339251881Speter        case 0xE0:
340251881Speter          concode = (concode << 4) | _getch();
341251881Speter          /* Catch {DELETE}, {<--}, Num{DEL} and Num{<--} */
342251881Speter          if (concode == 0xE53 || concode == 0xE4B
343251881Speter              || concode == 0x053 || concode == 0x04B)
344251881Speter            {
345251881Speter              *code = TERMINAL_DEL;
346251881Speter              if (can_erase)
347251881Speter                _cputs("\b \b");
348251881Speter            }
349251881Speter          else
350251881Speter            {
351251881Speter              *code = TERMINAL_NONE;
352251881Speter              _putch('\a');
353251881Speter            }
354251881Speter          break;
355251881Speter
356251881Speter        case '\b':                      /* BS */
357251881Speter        case 127:                       /* DEL */
358251881Speter          *code = TERMINAL_DEL;
359251881Speter          if (can_erase)
360251881Speter            _cputs("\b \b");
361251881Speter          break;
362251881Speter
363251881Speter        default:
364251881Speter          if (!apr_iscntrl(concode))
365251881Speter            {
366251881Speter              *code = (int)(unsigned char)concode;
367251881Speter              _putch(echo ? concode : '*');
368251881Speter            }
369251881Speter          else
370251881Speter            {
371251881Speter              *code = TERMINAL_NONE;
372251881Speter              _putch('\a');
373251881Speter            }
374251881Speter        }
375251881Speter      return SVN_NO_ERROR;
376251881Speter    }
377251881Speter#elif defined(HAVE_TERMIOS_H)
378251881Speter  if (terminal->restore_state)
379251881Speter    {
380251881Speter      /* We're using a bytewise-immediate termios input */
381251881Speter      const struct termios *const attr = &terminal->attr;
382251881Speter
383251881Speter      status = apr_file_getc(&ch, terminal->infd);
384251881Speter      if (status)
385251881Speter        return svn_error_wrap_apr(status, _("Can't read from terminal"));
386251881Speter
387251881Speter      if (ch == attr->c_cc[VINTR] || ch == attr->c_cc[VQUIT])
388251881Speter        {
389251881Speter          /* Break */
390251881Speter          echo_control_char(ch, terminal->outfd);
391251881Speter          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
392251881Speter        }
393251881Speter      else if (ch == '\r' || ch == '\n' || ch == attr->c_cc[VEOL])
394251881Speter        {
395251881Speter          /* Newline */
396251881Speter          *code = TERMINAL_EOL;
397251881Speter          apr_file_putc('\n', terminal->outfd);
398251881Speter        }
399251881Speter      else if (ch == '\b' || ch == attr->c_cc[VERASE])
400251881Speter        {
401251881Speter          /* Delete */
402251881Speter          *code = TERMINAL_DEL;
403251881Speter          if (can_erase)
404251881Speter            {
405251881Speter              apr_file_putc('\b', terminal->outfd);
406251881Speter              apr_file_putc(' ', terminal->outfd);
407251881Speter              apr_file_putc('\b', terminal->outfd);
408251881Speter            }
409251881Speter        }
410251881Speter      else if (ch == attr->c_cc[VEOF])
411251881Speter        {
412251881Speter          /* End of input */
413251881Speter          *code = TERMINAL_EOF;
414251881Speter          echo_control_char(ch, terminal->outfd);
415251881Speter        }
416251881Speter      else if (ch == attr->c_cc[VSUSP])
417251881Speter        {
418251881Speter          /* Suspend */
419251881Speter          *code = TERMINAL_NONE;
420251881Speter          kill(0, SIGTSTP);
421251881Speter        }
422251881Speter      else if (!apr_iscntrl(ch))
423251881Speter        {
424251881Speter          /* Normal character */
425251881Speter          *code = (int)(unsigned char)ch;
426251881Speter          apr_file_putc((echo ? ch : '*'), terminal->outfd);
427251881Speter        }
428251881Speter      else
429251881Speter        {
430251881Speter          /* Ignored character */
431251881Speter          *code = TERMINAL_NONE;
432251881Speter          apr_file_putc('\a', terminal->outfd);
433251881Speter        }
434251881Speter      return SVN_NO_ERROR;
435251881Speter    }
436251881Speter#endif /* HAVE_TERMIOS_H */
437251881Speter
438251881Speter  /* Fall back to plain stream-based I/O. */
439251881Speter#ifndef WIN32
440251881Speter  /* Wait for input on termin. This code is based on
441251881Speter     apr_wait_for_io_or_timeout().
442251881Speter     Note that this will return an EINTR on a signal. */
443251881Speter  {
444251881Speter    apr_pollfd_t pollset;
445251881Speter    int n;
446251881Speter
447251881Speter    pollset.desc_type = APR_POLL_FILE;
448251881Speter    pollset.desc.f = terminal->infd;
449251881Speter    pollset.p = pool;
450251881Speter    pollset.reqevents = APR_POLLIN;
451251881Speter
452251881Speter    status = apr_poll(&pollset, 1, &n, -1);
453251881Speter
454251881Speter    if (n == 1 && pollset.rtnevents & APR_POLLIN)
455251881Speter      status = APR_SUCCESS;
456251881Speter  }
457251881Speter#endif /* !WIN32 */
458251881Speter
459251881Speter  if (!status)
460251881Speter    status = apr_file_getc(&ch, terminal->infd);
461251881Speter  if (APR_STATUS_IS_EINTR(status))
462251881Speter    {
463251881Speter      *code = TERMINAL_NONE;
464251881Speter      return SVN_NO_ERROR;
465251881Speter    }
466251881Speter  else if (APR_STATUS_IS_EOF(status))
467251881Speter    {
468251881Speter      *code = TERMINAL_EOF;
469251881Speter      return SVN_NO_ERROR;
470251881Speter    }
471251881Speter  else if (status)
472251881Speter    return svn_error_wrap_apr(status, _("Can't read from terminal"));
473251881Speter
474251881Speter  *code = (int)(unsigned char)ch;
475251881Speter  return SVN_NO_ERROR;
476251881Speter}
477251881Speter
478251881Speter
479251881Speter/* Set @a *result to the result of prompting the user with @a
480251881Speter * prompt_msg.  Use @ *pb to get the cancel_func and cancel_baton.
481251881Speter * Do not call the cancel_func if @a *pb is NULL.
482251881Speter * Allocate @a *result in @a pool.
483251881Speter *
484251881Speter * If @a hide is true, then try to avoid displaying the user's input.
485251881Speter */
486251881Speterstatic svn_error_t *
487251881Speterprompt(const char **result,
488251881Speter       const char *prompt_msg,
489251881Speter       svn_boolean_t hide,
490251881Speter       svn_cmdline_prompt_baton2_t *pb,
491251881Speter       apr_pool_t *pool)
492251881Speter{
493251881Speter  /* XXX: If this functions ever starts using members of *pb
494251881Speter   * which were not included in svn_cmdline_prompt_baton_t,
495251881Speter   * we need to update svn_cmdline_prompt_user2 and its callers. */
496251881Speter
497251881Speter  svn_boolean_t saw_first_half_of_eol = FALSE;
498251881Speter  svn_stringbuf_t *strbuf = svn_stringbuf_create_empty(pool);
499251881Speter  terminal_handle_t *terminal;
500251881Speter  int code;
501251881Speter  char c;
502251881Speter
503251881Speter  SVN_ERR(terminal_open(&terminal, hide, pool));
504251881Speter  SVN_ERR(terminal_puts(prompt_msg, terminal, pool));
505251881Speter
506251881Speter  while (1)
507251881Speter    {
508251881Speter      SVN_ERR(terminal_getc(&code, terminal, (strbuf->len > 0), pool));
509251881Speter
510251881Speter      /* Check for cancellation after a character has been read, some
511251881Speter         input processing modes may eat ^C and we'll only notice a
512251881Speter         cancellation signal after characters have been read --
513251881Speter         sometimes even after a newline. */
514251881Speter      if (pb)
515251881Speter        SVN_ERR(pb->cancel_func(pb->cancel_baton));
516251881Speter
517251881Speter      switch (code)
518251881Speter        {
519251881Speter        case TERMINAL_NONE:
520251881Speter          /* Nothing useful happened; retry. */
521251881Speter          continue;
522251881Speter
523251881Speter        case TERMINAL_DEL:
524251881Speter          /* Delete the last input character. terminal_getc takes care
525251881Speter             of erasing the feedback from the terminal, if applicable. */
526251881Speter          svn_stringbuf_chop(strbuf, 1);
527251881Speter          continue;
528251881Speter
529251881Speter        case TERMINAL_EOL:
530251881Speter          /* End-of-line means end of input. Trick the EOL-detection code
531251881Speter             below to stop reading. */
532251881Speter          saw_first_half_of_eol = TRUE;
533251881Speter          c = APR_EOL_STR[1];   /* Could be \0 but still stops reading. */
534251881Speter          break;
535251881Speter
536251881Speter        case TERMINAL_EOF:
537251881Speter          return svn_error_create(
538251881Speter              APR_EOF,
539251881Speter              terminal_close(terminal),
540251881Speter              _("End of file while reading from terminal"));
541251881Speter
542251881Speter        default:
543251881Speter          /* Convert the returned code back to the character. */
544251881Speter          c = (char)code;
545251881Speter        }
546251881Speter
547251881Speter      if (saw_first_half_of_eol)
548251881Speter        {
549251881Speter          if (c == APR_EOL_STR[1])
550251881Speter            break;
551251881Speter          else
552251881Speter            saw_first_half_of_eol = FALSE;
553251881Speter        }
554251881Speter      else if (c == APR_EOL_STR[0])
555251881Speter        {
556251881Speter          /* GCC might complain here: "warning: will never be executed"
557251881Speter           * That's fine. This is a compile-time check for "\r\n\0" */
558251881Speter          if (sizeof(APR_EOL_STR) == 3)
559251881Speter            {
560251881Speter              saw_first_half_of_eol = TRUE;
561251881Speter              continue;
562251881Speter            }
563251881Speter          else if (sizeof(APR_EOL_STR) == 2)
564251881Speter            break;
565251881Speter          else
566251881Speter            /* ### APR_EOL_STR holds more than two chars?  Who
567251881Speter               ever heard of such a thing? */
568251881Speter            SVN_ERR_MALFUNCTION();
569251881Speter        }
570251881Speter
571251881Speter      svn_stringbuf_appendbyte(strbuf, c);
572251881Speter    }
573251881Speter
574251881Speter  if (terminal->noecho)
575251881Speter    {
576251881Speter      /* If terminal echo was turned off, make sure future output
577251881Speter         to the terminal starts on a new line, as expected. */
578251881Speter      SVN_ERR(terminal_puts(APR_EOL_STR, terminal, pool));
579251881Speter    }
580251881Speter  SVN_ERR(terminal_close(terminal));
581251881Speter
582251881Speter  return svn_cmdline_cstring_to_utf8(result, strbuf->data, pool);
583251881Speter}
584251881Speter
585251881Speter
586251881Speter
587251881Speter/** Prompt functions for auth providers. **/
588251881Speter
589251881Speter/* Helper function for auth provider prompters: mention the
590251881Speter * authentication @a realm on stderr, in a manner appropriate for
591251881Speter * preceding a prompt; or if @a realm is null, then do nothing.
592251881Speter */
593251881Speterstatic svn_error_t *
594251881Spetermaybe_print_realm(const char *realm, apr_pool_t *pool)
595251881Speter{
596251881Speter  if (realm)
597251881Speter    {
598251881Speter      terminal_handle_t *terminal;
599251881Speter      SVN_ERR(terminal_open(&terminal, FALSE, pool));
600251881Speter      SVN_ERR(terminal_puts(
601251881Speter                  apr_psprintf(pool,
602251881Speter                               _("Authentication realm: %s\n"), realm),
603251881Speter                  terminal, pool));
604251881Speter      SVN_ERR(terminal_close(terminal));
605251881Speter    }
606251881Speter
607251881Speter  return SVN_NO_ERROR;
608251881Speter}
609251881Speter
610251881Speter
611251881Speter/* This implements 'svn_auth_simple_prompt_func_t'. */
612251881Spetersvn_error_t *
613251881Spetersvn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t **cred_p,
614251881Speter                               void *baton,
615251881Speter                               const char *realm,
616251881Speter                               const char *username,
617251881Speter                               svn_boolean_t may_save,
618251881Speter                               apr_pool_t *pool)
619251881Speter{
620251881Speter  svn_auth_cred_simple_t *ret = apr_pcalloc(pool, sizeof(*ret));
621251881Speter  const char *pass_prompt;
622251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
623251881Speter
624251881Speter  SVN_ERR(maybe_print_realm(realm, pool));
625251881Speter
626251881Speter  if (username)
627251881Speter    ret->username = apr_pstrdup(pool, username);
628251881Speter  else
629251881Speter    SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
630251881Speter
631251881Speter  pass_prompt = apr_psprintf(pool, _("Password for '%s': "), ret->username);
632251881Speter  SVN_ERR(prompt(&(ret->password), pass_prompt, TRUE, pb, pool));
633251881Speter  ret->may_save = may_save;
634251881Speter  *cred_p = ret;
635251881Speter  return SVN_NO_ERROR;
636251881Speter}
637251881Speter
638251881Speter
639251881Speter/* This implements 'svn_auth_username_prompt_func_t'. */
640251881Spetersvn_error_t *
641251881Spetersvn_cmdline_auth_username_prompt(svn_auth_cred_username_t **cred_p,
642251881Speter                                 void *baton,
643251881Speter                                 const char *realm,
644251881Speter                                 svn_boolean_t may_save,
645251881Speter                                 apr_pool_t *pool)
646251881Speter{
647251881Speter  svn_auth_cred_username_t *ret = apr_pcalloc(pool, sizeof(*ret));
648251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
649251881Speter
650251881Speter  SVN_ERR(maybe_print_realm(realm, pool));
651251881Speter
652251881Speter  SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
653251881Speter  ret->may_save = may_save;
654251881Speter  *cred_p = ret;
655251881Speter  return SVN_NO_ERROR;
656251881Speter}
657251881Speter
658251881Speter
659251881Speter/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. */
660251881Spetersvn_error_t *
661251881Spetersvn_cmdline_auth_ssl_server_trust_prompt
662251881Speter  (svn_auth_cred_ssl_server_trust_t **cred_p,
663251881Speter   void *baton,
664251881Speter   const char *realm,
665251881Speter   apr_uint32_t failures,
666251881Speter   const svn_auth_ssl_server_cert_info_t *cert_info,
667251881Speter   svn_boolean_t may_save,
668251881Speter   apr_pool_t *pool)
669251881Speter{
670251881Speter  const char *choice;
671251881Speter  svn_stringbuf_t *msg;
672251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
673251881Speter  svn_stringbuf_t *buf = svn_stringbuf_createf
674251881Speter    (pool, _("Error validating server certificate for '%s':\n"), realm);
675251881Speter
676251881Speter  if (failures & SVN_AUTH_SSL_UNKNOWNCA)
677251881Speter    {
678251881Speter      svn_stringbuf_appendcstr
679251881Speter        (buf,
680251881Speter         _(" - The certificate is not issued by a trusted authority. Use the\n"
681251881Speter           "   fingerprint to validate the certificate manually!\n"));
682251881Speter    }
683251881Speter
684251881Speter  if (failures & SVN_AUTH_SSL_CNMISMATCH)
685251881Speter    {
686251881Speter      svn_stringbuf_appendcstr
687251881Speter        (buf, _(" - The certificate hostname does not match.\n"));
688251881Speter    }
689251881Speter
690251881Speter  if (failures & SVN_AUTH_SSL_NOTYETVALID)
691251881Speter    {
692251881Speter      svn_stringbuf_appendcstr
693251881Speter        (buf, _(" - The certificate is not yet valid.\n"));
694251881Speter    }
695251881Speter
696251881Speter  if (failures & SVN_AUTH_SSL_EXPIRED)
697251881Speter    {
698251881Speter      svn_stringbuf_appendcstr
699251881Speter        (buf, _(" - The certificate has expired.\n"));
700251881Speter    }
701251881Speter
702251881Speter  if (failures & SVN_AUTH_SSL_OTHER)
703251881Speter    {
704251881Speter      svn_stringbuf_appendcstr
705251881Speter        (buf, _(" - The certificate has an unknown error.\n"));
706251881Speter    }
707251881Speter
708251881Speter  msg = svn_stringbuf_createf
709251881Speter    (pool,
710251881Speter     _("Certificate information:\n"
711251881Speter       " - Hostname: %s\n"
712251881Speter       " - Valid: from %s until %s\n"
713251881Speter       " - Issuer: %s\n"
714251881Speter       " - Fingerprint: %s\n"),
715251881Speter     cert_info->hostname,
716251881Speter     cert_info->valid_from,
717251881Speter     cert_info->valid_until,
718251881Speter     cert_info->issuer_dname,
719251881Speter     cert_info->fingerprint);
720251881Speter  svn_stringbuf_appendstr(buf, msg);
721251881Speter
722251881Speter  if (may_save)
723251881Speter    {
724251881Speter      svn_stringbuf_appendcstr
725251881Speter        (buf, _("(R)eject, accept (t)emporarily or accept (p)ermanently? "));
726251881Speter    }
727251881Speter  else
728251881Speter    {
729251881Speter      svn_stringbuf_appendcstr(buf, _("(R)eject or accept (t)emporarily? "));
730251881Speter    }
731251881Speter  SVN_ERR(prompt(&choice, buf->data, FALSE, pb, pool));
732251881Speter
733251881Speter  if (choice[0] == 't' || choice[0] == 'T')
734251881Speter    {
735251881Speter      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
736251881Speter      (*cred_p)->may_save = FALSE;
737251881Speter      (*cred_p)->accepted_failures = failures;
738251881Speter    }
739251881Speter  else if (may_save && (choice[0] == 'p' || choice[0] == 'P'))
740251881Speter    {
741251881Speter      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
742251881Speter      (*cred_p)->may_save = TRUE;
743251881Speter      (*cred_p)->accepted_failures = failures;
744251881Speter    }
745251881Speter  else
746251881Speter    {
747251881Speter      *cred_p = NULL;
748251881Speter    }
749251881Speter
750251881Speter  return SVN_NO_ERROR;
751251881Speter}
752251881Speter
753251881Speter
754251881Speter/* This implements 'svn_auth_ssl_client_cert_prompt_func_t'. */
755251881Spetersvn_error_t *
756251881Spetersvn_cmdline_auth_ssl_client_cert_prompt
757251881Speter  (svn_auth_cred_ssl_client_cert_t **cred_p,
758251881Speter   void *baton,
759251881Speter   const char *realm,
760251881Speter   svn_boolean_t may_save,
761251881Speter   apr_pool_t *pool)
762251881Speter{
763251881Speter  svn_auth_cred_ssl_client_cert_t *cred = NULL;
764251881Speter  const char *cert_file = NULL;
765251881Speter  const char *abs_cert_file = NULL;
766251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
767251881Speter
768251881Speter  SVN_ERR(maybe_print_realm(realm, pool));
769251881Speter  SVN_ERR(prompt(&cert_file, _("Client certificate filename: "),
770251881Speter                 FALSE, pb, pool));
771251881Speter  SVN_ERR(svn_dirent_get_absolute(&abs_cert_file, cert_file, pool));
772251881Speter
773251881Speter  cred = apr_palloc(pool, sizeof(*cred));
774251881Speter  cred->cert_file = abs_cert_file;
775251881Speter  cred->may_save = may_save;
776251881Speter  *cred_p = cred;
777251881Speter
778251881Speter  return SVN_NO_ERROR;
779251881Speter}
780251881Speter
781251881Speter
782251881Speter/* This implements 'svn_auth_ssl_client_cert_pw_prompt_func_t'. */
783251881Spetersvn_error_t *
784251881Spetersvn_cmdline_auth_ssl_client_cert_pw_prompt
785251881Speter  (svn_auth_cred_ssl_client_cert_pw_t **cred_p,
786251881Speter   void *baton,
787251881Speter   const char *realm,
788251881Speter   svn_boolean_t may_save,
789251881Speter   apr_pool_t *pool)
790251881Speter{
791251881Speter  svn_auth_cred_ssl_client_cert_pw_t *cred = NULL;
792251881Speter  const char *result;
793251881Speter  const char *text = apr_psprintf(pool, _("Passphrase for '%s': "), realm);
794251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
795251881Speter
796251881Speter  SVN_ERR(prompt(&result, text, TRUE, pb, pool));
797251881Speter
798251881Speter  cred = apr_pcalloc(pool, sizeof(*cred));
799251881Speter  cred->password = result;
800251881Speter  cred->may_save = may_save;
801251881Speter  *cred_p = cred;
802251881Speter
803251881Speter  return SVN_NO_ERROR;
804251881Speter}
805251881Speter
806251881Speter/* This is a helper for plaintext prompt functions. */
807251881Speterstatic svn_error_t *
808251881Speterplaintext_prompt_helper(svn_boolean_t *may_save_plaintext,
809251881Speter                        const char *realmstring,
810251881Speter                        const char *prompt_string,
811251881Speter                        const char *prompt_text,
812251881Speter                        void *baton,
813251881Speter                        apr_pool_t *pool)
814251881Speter{
815251881Speter  const char *answer = NULL;
816251881Speter  svn_boolean_t answered = FALSE;
817251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
818251881Speter  const char *config_path = NULL;
819251881Speter  terminal_handle_t *terminal;
820251881Speter
821251881Speter  if (pb)
822251881Speter    SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir,
823251881Speter                                            SVN_CONFIG_CATEGORY_SERVERS, pool));
824251881Speter
825251881Speter  SVN_ERR(terminal_open(&terminal, FALSE, pool));
826251881Speter  SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text,
827251881Speter                                     realmstring, config_path),
828251881Speter                        terminal, pool));
829251881Speter  SVN_ERR(terminal_close(terminal));
830251881Speter
831251881Speter  do
832251881Speter    {
833251881Speter      svn_error_t *err = prompt(&answer, prompt_string, FALSE, pb, pool);
834251881Speter      if (err)
835251881Speter        {
836251881Speter          if (err->apr_err == SVN_ERR_CANCELLED)
837251881Speter            {
838251881Speter              svn_error_clear(err);
839251881Speter              *may_save_plaintext = FALSE;
840251881Speter              return SVN_NO_ERROR;
841251881Speter            }
842251881Speter          else
843251881Speter            return err;
844251881Speter        }
845251881Speter      if (apr_strnatcasecmp(answer, _("yes")) == 0 ||
846251881Speter          apr_strnatcasecmp(answer, _("y")) == 0)
847251881Speter        {
848251881Speter          *may_save_plaintext = TRUE;
849251881Speter          answered = TRUE;
850251881Speter        }
851251881Speter      else if (apr_strnatcasecmp(answer, _("no")) == 0 ||
852251881Speter               apr_strnatcasecmp(answer, _("n")) == 0)
853251881Speter        {
854251881Speter          *may_save_plaintext = FALSE;
855251881Speter          answered = TRUE;
856251881Speter        }
857251881Speter      else
858251881Speter          prompt_string = _("Please type 'yes' or 'no': ");
859251881Speter    }
860251881Speter  while (! answered);
861251881Speter
862251881Speter  return SVN_NO_ERROR;
863251881Speter}
864251881Speter
865251881Speter/* This implements 'svn_auth_plaintext_prompt_func_t'. */
866251881Spetersvn_error_t *
867251881Spetersvn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext,
868251881Speter                                  const char *realmstring,
869251881Speter                                  void *baton,
870251881Speter                                  apr_pool_t *pool)
871251881Speter{
872251881Speter  const char *prompt_string = _("Store password unencrypted (yes/no)? ");
873251881Speter  const char *prompt_text =
874251881Speter  _("\n-----------------------------------------------------------------------"
875251881Speter    "\nATTENTION!  Your password for authentication realm:\n"
876251881Speter    "\n"
877251881Speter    "   %s\n"
878251881Speter    "\n"
879251881Speter    "can only be stored to disk unencrypted!  You are advised to configure\n"
880251881Speter    "your system so that Subversion can store passwords encrypted, if\n"
881251881Speter    "possible.  See the documentation for details.\n"
882251881Speter    "\n"
883251881Speter    "You can avoid future appearances of this warning by setting the value\n"
884251881Speter    "of the 'store-plaintext-passwords' option to either 'yes' or 'no' in\n"
885251881Speter    "'%s'.\n"
886251881Speter    "-----------------------------------------------------------------------\n"
887251881Speter    );
888251881Speter
889251881Speter  return plaintext_prompt_helper(may_save_plaintext, realmstring,
890251881Speter                                 prompt_string, prompt_text, baton,
891251881Speter                                 pool);
892251881Speter}
893251881Speter
894251881Speter/* This implements 'svn_auth_plaintext_passphrase_prompt_func_t'. */
895251881Spetersvn_error_t *
896251881Spetersvn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext,
897251881Speter                                             const char *realmstring,
898251881Speter                                             void *baton,
899251881Speter                                             apr_pool_t *pool)
900251881Speter{
901251881Speter  const char *prompt_string = _("Store passphrase unencrypted (yes/no)? ");
902251881Speter  const char *prompt_text =
903251881Speter  _("\n-----------------------------------------------------------------------\n"
904251881Speter    "ATTENTION!  Your passphrase for client certificate:\n"
905251881Speter    "\n"
906251881Speter    "   %s\n"
907251881Speter    "\n"
908251881Speter    "can only be stored to disk unencrypted!  You are advised to configure\n"
909251881Speter    "your system so that Subversion can store passphrase encrypted, if\n"
910251881Speter    "possible.  See the documentation for details.\n"
911251881Speter    "\n"
912251881Speter    "You can avoid future appearances of this warning by setting the value\n"
913251881Speter    "of the 'store-ssl-client-cert-pp-plaintext' option to either 'yes' or\n"
914251881Speter    "'no' in '%s'.\n"
915251881Speter    "-----------------------------------------------------------------------\n"
916251881Speter    );
917251881Speter
918251881Speter  return plaintext_prompt_helper(may_save_plaintext, realmstring,
919251881Speter                                 prompt_string, prompt_text, baton,
920251881Speter                                 pool);
921251881Speter}
922251881Speter
923251881Speter
924251881Speter/** Generic prompting. **/
925251881Speter
926251881Spetersvn_error_t *
927251881Spetersvn_cmdline_prompt_user2(const char **result,
928251881Speter                         const char *prompt_str,
929251881Speter                         svn_cmdline_prompt_baton_t *baton,
930251881Speter                         apr_pool_t *pool)
931251881Speter{
932251881Speter  /* XXX: We know prompt doesn't use the new members
933251881Speter   * of svn_cmdline_prompt_baton2_t. */
934251881Speter  return prompt(result, prompt_str, FALSE /* don't hide input */,
935251881Speter                (svn_cmdline_prompt_baton2_t *)baton, pool);
936251881Speter}
937251881Speter
938251881Speter/* This implements 'svn_auth_gnome_keyring_unlock_prompt_func_t'. */
939251881Spetersvn_error_t *
940251881Spetersvn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password,
941251881Speter                                              const char *keyring_name,
942251881Speter                                              void *baton,
943251881Speter                                              apr_pool_t *pool)
944251881Speter{
945251881Speter  const char *password;
946251881Speter  const char *pass_prompt;
947251881Speter  svn_cmdline_prompt_baton2_t *pb = baton;
948251881Speter
949251881Speter  pass_prompt = apr_psprintf(pool, _("Password for '%s' GNOME keyring: "),
950251881Speter                             keyring_name);
951251881Speter  SVN_ERR(prompt(&password, pass_prompt, TRUE, pb, pool));
952251881Speter  *keyring_password = apr_pstrdup(pool, password);
953251881Speter  return SVN_NO_ERROR;
954251881Speter}
955