]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_subr/prompt.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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   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
275 static void
276 echo_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. */
302 static svn_error_t *
303 terminal_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  */
486 static svn_error_t *
487 prompt(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 \f
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  */
593 static svn_error_t *
594 maybe_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'. */
612 svn_error_t *
613 svn_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'. */
640 svn_error_t *
641 svn_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'. */
660 svn_error_t *
661 svn_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'. */
755 svn_error_t *
756 svn_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'. */
783 svn_error_t *
784 svn_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. */
807 static svn_error_t *
808 plaintext_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'. */
866 svn_error_t *
867 svn_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'. */
895 svn_error_t *
896 svn_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 \f
924 /** Generic prompting. **/
925
926 svn_error_t *
927 svn_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'. */
939 svn_error_t *
940 svn_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 }