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_FOPEN_READ | APR_FOPEN_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  const char *converted;
240
241  err = svn_cmdline_cstring_from_utf8(&converted, string, pool);
242  if (err)
243    {
244      svn_error_clear(err);
245      converted = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
246    }
247
248#ifdef WIN32
249  if (!terminal->outfd)
250    {
251      /* See terminal_open; we're using Console I/O. */
252      _cputs(converted);
253      return SVN_NO_ERROR;
254    }
255#endif
256
257  SVN_ERR(svn_io_file_write_full(terminal->outfd, converted,
258                                 strlen(converted), NULL, pool));
259
260  return svn_error_trace(svn_io_file_flush(terminal->outfd, pool));
261}
262
263/* These codes can be returned from terminal_getc instead of a character. */
264#define TERMINAL_NONE  0x80000               /* no character read, retry */
265#define TERMINAL_DEL   (TERMINAL_NONE + 1)   /* the input was a deleteion */
266#define TERMINAL_EOL   (TERMINAL_NONE + 2)   /* end of input/end of line */
267#define TERMINAL_EOF   (TERMINAL_NONE + 3)   /* end of file during input */
268
269/* Helper for terminal_getc: writes CH to OUTFD as a control char. */
270#ifndef WIN32
271static void
272echo_control_char(char ch, apr_file_t *outfd)
273{
274  if (svn_ctype_iscntrl(ch))
275    {
276      const char substitute = (ch < 32? '@' + ch : '?');
277      apr_file_putc('^', outfd);
278      apr_file_putc(substitute, outfd);
279    }
280  else if (svn_ctype_isprint(ch))
281    {
282      /* Pass printable characters unchanged. */
283      apr_file_putc(ch, outfd);
284    }
285  else
286    {
287      /* Everything else is strange. */
288      apr_file_putc('^', outfd);
289      apr_file_putc('!', outfd);
290    }
291}
292#endif /* WIN32 */
293
294/* Read one character or control code from TERMINAL, returning it in CODE.
295   if CAN_ERASE and the input was a deletion, emit codes to erase the
296   last character displayed on the terminal.
297   Use POOL for all allocations. */
298static svn_error_t *
299terminal_getc(int *code, terminal_handle_t *terminal,
300              svn_boolean_t can_erase, apr_pool_t *pool)
301{
302  const svn_boolean_t echo = !terminal->noecho;
303  apr_status_t status = APR_SUCCESS;
304  char ch;
305
306#ifdef WIN32
307  if (!terminal->infd)
308    {
309      /* See terminal_open; we're using Console I/O. */
310
311      /*  The following was hoisted from APR's getpass for Windows. */
312      int concode = _getch();
313      switch (concode)
314        {
315        case '\r':                      /* end-of-line */
316          *code = TERMINAL_EOL;
317          if (echo)
318            _cputs("\r\n");
319          break;
320
321        case EOF:                       /* end-of-file */
322        case 26:                        /* Ctrl+Z */
323          *code = TERMINAL_EOF;
324          if (echo)
325            _cputs((concode == EOF ? "[EOF]\r\n" : "^Z\r\n"));
326          break;
327
328        case 3:                         /* Ctrl+C, Ctrl+Break */
329          /* _getch() bypasses Ctrl+C but not Ctrl+Break detection! */
330          if (echo)
331            _cputs("^C\r\n");
332          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
333
334        case 0:                         /* Function code prefix */
335        case 0xE0:
336          concode = (concode << 4) | _getch();
337          /* Catch {DELETE}, {<--}, Num{DEL} and Num{<--} */
338          if (concode == 0xE53 || concode == 0xE4B
339              || concode == 0x053 || concode == 0x04B)
340            {
341              *code = TERMINAL_DEL;
342              if (can_erase)
343                _cputs("\b \b");
344            }
345          else
346            {
347              *code = TERMINAL_NONE;
348              _putch('\a');
349            }
350          break;
351
352        case '\b':                      /* BS */
353        case 127:                       /* DEL */
354          *code = TERMINAL_DEL;
355          if (can_erase)
356            _cputs("\b \b");
357          break;
358
359        default:
360          if (!apr_iscntrl(concode))
361            {
362              *code = (int)(unsigned char)concode;
363              _putch(echo ? concode : '*');
364            }
365          else
366            {
367              *code = TERMINAL_NONE;
368              _putch('\a');
369            }
370        }
371      return SVN_NO_ERROR;
372    }
373#elif defined(HAVE_TERMIOS_H)
374  if (terminal->restore_state)
375    {
376      /* We're using a bytewise-immediate termios input */
377      const struct termios *const attr = &terminal->attr;
378
379      status = apr_file_getc(&ch, terminal->infd);
380      if (status)
381        return svn_error_wrap_apr(status, _("Can't read from terminal"));
382
383      if (ch == attr->c_cc[VINTR] || ch == attr->c_cc[VQUIT])
384        {
385          /* Break */
386          echo_control_char(ch, terminal->outfd);
387          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
388        }
389      else if (ch == '\r' || ch == '\n' || ch == attr->c_cc[VEOL])
390        {
391          /* Newline */
392          *code = TERMINAL_EOL;
393          apr_file_putc('\n', terminal->outfd);
394        }
395      else if (ch == '\b' || ch == attr->c_cc[VERASE])
396        {
397          /* Delete */
398          *code = TERMINAL_DEL;
399          if (can_erase)
400            {
401              apr_file_putc('\b', terminal->outfd);
402              apr_file_putc(' ', terminal->outfd);
403              apr_file_putc('\b', terminal->outfd);
404            }
405        }
406      else if (ch == attr->c_cc[VEOF])
407        {
408          /* End of input */
409          *code = TERMINAL_EOF;
410          echo_control_char(ch, terminal->outfd);
411        }
412      else if (ch == attr->c_cc[VSUSP])
413        {
414          /* Suspend */
415          *code = TERMINAL_NONE;
416          kill(0, SIGTSTP);
417        }
418      else if (!apr_iscntrl(ch))
419        {
420          /* Normal character */
421          *code = (int)(unsigned char)ch;
422          apr_file_putc((echo ? ch : '*'), terminal->outfd);
423        }
424      else
425        {
426          /* Ignored character */
427          *code = TERMINAL_NONE;
428          apr_file_putc('\a', terminal->outfd);
429        }
430      return SVN_NO_ERROR;
431    }
432#endif /* HAVE_TERMIOS_H */
433
434  /* Fall back to plain stream-based I/O. */
435#ifndef WIN32
436  /* Wait for input on termin. This code is based on
437     apr_wait_for_io_or_timeout().
438     Note that this will return an EINTR on a signal. */
439  {
440    apr_pollfd_t pollset;
441    int n;
442
443    pollset.desc_type = APR_POLL_FILE;
444    pollset.desc.f = terminal->infd;
445    pollset.p = pool;
446    pollset.reqevents = APR_POLLIN;
447
448    status = apr_poll(&pollset, 1, &n, -1);
449
450    if (n == 1 && pollset.rtnevents & APR_POLLIN)
451      status = APR_SUCCESS;
452  }
453#endif /* !WIN32 */
454
455  if (!status)
456    status = apr_file_getc(&ch, terminal->infd);
457  if (APR_STATUS_IS_EINTR(status))
458    {
459      *code = TERMINAL_NONE;
460      return SVN_NO_ERROR;
461    }
462  else if (APR_STATUS_IS_EOF(status))
463    {
464      *code = TERMINAL_EOF;
465      return SVN_NO_ERROR;
466    }
467  else if (status)
468    return svn_error_wrap_apr(status, _("Can't read from terminal"));
469
470  *code = (int)(unsigned char)ch;
471  return SVN_NO_ERROR;
472}
473
474
475/* Set @a *result to the result of prompting the user with @a
476 * prompt_msg.  Use @ *pb to get the cancel_func and cancel_baton.
477 * Do not call the cancel_func if @a *pb is NULL.
478 * Allocate @a *result in @a pool.
479 *
480 * If @a hide is true, then try to avoid displaying the user's input.
481 */
482static svn_error_t *
483prompt(const char **result,
484       const char *prompt_msg,
485       svn_boolean_t hide,
486       svn_cmdline_prompt_baton2_t *pb,
487       apr_pool_t *pool)
488{
489  /* XXX: If this functions ever starts using members of *pb
490   * which were not included in svn_cmdline_prompt_baton_t,
491   * we need to update svn_cmdline_prompt_user2 and its callers. */
492
493  svn_boolean_t saw_first_half_of_eol = FALSE;
494  svn_stringbuf_t *strbuf = svn_stringbuf_create_empty(pool);
495  terminal_handle_t *terminal;
496  int code;
497  char c;
498
499  SVN_ERR(terminal_open(&terminal, hide, pool));
500  SVN_ERR(terminal_puts(prompt_msg, terminal, pool));
501
502  while (1)
503    {
504      SVN_ERR(terminal_getc(&code, terminal, (strbuf->len > 0), pool));
505
506      /* Check for cancellation after a character has been read, some
507         input processing modes may eat ^C and we'll only notice a
508         cancellation signal after characters have been read --
509         sometimes even after a newline. */
510      if (pb)
511        SVN_ERR(pb->cancel_func(pb->cancel_baton));
512
513      switch (code)
514        {
515        case TERMINAL_NONE:
516          /* Nothing useful happened; retry. */
517          continue;
518
519        case TERMINAL_DEL:
520          /* Delete the last input character. terminal_getc takes care
521             of erasing the feedback from the terminal, if applicable. */
522          svn_stringbuf_chop(strbuf, 1);
523          continue;
524
525        case TERMINAL_EOL:
526          /* End-of-line means end of input. Trick the EOL-detection code
527             below to stop reading. */
528          saw_first_half_of_eol = TRUE;
529          c = APR_EOL_STR[1];   /* Could be \0 but still stops reading. */
530          break;
531
532        case TERMINAL_EOF:
533          return svn_error_create(
534              APR_EOF,
535              terminal_close(terminal),
536              _("End of file while reading from terminal"));
537
538        default:
539          /* Convert the returned code back to the character. */
540          c = (char)code;
541        }
542
543      if (saw_first_half_of_eol)
544        {
545          if (c == APR_EOL_STR[1])
546            break;
547          else
548            saw_first_half_of_eol = FALSE;
549        }
550      else if (c == APR_EOL_STR[0])
551        {
552          /* GCC might complain here: "warning: will never be executed"
553           * That's fine. This is a compile-time check for "\r\n\0" */
554          if (sizeof(APR_EOL_STR) == 3)
555            {
556              saw_first_half_of_eol = TRUE;
557              continue;
558            }
559          else if (sizeof(APR_EOL_STR) == 2)
560            break;
561          else
562            /* ### APR_EOL_STR holds more than two chars?  Who
563               ever heard of such a thing? */
564            SVN_ERR_MALFUNCTION();
565        }
566
567      svn_stringbuf_appendbyte(strbuf, c);
568    }
569
570  if (terminal->noecho)
571    {
572      /* If terminal echo was turned off, make sure future output
573         to the terminal starts on a new line, as expected. */
574      SVN_ERR(terminal_puts(APR_EOL_STR, terminal, pool));
575    }
576  SVN_ERR(terminal_close(terminal));
577
578  return svn_cmdline_cstring_to_utf8(result, strbuf->data, pool);
579}
580
581
582
583/** Prompt functions for auth providers. **/
584
585/* Helper function for auth provider prompters: mention the
586 * authentication @a realm on stderr, in a manner appropriate for
587 * preceding a prompt; or if @a realm is null, then do nothing.
588 */
589static svn_error_t *
590maybe_print_realm(const char *realm, apr_pool_t *pool)
591{
592  if (realm)
593    {
594      terminal_handle_t *terminal;
595      SVN_ERR(terminal_open(&terminal, FALSE, pool));
596      SVN_ERR(terminal_puts(
597                  apr_psprintf(pool,
598                               _("Authentication realm: %s\n"), realm),
599                  terminal, pool));
600      SVN_ERR(terminal_close(terminal));
601    }
602
603  return SVN_NO_ERROR;
604}
605
606
607/* This implements 'svn_auth_simple_prompt_func_t'. */
608svn_error_t *
609svn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t **cred_p,
610                               void *baton,
611                               const char *realm,
612                               const char *username,
613                               svn_boolean_t may_save,
614                               apr_pool_t *pool)
615{
616  svn_auth_cred_simple_t *ret = apr_pcalloc(pool, sizeof(*ret));
617  const char *pass_prompt;
618  svn_cmdline_prompt_baton2_t *pb = baton;
619
620  SVN_ERR(maybe_print_realm(realm, pool));
621
622  if (username)
623    ret->username = apr_pstrdup(pool, username);
624  else
625    SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
626
627  pass_prompt = apr_psprintf(pool, _("Password for '%s': "), ret->username);
628  SVN_ERR(prompt(&(ret->password), pass_prompt, TRUE, pb, pool));
629  ret->may_save = may_save;
630  *cred_p = ret;
631  return SVN_NO_ERROR;
632}
633
634
635/* This implements 'svn_auth_username_prompt_func_t'. */
636svn_error_t *
637svn_cmdline_auth_username_prompt(svn_auth_cred_username_t **cred_p,
638                                 void *baton,
639                                 const char *realm,
640                                 svn_boolean_t may_save,
641                                 apr_pool_t *pool)
642{
643  svn_auth_cred_username_t *ret = apr_pcalloc(pool, sizeof(*ret));
644  svn_cmdline_prompt_baton2_t *pb = baton;
645
646  SVN_ERR(maybe_print_realm(realm, pool));
647
648  SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool));
649  ret->may_save = may_save;
650  *cred_p = ret;
651  return SVN_NO_ERROR;
652}
653
654
655/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. */
656svn_error_t *
657svn_cmdline_auth_ssl_server_trust_prompt
658  (svn_auth_cred_ssl_server_trust_t **cred_p,
659   void *baton,
660   const char *realm,
661   apr_uint32_t failures,
662   const svn_auth_ssl_server_cert_info_t *cert_info,
663   svn_boolean_t may_save,
664   apr_pool_t *pool)
665{
666  const char *choice;
667  svn_stringbuf_t *msg;
668  svn_cmdline_prompt_baton2_t *pb = baton;
669  svn_stringbuf_t *buf = svn_stringbuf_createf
670    (pool, _("Error validating server certificate for '%s':\n"), realm);
671
672  if (failures & SVN_AUTH_SSL_UNKNOWNCA)
673    {
674      svn_stringbuf_appendcstr
675        (buf,
676         _(" - The certificate is not issued by a trusted authority. Use the\n"
677           "   fingerprint to validate the certificate manually!\n"));
678    }
679
680  if (failures & SVN_AUTH_SSL_CNMISMATCH)
681    {
682      svn_stringbuf_appendcstr
683        (buf, _(" - The certificate hostname does not match.\n"));
684    }
685
686  if (failures & SVN_AUTH_SSL_NOTYETVALID)
687    {
688      svn_stringbuf_appendcstr
689        (buf, _(" - The certificate is not yet valid.\n"));
690    }
691
692  if (failures & SVN_AUTH_SSL_EXPIRED)
693    {
694      svn_stringbuf_appendcstr
695        (buf, _(" - The certificate has expired.\n"));
696    }
697
698  if (failures & SVN_AUTH_SSL_OTHER)
699    {
700      svn_stringbuf_appendcstr
701        (buf, _(" - The certificate has an unknown error.\n"));
702    }
703
704  msg = svn_stringbuf_createf
705    (pool,
706     _("Certificate information:\n"
707       " - Hostname: %s\n"
708       " - Valid: from %s until %s\n"
709       " - Issuer: %s\n"
710       " - Fingerprint: %s\n"),
711     cert_info->hostname,
712     cert_info->valid_from,
713     cert_info->valid_until,
714     cert_info->issuer_dname,
715     cert_info->fingerprint);
716  svn_stringbuf_appendstr(buf, msg);
717
718  if (may_save)
719    {
720      svn_stringbuf_appendcstr
721        (buf, _("(R)eject, accept (t)emporarily or accept (p)ermanently? "));
722    }
723  else
724    {
725      svn_stringbuf_appendcstr(buf, _("(R)eject or accept (t)emporarily? "));
726    }
727  SVN_ERR(prompt(&choice, buf->data, FALSE, pb, pool));
728
729  if (choice[0] == 't' || choice[0] == 'T')
730    {
731      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
732      (*cred_p)->may_save = FALSE;
733      (*cred_p)->accepted_failures = failures;
734    }
735  else if (may_save && (choice[0] == 'p' || choice[0] == 'P'))
736    {
737      *cred_p = apr_pcalloc(pool, sizeof(**cred_p));
738      (*cred_p)->may_save = TRUE;
739      (*cred_p)->accepted_failures = failures;
740    }
741  else
742    {
743      *cred_p = NULL;
744    }
745
746  return SVN_NO_ERROR;
747}
748
749
750/* This implements 'svn_auth_ssl_client_cert_prompt_func_t'. */
751svn_error_t *
752svn_cmdline_auth_ssl_client_cert_prompt
753  (svn_auth_cred_ssl_client_cert_t **cred_p,
754   void *baton,
755   const char *realm,
756   svn_boolean_t may_save,
757   apr_pool_t *pool)
758{
759  svn_auth_cred_ssl_client_cert_t *cred = NULL;
760  const char *cert_file = NULL;
761  const char *abs_cert_file = NULL;
762  svn_cmdline_prompt_baton2_t *pb = baton;
763
764  SVN_ERR(maybe_print_realm(realm, pool));
765  SVN_ERR(prompt(&cert_file, _("Client certificate filename: "),
766                 FALSE, pb, pool));
767  SVN_ERR(svn_dirent_get_absolute(&abs_cert_file, cert_file, pool));
768
769  cred = apr_palloc(pool, sizeof(*cred));
770  cred->cert_file = abs_cert_file;
771  cred->may_save = may_save;
772  *cred_p = cred;
773
774  return SVN_NO_ERROR;
775}
776
777
778/* This implements 'svn_auth_ssl_client_cert_pw_prompt_func_t'. */
779svn_error_t *
780svn_cmdline_auth_ssl_client_cert_pw_prompt
781  (svn_auth_cred_ssl_client_cert_pw_t **cred_p,
782   void *baton,
783   const char *realm,
784   svn_boolean_t may_save,
785   apr_pool_t *pool)
786{
787  svn_auth_cred_ssl_client_cert_pw_t *cred = NULL;
788  const char *result;
789  const char *text = apr_psprintf(pool, _("Passphrase for '%s': "), realm);
790  svn_cmdline_prompt_baton2_t *pb = baton;
791
792  SVN_ERR(prompt(&result, text, TRUE, pb, pool));
793
794  cred = apr_pcalloc(pool, sizeof(*cred));
795  cred->password = result;
796  cred->may_save = may_save;
797  *cred_p = cred;
798
799  return SVN_NO_ERROR;
800}
801
802/* This is a helper for plaintext prompt functions. */
803static svn_error_t *
804plaintext_prompt_helper(svn_boolean_t *may_save_plaintext,
805                        const char *realmstring,
806                        const char *prompt_string,
807                        const char *prompt_text,
808                        void *baton,
809                        apr_pool_t *pool)
810{
811  const char *answer = NULL;
812  svn_boolean_t answered = FALSE;
813  svn_cmdline_prompt_baton2_t *pb = baton;
814  const char *config_path = NULL;
815  terminal_handle_t *terminal;
816
817  *may_save_plaintext = FALSE; /* de facto API promise */
818
819  if (pb)
820    SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir,
821                                            SVN_CONFIG_CATEGORY_SERVERS, pool));
822
823  SVN_ERR(terminal_open(&terminal, FALSE, pool));
824  SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text,
825                                     realmstring, config_path),
826                        terminal, pool));
827  SVN_ERR(terminal_close(terminal));
828
829  do
830    {
831      SVN_ERR(prompt(&answer, prompt_string, FALSE, pb, pool));
832      if (apr_strnatcasecmp(answer, _("yes")) == 0 ||
833          apr_strnatcasecmp(answer, _("y")) == 0)
834        {
835          *may_save_plaintext = TRUE;
836          answered = TRUE;
837        }
838      else if (apr_strnatcasecmp(answer, _("no")) == 0 ||
839               apr_strnatcasecmp(answer, _("n")) == 0)
840        {
841          *may_save_plaintext = FALSE;
842          answered = TRUE;
843        }
844      else
845          prompt_string = _("Please type 'yes' or 'no': ");
846    }
847  while (! answered);
848
849  return SVN_NO_ERROR;
850}
851
852/* This implements 'svn_auth_plaintext_prompt_func_t'. */
853svn_error_t *
854svn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext,
855                                  const char *realmstring,
856                                  void *baton,
857                                  apr_pool_t *pool)
858{
859  const char *prompt_string = _("Store password unencrypted (yes/no)? ");
860  const char *prompt_text =
861  _("\n-----------------------------------------------------------------------"
862    "\nATTENTION!  Your password for authentication realm:\n"
863    "\n"
864    "   %s\n"
865    "\n"
866    "can only be stored to disk unencrypted!  You are advised to configure\n"
867    "your system so that Subversion can store passwords encrypted, if\n"
868    "possible.  See the documentation for details.\n"
869    "\n"
870    "You can avoid future appearances of this warning by setting the value\n"
871    "of the 'store-plaintext-passwords' option to either 'yes' or 'no' in\n"
872    "'%s'.\n"
873    "-----------------------------------------------------------------------\n"
874    );
875
876  return plaintext_prompt_helper(may_save_plaintext, realmstring,
877                                 prompt_string, prompt_text, baton,
878                                 pool);
879}
880
881/* This implements 'svn_auth_plaintext_passphrase_prompt_func_t'. */
882svn_error_t *
883svn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext,
884                                             const char *realmstring,
885                                             void *baton,
886                                             apr_pool_t *pool)
887{
888  const char *prompt_string = _("Store passphrase unencrypted (yes/no)? ");
889  const char *prompt_text =
890  _("\n-----------------------------------------------------------------------\n"
891    "ATTENTION!  Your passphrase for client certificate:\n"
892    "\n"
893    "   %s\n"
894    "\n"
895    "can only be stored to disk unencrypted!  You are advised to configure\n"
896    "your system so that Subversion can store passphrase encrypted, if\n"
897    "possible.  See the documentation for details.\n"
898    "\n"
899    "You can avoid future appearances of this warning by setting the value\n"
900    "of the 'store-ssl-client-cert-pp-plaintext' option to either 'yes' or\n"
901    "'no' in '%s'.\n"
902    "-----------------------------------------------------------------------\n"
903    );
904
905  return plaintext_prompt_helper(may_save_plaintext, realmstring,
906                                 prompt_string, prompt_text, baton,
907                                 pool);
908}
909
910
911/** Generic prompting. **/
912
913svn_error_t *
914svn_cmdline_prompt_user2(const char **result,
915                         const char *prompt_str,
916                         svn_cmdline_prompt_baton_t *baton,
917                         apr_pool_t *pool)
918{
919  /* XXX: We know prompt doesn't use the new members
920   * of svn_cmdline_prompt_baton2_t. */
921  return prompt(result, prompt_str, FALSE /* don't hide input */,
922                (svn_cmdline_prompt_baton2_t *)baton, pool);
923}
924
925/* This implements 'svn_auth_gnome_keyring_unlock_prompt_func_t'. */
926svn_error_t *
927svn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password,
928                                              const char *keyring_name,
929                                              void *baton,
930                                              apr_pool_t *pool)
931{
932  const char *password;
933  const char *pass_prompt;
934  svn_cmdline_prompt_baton2_t *pb = baton;
935
936  pass_prompt = apr_psprintf(pool, _("Password for '%s' GNOME keyring: "),
937                             keyring_name);
938  SVN_ERR(prompt(&password, pass_prompt, TRUE, pb, pool));
939  *keyring_password = apr_pstrdup(pool, password);
940  return SVN_NO_ERROR;
941}
942