Milestone 5: deliver embedded RDP sessions and lifecycle hardening

This commit is contained in:
Keith Smith
2026-03-03 18:59:26 -07:00
parent 230a401386
commit 36006bd4aa
2941 changed files with 724359 additions and 77 deletions

View File

@@ -0,0 +1,493 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Passphrase Handling Utils
*
* Copyright 2011 Shea Levy <shea@shealevy.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <winpr/environment.h>
#include <freerdp/config.h>
#include <freerdp/freerdp.h>
#include <errno.h>
#include <freerdp/utils/passphrase.h>
#ifdef _WIN32
#include <stdio.h>
#include <io.h>
#include <conio.h>
#include <wincred.h>
static char read_chr(FILE* f)
{
char chr;
const BOOL isTty = _isatty(_fileno(f));
if (isTty)
return fgetc(f);
if (fscanf_s(f, "%c", &chr, (UINT32)sizeof(char)) && !feof(f))
return chr;
return 0;
}
int freerdp_interruptible_getc(rdpContext* context, FILE* f)
{
return read_chr(f);
}
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
size_t bufsiz, int from_stdin)
{
WCHAR UserNameW[CREDUI_MAX_USERNAME_LENGTH + 1] = { 'p', 'r', 'e', 'f', 'i',
'l', 'l', 'e', 'd', '\0' };
WCHAR PasswordW[CREDUI_MAX_PASSWORD_LENGTH + 1] = WINPR_C_ARRAY_INIT;
BOOL fSave = FALSE;
DWORD dwFlags = 0;
WCHAR* promptW = ConvertUtf8ToWCharAlloc(prompt, nullptr);
const DWORD status =
CredUICmdLinePromptForCredentialsW(promptW, nullptr, 0, UserNameW, ARRAYSIZE(UserNameW),
PasswordW, ARRAYSIZE(PasswordW), &fSave, dwFlags);
free(promptW);
if (ConvertWCharNToUtf8(PasswordW, ARRAYSIZE(PasswordW), buf, bufsiz) < 0)
return nullptr;
return buf;
}
#elif !defined(ANDROID)
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <freerdp/utils/signal.h>
#include <freerdp/log.h>
#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
#include <poll.h>
#else
#include <time.h>
#include <sys/select.h>
#endif
#define TAG FREERDP_TAG("utils.passphrase")
static int wait_for_fd(int fd, int timeout)
{
int status = 0;
#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
struct pollfd pollset = WINPR_C_ARRAY_INIT;
pollset.fd = fd;
pollset.events = POLLIN;
pollset.revents = 0;
do
{
status = poll(&pollset, 1, timeout);
} while ((status < 0) && (errno == EINTR));
#else
fd_set rset = WINPR_C_ARRAY_INIT;
struct timeval tv = WINPR_C_ARRAY_INIT;
FD_ZERO(&rset);
FD_SET(fd, &rset);
if (timeout)
{
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
}
do
{
status = select(fd + 1, &rset, nullptr, nullptr, timeout ? &tv : nullptr);
} while ((status < 0) && (errno == EINTR));
#endif
return status;
}
static void replace_char(char* buffer, WINPR_ATTR_UNUSED size_t buffer_len, const char* toreplace)
{
while (*toreplace != '\0')
{
char* ptr = nullptr;
while ((ptr = strrchr(buffer, *toreplace)) != nullptr)
*ptr = '\0';
toreplace++;
}
}
static const char* freerdp_passphrase_read_tty(rdpContext* context, const char* prompt, char* buf,
size_t bufsiz, int from_stdin)
{
BOOL terminal_needs_reset = FALSE;
char term_name[L_ctermid] = WINPR_C_ARRAY_INIT;
FILE* fout = nullptr;
if (bufsiz == 0)
{
errno = EINVAL;
return nullptr;
}
ctermid(term_name);
int terminal_fildes = 0;
if (from_stdin || (strcmp(term_name, "") == 0))
{
fout = stdout;
terminal_fildes = STDIN_FILENO;
}
else
{
const int term_file = open(term_name, O_RDWR);
if (term_file < 0)
{
fout = stdout;
terminal_fildes = STDIN_FILENO;
}
else
{
fout = fdopen(term_file, "w");
if (!fout)
{
close(term_file);
return nullptr;
}
terminal_fildes = term_file;
}
}
struct termios orig_flags = WINPR_C_ARRAY_INIT;
if (tcgetattr(terminal_fildes, &orig_flags) != -1)
{
struct termios new_flags = WINPR_C_ARRAY_INIT;
new_flags = orig_flags;
new_flags.c_lflag &= (uint32_t)~ECHO;
new_flags.c_lflag |= ECHONL;
terminal_needs_reset = TRUE;
if (tcsetattr(terminal_fildes, TCSAFLUSH, &new_flags) == -1)
terminal_needs_reset = FALSE;
}
FILE* fp = fdopen(terminal_fildes, "r");
if (!fp)
goto error;
(void)fprintf(fout, "%s", prompt);
(void)fflush(fout);
{
char* ptr = nullptr;
size_t ptr_len = 0;
const SSIZE_T res = freerdp_interruptible_get_line(context, &ptr, &ptr_len, fp);
if (res < 0)
goto error;
replace_char(ptr, ptr_len, "\r\n");
strncpy(buf, ptr, MIN(bufsiz, ptr_len));
free(ptr);
}
if (terminal_needs_reset)
{
if (tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags) == -1)
goto error;
}
if (terminal_fildes != STDIN_FILENO)
(void)fclose(fp);
return buf;
error:
{
// NOLINTNEXTLINE(clang-analyzer-unix.Stream)
int saved_errno = errno;
if (terminal_needs_reset)
(void)tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags);
if (terminal_fildes != STDIN_FILENO)
{
if (fp)
(void)fclose(fp);
}
// NOLINTNEXTLINE(clang-analyzer-unix.Stream)
errno = saved_errno;
}
return nullptr;
}
static const char* freerdp_passphrase_read_askpass(const char* prompt, char* buf, size_t bufsiz,
char const* askpass_env)
{
char command[4096] = WINPR_C_ARRAY_INIT;
(void)sprintf_s(command, sizeof(command), "%s 'FreeRDP authentication\n%s'", askpass_env,
prompt);
// NOLINTNEXTLINE(clang-analyzer-optin.taint.GenericTaint)
FILE* askproc = popen(command, "r");
if (!askproc)
return nullptr;
WINPR_ASSERT(bufsiz <= INT32_MAX);
if (fgets(buf, (int)bufsiz, askproc) != nullptr)
buf[strcspn(buf, "\r\n")] = '\0';
else
buf = nullptr;
const int status = pclose(askproc);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
buf = nullptr;
return buf;
}
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
size_t bufsiz, int from_stdin)
{
// NOLINTNEXTLINE(concurrency-mt-unsafe)
const char* askpass_env = getenv("FREERDP_ASKPASS");
if (askpass_env)
return freerdp_passphrase_read_askpass(prompt, buf, bufsiz, askpass_env);
else
return freerdp_passphrase_read_tty(context, prompt, buf, bufsiz, from_stdin);
}
static BOOL set_termianl_nonblock(int ifd, BOOL nonblock);
static void restore_terminal(void)
{
(void)set_termianl_nonblock(-1, FALSE);
}
BOOL set_termianl_nonblock(int ifd, BOOL nonblock)
{
static int fd = -1;
static bool registered = false;
static int orig = 0;
static struct termios termios = WINPR_C_ARRAY_INIT;
if (ifd >= 0)
fd = ifd;
if (fd < 0)
return FALSE;
if (nonblock)
{
if (!registered)
{
(void)atexit(restore_terminal);
registered = true;
}
const int rc1 = fcntl(fd, F_SETFL, orig | O_NONBLOCK);
if (rc1 != 0)
{
char buffer[128] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
winpr_strerror(errno, buffer, sizeof(buffer)));
return FALSE;
}
const int rc2 = tcgetattr(fd, &termios);
if (rc2 != 0)
{
char buffer[128] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "tcgetattr() failed with %s",
winpr_strerror(errno, buffer, sizeof(buffer)));
return FALSE;
}
struct termios now = termios;
cfmakeraw(&now);
const int rc3 = tcsetattr(fd, TCSANOW, &now);
if (rc3 != 0)
{
char buffer[128] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
winpr_strerror(errno, buffer, sizeof(buffer)));
return FALSE;
}
}
else
{
const int rc1 = tcsetattr(fd, TCSANOW, &termios);
if (rc1 != 0)
{
char buffer[128] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
winpr_strerror(errno, buffer, sizeof(buffer)));
return FALSE;
}
const int rc2 = fcntl(fd, F_SETFL, orig);
if (rc2 != 0)
{
char buffer[128] = WINPR_C_ARRAY_INIT;
WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
winpr_strerror(errno, buffer, sizeof(buffer)));
return FALSE;
}
fd = -1;
}
return TRUE;
}
int freerdp_interruptible_getc(rdpContext* context, FILE* stream)
{
int rc = EOF;
const int fd = fileno(stream);
(void)set_termianl_nonblock(fd, TRUE);
do
{
const int res = wait_for_fd(fd, 10);
if (res != 0)
{
char c = 0;
const ssize_t rd = read(fd, &c, 1);
if (rd == 1)
{
if (c == 3) /* ctrl + c */
return EOF;
if (c == 4) /* ctrl + d */
return EOF;
if (c == 26) /* ctrl + z */
return EOF;
rc = (int)c;
}
break;
}
} while (!freerdp_shall_disconnect_context(context));
(void)set_termianl_nonblock(fd, FALSE);
return rc;
}
#else
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
size_t bufsiz, int from_stdin)
{
return nullptr;
}
int freerdp_interruptible_getc(rdpContext* context, FILE* f)
{
return EOF;
}
#endif
SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** plineptr, size_t* psize,
FILE* stream)
{
int c = 0;
char* n = nullptr;
size_t step = 32;
size_t used = 0;
char* ptr = nullptr;
size_t len = 0;
if (!plineptr || !psize)
{
errno = EINVAL;
return -1;
}
bool echo = true;
#if !defined(_WIN32) && !defined(ANDROID)
{
const int fd = fileno(stream);
struct termios termios = WINPR_C_ARRAY_INIT;
/* This might fail if /from-stdin is used. */
if (tcgetattr(fd, &termios) == 0)
echo = (termios.c_lflag & ECHO) != 0;
else
echo = false;
}
#endif
if (*plineptr && (*psize > 0))
{
ptr = *plineptr;
used = *psize;
if (echo)
{
printf("%s", ptr);
(void)fflush(stdout);
}
}
do
{
if (used + 2 >= len)
{
len += step;
n = realloc(ptr, len);
if (!n)
{
free(ptr);
*plineptr = nullptr;
return -1;
}
ptr = n;
}
c = freerdp_interruptible_getc(context, stream);
if (c == 127)
{
if (used > 0)
{
ptr[used--] = '\0';
if (echo)
{
printf("\b");
printf(" ");
printf("\b");
(void)fflush(stdout);
}
}
continue;
}
if (echo)
{
printf("%c", c);
(void)fflush(stdout);
}
if (c != EOF)
ptr[used++] = (char)c;
} while ((c != '\n') && (c != '\r') && (c != EOF));
printf("\n");
ptr[used] = '\0';
if (c == EOF)
{
free(ptr);
*plineptr = nullptr;
return EOF;
}
*plineptr = ptr;
*psize = used;
return WINPR_ASSERTING_INT_CAST(SSIZE_T, used);
}