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 */
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
terminal_handle_init(terminal_handle_t * terminal,apr_file_t * infd,apr_file_t * outfd,svn_boolean_t noecho,svn_boolean_t close_handles,apr_pool_t * pool)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
terminal_cleanup_handler(terminal_handle_t * terminal,svn_boolean_t close_handles,svn_boolean_t restore_state)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. */
terminal_plain_cleanup(void * baton)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. */
terminal_child_cleanup(void * baton)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 *
terminal_close(terminal_handle_t * terminal)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 *
terminal_open(terminal_handle_t ** terminal,svn_boolean_t noecho,apr_pool_t * pool)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 *
terminal_puts(const char * string,terminal_handle_t * terminal,apr_pool_t * pool)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
echo_control_char(char ch,apr_file_t * outfd)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 *
terminal_getc(int * code,terminal_handle_t * terminal,svn_boolean_t can_erase,apr_pool_t * pool)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 *
prompt(const char ** result,const char * prompt_msg,svn_boolean_t hide,svn_cmdline_prompt_baton2_t * pb,apr_pool_t * pool)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 
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 *
maybe_print_realm(const char * realm,apr_pool_t * pool)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 *
svn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t ** cred_p,void * baton,const char * realm,const char * username,svn_boolean_t may_save,apr_pool_t * pool)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 *
svn_cmdline_auth_username_prompt(svn_auth_cred_username_t ** cred_p,void * baton,const char * realm,svn_boolean_t may_save,apr_pool_t * pool)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 *
svn_cmdline_auth_ssl_server_trust_prompt(svn_auth_cred_ssl_server_trust_t ** cred_p,void * baton,const char * realm,apr_uint32_t failures,const svn_auth_ssl_server_cert_info_t * cert_info,svn_boolean_t may_save,apr_pool_t * pool)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 *
svn_cmdline_auth_ssl_client_cert_prompt(svn_auth_cred_ssl_client_cert_t ** cred_p,void * baton,const char * realm,svn_boolean_t may_save,apr_pool_t * pool)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 *
svn_cmdline_auth_ssl_client_cert_pw_prompt(svn_auth_cred_ssl_client_cert_pw_t ** cred_p,void * baton,const char * realm,svn_boolean_t may_save,apr_pool_t * pool)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 *
plaintext_prompt_helper(svn_boolean_t * may_save_plaintext,const char * realmstring,const char * prompt_string,const char * prompt_text,void * baton,apr_pool_t * pool)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   *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'. */
853 svn_error_t *
svn_cmdline_auth_plaintext_prompt(svn_boolean_t * may_save_plaintext,const char * realmstring,void * baton,apr_pool_t * pool)854 svn_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'. */
882 svn_error_t *
svn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t * may_save_plaintext,const char * realmstring,void * baton,apr_pool_t * pool)883 svn_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 
913 svn_error_t *
svn_cmdline_prompt_user2(const char ** result,const char * prompt_str,svn_cmdline_prompt_baton_t * baton,apr_pool_t * pool)914 svn_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'. */
926 svn_error_t *
svn_cmdline__auth_gnome_keyring_unlock_prompt(char ** keyring_password,const char * keyring_name,void * baton,apr_pool_t * pool)927 svn_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