prompt.c revision 269847
1/*
2 * prompt.c -- ask the user for authentication information.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#include <apr_lib.h>
31#include <apr_poll.h>
32#include <apr_portable.h>
33
34#include "svn_cmdline.h"
35#include "svn_ctype.h"
36#include "svn_string.h"
37#include "svn_auth.h"
38#include "svn_error.h"
39#include "svn_path.h"
40
41#include "private/svn_cmdline_private.h"
42#include "svn_private_config.h"
43
44#ifdef WIN32
45#include <conio.h>
46#elif defined(HAVE_TERMIOS_H)
47#include <signal.h>
48#include <termios.h>
49#endif
50
51
52
53/* Descriptor of an open terminal */
54typedef struct terminal_handle_t terminal_handle_t;
55struct terminal_handle_t
56{
57  apr_file_t *infd;              /* input file handle */
58  apr_file_t *outfd;             /* output file handle */
59  svn_boolean_t noecho;          /* terminal echo was turned off */
60  svn_boolean_t close_handles;   /* close handles when closing the terminal */
61  apr_pool_t *pool;              /* pool associated with the file handles */
62
63#ifdef HAVE_TERMIOS_H
64  svn_boolean_t restore_state;   /* terminal state was changed */
65  apr_os_file_t osinfd;          /* OS-specific handle for infd */
66  struct termios attr;           /* saved terminal attributes */
67#endif
68};
69
70/* Initialize safe state of terminal_handle_t. */
71static void
72terminal_handle_init(terminal_handle_t *terminal,
73                     apr_file_t *infd, apr_file_t *outfd,
74                     svn_boolean_t noecho, svn_boolean_t close_handles,
75                     apr_pool_t *pool)
76{
77  memset(terminal, 0, sizeof(*terminal));
78  terminal->infd = infd;
79  terminal->outfd = outfd;
80  terminal->noecho = noecho;
81  terminal->close_handles = close_handles;
82  terminal->pool = pool;
83}
84
85/*
86 * Common pool cleanup handler for terminal_handle_t. Closes TERMINAL.
87 * If CLOSE_HANDLES is TRUE, close the terminal file handles.
88 * If RESTORE_STATE is TRUE, restores the TERMIOS flags of the terminal.
89 */
90static apr_status_t
91terminal_cleanup_handler(terminal_handle_t *terminal,
92                         svn_boolean_t close_handles,
93                         svn_boolean_t restore_state)
94{
95  apr_status_t status = APR_SUCCESS;
96
97#ifdef HAVE_TERMIOS_H
98  /* Restore terminal state flags. */
99  if (restore_state && terminal->restore_state)
100    tcsetattr(terminal->osinfd, TCSANOW, &terminal->attr);
101#endif
102
103  /* Close terminal handles. */
104  if (close_handles && terminal->close_handles)
105    {
106      apr_file_t *const infd = terminal->infd;
107      apr_file_t *const outfd = terminal->outfd;
108
109      if (infd)
110        {
111          terminal->infd = NULL;
112          status = apr_file_close(infd);
113        }
114
115      if (!status && outfd && outfd != infd)
116        {
117          terminal->outfd = NULL;
118          status = apr_file_close(terminal->outfd);
119        }
120    }
121  return status;
122}
123
124/* Normal pool cleanup for a terminal. */
125static apr_status_t terminal_plain_cleanup(void *baton)
126{
127  return terminal_cleanup_handler(baton, FALSE, TRUE);
128}
129
130/* Child pool cleanup for a terminal -- does not restore echo state. */
131static apr_status_t terminal_child_cleanup(void *baton)
132{
133  return terminal_cleanup_handler(baton, FALSE, FALSE);
134}
135
136/* Explicitly close the terminal, removing its cleanup handlers. */
137static svn_error_t *
138terminal_close(terminal_handle_t *terminal)
139{
140  apr_status_t status;
141
142  /* apr_pool_cleanup_kill() removes both normal and child cleanup */
143  apr_pool_cleanup_kill(terminal->pool, terminal, terminal_plain_cleanup);
144
145  status = terminal_cleanup_handler(terminal, TRUE, TRUE);
146  if (status)
147    return svn_error_create(status, NULL, _("Can't close terminal"));
148  return SVN_NO_ERROR;
149}
150
151/* Allocate and open *TERMINAL. If NOECHO is TRUE, try to turn off
152   terminal echo.  Use POOL for all allocations.*/
153static svn_error_t *
154terminal_open(terminal_handle_t **terminal, svn_boolean_t noecho,
155              apr_pool_t *pool)
156{
157  apr_status_t status;
158
159#ifdef WIN32
160  /* On Windows, we'll use the console API directly if the process has
161     a console attached; otherwise we'll just use stdin and stderr. */
162  const HANDLE conin = CreateFileW(L"CONIN$", GENERIC_READ,
163                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
164                                   NULL, OPEN_EXISTING,
165                                   FILE_ATTRIBUTE_NORMAL, NULL);
166  *terminal = apr_palloc(pool, sizeof(terminal_handle_t));
167  if (conin != INVALID_HANDLE_VALUE)
168    {
169      /* The process has a console. */
170      CloseHandle(conin);
171      terminal_handle_init(*terminal, NULL, NULL, noecho, FALSE, NULL);
172      return SVN_NO_ERROR;
173    }
174#else  /* !WIN32 */
175  /* Without evidence to the contrary, we'll assume this is *nix and
176     try to open /dev/tty. If that fails, we'll use stdin for input
177     and stderr for prompting. */
178  apr_file_t *tmpfd;
179  status = apr_file_open(&tmpfd, "/dev/tty",
180                         APR_READ | APR_WRITE,
181                         APR_OS_DEFAULT, pool);
182  *terminal = apr_palloc(pool, sizeof(terminal_handle_t));
183  if (!status)
184    {
185      /* We have a terminal handle that we can use for input and output. */
186      terminal_handle_init(*terminal, tmpfd, tmpfd, FALSE, TRUE, pool);
187    }
188#endif /* !WIN32 */
189  else
190    {
191      /* There is no terminal. Sigh. */
192      apr_file_t *infd;
193      apr_file_t *outfd;
194
195      status = apr_file_open_stdin(&infd, pool);
196      if (status)
197        return svn_error_wrap_apr(status, _("Can't open stdin"));
198      status = apr_file_open_stderr(&outfd, pool);
199      if (status)
200        return svn_error_wrap_apr(status, _("Can't open stderr"));
201      terminal_handle_init(*terminal, infd, outfd, FALSE, FALSE, pool);
202    }
203
204#ifdef HAVE_TERMIOS_H
205  /* Set terminal state */
206  if (0 == apr_os_file_get(&(*terminal)->osinfd, (*terminal)->infd))
207    {
208      if (0 == tcgetattr((*terminal)->osinfd, &(*terminal)->attr))
209        {
210          struct termios attr = (*terminal)->attr;
211          /* Turn off signal handling and canonical input mode */
212          attr.c_lflag &= ~(ISIG | ICANON);
213          attr.c_cc[VMIN] = 1;          /* Read one byte at a time */
214          attr.c_cc[VTIME] = 0;         /* No timeout, wait indefinitely */
215          attr.c_lflag &= ~(ECHO);      /* Turn off echo */
216          if (0 == tcsetattr((*terminal)->osinfd, TCSAFLUSH, &attr))
217            {
218              (*terminal)->noecho = noecho;
219              (*terminal)->restore_state = TRUE;
220            }
221        }
222    }
223#endif /* HAVE_TERMIOS_H */
224
225  /* Register pool cleanup to close handles and restore echo state. */
226  apr_pool_cleanup_register((*terminal)->pool, *terminal,
227                            terminal_plain_cleanup,
228                            terminal_child_cleanup);
229  return SVN_NO_ERROR;
230}
231
232/* Write a null-terminated STRING to TERMINAL.
233   Use POOL for allocations related to converting STRING from UTF-8. */
234static svn_error_t *
235terminal_puts(const char *string, terminal_handle_t *terminal,
236              apr_pool_t *pool)
237{
238  svn_error_t *err;
239  apr_status_t status;
240  const char *converted;
241
242  err = svn_cmdline_cstring_from_utf8(&converted, string, pool);
243  if (err)
244    {
245      svn_error_clear(err);
246      converted = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
247    }
248
249#ifdef WIN32
250  if (!terminal->outfd)
251    {
252      /* See terminal_open; we're using Console I/O. */
253      _cputs(converted);
254      return SVN_NO_ERROR;
255    }
256#endif
257
258  status = apr_file_write_full(terminal->outfd, converted,
259                               strlen(converted), NULL);
260  if (!status)
261    status = apr_file_flush(terminal->outfd);
262  if (status)
263    return svn_error_wrap_apr(status, _("Can't write to terminal"));
264  return SVN_NO_ERROR;
265}
266
267/* These codes can be returned from terminal_getc instead of a character. */
268#define TERMINAL_NONE  0x80000               /* no character read, retry */
269#define TERMINAL_DEL   (TERMINAL_NONE + 1)   /* the input was a deleteion */
270#define TERMINAL_EOL   (TERMINAL_NONE + 2)   /* end of input/end of line */
271#define TERMINAL_EOF   (TERMINAL_NONE + 3)   /* end of file during input */
272
273/* Helper for terminal_getc: writes CH to OUTFD as a control char. */
274#ifndef WIN32
275static void
276echo_control_char(char ch, apr_file_t *outfd)
277{
278  if (svn_ctype_iscntrl(ch))
279    {
280      const char substitute = (ch < 32? '@' + ch : '?');
281      apr_file_putc('^', outfd);
282      apr_file_putc(substitute, outfd);
283    }
284  else if (svn_ctype_isprint(ch))
285    {
286      /* Pass printable characters unchanged. */
287      apr_file_putc(ch, outfd);
288    }
289  else
290    {
291      /* Everything else is strange. */
292      apr_file_putc('^', outfd);
293      apr_file_putc('!', outfd);
294    }
295}
296#endif /* WIN32 */
297
298/* Read one character or control code from TERMINAL, returning it in CODE.
299   if CAN_ERASE and the input was a deletion, emit codes to erase the
300   last character displayed on the terminal.
301   Use POOL for all allocations. */
302static svn_error_t *
303terminal_getc(int *code, terminal_handle_t *terminal,
304              svn_boolean_t can_erase, apr_pool_t *pool)
305{
306  const svn_boolean_t echo = !terminal->noecho;
307  apr_status_t status = APR_SUCCESS;
308  char ch;
309
310#ifdef WIN32
311  if (!terminal->infd)
312    {
313      /* See terminal_open; we're using Console I/O. */
314
315      /*  The following was hoisted from APR's getpass for Windows. */
316      int concode = _getch();
317      switch (concode)
318        {
319        case '\r':                      /* end-of-line */
320          *code = TERMINAL_EOL;
321          if (echo)
322            _cputs("\r\n");
323          break;
324
325        case EOF:                       /* end-of-file */
326        case 26:                        /* Ctrl+Z */
327          *code = TERMINAL_EOF;
328          if (echo)
329            _cputs((concode == EOF ? "[EOF]\r\n" : "^Z\r\n"));
330          break;
331
332        case 3:                         /* Ctrl+C, Ctrl+Break */
333          /* _getch() bypasses Ctrl+C but not Ctrl+Break detection! */
334          if (echo)
335            _cputs("^C\r\n");
336          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
337
338        case 0:                         /* Function code prefix */
339        case 0xE0:
340          concode = (concode << 4) | _getch();
341          /* Catch {DELETE}, {<--}, Num{DEL} and Num{<--} */
342          if (concode == 0xE53 || concode == 0xE4B
343              || concode == 0x053 || concode == 0x04B)
344            {
345              *code = TERMINAL_DEL;
346              if (can_erase)
347                _cputs("\b \b");
348            }
349          else
350            {
351              *code = TERMINAL_NONE;
352              _putch('\a');
353            }
354          break;
355
356        case '\b':                      /* BS */
357        case 127:                       /* DEL */
358          *code = TERMINAL_DEL;
359          if (can_erase)
360            _cputs("\b \b");
361          break;
362
363        default:
364          if (!apr_iscntrl(concode))
365            {
366              *code = (int)(unsigned char)concode;
367              _putch(echo ? concode : '*');
368            }
369          else
370            {
371              *code = TERMINAL_NONE;
372              _putch('\a');
373            }
374        }
375      return SVN_NO_ERROR;
376    }
377#elif defined(HAVE_TERMIOS_H)
378  if (terminal->restore_state)
379    {
380      /* We're using a bytewise-immediate termios input */
381      const struct termios *const attr = &terminal->attr;
382
383      status = apr_file_getc(&ch, terminal->infd);
384      if (status)
385        return svn_error_wrap_apr(status, _("Can't read from terminal"));
386
387      if (ch == attr->c_cc[VINTR] || ch == attr->c_cc[VQUIT])
388        {
389          /* Break */
390          echo_control_char(ch, terminal->outfd);
391          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
392        }
393      else if (ch == '\r' || ch == '\n' || ch == attr->c_cc[VEOL])
394        {
395          /* Newline */
396          *code = TERMINAL_EOL;
397          apr_file_putc('\n', terminal->outfd);
398        }
399      else if (ch == '\b' || ch == attr->c_cc[VERASE])
400        {
401          /* Delete */
402          *code = TERMINAL_DEL;
403          if (can_erase)
404            {
405              apr_file_putc('\b', terminal->outfd);
406              apr_file_putc(' ', terminal->outfd);
407              apr_file_putc('\b', terminal->outfd);
408            }
409        }
410      else if (ch == attr->c_cc[VEOF])
411        {
412          /* End of input */
413          *code = TERMINAL_EOF;
414          echo_control_char(ch, terminal->outfd);
415        }
416      else if (ch == attr->c_cc[VSUSP])
417        {
418          /* Suspend */
419          *code = TERMINAL_NONE;
420          kill(0, SIGTSTP);
421        }
422      else if (!apr_iscntrl(ch))
423        {
424          /* Normal character */
425          *code = (int)(unsigned char)ch;
426          apr_file_putc((echo ? ch : '*'), terminal->outfd);
427        }
428      else
429        {
430          /* Ignored character */
431          *code = TERMINAL_NONE;
432          apr_file_putc('\a', terminal->outfd);
433        }
434      return SVN_NO_ERROR;
435    }
436#endif /* HAVE_TERMIOS_H */
437
438  /* Fall back to plain stream-based I/O. */
439#ifndef WIN32
440  /* Wait for input on termin. This code is based on
441     apr_wait_for_io_or_timeout().
442     Note that this will return an EINTR on a signal. */
443  {
444    apr_pollfd_t pollset;
445    int n;
446
447    pollset.desc_type = APR_POLL_FILE;
448    pollset.desc.f = terminal->infd;
449    pollset.p = pool;
450    pollset.reqevents = APR_POLLIN;
451
452    status = apr_poll(&pollset, 1, &n, -1);
453
454    if (n == 1 && pollset.rtnevents & APR_POLLIN)
455      status = APR_SUCCESS;
456  }
457#endif /* !WIN32 */
458
459  if (!status)
460    status = apr_file_getc(&ch, terminal->infd);
461  if (APR_STATUS_IS_EINTR(status))
462    {
463      *code = TERMINAL_NONE;
464      return SVN_NO_ERROR;
465    }
466  else if (APR_STATUS_IS_EOF(status))
467    {
468      *code = TERMINAL_EOF;
469      return SVN_NO_ERROR;
470    }
471  else if (status)
472    return svn_error_wrap_apr(status, _("Can't read from terminal"));
473
474  *code = (int)(unsigned char)ch;
475  return SVN_NO_ERROR;
476}
477
478
479/* Set @a *result to the result of prompting the user with @a
480 * prompt_msg.  Use @ *pb to get the cancel_func and cancel_baton.
481 * Do not call the cancel_func if @a *pb is NULL.
482 * Allocate @a *result in @a pool.
483 *
484 * If @a hide is true, then try to avoid displaying the user's input.
485 */
486static svn_error_t *
487prompt(const char **result,
488       const char *prompt_msg,
489       svn_boolean_t hide,
490       svn_cmdline_prompt_baton2_t *pb,
491       apr_pool_t *pool)
492{
493  /* XXX: If this functions ever starts using members of *pb
494   * which were not included in svn_cmdline_prompt_baton_t,
495   * we need to update svn_cmdline_prompt_user2 and its callers. */
496
497  svn_boolean_t saw_first_half_of_eol = FALSE;
498  svn_stringbuf_t *strbuf = svn_stringbuf_create_empty(pool);
499  terminal_handle_t *terminal;
500  int code;
501  char c;
502
503  SVN_ERR(terminal_open(&terminal, hide, pool));
504  SVN_ERR(terminal_puts(prompt_msg, terminal, pool));
505
506  while (1)
507    {
508      SVN_ERR(terminal_getc(&code, terminal, (strbuf->len > 0), pool));
509
510      /* Check for cancellation after a character has been read, some
511         input processing modes may eat ^C and we'll only notice a
512         cancellation signal after characters have been read --
513         sometimes even after a newline. */
514      if (pb)
515        SVN_ERR(pb->cancel_func(pb->cancel_baton));
516
517      switch (code)
518        {
519        case TERMINAL_NONE:
520          /* Nothing useful happened; retry. */
521          continue;
522
523        case TERMINAL_DEL:
524          /* Delete the last input character. terminal_getc takes care
525             of erasing the feedback from the terminal, if applicable. */
526          svn_stringbuf_chop(strbuf, 1);
527          continue;
528
529        case TERMINAL_EOL:
530          /* End-of-line means end of input. Trick the EOL-detection code
531             below to stop reading. */
532          saw_first_half_of_eol = TRUE;
533          c = APR_EOL_STR[1];   /* Could be \0 but still stops reading. */
534          break;
535
536        case TERMINAL_EOF:
537          return svn_error_create(
538              APR_EOF,
539              terminal_close(terminal),
540              _("End of file while reading from terminal"));
541
542        default:
543          /* Convert the returned code back to the character. */
544          c = (char)code;
545        }
546
547      if (saw_first_half_of_eol)
548        {
549          if (c == APR_EOL_STR[1])
550            break;
551          else
552            saw_first_half_of_eol = FALSE;
553        }
554      else if (c == APR_EOL_STR[0])
555        {
556          /* GCC might complain here: "warning: will never be executed"
557           * That's fine. This is a compile-time check for "\r\n\0" */
558          if (sizeof(APR_EOL_STR) == 3)
559            {
560              saw_first_half_of_eol = TRUE;
561              continue;
562            }
563          else if (sizeof(APR_EOL_STR) == 2)
564            break;
565          else
566            /* ### APR_EOL_STR holds more than two chars?  Who
567               ever heard of such a thing? */
568            SVN_ERR_MALFUNCTION();
569        }
570
571      svn_stringbuf_appendbyte(strbuf, c);
572    }
573
574  if (terminal->noecho)
575    {
576      /* If terminal echo was turned off, make sure future output
577         to the terminal starts on a new line, as expected. */
578      SVN_ERR(terminal_puts(APR_EOL_STR, terminal, pool));
579    }
580  SVN_ERR(terminal_close(terminal));
581
582  return svn_cmdline_cstring_to_utf8(result, strbuf->data, pool);
583}
584
585
586
587/** Prompt functions for auth providers. **/
588
589/* Helper function for auth provider prompters: mention the
590 * authentication @a realm on stderr, in a manner appropriate for
591 * preceding a prompt; or if @a realm is null, then do nothing.
592 */
593static svn_error_t *
594maybe_print_realm(const char *realm, apr_pool_t *pool)
595{
596  if (realm)
597    {
598      terminal_handle_t *terminal;
599      SVN_ERR(terminal_open(&terminal, FALSE, pool));
600      SVN_ERR(terminal_puts(
601                  apr_psprintf(pool,
602                               _("Authentication realm: %s\n"), realm),
603                  terminal, pool));
604      SVN_ERR(terminal_close(terminal));
605    }
606
607  return SVN_NO_ERROR;
608}
609
610
611/* This implements 'svn_auth_simple_prompt_func_t'. */
612svn_error_t *
613svn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t **cred_p,
614                               void *baton,
615                               const char *realm,
616                               const char *username,
617                               svn_boolean_t may_save,
618                               apr_pool_t *pool)
619{
620  svn_auth_cred_simple_t *ret = apr_pcalloc(pool, sizeof(*ret));
621  const char *pass_prompt;
622  svn_cmdline_prompt_baton2_t *pb = baton;
623
624  SVN_ERR(maybe_print_realm(realm, pool));
625
626  if (username)
627    ret->username = apr_pstrdup(pool, username);
628  else
629    SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
630
631  pass_prompt = apr_psprintf(pool, _("Password for '%s': "), ret->username);
632  SVN_ERR(prompt(&(ret->password), pass_prompt, TRUE, pb, pool));
633  ret->may_save = may_save;
634  *cred_p = ret;
635  return SVN_NO_ERROR;
636}
637
638
639/* This implements 'svn_auth_username_prompt_func_t'. */
640svn_error_t *
641svn_cmdline_auth_username_prompt(svn_auth_cred_username_t **cred_p,
642                                 void *baton,
643                                 const char *realm,
644                                 svn_boolean_t may_save,
645                                 apr_pool_t *pool)
646{
647  svn_auth_cred_username_t *ret = apr_pcalloc(pool, sizeof(*ret));
648  svn_cmdline_prompt_baton2_t *pb = baton;
649
650  SVN_ERR(maybe_print_realm(realm, pool));
651
652  SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
653  ret->may_save = may_save;
654  *cred_p = ret;
655  return SVN_NO_ERROR;
656}
657
658
659/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. */
660svn_error_t *
661svn_cmdline_auth_ssl_server_trust_prompt
662  (svn_auth_cred_ssl_server_trust_t **cred_p,
663   void *baton,
664   const char *realm,
665   apr_uint32_t failures,
666   const svn_auth_ssl_server_cert_info_t *cert_info,
667   svn_boolean_t may_save,
668   apr_pool_t *pool)
669{
670  const char *choice;
671  svn_stringbuf_t *msg;
672  svn_cmdline_prompt_baton2_t *pb = baton;
673  svn_stringbuf_t *buf = svn_stringbuf_createf
674    (pool, _("Error validating server certificate for '%s':\n"), realm);
675
676  if (failures & SVN_AUTH_SSL_UNKNOWNCA)
677    {
678      svn_stringbuf_appendcstr
679        (buf,
680         _(" - The certificate is not issued by a trusted authority. Use the\n"
681           "   fingerprint to validate the certificate manually!\n"));
682    }
683
684  if (failures & SVN_AUTH_SSL_CNMISMATCH)
685    {
686      svn_stringbuf_appendcstr
687        (buf, _(" - The certificate hostname does not match.\n"));
688    }
689
690  if (failures & SVN_AUTH_SSL_NOTYETVALID)
691    {
692      svn_stringbuf_appendcstr
693        (buf, _(" - The certificate is not yet valid.\n"));
694    }
695
696  if (failures & SVN_AUTH_SSL_EXPIRED)
697    {
698      svn_stringbuf_appendcstr
699        (buf, _(" - The certificate has expired.\n"));
700    }
701
702  if (failures & SVN_AUTH_SSL_OTHER)
703    {
704      svn_stringbuf_appendcstr
705        (buf, _(" - The certificate has an unknown error.\n"));
706    }
707
708  msg = svn_stringbuf_createf
709    (pool,
710     _("Certificate information:\n"
711       " - Hostname: %s\n"
712       " - Valid: from %s until %s\n"
713       " - Issuer: %s\n"
714       " - Fingerprint: %s\n"),
715     cert_info->hostname,
716     cert_info->valid_from,
717     cert_info->valid_until,
718     cert_info->issuer_dname,
719     cert_info->fingerprint);
720  svn_stringbuf_appendstr(buf, msg);
721
722  if (may_save)
723    {
724      svn_stringbuf_appendcstr
725        (buf, _("(R)eject, accept (t)emporarily or accept (p)ermanently? "));
726    }
727  else
728    {
729      svn_stringbuf_appendcstr(buf, _("(R)eject or accept (t)emporarily? "));
730    }
731  SVN_ERR(prompt(&choice, buf->data, FALSE, pb, pool));
732
733  if (choice[0] == 't' || choice[0] == 'T')
734    {
735      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
736      (*cred_p)->may_save = FALSE;
737      (*cred_p)->accepted_failures = failures;
738    }
739  else if (may_save && (choice[0] == 'p' || choice[0] == 'P'))
740    {
741      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
742      (*cred_p)->may_save = TRUE;
743      (*cred_p)->accepted_failures = failures;
744    }
745  else
746    {
747      *cred_p = NULL;
748    }
749
750  return SVN_NO_ERROR;
751}
752
753
754/* This implements 'svn_auth_ssl_client_cert_prompt_func_t'. */
755svn_error_t *
756svn_cmdline_auth_ssl_client_cert_prompt
757  (svn_auth_cred_ssl_client_cert_t **cred_p,
758   void *baton,
759   const char *realm,
760   svn_boolean_t may_save,
761   apr_pool_t *pool)
762{
763  svn_auth_cred_ssl_client_cert_t *cred = NULL;
764  const char *cert_file = NULL;
765  const char *abs_cert_file = NULL;
766  svn_cmdline_prompt_baton2_t *pb = baton;
767
768  SVN_ERR(maybe_print_realm(realm, pool));
769  SVN_ERR(prompt(&cert_file, _("Client certificate filename: "),
770                 FALSE, pb, pool));
771  SVN_ERR(svn_dirent_get_absolute(&abs_cert_file, cert_file, pool));
772
773  cred = apr_palloc(pool, sizeof(*cred));
774  cred->cert_file = abs_cert_file;
775  cred->may_save = may_save;
776  *cred_p = cred;
777
778  return SVN_NO_ERROR;
779}
780
781
782/* This implements 'svn_auth_ssl_client_cert_pw_prompt_func_t'. */
783svn_error_t *
784svn_cmdline_auth_ssl_client_cert_pw_prompt
785  (svn_auth_cred_ssl_client_cert_pw_t **cred_p,
786   void *baton,
787   const char *realm,
788   svn_boolean_t may_save,
789   apr_pool_t *pool)
790{
791  svn_auth_cred_ssl_client_cert_pw_t *cred = NULL;
792  const char *result;
793  const char *text = apr_psprintf(pool, _("Passphrase for '%s': "), realm);
794  svn_cmdline_prompt_baton2_t *pb = baton;
795
796  SVN_ERR(prompt(&result, text, TRUE, pb, pool));
797
798  cred = apr_pcalloc(pool, sizeof(*cred));
799  cred->password = result;
800  cred->may_save = may_save;
801  *cred_p = cred;
802
803  return SVN_NO_ERROR;
804}
805
806/* This is a helper for plaintext prompt functions. */
807static svn_error_t *
808plaintext_prompt_helper(svn_boolean_t *may_save_plaintext,
809                        const char *realmstring,
810                        const char *prompt_string,
811                        const char *prompt_text,
812                        void *baton,
813                        apr_pool_t *pool)
814{
815  const char *answer = NULL;
816  svn_boolean_t answered = FALSE;
817  svn_cmdline_prompt_baton2_t *pb = baton;
818  const char *config_path = NULL;
819  terminal_handle_t *terminal;
820
821  if (pb)
822    SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir,
823                                            SVN_CONFIG_CATEGORY_SERVERS, pool));
824
825  SVN_ERR(terminal_open(&terminal, FALSE, pool));
826  SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text,
827                                     realmstring, config_path),
828                        terminal, pool));
829  SVN_ERR(terminal_close(terminal));
830
831  do
832    {
833      svn_error_t *err = prompt(&answer, prompt_string, FALSE, pb, pool);
834      if (err)
835        {
836          if (err->apr_err == SVN_ERR_CANCELLED)
837            {
838              svn_error_clear(err);
839              *may_save_plaintext = FALSE;
840              return SVN_NO_ERROR;
841            }
842          else
843            return err;
844        }
845      if (apr_strnatcasecmp(answer, _("yes")) == 0 ||
846          apr_strnatcasecmp(answer, _("y")) == 0)
847        {
848          *may_save_plaintext = TRUE;
849          answered = TRUE;
850        }
851      else if (apr_strnatcasecmp(answer, _("no")) == 0 ||
852               apr_strnatcasecmp(answer, _("n")) == 0)
853        {
854          *may_save_plaintext = FALSE;
855          answered = TRUE;
856        }
857      else
858          prompt_string = _("Please type 'yes' or 'no': ");
859    }
860  while (! answered);
861
862  return SVN_NO_ERROR;
863}
864
865/* This implements 'svn_auth_plaintext_prompt_func_t'. */
866svn_error_t *
867svn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext,
868                                  const char *realmstring,
869                                  void *baton,
870                                  apr_pool_t *pool)
871{
872  const char *prompt_string = _("Store password unencrypted (yes/no)? ");
873  const char *prompt_text =
874  _("\n-----------------------------------------------------------------------"
875    "\nATTENTION!  Your password for authentication realm:\n"
876    "\n"
877    "   %s\n"
878    "\n"
879    "can only be stored to disk unencrypted!  You are advised to configure\n"
880    "your system so that Subversion can store passwords encrypted, if\n"
881    "possible.  See the documentation for details.\n"
882    "\n"
883    "You can avoid future appearances of this warning by setting the value\n"
884    "of the 'store-plaintext-passwords' option to either 'yes' or 'no' in\n"
885    "'%s'.\n"
886    "-----------------------------------------------------------------------\n"
887    );
888
889  return plaintext_prompt_helper(may_save_plaintext, realmstring,
890                                 prompt_string, prompt_text, baton,
891                                 pool);
892}
893
894/* This implements 'svn_auth_plaintext_passphrase_prompt_func_t'. */
895svn_error_t *
896svn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext,
897                                             const char *realmstring,
898                                             void *baton,
899                                             apr_pool_t *pool)
900{
901  const char *prompt_string = _("Store passphrase unencrypted (yes/no)? ");
902  const char *prompt_text =
903  _("\n-----------------------------------------------------------------------\n"
904    "ATTENTION!  Your passphrase for client certificate:\n"
905    "\n"
906    "   %s\n"
907    "\n"
908    "can only be stored to disk unencrypted!  You are advised to configure\n"
909    "your system so that Subversion can store passphrase encrypted, if\n"
910    "possible.  See the documentation for details.\n"
911    "\n"
912    "You can avoid future appearances of this warning by setting the value\n"
913    "of the 'store-ssl-client-cert-pp-plaintext' option to either 'yes' or\n"
914    "'no' in '%s'.\n"
915    "-----------------------------------------------------------------------\n"
916    );
917
918  return plaintext_prompt_helper(may_save_plaintext, realmstring,
919                                 prompt_string, prompt_text, baton,
920                                 pool);
921}
922
923
924/** Generic prompting. **/
925
926svn_error_t *
927svn_cmdline_prompt_user2(const char **result,
928                         const char *prompt_str,
929                         svn_cmdline_prompt_baton_t *baton,
930                         apr_pool_t *pool)
931{
932  /* XXX: We know prompt doesn't use the new members
933   * of svn_cmdline_prompt_baton2_t. */
934  return prompt(result, prompt_str, FALSE /* don't hide input */,
935                (svn_cmdline_prompt_baton2_t *)baton, pool);
936}
937
938/* This implements 'svn_auth_gnome_keyring_unlock_prompt_func_t'. */
939svn_error_t *
940svn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password,
941                                              const char *keyring_name,
942                                              void *baton,
943                                              apr_pool_t *pool)
944{
945  const char *password;
946  const char *pass_prompt;
947  svn_cmdline_prompt_baton2_t *pb = baton;
948
949  pass_prompt = apr_psprintf(pool, _("Password for '%s' GNOME keyring: "),
950                             keyring_name);
951  SVN_ERR(prompt(&password, pass_prompt, TRUE, pb, pool));
952  *keyring_password = apr_pstrdup(pool, password);
953  return SVN_NO_ERROR;
954}
955