mirror of https://github.com/xemu-project/xemu.git
qga-pull-2024-05-01
-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEwsLBCepDxjwUI+uE711egWG6hOcFAmYx8fgACgkQ711egWG6 hOflAw//fDHAQzcrWFggn4Ly1p1QQK+AYdQPbmKCIV7j64k05kCFU+bQxbMT9Fmr RsWPXHv5t36ySKxaML412r7fqX19bm7RP31hlau76KtZsTJXFR/dbC6jNWXi/Qfd 5Z1mwK4lyW+TROPx6gA2tZddqAZsSxLlArhXGj9nUcQBXDebKD/rO4jYrRBWI7uG hV2mmolGbsNSzinfhujY2yVpm3SMEEc9IQ/CDd11HUsgJjAkXVxCDfKDXCmMKUAm 7B6VYjQpy6zjXG/eWxIp2b7HVyEEAazHizk431IwDPXpf4G8kecEVTEdQrh6tea1 ojlfv4KhA5TuKSrhUKO+hGWeXbHfORhxryjagbwGnTd15Dq7B8SEMcubuNXJJiLJ G9kuqvAOrZcE/TQbdAr5Zv2vpg0Hh0ZsOrFCn+THES31oD5mgeLTwmXcguPwEyBV BT2Pd1UwOXumS+L065Am7PRm+i80C1J3e1dcN9+puBdNkp/kwR9RLMxDpKwEEVtI CMpiay4K5evFvXPAl6zFLKOYaeUiEKsxSwfj6A4ZgkuKWPb0TpIqY3vdw6TwvXI+ lk136hcOxQ6SKJOw11wESOsWgTbqOzgevNsLlQIm3l7MSGJcQOKJwWIU7VFp4qbp kJnMeHtlXkkpppXMMKZsa0hXWWXM+miQNSFQhdCEW7KWAWNU5dk= =Q49V -----END PGP SIGNATURE----- Merge tag 'qga-pull-2024-05-01' of https://github.com/kostyanf14/qemu into staging qga-pull-2024-05-01 # -----BEGIN PGP SIGNATURE----- # # iQIzBAABCAAdFiEEwsLBCepDxjwUI+uE711egWG6hOcFAmYx8fgACgkQ711egWG6 # hOflAw//fDHAQzcrWFggn4Ly1p1QQK+AYdQPbmKCIV7j64k05kCFU+bQxbMT9Fmr # RsWPXHv5t36ySKxaML412r7fqX19bm7RP31hlau76KtZsTJXFR/dbC6jNWXi/Qfd # 5Z1mwK4lyW+TROPx6gA2tZddqAZsSxLlArhXGj9nUcQBXDebKD/rO4jYrRBWI7uG # hV2mmolGbsNSzinfhujY2yVpm3SMEEc9IQ/CDd11HUsgJjAkXVxCDfKDXCmMKUAm # 7B6VYjQpy6zjXG/eWxIp2b7HVyEEAazHizk431IwDPXpf4G8kecEVTEdQrh6tea1 # ojlfv4KhA5TuKSrhUKO+hGWeXbHfORhxryjagbwGnTd15Dq7B8SEMcubuNXJJiLJ # G9kuqvAOrZcE/TQbdAr5Zv2vpg0Hh0ZsOrFCn+THES31oD5mgeLTwmXcguPwEyBV # BT2Pd1UwOXumS+L065Am7PRm+i80C1J3e1dcN9+puBdNkp/kwR9RLMxDpKwEEVtI # CMpiay4K5evFvXPAl6zFLKOYaeUiEKsxSwfj6A4ZgkuKWPb0TpIqY3vdw6TwvXI+ # lk136hcOxQ6SKJOw11wESOsWgTbqOzgevNsLlQIm3l7MSGJcQOKJwWIU7VFp4qbp # kJnMeHtlXkkpppXMMKZsa0hXWWXM+miQNSFQhdCEW7KWAWNU5dk= # =Q49V # -----END PGP SIGNATURE----- # gpg: Signature made Wed 01 May 2024 12:40:40 AM PDT # gpg: using RSA key C2C2C109EA43C63C1423EB84EF5D5E8161BA84E7 # gpg: Good signature from "Kostiantyn Kostiuk (Upstream PR sign) <kkostiuk@redhat.com>" [unknown] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: C2C2 C109 EA43 C63C 1423 EB84 EF5D 5E81 61BA 84E7 * tag 'qga-pull-2024-05-01' of https://github.com/kostyanf14/qemu: qga: Implement SSH commands for Windows qga: Refactor common SSH functions qga/commands-posix: qmp_guest_set_user_password: use ga_run_command helper qga/commands-posix: don't do fork()/exec() when suspending via sysfs qga/commands-posix: execute_fsfreeze_hook: use ga_run_command helper qga/commands-posix: qmp_guest_set_time: use ga_run_command helper qga/commands-posix: qmp_guest_shutdown: use ga_run_command helper qga: introduce ga_run_command() helper for guest cmd execution qga: guest-get-fsinfo: add optional 'total-bytes-privileged' field Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
commit
d5a8f0b200
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "qapi/error.h"
|
||||||
|
#include "commands-common-ssh.h"
|
||||||
|
|
||||||
|
GStrv read_authkeys(const char *path, Error **errp)
|
||||||
|
{
|
||||||
|
g_autoptr(GError) err = NULL;
|
||||||
|
g_autofree char *contents = NULL;
|
||||||
|
|
||||||
|
if (!g_file_get_contents(path, &contents, NULL, &err)) {
|
||||||
|
error_setg(errp, "failed to read '%s': %s", path, err->message);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_strsplit(contents, "\n", -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
strList *k;
|
||||||
|
|
||||||
|
for (k = keys; k != NULL; k = k->next) {
|
||||||
|
if (!check_openssh_pub_key(k->value, errp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nkeys) {
|
||||||
|
*nkeys = n;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_openssh_pub_key(const char *key, Error **errp)
|
||||||
|
{
|
||||||
|
/* simple sanity-check, we may want more? */
|
||||||
|
if (!key || key[0] == '#' || strchr(key, '\n')) {
|
||||||
|
error_setg(errp, "invalid OpenSSH public key: '%s'", key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qapi/qapi-builtin-types.h"
|
||||||
|
|
||||||
|
GStrv read_authkeys(const char *path, Error **errp);
|
||||||
|
bool check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp);
|
||||||
|
bool check_openssh_pub_key(const char *key, Error **errp);
|
|
@ -9,6 +9,7 @@
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
|
|
||||||
|
#include "commands-common-ssh.h"
|
||||||
#include "qapi/error.h"
|
#include "qapi/error.h"
|
||||||
#include "qga-qapi-commands.h"
|
#include "qga-qapi-commands.h"
|
||||||
|
|
||||||
|
@ -80,37 +81,6 @@ mkdir_for_user(const char *path, const struct passwd *p,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
check_openssh_pub_key(const char *key, Error **errp)
|
|
||||||
{
|
|
||||||
/* simple sanity-check, we may want more? */
|
|
||||||
if (!key || key[0] == '#' || strchr(key, '\n')) {
|
|
||||||
error_setg(errp, "invalid OpenSSH public key: '%s'", key);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
check_openssh_pub_keys(strList *keys, size_t *nkeys, Error **errp)
|
|
||||||
{
|
|
||||||
size_t n = 0;
|
|
||||||
strList *k;
|
|
||||||
|
|
||||||
for (k = keys; k != NULL; k = k->next) {
|
|
||||||
if (!check_openssh_pub_key(k->value, errp)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nkeys) {
|
|
||||||
*nkeys = n;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
write_authkeys(const char *path, const GStrv keys,
|
write_authkeys(const char *path, const GStrv keys,
|
||||||
const struct passwd *p, Error **errp)
|
const struct passwd *p, Error **errp)
|
||||||
|
@ -139,21 +109,6 @@ write_authkeys(const char *path, const GStrv keys,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GStrv
|
|
||||||
read_authkeys(const char *path, Error **errp)
|
|
||||||
{
|
|
||||||
g_autoptr(GError) err = NULL;
|
|
||||||
g_autofree char *contents = NULL;
|
|
||||||
|
|
||||||
if (!g_file_get_contents(path, &contents, NULL, &err)) {
|
|
||||||
error_setg(errp, "failed to read '%s': %s", path, err->message);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return g_strsplit(contents, "\n", -1);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys,
|
qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys,
|
||||||
bool has_reset, bool reset,
|
bool has_reset, bool reset,
|
||||||
|
|
|
@ -76,12 +76,159 @@ static void ga_wait_child(pid_t pid, int *status, Error **errp)
|
||||||
g_assert(rpid == pid);
|
g_assert(rpid == pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t ga_pipe_read_str(int fd[2], char **str)
|
||||||
|
{
|
||||||
|
ssize_t n, len = 0;
|
||||||
|
char buf[1024];
|
||||||
|
|
||||||
|
close(fd[1]);
|
||||||
|
fd[1] = -1;
|
||||||
|
while ((n = read(fd[0], buf, sizeof(buf))) != 0) {
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
len = -errno;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*str = g_realloc(*str, len + n + 1);
|
||||||
|
memcpy(*str + len, buf, n);
|
||||||
|
len += n;
|
||||||
|
*str[len] = '\0';
|
||||||
|
}
|
||||||
|
close(fd[0]);
|
||||||
|
fd[0] = -1;
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper to run command with input/output redirection,
|
||||||
|
* sending string to stdin and taking error message from
|
||||||
|
* stdout/err.
|
||||||
|
*/
|
||||||
|
static int ga_run_command(const char *argv[], const char *in_str,
|
||||||
|
const char *action, Error **errp)
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
int status;
|
||||||
|
int retcode = -1;
|
||||||
|
int infd[2] = { -1, -1 };
|
||||||
|
int outfd[2] = { -1, -1 };
|
||||||
|
char *str = NULL;
|
||||||
|
ssize_t len = 0;
|
||||||
|
|
||||||
|
if ((in_str && !g_unix_open_pipe(infd, FD_CLOEXEC, NULL)) ||
|
||||||
|
!g_unix_open_pipe(outfd, FD_CLOEXEC, NULL)) {
|
||||||
|
error_setg(errp, "cannot create pipe FDs");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid = fork();
|
||||||
|
if (pid == 0) {
|
||||||
|
char *cherr = NULL;
|
||||||
|
|
||||||
|
setsid();
|
||||||
|
|
||||||
|
if (in_str) {
|
||||||
|
/* Redirect stdin to infd. */
|
||||||
|
close(infd[1]);
|
||||||
|
dup2(infd[0], 0);
|
||||||
|
close(infd[0]);
|
||||||
|
} else {
|
||||||
|
reopen_fd_to_null(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Redirect stdout/stderr to outfd. */
|
||||||
|
close(outfd[0]);
|
||||||
|
dup2(outfd[1], 1);
|
||||||
|
dup2(outfd[1], 2);
|
||||||
|
close(outfd[1]);
|
||||||
|
|
||||||
|
execvp(argv[0], (char *const *)argv);
|
||||||
|
|
||||||
|
/* Write the cause of failed exec to pipe for the parent to read it. */
|
||||||
|
cherr = g_strdup_printf("failed to exec '%s'", argv[0]);
|
||||||
|
perror(cherr);
|
||||||
|
g_free(cherr);
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
} else if (pid < 0) {
|
||||||
|
error_setg_errno(errp, errno, "failed to create child process");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_str) {
|
||||||
|
close(infd[0]);
|
||||||
|
infd[0] = -1;
|
||||||
|
if (qemu_write_full(infd[1], in_str, strlen(in_str)) !=
|
||||||
|
strlen(in_str)) {
|
||||||
|
error_setg_errno(errp, errno, "%s: cannot write to stdin pipe",
|
||||||
|
action);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
close(infd[1]);
|
||||||
|
infd[1] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = ga_pipe_read_str(outfd, &str);
|
||||||
|
if (len < 0) {
|
||||||
|
error_setg_errno(errp, -len, "%s: cannot read from stdout/stderr pipe",
|
||||||
|
action);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ga_wait_child(pid, &status, errp);
|
||||||
|
if (*errp) {
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WIFEXITED(status)) {
|
||||||
|
if (len) {
|
||||||
|
error_setg(errp, "child process has terminated abnormally: %s",
|
||||||
|
str);
|
||||||
|
} else {
|
||||||
|
error_setg(errp, "child process has terminated abnormally");
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
retcode = WEXITSTATUS(status);
|
||||||
|
|
||||||
|
if (WEXITSTATUS(status)) {
|
||||||
|
if (len) {
|
||||||
|
error_setg(errp, "child process has failed to %s: %s",
|
||||||
|
action, str);
|
||||||
|
} else {
|
||||||
|
error_setg(errp, "child process has failed to %s: exit status %d",
|
||||||
|
action, WEXITSTATUS(status));
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
g_free(str);
|
||||||
|
|
||||||
|
if (infd[0] != -1) {
|
||||||
|
close(infd[0]);
|
||||||
|
}
|
||||||
|
if (infd[1] != -1) {
|
||||||
|
close(infd[1]);
|
||||||
|
}
|
||||||
|
if (outfd[0] != -1) {
|
||||||
|
close(outfd[0]);
|
||||||
|
}
|
||||||
|
if (outfd[1] != -1) {
|
||||||
|
close(outfd[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retcode;
|
||||||
|
}
|
||||||
|
|
||||||
void qmp_guest_shutdown(const char *mode, Error **errp)
|
void qmp_guest_shutdown(const char *mode, Error **errp)
|
||||||
{
|
{
|
||||||
const char *shutdown_flag;
|
const char *shutdown_flag;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
pid_t pid;
|
|
||||||
int status;
|
|
||||||
|
|
||||||
#ifdef CONFIG_SOLARIS
|
#ifdef CONFIG_SOLARIS
|
||||||
const char *powerdown_flag = "-i5";
|
const char *powerdown_flag = "-i5";
|
||||||
|
@ -110,67 +257,31 @@ void qmp_guest_shutdown(const char *mode, Error **errp)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pid = fork();
|
const char *argv[] = {"/sbin/shutdown",
|
||||||
if (pid == 0) {
|
|
||||||
/* child, start the shutdown */
|
|
||||||
setsid();
|
|
||||||
reopen_fd_to_null(0);
|
|
||||||
reopen_fd_to_null(1);
|
|
||||||
reopen_fd_to_null(2);
|
|
||||||
|
|
||||||
#ifdef CONFIG_SOLARIS
|
#ifdef CONFIG_SOLARIS
|
||||||
execl("/sbin/shutdown", "shutdown", shutdown_flag, "-g0", "-y",
|
shutdown_flag, "-g0", "-y",
|
||||||
"hypervisor initiated shutdown", (char *)NULL);
|
|
||||||
#elif defined(CONFIG_BSD)
|
#elif defined(CONFIG_BSD)
|
||||||
execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0",
|
shutdown_flag, "+0",
|
||||||
"hypervisor initiated shutdown", (char *)NULL);
|
|
||||||
#else
|
#else
|
||||||
execl("/sbin/shutdown", "shutdown", "-h", shutdown_flag, "+0",
|
"-h", shutdown_flag, "+0",
|
||||||
"hypervisor initiated shutdown", (char *)NULL);
|
|
||||||
#endif
|
#endif
|
||||||
_exit(EXIT_FAILURE);
|
"hypervisor initiated shutdown", (char *) NULL};
|
||||||
} else if (pid < 0) {
|
|
||||||
error_setg_errno(errp, errno, "failed to create child process");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ga_wait_child(pid, &status, &local_err);
|
ga_run_command(argv, NULL, "shutdown", &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!WIFEXITED(status)) {
|
|
||||||
error_setg(errp, "child process has terminated abnormally");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WEXITSTATUS(status)) {
|
|
||||||
error_setg(errp, "child process has failed to shutdown");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* succeeded */
|
/* succeeded */
|
||||||
}
|
}
|
||||||
|
|
||||||
void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
|
void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
int status;
|
|
||||||
pid_t pid;
|
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
static const char hwclock_path[] = "/sbin/hwclock";
|
const char *argv[] = {"/sbin/hwclock", has_time ? "-w" : "-s", NULL};
|
||||||
static int hwclock_available = -1;
|
|
||||||
|
|
||||||
if (hwclock_available < 0) {
|
|
||||||
hwclock_available = (access(hwclock_path, X_OK) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hwclock_available) {
|
|
||||||
error_setg(errp, QERR_UNSUPPORTED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If user has passed a time, validate and set it. */
|
/* If user has passed a time, validate and set it. */
|
||||||
if (has_time) {
|
if (has_time) {
|
||||||
|
@ -201,37 +312,12 @@ void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
|
||||||
* just need to synchronize the hardware clock. However, if no time was
|
* just need to synchronize the hardware clock. However, if no time was
|
||||||
* passed, user is requesting the opposite: set the system time from the
|
* passed, user is requesting the opposite: set the system time from the
|
||||||
* hardware clock (RTC). */
|
* hardware clock (RTC). */
|
||||||
pid = fork();
|
ga_run_command(argv, NULL, "set hardware clock to system time",
|
||||||
if (pid == 0) {
|
&local_err);
|
||||||
setsid();
|
|
||||||
reopen_fd_to_null(0);
|
|
||||||
reopen_fd_to_null(1);
|
|
||||||
reopen_fd_to_null(2);
|
|
||||||
|
|
||||||
/* Use '/sbin/hwclock -w' to set RTC from the system time,
|
|
||||||
* or '/sbin/hwclock -s' to set the system time from RTC. */
|
|
||||||
execl(hwclock_path, "hwclock", has_time ? "-w" : "-s", NULL);
|
|
||||||
_exit(EXIT_FAILURE);
|
|
||||||
} else if (pid < 0) {
|
|
||||||
error_setg_errno(errp, errno, "failed to create child process");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ga_wait_child(pid, &status, &local_err);
|
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!WIFEXITED(status)) {
|
|
||||||
error_setg(errp, "child process has terminated abnormally");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WEXITSTATUS(status)) {
|
|
||||||
error_setg(errp, "hwclock failed to set hardware clock to system time");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -650,8 +736,6 @@ static const char *fsfreeze_hook_arg_string[] = {
|
||||||
|
|
||||||
static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
|
static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
|
||||||
{
|
{
|
||||||
int status;
|
|
||||||
pid_t pid;
|
|
||||||
const char *hook;
|
const char *hook;
|
||||||
const char *arg_str = fsfreeze_hook_arg_string[arg];
|
const char *arg_str = fsfreeze_hook_arg_string[arg];
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
@ -660,42 +744,15 @@ static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (access(hook, X_OK) != 0) {
|
|
||||||
error_setg_errno(errp, errno, "can't access fsfreeze hook '%s'", hook);
|
const char *argv[] = {hook, arg_str, NULL};
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
slog("executing fsfreeze hook with arg '%s'", arg_str);
|
slog("executing fsfreeze hook with arg '%s'", arg_str);
|
||||||
pid = fork();
|
ga_run_command(argv, NULL, "execute fsfreeze hook", &local_err);
|
||||||
if (pid == 0) {
|
|
||||||
setsid();
|
|
||||||
reopen_fd_to_null(0);
|
|
||||||
reopen_fd_to_null(1);
|
|
||||||
reopen_fd_to_null(2);
|
|
||||||
|
|
||||||
execl(hook, hook, arg_str, NULL);
|
|
||||||
_exit(EXIT_FAILURE);
|
|
||||||
} else if (pid < 0) {
|
|
||||||
error_setg_errno(errp, errno, "failed to create child process");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ga_wait_child(pid, &status, &local_err);
|
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!WIFEXITED(status)) {
|
|
||||||
error_setg(errp, "fsfreeze hook has terminated abnormally");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = WEXITSTATUS(status);
|
|
||||||
if (status) {
|
|
||||||
error_setg(errp, "fsfreeze hook has failed with status %d", status);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1569,8 +1626,10 @@ static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
|
||||||
nonroot_total = used + buf.f_bavail;
|
nonroot_total = used + buf.f_bavail;
|
||||||
fs->used_bytes = used * fr_size;
|
fs->used_bytes = used * fr_size;
|
||||||
fs->total_bytes = nonroot_total * fr_size;
|
fs->total_bytes = nonroot_total * fr_size;
|
||||||
|
fs->total_bytes_privileged = buf.f_blocks * fr_size;
|
||||||
|
|
||||||
fs->has_total_bytes = true;
|
fs->has_total_bytes = true;
|
||||||
|
fs->has_total_bytes_privileged = true;
|
||||||
fs->has_used_bytes = true;
|
fs->has_used_bytes = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1869,52 +1928,21 @@ static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp)
|
||||||
|
|
||||||
static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
|
static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
g_autoptr(GError) local_gerr = NULL;
|
||||||
const char *sysfile_strs[3] = {"disk", "mem", NULL};
|
const char *sysfile_strs[3] = {"disk", "mem", NULL};
|
||||||
const char *sysfile_str = sysfile_strs[mode];
|
const char *sysfile_str = sysfile_strs[mode];
|
||||||
pid_t pid;
|
|
||||||
int status;
|
|
||||||
|
|
||||||
if (!sysfile_str) {
|
if (!sysfile_str) {
|
||||||
error_setg(errp, "unknown guest suspend mode");
|
error_setg(errp, "unknown guest suspend mode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pid = fork();
|
if (!g_file_set_contents(LINUX_SYS_STATE_FILE, sysfile_str,
|
||||||
if (!pid) {
|
-1, &local_gerr)) {
|
||||||
/* child */
|
error_setg(errp, "suspend: cannot write to '%s': %s",
|
||||||
int fd;
|
LINUX_SYS_STATE_FILE, local_gerr->message);
|
||||||
|
|
||||||
setsid();
|
|
||||||
reopen_fd_to_null(0);
|
|
||||||
reopen_fd_to_null(1);
|
|
||||||
reopen_fd_to_null(2);
|
|
||||||
|
|
||||||
fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
|
|
||||||
if (fd < 0) {
|
|
||||||
_exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
|
|
||||||
_exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
_exit(EXIT_SUCCESS);
|
|
||||||
} else if (pid < 0) {
|
|
||||||
error_setg_errno(errp, errno, "failed to create child process");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ga_wait_child(pid, &status, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WEXITSTATUS(status)) {
|
|
||||||
error_setg(errp, "child process has failed to suspend");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void guest_suspend(SuspendMode mode, Error **errp)
|
static void guest_suspend(SuspendMode mode, Error **errp)
|
||||||
|
@ -2123,14 +2151,8 @@ void qmp_guest_set_user_password(const char *username,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
char *passwd_path = NULL;
|
g_autofree char *rawpasswddata = NULL;
|
||||||
pid_t pid;
|
|
||||||
int status;
|
|
||||||
int datafd[2] = { -1, -1 };
|
|
||||||
char *rawpasswddata = NULL;
|
|
||||||
size_t rawpasswdlen;
|
size_t rawpasswdlen;
|
||||||
char *chpasswddata = NULL;
|
|
||||||
size_t chpasswdlen;
|
|
||||||
|
|
||||||
rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
|
rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
|
||||||
if (!rawpasswddata) {
|
if (!rawpasswddata) {
|
||||||
|
@ -2141,95 +2163,31 @@ void qmp_guest_set_user_password(const char *username,
|
||||||
|
|
||||||
if (strchr(rawpasswddata, '\n')) {
|
if (strchr(rawpasswddata, '\n')) {
|
||||||
error_setg(errp, "forbidden characters in raw password");
|
error_setg(errp, "forbidden characters in raw password");
|
||||||
goto out;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strchr(username, '\n') ||
|
if (strchr(username, '\n') ||
|
||||||
strchr(username, ':')) {
|
strchr(username, ':')) {
|
||||||
error_setg(errp, "forbidden characters in username");
|
error_setg(errp, "forbidden characters in username");
|
||||||
goto out;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __FreeBSD__
|
#ifdef __FreeBSD__
|
||||||
chpasswddata = g_strdup(rawpasswddata);
|
g_autofree char *chpasswdata = g_strdup(rawpasswddata);
|
||||||
passwd_path = g_find_program_in_path("pw");
|
const char *crypt_flag = crypted ? "-H" : "-h";
|
||||||
|
const char *argv[] = {"pw", "usermod", "-n", username,
|
||||||
|
crypt_flag, "0", NULL};
|
||||||
#else
|
#else
|
||||||
chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
|
g_autofree char *chpasswddata = g_strdup_printf("%s:%s\n", username,
|
||||||
passwd_path = g_find_program_in_path("chpasswd");
|
rawpasswddata);
|
||||||
|
const char *crypt_flag = crypted ? "-e" : NULL;
|
||||||
|
const char *argv[] = {"chpasswd", crypt_flag, NULL};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
chpasswdlen = strlen(chpasswddata);
|
ga_run_command(argv, chpasswddata, "set user password", &local_err);
|
||||||
|
|
||||||
if (!passwd_path) {
|
|
||||||
error_setg(errp, "cannot find 'passwd' program in PATH");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
|
|
||||||
error_setg(errp, "cannot create pipe FDs");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
pid = fork();
|
|
||||||
if (pid == 0) {
|
|
||||||
close(datafd[1]);
|
|
||||||
/* child */
|
|
||||||
setsid();
|
|
||||||
dup2(datafd[0], 0);
|
|
||||||
reopen_fd_to_null(1);
|
|
||||||
reopen_fd_to_null(2);
|
|
||||||
|
|
||||||
#ifdef __FreeBSD__
|
|
||||||
const char *h_arg;
|
|
||||||
h_arg = (crypted) ? "-H" : "-h";
|
|
||||||
execl(passwd_path, "pw", "usermod", "-n", username, h_arg, "0", NULL);
|
|
||||||
#else
|
|
||||||
if (crypted) {
|
|
||||||
execl(passwd_path, "chpasswd", "-e", NULL);
|
|
||||||
} else {
|
|
||||||
execl(passwd_path, "chpasswd", NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
_exit(EXIT_FAILURE);
|
|
||||||
} else if (pid < 0) {
|
|
||||||
error_setg_errno(errp, errno, "failed to create child process");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
close(datafd[0]);
|
|
||||||
datafd[0] = -1;
|
|
||||||
|
|
||||||
if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) {
|
|
||||||
error_setg_errno(errp, errno, "cannot write new account password");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
close(datafd[1]);
|
|
||||||
datafd[1] = -1;
|
|
||||||
|
|
||||||
ga_wait_child(pid, &status, &local_err);
|
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
goto out;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (!WIFEXITED(status)) {
|
|
||||||
error_setg(errp, "child process has terminated abnormally");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WEXITSTATUS(status)) {
|
|
||||||
error_setg(errp, "child process has failed to set user password");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
g_free(chpasswddata);
|
|
||||||
g_free(rawpasswddata);
|
|
||||||
g_free(passwd_path);
|
|
||||||
if (datafd[0] != -1) {
|
|
||||||
close(datafd[0]);
|
|
||||||
}
|
|
||||||
if (datafd[1] != -1) {
|
|
||||||
close(datafd[1]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else /* __linux__ || __FreeBSD__ */
|
#else /* __linux__ || __FreeBSD__ */
|
||||||
|
|
|
@ -1143,6 +1143,7 @@ static GuestFilesystemInfo *build_guest_fsinfo(char *guid, Error **errp)
|
||||||
fs = g_malloc(sizeof(*fs));
|
fs = g_malloc(sizeof(*fs));
|
||||||
fs->name = g_strdup(guid);
|
fs->name = g_strdup(guid);
|
||||||
fs->has_total_bytes = false;
|
fs->has_total_bytes = false;
|
||||||
|
fs->has_total_bytes_privileged = false;
|
||||||
fs->has_used_bytes = false;
|
fs->has_used_bytes = false;
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
fs->mountpoint = g_strdup("System Reserved");
|
fs->mountpoint = g_strdup("System Reserved");
|
||||||
|
|
|
@ -0,0 +1,712 @@
|
||||||
|
/*
|
||||||
|
* QEMU Guest Agent win32-specific command implementations for SSH keys.
|
||||||
|
* The implementation is opinionated and expects the SSH implementation to
|
||||||
|
* be OpenSSH.
|
||||||
|
*
|
||||||
|
* Copyright Schweitzer Engineering Laboratories. 2024
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Aidan Leuck <aidan_leuck@selinc.com>
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include <aclapi.h>
|
||||||
|
#include <qga-qapi-types.h>
|
||||||
|
|
||||||
|
#include "commands-common-ssh.h"
|
||||||
|
#include "commands-windows-ssh.h"
|
||||||
|
#include "guest-agent-core.h"
|
||||||
|
#include "limits.h"
|
||||||
|
#include "lmaccess.h"
|
||||||
|
#include "lmapibuf.h"
|
||||||
|
#include "lmerr.h"
|
||||||
|
#include "qapi/error.h"
|
||||||
|
|
||||||
|
#include "qga-qapi-commands.h"
|
||||||
|
#include "sddl.h"
|
||||||
|
#include "shlobj.h"
|
||||||
|
#include "userenv.h"
|
||||||
|
|
||||||
|
#define AUTHORIZED_KEY_FILE "authorized_keys"
|
||||||
|
#define AUTHORIZED_KEY_FILE_ADMIN "administrators_authorized_keys"
|
||||||
|
#define LOCAL_SYSTEM_SID "S-1-5-18"
|
||||||
|
#define ADMIN_SID "S-1-5-32-544"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Frees userInfo structure. This implements the g_auto cleanup
|
||||||
|
* for the structure.
|
||||||
|
*/
|
||||||
|
void free_userInfo(PWindowsUserInfo info)
|
||||||
|
{
|
||||||
|
g_free(info->sshDirectory);
|
||||||
|
g_free(info->authorizedKeyFile);
|
||||||
|
LocalFree(info->SSID);
|
||||||
|
g_free(info->username);
|
||||||
|
g_free(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets the admin SSH folder for OpenSSH. OpenSSH does not store
|
||||||
|
* the authorized_key file in the users home directory for security reasons and
|
||||||
|
* instead stores it at %PROGRAMDATA%/ssh. This function returns the path to
|
||||||
|
* that directory on the users machine
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* errp -> error structure to set when an error occurs
|
||||||
|
* returns: The path to the ssh folder in %PROGRAMDATA% or NULL if an error
|
||||||
|
* occurred.
|
||||||
|
*/
|
||||||
|
static char *get_admin_ssh_folder(Error **errp)
|
||||||
|
{
|
||||||
|
/* Allocate memory for the program data path */
|
||||||
|
g_autofree char *programDataPath = NULL;
|
||||||
|
char *authkeys_path = NULL;
|
||||||
|
PWSTR pgDataW = NULL;
|
||||||
|
g_autoptr(GError) gerr = NULL;
|
||||||
|
|
||||||
|
/* Get the KnownFolderPath on the machine. */
|
||||||
|
HRESULT folderResult =
|
||||||
|
SHGetKnownFolderPath(&FOLDERID_ProgramData, 0, NULL, &pgDataW);
|
||||||
|
if (folderResult != S_OK) {
|
||||||
|
error_setg(errp, "Failed to retrieve ProgramData folder");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert from a wide string back to a standard character string. */
|
||||||
|
programDataPath = g_utf16_to_utf8(pgDataW, -1, NULL, NULL, &gerr);
|
||||||
|
CoTaskMemFree(pgDataW);
|
||||||
|
if (!programDataPath) {
|
||||||
|
error_setg(errp,
|
||||||
|
"Failed converting ProgramData folder path to UTF-16 %s",
|
||||||
|
gerr->message);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build the path to the file. */
|
||||||
|
authkeys_path = g_build_filename(programDataPath, "ssh", NULL);
|
||||||
|
return authkeys_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets the path to the SSH folder for the specified user. If the user is an
|
||||||
|
* admin it returns the ssh folder located at %PROGRAMDATA%/ssh. If the user is
|
||||||
|
* not an admin it returns %USERPROFILE%/.ssh
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* username -> Username to get the SSH folder for
|
||||||
|
* isAdmin -> Whether the user is an admin or not
|
||||||
|
* errp -> Error structure to set any errors that occur.
|
||||||
|
* returns: path to the ssh folder as a string.
|
||||||
|
*/
|
||||||
|
static char *get_ssh_folder(const char *username, const bool isAdmin,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
DWORD maxSize = MAX_PATH;
|
||||||
|
g_autofree char *profilesDir = g_new0(char, maxSize);
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
return get_admin_ssh_folder(errp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If not an Admin the SSH key is in the user directory. */
|
||||||
|
/* Get the user profile directory on the machine. */
|
||||||
|
BOOL ret = GetProfilesDirectory(profilesDir, &maxSize);
|
||||||
|
if (!ret) {
|
||||||
|
error_setg_win32(errp, GetLastError(),
|
||||||
|
"failed to retrieve profiles directory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Builds the filename */
|
||||||
|
return g_build_filename(profilesDir, username, ".ssh", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Creates an entry for the user so they can access the ssh folder in their
|
||||||
|
* userprofile.
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* userInfo -> Information about the current user
|
||||||
|
* pACL -> Pointer to an ACL structure
|
||||||
|
* errp -> Error structure to set any errors that occur
|
||||||
|
* returns -> 1 on success, 0 otherwise
|
||||||
|
*/
|
||||||
|
static bool create_acl_user(PWindowsUserInfo userInfo, PACL *pACL, Error **errp)
|
||||||
|
{
|
||||||
|
const int aclSize = 1;
|
||||||
|
PACL newACL = NULL;
|
||||||
|
EXPLICIT_ACCESS eAccess[1];
|
||||||
|
PSID userPSID = NULL;
|
||||||
|
|
||||||
|
/* Get a pointer to the internal SID object in Windows */
|
||||||
|
bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID);
|
||||||
|
if (!converted) {
|
||||||
|
error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID",
|
||||||
|
userInfo->username);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the permissions for the user. */
|
||||||
|
eAccess[0].grfAccessPermissions = GENERIC_ALL;
|
||||||
|
eAccess[0].grfAccessMode = SET_ACCESS;
|
||||||
|
eAccess[0].grfInheritance = NO_INHERITANCE;
|
||||||
|
eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||||
|
eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
|
||||||
|
eAccess[0].Trustee.ptstrName = (LPTSTR)userPSID;
|
||||||
|
|
||||||
|
/* Set the ACL entries */
|
||||||
|
DWORD setResult;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we are given a pointer that is already initialized, then we can merge
|
||||||
|
* the existing entries instead of overwriting them.
|
||||||
|
*/
|
||||||
|
if (*pACL) {
|
||||||
|
setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &newACL);
|
||||||
|
} else {
|
||||||
|
setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &newACL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setResult != ERROR_SUCCESS) {
|
||||||
|
error_setg_win32(errp, GetLastError(),
|
||||||
|
"failed to set ACL entries for user %s %lu",
|
||||||
|
userInfo->username, setResult);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free any old memory since we are going to overwrite the users pointer. */
|
||||||
|
LocalFree(*pACL);
|
||||||
|
*pACL = newACL;
|
||||||
|
|
||||||
|
LocalFree(userPSID);
|
||||||
|
return true;
|
||||||
|
error:
|
||||||
|
LocalFree(userPSID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Creates a base ACL for both normal users and admins to share
|
||||||
|
* pACL -> Pointer to an ACL structure
|
||||||
|
* errp -> Error structure to set any errors that occur
|
||||||
|
* returns: 1 on success, 0 otherwise
|
||||||
|
*/
|
||||||
|
static bool create_acl_base(PACL *pACL, Error **errp)
|
||||||
|
{
|
||||||
|
PSID adminGroupPSID = NULL;
|
||||||
|
PSID systemPSID = NULL;
|
||||||
|
|
||||||
|
const int aclSize = 2;
|
||||||
|
EXPLICIT_ACCESS eAccess[2];
|
||||||
|
|
||||||
|
/* Create an entry for the system user. */
|
||||||
|
const char *systemSID = LOCAL_SYSTEM_SID;
|
||||||
|
bool converted = ConvertStringSidToSid(systemSID, &systemPSID);
|
||||||
|
if (!converted) {
|
||||||
|
error_setg_win32(errp, GetLastError(), "failed to retrieve system SID");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set permissions for system user */
|
||||||
|
eAccess[0].grfAccessPermissions = GENERIC_ALL;
|
||||||
|
eAccess[0].grfAccessMode = SET_ACCESS;
|
||||||
|
eAccess[0].grfInheritance = NO_INHERITANCE;
|
||||||
|
eAccess[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||||
|
eAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
|
||||||
|
eAccess[0].Trustee.ptstrName = (LPTSTR)systemPSID;
|
||||||
|
|
||||||
|
/* Create an entry for the admin user. */
|
||||||
|
const char *adminSID = ADMIN_SID;
|
||||||
|
converted = ConvertStringSidToSid(adminSID, &adminGroupPSID);
|
||||||
|
if (!converted) {
|
||||||
|
error_setg_win32(errp, GetLastError(), "failed to retrieve Admin SID");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set permissions for admin group. */
|
||||||
|
eAccess[1].grfAccessPermissions = GENERIC_ALL;
|
||||||
|
eAccess[1].grfAccessMode = SET_ACCESS;
|
||||||
|
eAccess[1].grfInheritance = NO_INHERITANCE;
|
||||||
|
eAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||||
|
eAccess[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
|
||||||
|
eAccess[1].Trustee.ptstrName = (LPTSTR)adminGroupPSID;
|
||||||
|
|
||||||
|
/* Put the entries in an ACL object. */
|
||||||
|
PACL pNewACL = NULL;
|
||||||
|
DWORD setResult;
|
||||||
|
|
||||||
|
/*
|
||||||
|
*If we are given a pointer that is already initialized, then we can merge
|
||||||
|
*the existing entries instead of overwriting them.
|
||||||
|
*/
|
||||||
|
if (*pACL) {
|
||||||
|
setResult = SetEntriesInAcl(aclSize, eAccess, *pACL, &pNewACL);
|
||||||
|
} else {
|
||||||
|
setResult = SetEntriesInAcl(aclSize, eAccess, NULL, &pNewACL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setResult != ERROR_SUCCESS) {
|
||||||
|
error_setg_win32(errp, GetLastError(),
|
||||||
|
"failed to set base ACL entries for system user and "
|
||||||
|
"admin group %lu",
|
||||||
|
setResult);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalFree(adminGroupPSID);
|
||||||
|
LocalFree(systemPSID);
|
||||||
|
|
||||||
|
/* Free any old memory since we are going to overwrite the users pointer. */
|
||||||
|
LocalFree(*pACL);
|
||||||
|
|
||||||
|
*pACL = pNewACL;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
LocalFree(adminGroupPSID);
|
||||||
|
LocalFree(systemPSID);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets the access control on the authorized_keys file and any ssh folders that
|
||||||
|
* need to be created. For administrators the required permissions on the
|
||||||
|
* file/folders are that only administrators and the LocalSystem account can
|
||||||
|
* access the folders. For normal user accounts only the specified user,
|
||||||
|
* LocalSystem and Administrators can have access to the key.
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* userInfo -> pointer to structure that contains information about the user
|
||||||
|
* PACL -> pointer to an access control structure that will be set upon
|
||||||
|
* successful completion of the function.
|
||||||
|
* errp -> error structure that will be set upon error.
|
||||||
|
* returns: 1 upon success 0 upon failure.
|
||||||
|
*/
|
||||||
|
static bool create_acl(PWindowsUserInfo userInfo, PACL *pACL, Error **errp)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Creates a base ACL that both admins and users will share
|
||||||
|
* This adds the Administrators group and the SYSTEM group
|
||||||
|
*/
|
||||||
|
if (!create_acl_base(pACL, errp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the user is not an admin give the user creating the key permission to
|
||||||
|
* access the file.
|
||||||
|
*/
|
||||||
|
if (!userInfo->isAdmin) {
|
||||||
|
if (!create_acl_user(userInfo, pACL, errp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Create the SSH directory for the user and d sets appropriate permissions.
|
||||||
|
* In general the directory will be %PROGRAMDATA%/ssh if the user is an admin.
|
||||||
|
* %USERPOFILE%/.ssh if not an admin
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* userInfo -> Contains information about the user
|
||||||
|
* errp -> Structure that will contain errors if the function fails.
|
||||||
|
* returns: zero upon failure, 1 upon success
|
||||||
|
*/
|
||||||
|
static bool create_ssh_directory(WindowsUserInfo *userInfo, Error **errp)
|
||||||
|
{
|
||||||
|
PACL pNewACL = NULL;
|
||||||
|
g_autofree PSECURITY_DESCRIPTOR pSD = NULL;
|
||||||
|
|
||||||
|
/* Gets the appropriate ACL for the user */
|
||||||
|
if (!create_acl(userInfo, &pNewACL, errp)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for a security descriptor */
|
||||||
|
pSD = g_malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
|
||||||
|
if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) {
|
||||||
|
error_setg_win32(errp, GetLastError(),
|
||||||
|
"Failed to initialize security descriptor");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Associate the security descriptor with the ACL permissions. */
|
||||||
|
if (!SetSecurityDescriptorDacl(pSD, TRUE, pNewACL, FALSE)) {
|
||||||
|
error_setg_win32(errp, GetLastError(),
|
||||||
|
"Failed to set security descriptor ACL");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the security attributes on the folder */
|
||||||
|
SECURITY_ATTRIBUTES sAttr;
|
||||||
|
sAttr.bInheritHandle = FALSE;
|
||||||
|
sAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||||
|
sAttr.lpSecurityDescriptor = pSD;
|
||||||
|
|
||||||
|
/* Create the directory with the created permissions */
|
||||||
|
BOOL created = CreateDirectory(userInfo->sshDirectory, &sAttr);
|
||||||
|
if (!created) {
|
||||||
|
error_setg_win32(errp, GetLastError(), "failed to create directory %s",
|
||||||
|
userInfo->sshDirectory);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free memory */
|
||||||
|
LocalFree(pNewACL);
|
||||||
|
return true;
|
||||||
|
error:
|
||||||
|
LocalFree(pNewACL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets permissions on the authorized_key_file that is created.
|
||||||
|
*
|
||||||
|
* parameters: userInfo -> Information about the user
|
||||||
|
* errp -> error structure that will contain errors upon failure
|
||||||
|
* returns: 1 upon success, zero upon failure.
|
||||||
|
*/
|
||||||
|
static bool set_file_permissions(PWindowsUserInfo userInfo, Error **errp)
|
||||||
|
{
|
||||||
|
PACL pACL = NULL;
|
||||||
|
PSID userPSID;
|
||||||
|
|
||||||
|
/* Creates the access control structure */
|
||||||
|
if (!create_acl(userInfo, &pACL, errp)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the PSID structure for the user based off the string SID. */
|
||||||
|
bool converted = ConvertStringSidToSid(userInfo->SSID, &userPSID);
|
||||||
|
if (!converted) {
|
||||||
|
error_setg_win32(errp, GetLastError(), "failed to retrieve user %s SID",
|
||||||
|
userInfo->username);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevents permissions from being inherited and use the DACL provided. */
|
||||||
|
const SE_OBJECT_TYPE securityBitFlags =
|
||||||
|
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION;
|
||||||
|
|
||||||
|
/* Set the ACL on the file. */
|
||||||
|
if (SetNamedSecurityInfo(userInfo->authorizedKeyFile, SE_FILE_OBJECT,
|
||||||
|
securityBitFlags, userPSID, NULL, pACL,
|
||||||
|
NULL) != ERROR_SUCCESS) {
|
||||||
|
error_setg_win32(errp, GetLastError(),
|
||||||
|
"failed to set file security for file %s",
|
||||||
|
userInfo->authorizedKeyFile);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalFree(pACL);
|
||||||
|
LocalFree(userPSID);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
LocalFree(pACL);
|
||||||
|
LocalFree(userPSID);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Writes the specified keys to the authenticated keys file.
|
||||||
|
* parameters:
|
||||||
|
* userInfo: Information about the user we are writing the authkeys file to.
|
||||||
|
* authkeys: Array of keys to write to disk
|
||||||
|
* errp: Error structure that will contain any errors if they occur.
|
||||||
|
* returns: 1 if successful, 0 otherwise.
|
||||||
|
*/
|
||||||
|
static bool write_authkeys(WindowsUserInfo *userInfo, GStrv authkeys,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
g_autofree char *contents = NULL;
|
||||||
|
g_autoptr(GError) err = NULL;
|
||||||
|
|
||||||
|
contents = g_strjoinv("\n", authkeys);
|
||||||
|
|
||||||
|
if (!g_file_set_contents(userInfo->authorizedKeyFile, contents, -1, &err)) {
|
||||||
|
error_setg(errp, "failed to write to '%s': %s",
|
||||||
|
userInfo->authorizedKeyFile, err->message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!set_file_permissions(userInfo, errp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retrieves information about a Windows user by their username
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* userInfo -> Double pointer to a WindowsUserInfo structure. Upon success, it
|
||||||
|
* will be allocated with information about the user and need to be freed.
|
||||||
|
* username -> Name of the user to lookup.
|
||||||
|
* errp -> Contains any errors that occur.
|
||||||
|
* returns: 1 upon success, 0 upon failure.
|
||||||
|
*/
|
||||||
|
static bool get_user_info(PWindowsUserInfo *userInfo, const char *username,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
DWORD infoLevel = 4;
|
||||||
|
LPUSER_INFO_4 uBuf = NULL;
|
||||||
|
g_autofree wchar_t *wideUserName = NULL;
|
||||||
|
g_autoptr(GError) gerr = NULL;
|
||||||
|
PSID psid = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts a string to a Windows wide string since the GetNetUserInfo
|
||||||
|
* function requires it.
|
||||||
|
*/
|
||||||
|
wideUserName = g_utf8_to_utf16(username, -1, NULL, NULL, &gerr);
|
||||||
|
if (!wideUserName) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* allocate data */
|
||||||
|
PWindowsUserInfo uData = g_new0(WindowsUserInfo, 1);
|
||||||
|
|
||||||
|
/* Set pointer so it can be cleaned up by the callee, even upon error. */
|
||||||
|
*userInfo = uData;
|
||||||
|
|
||||||
|
/* Find the information */
|
||||||
|
NET_API_STATUS result =
|
||||||
|
NetUserGetInfo(NULL, wideUserName, infoLevel, (LPBYTE *)&uBuf);
|
||||||
|
if (result != NERR_Success) {
|
||||||
|
/* Give a friendlier error message if the user was not found. */
|
||||||
|
if (result == NERR_UserNotFound) {
|
||||||
|
error_setg(errp, "User %s was not found", username);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_setg(errp,
|
||||||
|
"Received unexpected error when asking for user info: Error "
|
||||||
|
"Code %lu",
|
||||||
|
result);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get information from the buffer returned by NetUserGetInfo. */
|
||||||
|
uData->username = g_strdup(username);
|
||||||
|
uData->isAdmin = uBuf->usri4_priv == USER_PRIV_ADMIN;
|
||||||
|
psid = uBuf->usri4_user_sid;
|
||||||
|
|
||||||
|
char *sidStr = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We store the string representation of the SID not SID structure in
|
||||||
|
* memory. Callees wanting to use the SID structure should call
|
||||||
|
* ConvertStringSidToSID.
|
||||||
|
*/
|
||||||
|
if (!ConvertSidToStringSid(psid, &sidStr)) {
|
||||||
|
error_setg_win32(errp, GetLastError(),
|
||||||
|
"failed to get SID string for user %s", username);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store the SSID */
|
||||||
|
uData->SSID = sidStr;
|
||||||
|
|
||||||
|
/* Get the SSH folder for the user. */
|
||||||
|
char *sshFolder = get_ssh_folder(username, uData->isAdmin, errp);
|
||||||
|
if (sshFolder == NULL) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the authorized key file path */
|
||||||
|
const char *authorizedKeyFile =
|
||||||
|
uData->isAdmin ? AUTHORIZED_KEY_FILE_ADMIN : AUTHORIZED_KEY_FILE;
|
||||||
|
char *authorizedKeyPath =
|
||||||
|
g_build_filename(sshFolder, authorizedKeyFile, NULL);
|
||||||
|
uData->sshDirectory = sshFolder;
|
||||||
|
uData->authorizedKeyFile = authorizedKeyPath;
|
||||||
|
|
||||||
|
/* Free */
|
||||||
|
NetApiBufferFree(uBuf);
|
||||||
|
return true;
|
||||||
|
error:
|
||||||
|
if (uBuf) {
|
||||||
|
NetApiBufferFree(uBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gets the list of authorized keys for a user.
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* username -> Username to retrieve the keys for.
|
||||||
|
* errp -> Error structure that will display any errors through QMP.
|
||||||
|
* returns: List of keys associated with the user.
|
||||||
|
*/
|
||||||
|
GuestAuthorizedKeys *qmp_guest_ssh_get_authorized_keys(const char *username,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
GuestAuthorizedKeys *keys = NULL;
|
||||||
|
g_auto(GStrv) authKeys = NULL;
|
||||||
|
g_autoptr(GuestAuthorizedKeys) ret = NULL;
|
||||||
|
g_auto(PWindowsUserInfo) userInfo = NULL;
|
||||||
|
|
||||||
|
/* Gets user information */
|
||||||
|
if (!get_user_info(&userInfo, username, errp)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads authkeys for the user */
|
||||||
|
authKeys = read_authkeys(userInfo->authorizedKeyFile, errp);
|
||||||
|
if (authKeys == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the GuestAuthorizedKey struct with keys from the file */
|
||||||
|
ret = g_new0(GuestAuthorizedKeys, 1);
|
||||||
|
for (int i = 0; authKeys[i] != NULL; i++) {
|
||||||
|
g_strstrip(authKeys[i]);
|
||||||
|
if (!authKeys[i][0] || authKeys[i][0] == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QAPI_LIST_PREPEND(ret->keys, g_strdup(authKeys[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Steal the pointer because it is up for the callee to deallocate the
|
||||||
|
* memory.
|
||||||
|
*/
|
||||||
|
keys = g_steal_pointer(&ret);
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adds an ssh key for a user.
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* username -> User to add the SSH key to
|
||||||
|
* strList -> Array of keys to add to the list
|
||||||
|
* has_reset -> Whether the keys have been reset
|
||||||
|
* reset -> Boolean to reset the keys (If this is set the existing list will be
|
||||||
|
* cleared) and the other key reset. errp -> Pointer to an error structure that
|
||||||
|
* will get returned over QMP if anything goes wrong.
|
||||||
|
*/
|
||||||
|
void qmp_guest_ssh_add_authorized_keys(const char *username, strList *keys,
|
||||||
|
bool has_reset, bool reset, Error **errp)
|
||||||
|
{
|
||||||
|
g_auto(PWindowsUserInfo) userInfo = NULL;
|
||||||
|
g_auto(GStrv) authkeys = NULL;
|
||||||
|
strList *k;
|
||||||
|
size_t nkeys, nauthkeys;
|
||||||
|
|
||||||
|
/* Make sure the keys given are valid */
|
||||||
|
if (!check_openssh_pub_keys(keys, &nkeys, errp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gets user information */
|
||||||
|
if (!get_user_info(&userInfo, username, errp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine whether we should reset the keys */
|
||||||
|
reset = has_reset && reset;
|
||||||
|
if (!reset) {
|
||||||
|
/* Read existing keys into memory */
|
||||||
|
authkeys = read_authkeys(userInfo->authorizedKeyFile, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that the SSH key directory exists for the user. */
|
||||||
|
if (!g_file_test(userInfo->sshDirectory, G_FILE_TEST_IS_DIR)) {
|
||||||
|
BOOL success = create_ssh_directory(userInfo, errp);
|
||||||
|
if (!success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reallocates the buffer to fit the new keys. */
|
||||||
|
nauthkeys = authkeys ? g_strv_length(authkeys) : 0;
|
||||||
|
authkeys = g_realloc_n(authkeys, nauthkeys + nkeys + 1, sizeof(char *));
|
||||||
|
|
||||||
|
/* zero out the memory for the reallocated buffer */
|
||||||
|
memset(authkeys + nauthkeys, 0, (nkeys + 1) * sizeof(char *));
|
||||||
|
|
||||||
|
/* Adds the keys */
|
||||||
|
for (k = keys; k != NULL; k = k->next) {
|
||||||
|
/* Check that the key doesn't already exist */
|
||||||
|
if (g_strv_contains((const gchar *const *)authkeys, k->value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
authkeys[nauthkeys++] = g_strdup(k->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the authkeys to the file. */
|
||||||
|
write_authkeys(userInfo, authkeys, errp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Removes an SSH key for a user
|
||||||
|
*
|
||||||
|
* parameters:
|
||||||
|
* username -> Username to remove the key from
|
||||||
|
* strList -> List of strings to remove
|
||||||
|
* errp -> Contains any errors that occur.
|
||||||
|
*/
|
||||||
|
void qmp_guest_ssh_remove_authorized_keys(const char *username, strList *keys,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
g_auto(PWindowsUserInfo) userInfo = NULL;
|
||||||
|
g_autofree struct passwd *p = NULL;
|
||||||
|
g_autofree GStrv new_keys = NULL; /* do not own the strings */
|
||||||
|
g_auto(GStrv) authkeys = NULL;
|
||||||
|
GStrv a;
|
||||||
|
size_t nkeys = 0;
|
||||||
|
|
||||||
|
/* Validates the keys passed in by the user */
|
||||||
|
if (!check_openssh_pub_keys(keys, NULL, errp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gets user information */
|
||||||
|
if (!get_user_info(&userInfo, username, errp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads the authkeys for the user */
|
||||||
|
authkeys = read_authkeys(userInfo->authorizedKeyFile, errp);
|
||||||
|
if (authkeys == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a new buffer to hold the keys */
|
||||||
|
new_keys = g_new0(char *, g_strv_length(authkeys) + 1);
|
||||||
|
for (a = authkeys; *a != NULL; a++) {
|
||||||
|
strList *k;
|
||||||
|
|
||||||
|
/* Filters out keys that are equal to ones the user specified. */
|
||||||
|
for (k = keys; k != NULL; k = k->next) {
|
||||||
|
if (g_str_equal(k->value, *a)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k != NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_keys[nkeys++] = *a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the new authkeys to the file. */
|
||||||
|
write_authkeys(userInfo, new_keys, errp);
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Header file for commands-windows-ssh.c
|
||||||
|
*
|
||||||
|
* Copyright Schweitzer Engineering Laboratories. 2024
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Aidan Leuck <aidan_leuck@selinc.com>
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
|
* See the COPYING file in the top-level directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <glib/gstrfuncs.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
typedef struct WindowsUserInfo {
|
||||||
|
char *sshDirectory;
|
||||||
|
char *authorizedKeyFile;
|
||||||
|
char *username;
|
||||||
|
char *SSID;
|
||||||
|
bool isAdmin;
|
||||||
|
} WindowsUserInfo;
|
||||||
|
|
||||||
|
typedef WindowsUserInfo *PWindowsUserInfo;
|
||||||
|
|
||||||
|
void free_userInfo(PWindowsUserInfo info);
|
||||||
|
G_DEFINE_AUTO_CLEANUP_FREE_FUNC(PWindowsUserInfo, free_userInfo, NULL);
|
|
@ -66,13 +66,15 @@ qga_ss.add(files(
|
||||||
'guest-agent-command-state.c',
|
'guest-agent-command-state.c',
|
||||||
'main.c',
|
'main.c',
|
||||||
'cutils.c',
|
'cutils.c',
|
||||||
|
'commands-common-ssh.c'
|
||||||
))
|
))
|
||||||
if host_os == 'windows'
|
if host_os == 'windows'
|
||||||
qga_ss.add(files(
|
qga_ss.add(files(
|
||||||
'channel-win32.c',
|
'channel-win32.c',
|
||||||
'commands-win32.c',
|
'commands-win32.c',
|
||||||
'service-win32.c',
|
'service-win32.c',
|
||||||
'vss-win32.c'
|
'vss-win32.c',
|
||||||
|
'commands-windows-ssh.c'
|
||||||
))
|
))
|
||||||
else
|
else
|
||||||
qga_ss.add(files(
|
qga_ss.add(files(
|
||||||
|
@ -93,7 +95,7 @@ gen_tlb = []
|
||||||
qga_libs = []
|
qga_libs = []
|
||||||
if host_os == 'windows'
|
if host_os == 'windows'
|
||||||
qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', '-lwininet', '-liphlpapi', '-lnetapi32',
|
qga_libs += ['-lws2_32', '-lwinmm', '-lpowrprof', '-lwtsapi32', '-lwininet', '-liphlpapi', '-lnetapi32',
|
||||||
'-lsetupapi', '-lcfgmgr32']
|
'-lsetupapi', '-lcfgmgr32', '-luserenv']
|
||||||
if have_qga_vss
|
if have_qga_vss
|
||||||
qga_libs += ['-lole32', '-loleaut32', '-lshlwapi', '-lstdc++', '-Wl,--enable-stdcall-fixup']
|
qga_libs += ['-lole32', '-loleaut32', '-lshlwapi', '-lstdc++', '-Wl,--enable-stdcall-fixup']
|
||||||
subdir('vss-win32')
|
subdir('vss-win32')
|
||||||
|
@ -186,7 +188,7 @@ test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
|
||||||
# this when an alternative is implemented or when the underlying glib
|
# this when an alternative is implemented or when the underlying glib
|
||||||
# issue is identified/fix
|
# issue is identified/fix
|
||||||
if host_os != 'windows' and not get_option('fuzzing')
|
if host_os != 'windows' and not get_option('fuzzing')
|
||||||
srcs = [files('commands-posix-ssh.c')]
|
srcs = [files('commands-common-ssh.c', 'commands-posix-ssh.c')]
|
||||||
i = 0
|
i = 0
|
||||||
foreach output: qga_qapi_outputs
|
foreach output: qga_qapi_outputs
|
||||||
if output.startswith('qga-qapi-types') or output.startswith('qga-qapi-visit')
|
if output.startswith('qga-qapi-types') or output.startswith('qga-qapi-visit')
|
||||||
|
|
|
@ -1026,7 +1026,10 @@
|
||||||
#
|
#
|
||||||
# @used-bytes: file system used bytes (since 3.0)
|
# @used-bytes: file system used bytes (since 3.0)
|
||||||
#
|
#
|
||||||
# @total-bytes: non-root file system total bytes (since 3.0)
|
# @total-bytes: filesystem capacity in bytes for unprivileged users (since 3.0)
|
||||||
|
#
|
||||||
|
# @total-bytes-privileged: filesystem capacity in bytes for privileged users
|
||||||
|
# (since 9.1)
|
||||||
#
|
#
|
||||||
# @disk: an array of disk hardware information that the volume lies
|
# @disk: an array of disk hardware information that the volume lies
|
||||||
# on, which may be empty if the disk type is not supported
|
# on, which may be empty if the disk type is not supported
|
||||||
|
@ -1036,7 +1039,7 @@
|
||||||
{ 'struct': 'GuestFilesystemInfo',
|
{ 'struct': 'GuestFilesystemInfo',
|
||||||
'data': {'name': 'str', 'mountpoint': 'str', 'type': 'str',
|
'data': {'name': 'str', 'mountpoint': 'str', 'type': 'str',
|
||||||
'*used-bytes': 'uint64', '*total-bytes': 'uint64',
|
'*used-bytes': 'uint64', '*total-bytes': 'uint64',
|
||||||
'disk': ['GuestDiskAddress']} }
|
'*total-bytes-privileged': 'uint64', 'disk': ['GuestDiskAddress']} }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @guest-get-fsinfo:
|
# @guest-get-fsinfo:
|
||||||
|
@ -1567,9 +1570,8 @@
|
||||||
{ 'struct': 'GuestAuthorizedKeys',
|
{ 'struct': 'GuestAuthorizedKeys',
|
||||||
'data': {
|
'data': {
|
||||||
'keys': ['str']
|
'keys': ['str']
|
||||||
},
|
}
|
||||||
'if': 'CONFIG_POSIX' }
|
}
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# @guest-ssh-get-authorized-keys:
|
# @guest-ssh-get-authorized-keys:
|
||||||
|
@ -1585,8 +1587,8 @@
|
||||||
##
|
##
|
||||||
{ 'command': 'guest-ssh-get-authorized-keys',
|
{ 'command': 'guest-ssh-get-authorized-keys',
|
||||||
'data': { 'username': 'str' },
|
'data': { 'username': 'str' },
|
||||||
'returns': 'GuestAuthorizedKeys',
|
'returns': 'GuestAuthorizedKeys'
|
||||||
'if': 'CONFIG_POSIX' }
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# @guest-ssh-add-authorized-keys:
|
# @guest-ssh-add-authorized-keys:
|
||||||
|
@ -1604,8 +1606,8 @@
|
||||||
# Since: 5.2
|
# Since: 5.2
|
||||||
##
|
##
|
||||||
{ 'command': 'guest-ssh-add-authorized-keys',
|
{ 'command': 'guest-ssh-add-authorized-keys',
|
||||||
'data': { 'username': 'str', 'keys': ['str'], '*reset': 'bool' },
|
'data': { 'username': 'str', 'keys': ['str'], '*reset': 'bool' }
|
||||||
'if': 'CONFIG_POSIX' }
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# @guest-ssh-remove-authorized-keys:
|
# @guest-ssh-remove-authorized-keys:
|
||||||
|
@ -1622,8 +1624,8 @@
|
||||||
# Since: 5.2
|
# Since: 5.2
|
||||||
##
|
##
|
||||||
{ 'command': 'guest-ssh-remove-authorized-keys',
|
{ 'command': 'guest-ssh-remove-authorized-keys',
|
||||||
'data': { 'username': 'str', 'keys': ['str'] },
|
'data': { 'username': 'str', 'keys': ['str'] }
|
||||||
'if': 'CONFIG_POSIX' }
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# @GuestDiskStats:
|
# @GuestDiskStats:
|
||||||
|
|
Loading…
Reference in New Issue