1251881Speter/* 2251881Speter * prompt.c -- ask the user for authentication information. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include <apr_lib.h> 31251881Speter#include <apr_poll.h> 32251881Speter#include <apr_portable.h> 33251881Speter 34251881Speter#include "svn_cmdline.h" 35251881Speter#include "svn_ctype.h" 36251881Speter#include "svn_string.h" 37251881Speter#include "svn_auth.h" 38251881Speter#include "svn_error.h" 39251881Speter#include "svn_path.h" 40251881Speter 41251881Speter#include "private/svn_cmdline_private.h" 42251881Speter#include "svn_private_config.h" 43251881Speter 44251881Speter#ifdef WIN32 45251881Speter#include <conio.h> 46251881Speter#elif defined(HAVE_TERMIOS_H) 47251881Speter#include <signal.h> 48251881Speter#include <termios.h> 49251881Speter#endif 50251881Speter 51251881Speter 52251881Speter 53251881Speter/* Descriptor of an open terminal */ 54251881Spetertypedef struct terminal_handle_t terminal_handle_t; 55251881Speterstruct terminal_handle_t 56251881Speter{ 57251881Speter apr_file_t *infd; /* input file handle */ 58251881Speter apr_file_t *outfd; /* output file handle */ 59251881Speter svn_boolean_t noecho; /* terminal echo was turned off */ 60251881Speter svn_boolean_t close_handles; /* close handles when closing the terminal */ 61251881Speter apr_pool_t *pool; /* pool associated with the file handles */ 62251881Speter 63251881Speter#ifdef HAVE_TERMIOS_H 64251881Speter svn_boolean_t restore_state; /* terminal state was changed */ 65251881Speter apr_os_file_t osinfd; /* OS-specific handle for infd */ 66251881Speter struct termios attr; /* saved terminal attributes */ 67251881Speter#endif 68251881Speter}; 69251881Speter 70251881Speter/* Initialize safe state of terminal_handle_t. */ 71251881Speterstatic void 72251881Speterterminal_handle_init(terminal_handle_t *terminal, 73251881Speter apr_file_t *infd, apr_file_t *outfd, 74251881Speter svn_boolean_t noecho, svn_boolean_t close_handles, 75251881Speter apr_pool_t *pool) 76251881Speter{ 77251881Speter memset(terminal, 0, sizeof(*terminal)); 78251881Speter terminal->infd = infd; 79251881Speter terminal->outfd = outfd; 80251881Speter terminal->noecho = noecho; 81251881Speter terminal->close_handles = close_handles; 82251881Speter terminal->pool = pool; 83251881Speter} 84251881Speter 85251881Speter/* 86251881Speter * Common pool cleanup handler for terminal_handle_t. Closes TERMINAL. 87251881Speter * If CLOSE_HANDLES is TRUE, close the terminal file handles. 88251881Speter * If RESTORE_STATE is TRUE, restores the TERMIOS flags of the terminal. 89251881Speter */ 90251881Speterstatic apr_status_t 91251881Speterterminal_cleanup_handler(terminal_handle_t *terminal, 92251881Speter svn_boolean_t close_handles, 93251881Speter svn_boolean_t restore_state) 94251881Speter{ 95251881Speter apr_status_t status = APR_SUCCESS; 96251881Speter 97251881Speter#ifdef HAVE_TERMIOS_H 98251881Speter /* Restore terminal state flags. */ 99251881Speter if (restore_state && terminal->restore_state) 100251881Speter tcsetattr(terminal->osinfd, TCSANOW, &terminal->attr); 101251881Speter#endif 102251881Speter 103251881Speter /* Close terminal handles. */ 104251881Speter if (close_handles && terminal->close_handles) 105251881Speter { 106251881Speter apr_file_t *const infd = terminal->infd; 107251881Speter apr_file_t *const outfd = terminal->outfd; 108251881Speter 109251881Speter if (infd) 110251881Speter { 111251881Speter terminal->infd = NULL; 112251881Speter status = apr_file_close(infd); 113251881Speter } 114251881Speter 115251881Speter if (!status && outfd && outfd != infd) 116251881Speter { 117251881Speter terminal->outfd = NULL; 118251881Speter status = apr_file_close(terminal->outfd); 119251881Speter } 120251881Speter } 121251881Speter return status; 122251881Speter} 123251881Speter 124251881Speter/* Normal pool cleanup for a terminal. */ 125251881Speterstatic apr_status_t terminal_plain_cleanup(void *baton) 126251881Speter{ 127251881Speter return terminal_cleanup_handler(baton, FALSE, TRUE); 128251881Speter} 129251881Speter 130251881Speter/* Child pool cleanup for a terminal -- does not restore echo state. */ 131251881Speterstatic apr_status_t terminal_child_cleanup(void *baton) 132251881Speter{ 133251881Speter return terminal_cleanup_handler(baton, FALSE, FALSE); 134251881Speter} 135251881Speter 136251881Speter/* Explicitly close the terminal, removing its cleanup handlers. */ 137251881Speterstatic svn_error_t * 138251881Speterterminal_close(terminal_handle_t *terminal) 139251881Speter{ 140251881Speter apr_status_t status; 141251881Speter 142251881Speter /* apr_pool_cleanup_kill() removes both normal and child cleanup */ 143251881Speter apr_pool_cleanup_kill(terminal->pool, terminal, terminal_plain_cleanup); 144251881Speter 145251881Speter status = terminal_cleanup_handler(terminal, TRUE, TRUE); 146251881Speter if (status) 147251881Speter return svn_error_create(status, NULL, _("Can't close terminal")); 148251881Speter return SVN_NO_ERROR; 149251881Speter} 150251881Speter 151251881Speter/* Allocate and open *TERMINAL. If NOECHO is TRUE, try to turn off 152251881Speter terminal echo. Use POOL for all allocations.*/ 153251881Speterstatic svn_error_t * 154251881Speterterminal_open(terminal_handle_t **terminal, svn_boolean_t noecho, 155251881Speter apr_pool_t *pool) 156251881Speter{ 157251881Speter apr_status_t status; 158251881Speter 159251881Speter#ifdef WIN32 160251881Speter /* On Windows, we'll use the console API directly if the process has 161251881Speter a console attached; otherwise we'll just use stdin and stderr. */ 162251881Speter const HANDLE conin = CreateFileW(L"CONIN$", GENERIC_READ, 163251881Speter FILE_SHARE_READ | FILE_SHARE_WRITE, 164251881Speter NULL, OPEN_EXISTING, 165251881Speter FILE_ATTRIBUTE_NORMAL, NULL); 166251881Speter *terminal = apr_palloc(pool, sizeof(terminal_handle_t)); 167251881Speter if (conin != INVALID_HANDLE_VALUE) 168251881Speter { 169251881Speter /* The process has a console. */ 170251881Speter CloseHandle(conin); 171251881Speter terminal_handle_init(*terminal, NULL, NULL, noecho, FALSE, NULL); 172251881Speter return SVN_NO_ERROR; 173251881Speter } 174251881Speter#else /* !WIN32 */ 175251881Speter /* Without evidence to the contrary, we'll assume this is *nix and 176251881Speter try to open /dev/tty. If that fails, we'll use stdin for input 177251881Speter and stderr for prompting. */ 178251881Speter apr_file_t *tmpfd; 179251881Speter status = apr_file_open(&tmpfd, "/dev/tty", 180289180Speter APR_FOPEN_READ | APR_FOPEN_WRITE, 181251881Speter APR_OS_DEFAULT, pool); 182251881Speter *terminal = apr_palloc(pool, sizeof(terminal_handle_t)); 183251881Speter if (!status) 184251881Speter { 185251881Speter /* We have a terminal handle that we can use for input and output. */ 186251881Speter terminal_handle_init(*terminal, tmpfd, tmpfd, FALSE, TRUE, pool); 187251881Speter } 188251881Speter#endif /* !WIN32 */ 189251881Speter else 190251881Speter { 191251881Speter /* There is no terminal. Sigh. */ 192251881Speter apr_file_t *infd; 193251881Speter apr_file_t *outfd; 194251881Speter 195251881Speter status = apr_file_open_stdin(&infd, pool); 196251881Speter if (status) 197251881Speter return svn_error_wrap_apr(status, _("Can't open stdin")); 198251881Speter status = apr_file_open_stderr(&outfd, pool); 199251881Speter if (status) 200251881Speter return svn_error_wrap_apr(status, _("Can't open stderr")); 201251881Speter terminal_handle_init(*terminal, infd, outfd, FALSE, FALSE, pool); 202251881Speter } 203251881Speter 204251881Speter#ifdef HAVE_TERMIOS_H 205251881Speter /* Set terminal state */ 206251881Speter if (0 == apr_os_file_get(&(*terminal)->osinfd, (*terminal)->infd)) 207251881Speter { 208251881Speter if (0 == tcgetattr((*terminal)->osinfd, &(*terminal)->attr)) 209251881Speter { 210251881Speter struct termios attr = (*terminal)->attr; 211251881Speter /* Turn off signal handling and canonical input mode */ 212251881Speter attr.c_lflag &= ~(ISIG | ICANON); 213251881Speter attr.c_cc[VMIN] = 1; /* Read one byte at a time */ 214251881Speter attr.c_cc[VTIME] = 0; /* No timeout, wait indefinitely */ 215251881Speter attr.c_lflag &= ~(ECHO); /* Turn off echo */ 216251881Speter if (0 == tcsetattr((*terminal)->osinfd, TCSAFLUSH, &attr)) 217251881Speter { 218251881Speter (*terminal)->noecho = noecho; 219251881Speter (*terminal)->restore_state = TRUE; 220251881Speter } 221251881Speter } 222251881Speter } 223251881Speter#endif /* HAVE_TERMIOS_H */ 224251881Speter 225251881Speter /* Register pool cleanup to close handles and restore echo state. */ 226251881Speter apr_pool_cleanup_register((*terminal)->pool, *terminal, 227251881Speter terminal_plain_cleanup, 228251881Speter terminal_child_cleanup); 229251881Speter return SVN_NO_ERROR; 230251881Speter} 231251881Speter 232251881Speter/* Write a null-terminated STRING to TERMINAL. 233251881Speter Use POOL for allocations related to converting STRING from UTF-8. */ 234251881Speterstatic svn_error_t * 235251881Speterterminal_puts(const char *string, terminal_handle_t *terminal, 236251881Speter apr_pool_t *pool) 237251881Speter{ 238251881Speter svn_error_t *err; 239251881Speter const char *converted; 240251881Speter 241251881Speter err = svn_cmdline_cstring_from_utf8(&converted, string, pool); 242251881Speter if (err) 243251881Speter { 244251881Speter svn_error_clear(err); 245251881Speter converted = svn_cmdline_cstring_from_utf8_fuzzy(string, pool); 246251881Speter } 247251881Speter 248251881Speter#ifdef WIN32 249251881Speter if (!terminal->outfd) 250251881Speter { 251251881Speter /* See terminal_open; we're using Console I/O. */ 252251881Speter _cputs(converted); 253251881Speter return SVN_NO_ERROR; 254251881Speter } 255251881Speter#endif 256251881Speter 257289180Speter SVN_ERR(svn_io_file_write_full(terminal->outfd, converted, 258289180Speter strlen(converted), NULL, pool)); 259289180Speter 260289180Speter return svn_error_trace(svn_io_file_flush(terminal->outfd, pool)); 261251881Speter} 262251881Speter 263251881Speter/* These codes can be returned from terminal_getc instead of a character. */ 264251881Speter#define TERMINAL_NONE 0x80000 /* no character read, retry */ 265251881Speter#define TERMINAL_DEL (TERMINAL_NONE + 1) /* the input was a deleteion */ 266251881Speter#define TERMINAL_EOL (TERMINAL_NONE + 2) /* end of input/end of line */ 267251881Speter#define TERMINAL_EOF (TERMINAL_NONE + 3) /* end of file during input */ 268251881Speter 269251881Speter/* Helper for terminal_getc: writes CH to OUTFD as a control char. */ 270251881Speter#ifndef WIN32 271251881Speterstatic void 272251881Speterecho_control_char(char ch, apr_file_t *outfd) 273251881Speter{ 274251881Speter if (svn_ctype_iscntrl(ch)) 275251881Speter { 276251881Speter const char substitute = (ch < 32? '@' + ch : '?'); 277251881Speter apr_file_putc('^', outfd); 278251881Speter apr_file_putc(substitute, outfd); 279251881Speter } 280251881Speter else if (svn_ctype_isprint(ch)) 281251881Speter { 282251881Speter /* Pass printable characters unchanged. */ 283251881Speter apr_file_putc(ch, outfd); 284251881Speter } 285251881Speter else 286251881Speter { 287251881Speter /* Everything else is strange. */ 288251881Speter apr_file_putc('^', outfd); 289251881Speter apr_file_putc('!', outfd); 290251881Speter } 291251881Speter} 292251881Speter#endif /* WIN32 */ 293251881Speter 294251881Speter/* Read one character or control code from TERMINAL, returning it in CODE. 295251881Speter if CAN_ERASE and the input was a deletion, emit codes to erase the 296251881Speter last character displayed on the terminal. 297251881Speter Use POOL for all allocations. */ 298251881Speterstatic svn_error_t * 299251881Speterterminal_getc(int *code, terminal_handle_t *terminal, 300251881Speter svn_boolean_t can_erase, apr_pool_t *pool) 301251881Speter{ 302251881Speter const svn_boolean_t echo = !terminal->noecho; 303251881Speter apr_status_t status = APR_SUCCESS; 304251881Speter char ch; 305251881Speter 306251881Speter#ifdef WIN32 307251881Speter if (!terminal->infd) 308251881Speter { 309251881Speter /* See terminal_open; we're using Console I/O. */ 310251881Speter 311251881Speter /* The following was hoisted from APR's getpass for Windows. */ 312251881Speter int concode = _getch(); 313251881Speter switch (concode) 314251881Speter { 315251881Speter case '\r': /* end-of-line */ 316251881Speter *code = TERMINAL_EOL; 317251881Speter if (echo) 318251881Speter _cputs("\r\n"); 319251881Speter break; 320251881Speter 321251881Speter case EOF: /* end-of-file */ 322251881Speter case 26: /* Ctrl+Z */ 323251881Speter *code = TERMINAL_EOF; 324251881Speter if (echo) 325251881Speter _cputs((concode == EOF ? "[EOF]\r\n" : "^Z\r\n")); 326251881Speter break; 327251881Speter 328251881Speter case 3: /* Ctrl+C, Ctrl+Break */ 329251881Speter /* _getch() bypasses Ctrl+C but not Ctrl+Break detection! */ 330251881Speter if (echo) 331251881Speter _cputs("^C\r\n"); 332251881Speter return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL); 333251881Speter 334251881Speter case 0: /* Function code prefix */ 335251881Speter case 0xE0: 336251881Speter concode = (concode << 4) | _getch(); 337251881Speter /* Catch {DELETE}, {<--}, Num{DEL} and Num{<--} */ 338251881Speter if (concode == 0xE53 || concode == 0xE4B 339251881Speter || concode == 0x053 || concode == 0x04B) 340251881Speter { 341251881Speter *code = TERMINAL_DEL; 342251881Speter if (can_erase) 343251881Speter _cputs("\b \b"); 344251881Speter } 345251881Speter else 346251881Speter { 347251881Speter *code = TERMINAL_NONE; 348251881Speter _putch('\a'); 349251881Speter } 350251881Speter break; 351251881Speter 352251881Speter case '\b': /* BS */ 353251881Speter case 127: /* DEL */ 354251881Speter *code = TERMINAL_DEL; 355251881Speter if (can_erase) 356251881Speter _cputs("\b \b"); 357251881Speter break; 358251881Speter 359251881Speter default: 360251881Speter if (!apr_iscntrl(concode)) 361251881Speter { 362251881Speter *code = (int)(unsigned char)concode; 363251881Speter _putch(echo ? concode : '*'); 364251881Speter } 365251881Speter else 366251881Speter { 367251881Speter *code = TERMINAL_NONE; 368251881Speter _putch('\a'); 369251881Speter } 370251881Speter } 371251881Speter return SVN_NO_ERROR; 372251881Speter } 373251881Speter#elif defined(HAVE_TERMIOS_H) 374251881Speter if (terminal->restore_state) 375251881Speter { 376251881Speter /* We're using a bytewise-immediate termios input */ 377251881Speter const struct termios *const attr = &terminal->attr; 378251881Speter 379251881Speter status = apr_file_getc(&ch, terminal->infd); 380251881Speter if (status) 381251881Speter return svn_error_wrap_apr(status, _("Can't read from terminal")); 382251881Speter 383251881Speter if (ch == attr->c_cc[VINTR] || ch == attr->c_cc[VQUIT]) 384251881Speter { 385251881Speter /* Break */ 386251881Speter echo_control_char(ch, terminal->outfd); 387251881Speter return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL); 388251881Speter } 389251881Speter else if (ch == '\r' || ch == '\n' || ch == attr->c_cc[VEOL]) 390251881Speter { 391251881Speter /* Newline */ 392251881Speter *code = TERMINAL_EOL; 393251881Speter apr_file_putc('\n', terminal->outfd); 394251881Speter } 395251881Speter else if (ch == '\b' || ch == attr->c_cc[VERASE]) 396251881Speter { 397251881Speter /* Delete */ 398251881Speter *code = TERMINAL_DEL; 399251881Speter if (can_erase) 400251881Speter { 401251881Speter apr_file_putc('\b', terminal->outfd); 402251881Speter apr_file_putc(' ', terminal->outfd); 403251881Speter apr_file_putc('\b', terminal->outfd); 404251881Speter } 405251881Speter } 406251881Speter else if (ch == attr->c_cc[VEOF]) 407251881Speter { 408251881Speter /* End of input */ 409251881Speter *code = TERMINAL_EOF; 410251881Speter echo_control_char(ch, terminal->outfd); 411251881Speter } 412251881Speter else if (ch == attr->c_cc[VSUSP]) 413251881Speter { 414251881Speter /* Suspend */ 415251881Speter *code = TERMINAL_NONE; 416251881Speter kill(0, SIGTSTP); 417251881Speter } 418251881Speter else if (!apr_iscntrl(ch)) 419251881Speter { 420251881Speter /* Normal character */ 421251881Speter *code = (int)(unsigned char)ch; 422251881Speter apr_file_putc((echo ? ch : '*'), terminal->outfd); 423251881Speter } 424251881Speter else 425251881Speter { 426251881Speter /* Ignored character */ 427251881Speter *code = TERMINAL_NONE; 428251881Speter apr_file_putc('\a', terminal->outfd); 429251881Speter } 430251881Speter return SVN_NO_ERROR; 431251881Speter } 432251881Speter#endif /* HAVE_TERMIOS_H */ 433251881Speter 434251881Speter /* Fall back to plain stream-based I/O. */ 435251881Speter#ifndef WIN32 436251881Speter /* Wait for input on termin. This code is based on 437251881Speter apr_wait_for_io_or_timeout(). 438251881Speter Note that this will return an EINTR on a signal. */ 439251881Speter { 440251881Speter apr_pollfd_t pollset; 441251881Speter int n; 442251881Speter 443251881Speter pollset.desc_type = APR_POLL_FILE; 444251881Speter pollset.desc.f = terminal->infd; 445251881Speter pollset.p = pool; 446251881Speter pollset.reqevents = APR_POLLIN; 447251881Speter 448251881Speter status = apr_poll(&pollset, 1, &n, -1); 449251881Speter 450251881Speter if (n == 1 && pollset.rtnevents & APR_POLLIN) 451251881Speter status = APR_SUCCESS; 452251881Speter } 453251881Speter#endif /* !WIN32 */ 454251881Speter 455251881Speter if (!status) 456251881Speter status = apr_file_getc(&ch, terminal->infd); 457251881Speter if (APR_STATUS_IS_EINTR(status)) 458251881Speter { 459251881Speter *code = TERMINAL_NONE; 460251881Speter return SVN_NO_ERROR; 461251881Speter } 462251881Speter else if (APR_STATUS_IS_EOF(status)) 463251881Speter { 464251881Speter *code = TERMINAL_EOF; 465251881Speter return SVN_NO_ERROR; 466251881Speter } 467251881Speter else if (status) 468251881Speter return svn_error_wrap_apr(status, _("Can't read from terminal")); 469251881Speter 470251881Speter *code = (int)(unsigned char)ch; 471251881Speter return SVN_NO_ERROR; 472251881Speter} 473251881Speter 474251881Speter 475251881Speter/* Set @a *result to the result of prompting the user with @a 476251881Speter * prompt_msg. Use @ *pb to get the cancel_func and cancel_baton. 477251881Speter * Do not call the cancel_func if @a *pb is NULL. 478251881Speter * Allocate @a *result in @a pool. 479251881Speter * 480251881Speter * If @a hide is true, then try to avoid displaying the user's input. 481251881Speter */ 482251881Speterstatic svn_error_t * 483251881Speterprompt(const char **result, 484251881Speter const char *prompt_msg, 485251881Speter svn_boolean_t hide, 486251881Speter svn_cmdline_prompt_baton2_t *pb, 487251881Speter apr_pool_t *pool) 488251881Speter{ 489251881Speter /* XXX: If this functions ever starts using members of *pb 490251881Speter * which were not included in svn_cmdline_prompt_baton_t, 491251881Speter * we need to update svn_cmdline_prompt_user2 and its callers. */ 492251881Speter 493251881Speter svn_boolean_t saw_first_half_of_eol = FALSE; 494251881Speter svn_stringbuf_t *strbuf = svn_stringbuf_create_empty(pool); 495251881Speter terminal_handle_t *terminal; 496251881Speter int code; 497251881Speter char c; 498251881Speter 499251881Speter SVN_ERR(terminal_open(&terminal, hide, pool)); 500251881Speter SVN_ERR(terminal_puts(prompt_msg, terminal, pool)); 501251881Speter 502251881Speter while (1) 503251881Speter { 504251881Speter SVN_ERR(terminal_getc(&code, terminal, (strbuf->len > 0), pool)); 505251881Speter 506251881Speter /* Check for cancellation after a character has been read, some 507251881Speter input processing modes may eat ^C and we'll only notice a 508251881Speter cancellation signal after characters have been read -- 509251881Speter sometimes even after a newline. */ 510251881Speter if (pb) 511251881Speter SVN_ERR(pb->cancel_func(pb->cancel_baton)); 512251881Speter 513251881Speter switch (code) 514251881Speter { 515251881Speter case TERMINAL_NONE: 516251881Speter /* Nothing useful happened; retry. */ 517251881Speter continue; 518251881Speter 519251881Speter case TERMINAL_DEL: 520251881Speter /* Delete the last input character. terminal_getc takes care 521251881Speter of erasing the feedback from the terminal, if applicable. */ 522251881Speter svn_stringbuf_chop(strbuf, 1); 523251881Speter continue; 524251881Speter 525251881Speter case TERMINAL_EOL: 526251881Speter /* End-of-line means end of input. Trick the EOL-detection code 527251881Speter below to stop reading. */ 528251881Speter saw_first_half_of_eol = TRUE; 529251881Speter c = APR_EOL_STR[1]; /* Could be \0 but still stops reading. */ 530251881Speter break; 531251881Speter 532251881Speter case TERMINAL_EOF: 533251881Speter return svn_error_create( 534251881Speter APR_EOF, 535251881Speter terminal_close(terminal), 536251881Speter _("End of file while reading from terminal")); 537251881Speter 538251881Speter default: 539251881Speter /* Convert the returned code back to the character. */ 540251881Speter c = (char)code; 541251881Speter } 542251881Speter 543251881Speter if (saw_first_half_of_eol) 544251881Speter { 545251881Speter if (c == APR_EOL_STR[1]) 546251881Speter break; 547251881Speter else 548251881Speter saw_first_half_of_eol = FALSE; 549251881Speter } 550251881Speter else if (c == APR_EOL_STR[0]) 551251881Speter { 552251881Speter /* GCC might complain here: "warning: will never be executed" 553251881Speter * That's fine. This is a compile-time check for "\r\n\0" */ 554251881Speter if (sizeof(APR_EOL_STR) == 3) 555251881Speter { 556251881Speter saw_first_half_of_eol = TRUE; 557251881Speter continue; 558251881Speter } 559251881Speter else if (sizeof(APR_EOL_STR) == 2) 560251881Speter break; 561251881Speter else 562251881Speter /* ### APR_EOL_STR holds more than two chars? Who 563251881Speter ever heard of such a thing? */ 564251881Speter SVN_ERR_MALFUNCTION(); 565251881Speter } 566251881Speter 567251881Speter svn_stringbuf_appendbyte(strbuf, c); 568251881Speter } 569251881Speter 570251881Speter if (terminal->noecho) 571251881Speter { 572251881Speter /* If terminal echo was turned off, make sure future output 573251881Speter to the terminal starts on a new line, as expected. */ 574251881Speter SVN_ERR(terminal_puts(APR_EOL_STR, terminal, pool)); 575251881Speter } 576251881Speter SVN_ERR(terminal_close(terminal)); 577251881Speter 578251881Speter return svn_cmdline_cstring_to_utf8(result, strbuf->data, pool); 579251881Speter} 580251881Speter 581251881Speter 582251881Speter 583251881Speter/** Prompt functions for auth providers. **/ 584251881Speter 585251881Speter/* Helper function for auth provider prompters: mention the 586251881Speter * authentication @a realm on stderr, in a manner appropriate for 587251881Speter * preceding a prompt; or if @a realm is null, then do nothing. 588251881Speter */ 589251881Speterstatic svn_error_t * 590251881Spetermaybe_print_realm(const char *realm, apr_pool_t *pool) 591251881Speter{ 592251881Speter if (realm) 593251881Speter { 594251881Speter terminal_handle_t *terminal; 595251881Speter SVN_ERR(terminal_open(&terminal, FALSE, pool)); 596251881Speter SVN_ERR(terminal_puts( 597251881Speter apr_psprintf(pool, 598251881Speter _("Authentication realm: %s\n"), realm), 599251881Speter terminal, pool)); 600251881Speter SVN_ERR(terminal_close(terminal)); 601251881Speter } 602251881Speter 603251881Speter return SVN_NO_ERROR; 604251881Speter} 605251881Speter 606251881Speter 607251881Speter/* This implements 'svn_auth_simple_prompt_func_t'. */ 608251881Spetersvn_error_t * 609251881Spetersvn_cmdline_auth_simple_prompt(svn_auth_cred_simple_t **cred_p, 610251881Speter void *baton, 611251881Speter const char *realm, 612251881Speter const char *username, 613251881Speter svn_boolean_t may_save, 614251881Speter apr_pool_t *pool) 615251881Speter{ 616251881Speter svn_auth_cred_simple_t *ret = apr_pcalloc(pool, sizeof(*ret)); 617251881Speter const char *pass_prompt; 618251881Speter svn_cmdline_prompt_baton2_t *pb = baton; 619251881Speter 620251881Speter SVN_ERR(maybe_print_realm(realm, pool)); 621251881Speter 622251881Speter if (username) 623251881Speter ret->username = apr_pstrdup(pool, username); 624251881Speter else 625251881Speter SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool)); 626251881Speter 627251881Speter pass_prompt = apr_psprintf(pool, _("Password for '%s': "), ret->username); 628251881Speter SVN_ERR(prompt(&(ret->password), pass_prompt, TRUE, pb, pool)); 629251881Speter ret->may_save = may_save; 630251881Speter *cred_p = ret; 631251881Speter return SVN_NO_ERROR; 632251881Speter} 633251881Speter 634251881Speter 635251881Speter/* This implements 'svn_auth_username_prompt_func_t'. */ 636251881Spetersvn_error_t * 637251881Spetersvn_cmdline_auth_username_prompt(svn_auth_cred_username_t **cred_p, 638251881Speter void *baton, 639251881Speter const char *realm, 640251881Speter svn_boolean_t may_save, 641251881Speter apr_pool_t *pool) 642251881Speter{ 643251881Speter svn_auth_cred_username_t *ret = apr_pcalloc(pool, sizeof(*ret)); 644251881Speter svn_cmdline_prompt_baton2_t *pb = baton; 645251881Speter 646251881Speter SVN_ERR(maybe_print_realm(realm, pool)); 647251881Speter 648251881Speter SVN_ERR(prompt(&(ret->username), _("Username: "), FALSE, pb, pool)); 649251881Speter ret->may_save = may_save; 650251881Speter *cred_p = ret; 651251881Speter return SVN_NO_ERROR; 652251881Speter} 653251881Speter 654251881Speter 655251881Speter/* This implements 'svn_auth_ssl_server_trust_prompt_func_t'. */ 656251881Spetersvn_error_t * 657251881Spetersvn_cmdline_auth_ssl_server_trust_prompt 658251881Speter (svn_auth_cred_ssl_server_trust_t **cred_p, 659251881Speter void *baton, 660251881Speter const char *realm, 661251881Speter apr_uint32_t failures, 662251881Speter const svn_auth_ssl_server_cert_info_t *cert_info, 663251881Speter svn_boolean_t may_save, 664251881Speter apr_pool_t *pool) 665251881Speter{ 666251881Speter const char *choice; 667251881Speter svn_stringbuf_t *msg; 668251881Speter svn_cmdline_prompt_baton2_t *pb = baton; 669251881Speter svn_stringbuf_t *buf = svn_stringbuf_createf 670251881Speter (pool, _("Error validating server certificate for '%s':\n"), realm); 671251881Speter 672251881Speter if (failures & SVN_AUTH_SSL_UNKNOWNCA) 673251881Speter { 674251881Speter svn_stringbuf_appendcstr 675251881Speter (buf, 676251881Speter _(" - The certificate is not issued by a trusted authority. Use the\n" 677251881Speter " fingerprint to validate the certificate manually!\n")); 678251881Speter } 679251881Speter 680251881Speter if (failures & SVN_AUTH_SSL_CNMISMATCH) 681251881Speter { 682251881Speter svn_stringbuf_appendcstr 683251881Speter (buf, _(" - The certificate hostname does not match.\n")); 684251881Speter } 685251881Speter 686251881Speter if (failures & SVN_AUTH_SSL_NOTYETVALID) 687251881Speter { 688251881Speter svn_stringbuf_appendcstr 689251881Speter (buf, _(" - The certificate is not yet valid.\n")); 690251881Speter } 691251881Speter 692251881Speter if (failures & SVN_AUTH_SSL_EXPIRED) 693251881Speter { 694251881Speter svn_stringbuf_appendcstr 695251881Speter (buf, _(" - The certificate has expired.\n")); 696251881Speter } 697251881Speter 698251881Speter if (failures & SVN_AUTH_SSL_OTHER) 699251881Speter { 700251881Speter svn_stringbuf_appendcstr 701251881Speter (buf, _(" - The certificate has an unknown error.\n")); 702251881Speter } 703251881Speter 704251881Speter msg = svn_stringbuf_createf 705251881Speter (pool, 706251881Speter _("Certificate information:\n" 707251881Speter " - Hostname: %s\n" 708251881Speter " - Valid: from %s until %s\n" 709251881Speter " - Issuer: %s\n" 710251881Speter " - Fingerprint: %s\n"), 711251881Speter cert_info->hostname, 712251881Speter cert_info->valid_from, 713251881Speter cert_info->valid_until, 714251881Speter cert_info->issuer_dname, 715251881Speter cert_info->fingerprint); 716251881Speter svn_stringbuf_appendstr(buf, msg); 717251881Speter 718251881Speter if (may_save) 719251881Speter { 720251881Speter svn_stringbuf_appendcstr 721251881Speter (buf, _("(R)eject, accept (t)emporarily or accept (p)ermanently? ")); 722251881Speter } 723251881Speter else 724251881Speter { 725251881Speter svn_stringbuf_appendcstr(buf, _("(R)eject or accept (t)emporarily? ")); 726251881Speter } 727251881Speter SVN_ERR(prompt(&choice, buf->data, FALSE, pb, pool)); 728251881Speter 729251881Speter if (choice[0] == 't' || choice[0] == 'T') 730251881Speter { 731251881Speter *cred_p = apr_pcalloc(pool, sizeof(**cred_p)); 732251881Speter (*cred_p)->may_save = FALSE; 733251881Speter (*cred_p)->accepted_failures = failures; 734251881Speter } 735251881Speter else if (may_save && (choice[0] == 'p' || choice[0] == 'P')) 736251881Speter { 737251881Speter *cred_p = apr_pcalloc(pool, sizeof(**cred_p)); 738251881Speter (*cred_p)->may_save = TRUE; 739251881Speter (*cred_p)->accepted_failures = failures; 740251881Speter } 741251881Speter else 742251881Speter { 743251881Speter *cred_p = NULL; 744251881Speter } 745251881Speter 746251881Speter return SVN_NO_ERROR; 747251881Speter} 748251881Speter 749251881Speter 750251881Speter/* This implements 'svn_auth_ssl_client_cert_prompt_func_t'. */ 751251881Spetersvn_error_t * 752251881Spetersvn_cmdline_auth_ssl_client_cert_prompt 753251881Speter (svn_auth_cred_ssl_client_cert_t **cred_p, 754251881Speter void *baton, 755251881Speter const char *realm, 756251881Speter svn_boolean_t may_save, 757251881Speter apr_pool_t *pool) 758251881Speter{ 759251881Speter svn_auth_cred_ssl_client_cert_t *cred = NULL; 760251881Speter const char *cert_file = NULL; 761251881Speter const char *abs_cert_file = NULL; 762251881Speter svn_cmdline_prompt_baton2_t *pb = baton; 763251881Speter 764251881Speter SVN_ERR(maybe_print_realm(realm, pool)); 765251881Speter SVN_ERR(prompt(&cert_file, _("Client certificate filename: "), 766251881Speter FALSE, pb, pool)); 767251881Speter SVN_ERR(svn_dirent_get_absolute(&abs_cert_file, cert_file, pool)); 768251881Speter 769251881Speter cred = apr_palloc(pool, sizeof(*cred)); 770251881Speter cred->cert_file = abs_cert_file; 771251881Speter cred->may_save = may_save; 772251881Speter *cred_p = cred; 773251881Speter 774251881Speter return SVN_NO_ERROR; 775251881Speter} 776251881Speter 777251881Speter 778251881Speter/* This implements 'svn_auth_ssl_client_cert_pw_prompt_func_t'. */ 779251881Spetersvn_error_t * 780251881Spetersvn_cmdline_auth_ssl_client_cert_pw_prompt 781251881Speter (svn_auth_cred_ssl_client_cert_pw_t **cred_p, 782251881Speter void *baton, 783251881Speter const char *realm, 784251881Speter svn_boolean_t may_save, 785251881Speter apr_pool_t *pool) 786251881Speter{ 787251881Speter svn_auth_cred_ssl_client_cert_pw_t *cred = NULL; 788251881Speter const char *result; 789251881Speter const char *text = apr_psprintf(pool, _("Passphrase for '%s': "), realm); 790251881Speter svn_cmdline_prompt_baton2_t *pb = baton; 791251881Speter 792251881Speter SVN_ERR(prompt(&result, text, TRUE, pb, pool)); 793251881Speter 794251881Speter cred = apr_pcalloc(pool, sizeof(*cred)); 795251881Speter cred->password = result; 796251881Speter cred->may_save = may_save; 797251881Speter *cred_p = cred; 798251881Speter 799251881Speter return SVN_NO_ERROR; 800251881Speter} 801251881Speter 802251881Speter/* This is a helper for plaintext prompt functions. */ 803251881Speterstatic svn_error_t * 804251881Speterplaintext_prompt_helper(svn_boolean_t *may_save_plaintext, 805251881Speter const char *realmstring, 806251881Speter const char *prompt_string, 807251881Speter const char *prompt_text, 808251881Speter void *baton, 809251881Speter apr_pool_t *pool) 810251881Speter{ 811251881Speter const char *answer = NULL; 812251881Speter svn_boolean_t answered = FALSE; 813251881Speter svn_cmdline_prompt_baton2_t *pb = baton; 814251881Speter const char *config_path = NULL; 815251881Speter terminal_handle_t *terminal; 816251881Speter 817362181Sdim *may_save_plaintext = FALSE; /* de facto API promise */ 818362181Sdim 819251881Speter if (pb) 820251881Speter SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir, 821251881Speter SVN_CONFIG_CATEGORY_SERVERS, pool)); 822251881Speter 823251881Speter SVN_ERR(terminal_open(&terminal, FALSE, pool)); 824251881Speter SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text, 825251881Speter realmstring, config_path), 826251881Speter terminal, pool)); 827251881Speter SVN_ERR(terminal_close(terminal)); 828251881Speter 829251881Speter do 830251881Speter { 831362181Sdim SVN_ERR(prompt(&answer, prompt_string, FALSE, pb, pool)); 832251881Speter if (apr_strnatcasecmp(answer, _("yes")) == 0 || 833251881Speter apr_strnatcasecmp(answer, _("y")) == 0) 834251881Speter { 835251881Speter *may_save_plaintext = TRUE; 836251881Speter answered = TRUE; 837251881Speter } 838251881Speter else if (apr_strnatcasecmp(answer, _("no")) == 0 || 839251881Speter apr_strnatcasecmp(answer, _("n")) == 0) 840251881Speter { 841251881Speter *may_save_plaintext = FALSE; 842251881Speter answered = TRUE; 843251881Speter } 844251881Speter else 845251881Speter prompt_string = _("Please type 'yes' or 'no': "); 846251881Speter } 847251881Speter while (! answered); 848251881Speter 849251881Speter return SVN_NO_ERROR; 850251881Speter} 851251881Speter 852251881Speter/* This implements 'svn_auth_plaintext_prompt_func_t'. */ 853251881Spetersvn_error_t * 854251881Spetersvn_cmdline_auth_plaintext_prompt(svn_boolean_t *may_save_plaintext, 855251881Speter const char *realmstring, 856251881Speter void *baton, 857251881Speter apr_pool_t *pool) 858251881Speter{ 859251881Speter const char *prompt_string = _("Store password unencrypted (yes/no)? "); 860251881Speter const char *prompt_text = 861251881Speter _("\n-----------------------------------------------------------------------" 862251881Speter "\nATTENTION! Your password for authentication realm:\n" 863251881Speter "\n" 864251881Speter " %s\n" 865251881Speter "\n" 866251881Speter "can only be stored to disk unencrypted! You are advised to configure\n" 867251881Speter "your system so that Subversion can store passwords encrypted, if\n" 868251881Speter "possible. See the documentation for details.\n" 869251881Speter "\n" 870251881Speter "You can avoid future appearances of this warning by setting the value\n" 871251881Speter "of the 'store-plaintext-passwords' option to either 'yes' or 'no' in\n" 872251881Speter "'%s'.\n" 873251881Speter "-----------------------------------------------------------------------\n" 874251881Speter ); 875251881Speter 876251881Speter return plaintext_prompt_helper(may_save_plaintext, realmstring, 877251881Speter prompt_string, prompt_text, baton, 878251881Speter pool); 879251881Speter} 880251881Speter 881251881Speter/* This implements 'svn_auth_plaintext_passphrase_prompt_func_t'. */ 882251881Spetersvn_error_t * 883251881Spetersvn_cmdline_auth_plaintext_passphrase_prompt(svn_boolean_t *may_save_plaintext, 884251881Speter const char *realmstring, 885251881Speter void *baton, 886251881Speter apr_pool_t *pool) 887251881Speter{ 888251881Speter const char *prompt_string = _("Store passphrase unencrypted (yes/no)? "); 889251881Speter const char *prompt_text = 890251881Speter _("\n-----------------------------------------------------------------------\n" 891251881Speter "ATTENTION! Your passphrase for client certificate:\n" 892251881Speter "\n" 893251881Speter " %s\n" 894251881Speter "\n" 895251881Speter "can only be stored to disk unencrypted! You are advised to configure\n" 896251881Speter "your system so that Subversion can store passphrase encrypted, if\n" 897251881Speter "possible. See the documentation for details.\n" 898251881Speter "\n" 899251881Speter "You can avoid future appearances of this warning by setting the value\n" 900251881Speter "of the 'store-ssl-client-cert-pp-plaintext' option to either 'yes' or\n" 901251881Speter "'no' in '%s'.\n" 902251881Speter "-----------------------------------------------------------------------\n" 903251881Speter ); 904251881Speter 905251881Speter return plaintext_prompt_helper(may_save_plaintext, realmstring, 906251881Speter prompt_string, prompt_text, baton, 907251881Speter pool); 908251881Speter} 909251881Speter 910251881Speter 911251881Speter/** Generic prompting. **/ 912251881Speter 913251881Spetersvn_error_t * 914251881Spetersvn_cmdline_prompt_user2(const char **result, 915251881Speter const char *prompt_str, 916251881Speter svn_cmdline_prompt_baton_t *baton, 917251881Speter apr_pool_t *pool) 918251881Speter{ 919251881Speter /* XXX: We know prompt doesn't use the new members 920251881Speter * of svn_cmdline_prompt_baton2_t. */ 921251881Speter return prompt(result, prompt_str, FALSE /* don't hide input */, 922251881Speter (svn_cmdline_prompt_baton2_t *)baton, pool); 923251881Speter} 924251881Speter 925251881Speter/* This implements 'svn_auth_gnome_keyring_unlock_prompt_func_t'. */ 926251881Spetersvn_error_t * 927251881Spetersvn_cmdline__auth_gnome_keyring_unlock_prompt(char **keyring_password, 928251881Speter const char *keyring_name, 929251881Speter void *baton, 930251881Speter apr_pool_t *pool) 931251881Speter{ 932251881Speter const char *password; 933251881Speter const char *pass_prompt; 934251881Speter svn_cmdline_prompt_baton2_t *pb = baton; 935251881Speter 936251881Speter pass_prompt = apr_psprintf(pool, _("Password for '%s' GNOME keyring: "), 937251881Speter keyring_name); 938251881Speter SVN_ERR(prompt(&password, pass_prompt, TRUE, pb, pool)); 939251881Speter *keyring_password = apr_pstrdup(pool, password); 940251881Speter return SVN_NO_ERROR; 941251881Speter} 942