]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_subr/prompt.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_subr / prompt.c
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 \f
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 \f
52
53 /* Descriptor of an open terminal */
54 typedef struct terminal_handle_t terminal_handle_t;
55 struct 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. */
71 static void
72 terminal_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  */
90 static apr_status_t
91 terminal_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. */
125 static 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. */
131 static 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. */
137 static svn_error_t *
138 terminal_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.*/
153 static svn_error_t *
154 terminal_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. */
234 static svn_error_t *
235 terminal_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
271 static void
272 echo_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. */
298 static svn_error_t *
299 terminal_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  */
482 static svn_error_t *
483 prompt(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 \f
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  */
589 static svn_error_t *
590 maybe_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'. */
608 svn_error_t *
609 svn_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'. */
636 svn_error_t *
637 svn_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'. */
656 svn_error_t *
657 svn_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'. */
751 svn_error_t *
752 svn_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'. */
779 svn_error_t *
780 svn_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. */
803 static svn_error_t *
804 plaintext_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   if (pb)
818     SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir,
819                                             SVN_CONFIG_CATEGORY_SERVERS, pool));
820
821   SVN_ERR(terminal_open(&terminal, FALSE, pool));
822   SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text,
823                                      realmstring, config_path),
824                         terminal, pool));
825   SVN_ERR(terminal_close(terminal));
826
827   do
828     {
829       svn_error_t *err = prompt(&answer, prompt_string, FALSE, pb, pool);
830       if (err)
831         {
832           if (err->apr_err == SVN_ERR_CANCELLED)
833             {
834               *may_save_plaintext = FALSE;
835               return err;
836             }
837           else
838             return err;
839         }
840       if (apr_strnatcasecmp(answer, _("yes")) == 0 ||
841           apr_strnatcasecmp(answer, _("y")) == 0)
842         {
843           *may_save_plaintext = TRUE;
844           answered = TRUE;
845         }
846       else if (apr_strnatcasecmp(answer, _("no")) == 0 ||
847                apr_strnatcasecmp(answer, _("n")) == 0)
848         {
849           *may_save_plaintext = FALSE;
850           answered = TRUE;
851         }
852       else
853           prompt_string = _("Please type 'yes' or 'no': ");
854     }
855   while (! answered);
856
857   return SVN_NO_ERROR;
858 }
859
860 /* This implements 'svn_auth_plaintext_prompt_func_t'. */
861 svn_error_t *
862 svn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext,
863                                   const char *realmstring,
864                                   void *baton,
865                                   apr_pool_t *pool)
866 {
867   const char *prompt_string = _("Store password unencrypted (yes/no)? ");
868   const char *prompt_text =
869   _("\n-----------------------------------------------------------------------"
870     "\nATTENTION!  Your password for authentication realm:\n"
871     "\n"
872     "   %s\n"
873     "\n"
874     "can only be stored to disk unencrypted!  You are advised to configure\n"
875     "your system so that Subversion can store passwords encrypted, if\n"
876     "possible.  See the documentation for details.\n"
877     "\n"
878     "You can avoid future appearances of this warning by setting the value\n"
879     "of the 'store-plaintext-passwords' option to either 'yes' or 'no' in\n"
880     "'%s'.\n"
881     "-----------------------------------------------------------------------\n"
882     );
883
884   return plaintext_prompt_helper(may_save_plaintext, realmstring,
885                                  prompt_string, prompt_text, baton,
886                                  pool);
887 }
888
889 /* This implements 'svn_auth_plaintext_passphrase_prompt_func_t'. */
890 svn_error_t *
891 svn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext,
892                                              const char *realmstring,
893                                              void *baton,
894                                              apr_pool_t *pool)
895 {
896   const char *prompt_string = _("Store passphrase unencrypted (yes/no)? ");
897   const char *prompt_text =
898   _("\n-----------------------------------------------------------------------\n"
899     "ATTENTION!  Your passphrase for client certificate:\n"
900     "\n"
901     "   %s\n"
902     "\n"
903     "can only be stored to disk unencrypted!  You are advised to configure\n"
904     "your system so that Subversion can store passphrase encrypted, if\n"
905     "possible.  See the documentation for details.\n"
906     "\n"
907     "You can avoid future appearances of this warning by setting the value\n"
908     "of the 'store-ssl-client-cert-pp-plaintext' option to either 'yes' or\n"
909     "'no' in '%s'.\n"
910     "-----------------------------------------------------------------------\n"
911     );
912
913   return plaintext_prompt_helper(may_save_plaintext, realmstring,
914                                  prompt_string, prompt_text, baton,
915                                  pool);
916 }
917
918 \f
919 /** Generic prompting. **/
920
921 svn_error_t *
922 svn_cmdline_prompt_user2(const char **result,
923                          const char *prompt_str,
924                          svn_cmdline_prompt_baton_t *baton,
925                          apr_pool_t *pool)
926 {
927   /* XXX: We know prompt doesn't use the new members
928    * of svn_cmdline_prompt_baton2_t. */
929   return prompt(result, prompt_str, FALSE /* don't hide input */,
930                 (svn_cmdline_prompt_baton2_t *)baton, pool);
931 }
932
933 /* This implements 'svn_auth_gnome_keyring_unlock_prompt_func_t'. */
934 svn_error_t *
935 svn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password,
936                                               const char *keyring_name,
937                                               void *baton,
938                                               apr_pool_t *pool)
939 {
940   const char *password;
941   const char *pass_prompt;
942   svn_cmdline_prompt_baton2_t *pb = baton;
943
944   pass_prompt = apr_psprintf(pool, _("Password for '%s' GNOME keyring: "),
945                              keyring_name);
946   SVN_ERR(prompt(&password, pass_prompt, TRUE, pb, pool));
947   *keyring_password = apr_pstrdup(pool, password);
948   return SVN_NO_ERROR;
949 }