diff mbox

[v2] qga: add guest-set-admin-password command

Message ID 1421078294-26234-1-git-send-email-berrange@redhat.com
State New
Headers show

Commit Message

Daniel P. Berrangé Jan. 12, 2015, 3:58 p.m. UTC
Add a new 'guest-set-admin-password' command for changing the
root/administrator password. This command is needed to allow
OpenStack to support its API for changing the admin password
on a running guest.

Accepts either the raw password string:

$ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
   '{ "execute": "guest-set-admin-password", "arguments":
     { "crypted": false, "password": "12345678" } }'
  {"return":{}}

Or a pre-encrypted string (recommended)

$ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
   '{ "execute": "guest-set-admin-password", "arguments":
     { "crypted": true, "password":
        "$6$T9O/j/aGPrE...snip....rQoRN4F0.GG0MPjNUNyml." } }'

NB windows support is desirable, but not implemented in this
patch.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qga/commands-posix.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 qga/commands-win32.c |  6 ++++
 qga/qapi-schema.json | 19 +++++++++++
 3 files changed, 115 insertions(+)

Comments

Daniel P. Berrangé Feb. 2, 2015, 3:45 p.m. UTC | #1
Ping

On Mon, Jan 12, 2015 at 03:58:14PM +0000, Daniel P. Berrange wrote:
> Add a new 'guest-set-admin-password' command for changing the
> root/administrator password. This command is needed to allow
> OpenStack to support its API for changing the admin password
> on a running guest.
> 
> Accepts either the raw password string:
> 
> $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
>    '{ "execute": "guest-set-admin-password", "arguments":
>      { "crypted": false, "password": "12345678" } }'
>   {"return":{}}
> 
> Or a pre-encrypted string (recommended)
> 
> $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
>    '{ "execute": "guest-set-admin-password", "arguments":
>      { "crypted": true, "password":
>         "$6$T9O/j/aGPrE...snip....rQoRN4F0.GG0MPjNUNyml." } }'
> 
> NB windows support is desirable, but not implemented in this
> patch.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  qga/commands-posix.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  qga/commands-win32.c |  6 ++++
>  qga/qapi-schema.json | 19 +++++++++++
>  3 files changed, 115 insertions(+)
> 
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index f6f3e3c..4887889 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -1875,6 +1875,90 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
>      return processed;
>  }
>  
> +void qmp_guest_set_admin_password(bool crypted, const char *password,
> +                                  Error **errp)
> +{
> +    Error *local_err = NULL;
> +    char *passwd_path = NULL;
> +    pid_t pid;
> +    int status;
> +    int datafd[2] = { -1, -1 };
> +    char *acctpw = g_strdup_printf("root:%s\n", password);
> +    size_t acctpwlen = strlen(acctpw);
> +
> +    if (strchr(password, '\n')) {
> +        error_setg(errp, "forbidden characters in new password");
> +        goto out;
> +    }
> +
> +    passwd_path = g_find_program_in_path("chpasswd");
> +
> +    if (!passwd_path) {
> +        error_setg(errp, "cannot find 'passwd' program in PATH");
> +        goto out;
> +    }
> +
> +    if (pipe(datafd) < 0) {
> +        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);
> +
> +        if (crypted) {
> +            execle(passwd_path, "chpasswd", "-e", NULL, environ);
> +        } else {
> +            execle(passwd_path, "chpasswd", NULL, environ);
> +        }
> +        _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], acctpw, acctpwlen) != acctpwlen) {
> +        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) {
> +        error_propagate(errp, local_err);
> +        goto out;
> +    }
> +
> +    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 admin password");
> +        goto out;
> +    }
> +
> +out:
> +    g_free(acctpw);
> +    g_free(passwd_path);
> +    if (datafd[0] != -1) {
> +        close(datafd[0]);
> +    }
> +    if (datafd[1] != -1) {
> +        close(datafd[1]);
> +    }
> +}
> +
>  #else /* defined(__linux__) */
>  
>  void qmp_guest_suspend_disk(Error **errp)
> @@ -1910,6 +1994,12 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
>      return -1;
>  }
>  
> +void qmp_guest_set_admin_password(bool crypted, const char *password,
> +                                  Error **errp)
> +{
> +    error_set(errp, QERR_UNSUPPORTED);
> +}
> +
>  #endif
>  
>  #if !defined(CONFIG_FSFREEZE)
> diff --git a/qga/commands-win32.c b/qga/commands-win32.c
> index 3bcbeae..56854d5 100644
> --- a/qga/commands-win32.c
> +++ b/qga/commands-win32.c
> @@ -446,6 +446,12 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
>      return -1;
>  }
>  
> +void qmp_guest_set_admin_password(bool crypted, const char *password,
> +                                  Error **errp)
> +{
> +    error_set(errp, QERR_UNSUPPORTED);
> +}
> +
>  /* add unsupported commands to the blacklist */
>  GList *ga_command_blacklist_init(GList *blacklist)
>  {
> diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
> index 376e79f..25118e2 100644
> --- a/qga/qapi-schema.json
> +++ b/qga/qapi-schema.json
> @@ -738,3 +738,22 @@
>  ##
>  { 'command': 'guest-get-fsinfo',
>    'returns': ['GuestFilesystemInfo'] }
> +
> +##
> +# @guest-set-admin-password
> +#
> +# @crypted: true if password is already crypt()d, false if raw
> +# @password: the new password entry
> +#
> +# If the @crypted flag is true, it is the callers responsibility
> +# to ensure the correct crypt() encryption scheme is used. This
> +# command does not attempt to interpret or report on the encryption
> +# scheme. Refer to the documentation of the guest operating system
> +# in question to determine what is supported.
> +#
> +# Returns: Nothing on success.
> +#
> +# Since 2.3
> +##
> +{ 'command': 'guest-set-admin-password',
> +  'data': { 'crypted': 'bool', 'password': 'str' } }
> -- 
> 2.1.0
> 

Regards,
Daniel
Eric Blake Feb. 3, 2015, 10:16 p.m. UTC | #2
On 01/12/2015 08:58 AM, Daniel P. Berrange wrote:
> Add a new 'guest-set-admin-password' command for changing the
> root/administrator password. This command is needed to allow
> OpenStack to support its API for changing the admin password
> on a running guest.
> 
> Accepts either the raw password string:
> 
> $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
>    '{ "execute": "guest-set-admin-password", "arguments":
>      { "crypted": false, "password": "12345678" } }'
>   {"return":{}}
> 
> Or a pre-encrypted string (recommended)
> 
> $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
>    '{ "execute": "guest-set-admin-password", "arguments":
>      { "crypted": true, "password":
>         "$6$T9O/j/aGPrE...snip....rQoRN4F0.GG0MPjNUNyml." } }'
> 
> NB windows support is desirable, but not implemented in this
> patch.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  qga/commands-posix.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  qga/commands-win32.c |  6 ++++
>  qga/qapi-schema.json | 19 +++++++++++
>  3 files changed, 115 insertions(+)
> 

> +++ b/qga/qapi-schema.json
> @@ -738,3 +738,22 @@
>  ##
>  { 'command': 'guest-get-fsinfo',
>    'returns': ['GuestFilesystemInfo'] }
> +
> +##
> +# @guest-set-admin-password
> +#
> +# @crypted: true if password is already crypt()d, false if raw
> +# @password: the new password entry
> +#
> +# If the @crypted flag is true, it is the callers responsibility

s/callers/caller's/

> +# to ensure the correct crypt() encryption scheme is used. This
> +# command does not attempt to interpret or report on the encryption
> +# scheme. Refer to the documentation of the guest operating system
> +# in question to determine what is supported.
> +#
> +# Returns: Nothing on success.
> +#
> +# Since 2.3
> +##
> +{ 'command': 'guest-set-admin-password',
> +  'data': { 'crypted': 'bool', 'password': 'str' } }
> 

Normally, 'password':'str' means we are passing UTF8 JSON.  But what if
the desired password is NOT valid UTF8, but still valid to the end user
(for example, a user that intentionally wants a Latin1 encoded password
that uses 8-bit characters)?  In other interfaces, we've allowed an enum
that specifies whether a raw data string is 'utf8' or 'base64' encoded;
should we have such a parameter here?
Daniel P. Berrangé Feb. 4, 2015, 9:19 a.m. UTC | #3
On Tue, Feb 03, 2015 at 03:16:08PM -0700, Eric Blake wrote:
> On 01/12/2015 08:58 AM, Daniel P. Berrange wrote:
> > Add a new 'guest-set-admin-password' command for changing the
> > root/administrator password. This command is needed to allow
> > OpenStack to support its API for changing the admin password
> > on a running guest.
> > 
> > Accepts either the raw password string:
> > 
> > $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
> >    '{ "execute": "guest-set-admin-password", "arguments":
> >      { "crypted": false, "password": "12345678" } }'
> >   {"return":{}}
> > 
> > Or a pre-encrypted string (recommended)
> > 
> > $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
> >    '{ "execute": "guest-set-admin-password", "arguments":
> >      { "crypted": true, "password":
> >         "$6$T9O/j/aGPrE...snip....rQoRN4F0.GG0MPjNUNyml." } }'
> > 
> > NB windows support is desirable, but not implemented in this
> > patch.
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  qga/commands-posix.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  qga/commands-win32.c |  6 ++++
> >  qga/qapi-schema.json | 19 +++++++++++
> >  3 files changed, 115 insertions(+)
> > 
> 
> > +++ b/qga/qapi-schema.json
> > @@ -738,3 +738,22 @@
> >  ##
> >  { 'command': 'guest-get-fsinfo',
> >    'returns': ['GuestFilesystemInfo'] }
> > +
> > +##
> > +# @guest-set-admin-password
> > +#
> > +# @crypted: true if password is already crypt()d, false if raw
> > +# @password: the new password entry
> > +#
> > +# If the @crypted flag is true, it is the callers responsibility
> 
> s/callers/caller's/
> 
> > +# to ensure the correct crypt() encryption scheme is used. This
> > +# command does not attempt to interpret or report on the encryption
> > +# scheme. Refer to the documentation of the guest operating system
> > +# in question to determine what is supported.
> > +#
> > +# Returns: Nothing on success.
> > +#
> > +# Since 2.3
> > +##
> > +{ 'command': 'guest-set-admin-password',
> > +  'data': { 'crypted': 'bool', 'password': 'str' } }
> > 
> 
> Normally, 'password':'str' means we are passing UTF8 JSON.  But what if
> the desired password is NOT valid UTF8, but still valid to the end user
> (for example, a user that intentionally wants a Latin1 encoded password
> that uses 8-bit characters)?  In other interfaces, we've allowed an enum
> that specifies whether a raw data string is 'utf8' or 'base64' encoded;
> should we have such a parameter here?

IMHO, QEMU itself needs to avoid interpreting the password at all and
just pass the raw bytes through to the operating system specific
command as-is. ie we should be 8-bit pure in what we accept. This
probably argues for unconditionally using base64 encoded data for
the parameter.


Regards,
Daniel
Roman Kagan Feb. 4, 2015, 10:48 a.m. UTC | #4
On Mon, Jan 12, 2015 at 03:58:14PM +0000, Daniel P. Berrange wrote:
> Add a new 'guest-set-admin-password' command for changing the
> root/administrator password. This command is needed to allow
> OpenStack to support its API for changing the admin password
> on a running guest.
> 
> Accepts either the raw password string:
> 
> $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
>    '{ "execute": "guest-set-admin-password", "arguments":
>      { "crypted": false, "password": "12345678" } }'
>   {"return":{}}
> 
> Or a pre-encrypted string (recommended)
> 
> $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
>    '{ "execute": "guest-set-admin-password", "arguments":
>      { "crypted": true, "password":
>         "$6$T9O/j/aGPrE...snip....rQoRN4F0.GG0MPjNUNyml." } }'

Does it have to be a QMP command?  Wouldn't the recently (re-)submitted
guest-exec allow to do the same, by running "chpasswd" in the guest and
piping the username:password into its stdin?

Besides I think it makes sense to (optionally) pass the username, to
allow to change the password for arbitrary users.  This would make the
functionality useful for systems where root password plays no role as
root logins are disallowed, and the only access to root shell is via
sudo from a user belonging to a particular group (IIRC Ubuntu is usually
set up like that).

> NB windows support is desirable, but not implemented in this
> patch.

Yes Windows may have an issue with username here too, because the admin
user can be any user (and even "Administrator" can be localized).

Roman.
Daniel P. Berrangé Feb. 4, 2015, 10:52 a.m. UTC | #5
On Wed, Feb 04, 2015 at 01:48:40PM +0300, Roman Kagan wrote:
> On Mon, Jan 12, 2015 at 03:58:14PM +0000, Daniel P. Berrange wrote:
> > Add a new 'guest-set-admin-password' command for changing the
> > root/administrator password. This command is needed to allow
> > OpenStack to support its API for changing the admin password
> > on a running guest.
> > 
> > Accepts either the raw password string:
> > 
> > $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
> >    '{ "execute": "guest-set-admin-password", "arguments":
> >      { "crypted": false, "password": "12345678" } }'
> >   {"return":{}}
> > 
> > Or a pre-encrypted string (recommended)
> > 
> > $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
> >    '{ "execute": "guest-set-admin-password", "arguments":
> >      { "crypted": true, "password":
> >         "$6$T9O/j/aGPrE...snip....rQoRN4F0.GG0MPjNUNyml." } }'
> 
> Does it have to be a QMP command?  Wouldn't the recently (re-)submitted
> guest-exec allow to do the same, by running "chpasswd" in the guest and
> piping the username:password into its stdin?

guest-exec puts the burden on the calling application to figure out which
command to invoke and what syntax it has. This is really sucky for any
kind of cross-OS portability. ie windows is going to be completely different
from Linux, and even different UNIX variants are different to some extent.

I don't consider guest-exec to be something that managment applications
should *ever* use to build features around. It is just a useful mechanism
for human administrators to do ad-hoc interactions with guests.

> Besides I think it makes sense to (optionally) pass the username, to
> allow to change the password for arbitrary users.  This would make the
> functionality useful for systems where root password plays no role as
> root logins are disallowed, and the only access to root shell is via
> sudo from a user belonging to a particular group (IIRC Ubuntu is usually
> set up like that).

Yep, extending it to any username is a possibility if it is thought to
be useful

> 
> > NB windows support is desirable, but not implemented in this
> > patch.
> 
> Yes Windows may have an issue with username here too, because the admin
> user can be any user (and even "Administrator" can be localized).

Regards,
Daniel
Olga Krishtal Feb. 4, 2015, 1:25 p.m. UTC | #6
On 12/01/15 18:58, Daniel P. Berrange wrote:
> Add a new 'guest-set-admin-password' command for changing the
> root/administrator password. This command is needed to allow
> OpenStack to support its API for changing the admin password
> on a running guest.
>
> Accepts either the raw password string:
>
> $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
>     '{ "execute": "guest-set-admin-password", "arguments":
>       { "crypted": false, "password": "12345678" } }'
>    {"return":{}}
>
> Or a pre-encrypted string (recommended)
>
> $ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
>     '{ "execute": "guest-set-admin-password", "arguments":
>       { "crypted": true, "password":
>          "$6$T9O/j/aGPrE...snip....rQoRN4F0.GG0MPjNUNyml." } }'
>
> NB windows support is desirable, but not implemented in this
> patch.
>
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>   qga/commands-posix.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>   qga/commands-win32.c |  6 ++++
>   qga/qapi-schema.json | 19 +++++++++++
>   3 files changed, 115 insertions(+)
>
> diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> index f6f3e3c..4887889 100644
> --- a/qga/commands-posix.c
> +++ b/qga/commands-posix.c
> @@ -1875,6 +1875,90 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
>       return processed;
>   }
>   
> +void qmp_guest_set_admin_password(bool crypted, const char *password,
> +                                  Error **errp)
> +{
> +    Error *local_err = NULL;
> +    char *passwd_path = NULL;
> +    pid_t pid;
> +    int status;
> +    int datafd[2] = { -1, -1 };
> +    char *acctpw = g_strdup_printf("root:%s\n", password);
> +    size_t acctpwlen = strlen(acctpw);
> +
> +    if (strchr(password, '\n')) {
> +        error_setg(errp, "forbidden characters in new password");
> +        goto out;
> +    }
> +
> +    passwd_path = g_find_program_in_path("chpasswd");
> +
> +    if (!passwd_path) {
> +        error_setg(errp, "cannot find 'passwd' program in PATH");
> +        goto out;
> +    }
> +
> +    if (pipe(datafd) < 0) {
> +        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);
> +
> +        if (crypted) {
> +            execle(passwd_path, "chpasswd", "-e", NULL, environ);
> +        } else {
> +            execle(passwd_path, "chpasswd", NULL, environ);
> +        }
> +        _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], acctpw, acctpwlen) != acctpwlen) {
> +        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) {
> +        error_propagate(errp, local_err);
> +        goto out;
> +    }
> +
> +    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 admin password");
> +        goto out;
> +    }
> +
> +out:
> +    g_free(acctpw);
> +    g_free(passwd_path);
> +    if (datafd[0] != -1) {
> +        close(datafd[0]);
> +    }
> +    if (datafd[1] != -1) {
> +        close(datafd[1]);
> +    }
> +}
> +
>   #else /* defined(__linux__) */
>   
>   void qmp_guest_suspend_disk(Error **errp)
> @@ -1910,6 +1994,12 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
>       return -1;
>   }
>   
> +void qmp_guest_set_admin_password(bool crypted, const char *password,
> +                                  Error **errp)
> +{
> +    error_set(errp, QERR_UNSUPPORTED);
> +}
> +
>   #endif
>   
>   #if !defined(CONFIG_FSFREEZE)
> diff --git a/qga/commands-win32.c b/qga/commands-win32.c
> index 3bcbeae..56854d5 100644
> --- a/qga/commands-win32.c
> +++ b/qga/commands-win32.c
> @@ -446,6 +446,12 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
>       return -1;
>   }
>   
> +void qmp_guest_set_admin_password(bool crypted, const char *password,
> +                                  Error **errp)
> +{
> +    error_set(errp, QERR_UNSUPPORTED);
> +}
> +
>   /* add unsupported commands to the blacklist */
>   GList *ga_command_blacklist_init(GList *blacklist)
>   {
> diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
> index 376e79f..25118e2 100644
> --- a/qga/qapi-schema.json
> +++ b/qga/qapi-schema.json
> @@ -738,3 +738,22 @@
>   ##
>   { 'command': 'guest-get-fsinfo',
>     'returns': ['GuestFilesystemInfo'] }
> +
> +##
> +# @guest-set-admin-password
> +#
> +# @crypted: true if password is already crypt()d, false if raw
> +# @password: the new password entry
> +#
> +# If the @crypted flag is true, it is the callers responsibility
> +# to ensure the correct crypt() encryption scheme is used. This
> +# command does not attempt to interpret or report on the encryption
> +# scheme. Refer to the documentation of the guest operating system
> +# in question to determine what is supported.
> +#
> +# Returns: Nothing on success.
> +#
> +# Since 2.3
> +##
> +{ 'command': 'guest-set-admin-password',
> +  'data': { 'crypted': 'bool', 'password': 'str' } }
While implementing such functionality for Windows NT we can suffer from 
particular problem:
-The password must be passed to WinApi function as a plain text, so we 
would need entire the encryption mechanism if we used ctypted: true
Daniel P. Berrangé Feb. 4, 2015, 2:10 p.m. UTC | #7
On Wed, Feb 04, 2015 at 04:25:47PM +0300, Olga Krishtal wrote:
> On 12/01/15 18:58, Daniel P. Berrange wrote:
> >Add a new 'guest-set-admin-password' command for changing the
> >root/administrator password. This command is needed to allow
> >OpenStack to support its API for changing the admin password
> >on a running guest.
> >
> >Accepts either the raw password string:
> >
> >$ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
> >    '{ "execute": "guest-set-admin-password", "arguments":
> >      { "crypted": false, "password": "12345678" } }'
> >   {"return":{}}
> >
> >Or a pre-encrypted string (recommended)
> >
> >$ virsh -c qemu:///system  qemu-agent-command f21x86_64 \
> >    '{ "execute": "guest-set-admin-password", "arguments":
> >      { "crypted": true, "password":
> >         "$6$T9O/j/aGPrE...snip....rQoRN4F0.GG0MPjNUNyml." } }'
> >
> >NB windows support is desirable, but not implemented in this
> >patch.
> >
> >Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> >---
> >  qga/commands-posix.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  qga/commands-win32.c |  6 ++++
> >  qga/qapi-schema.json | 19 +++++++++++
> >  3 files changed, 115 insertions(+)
> >
> >diff --git a/qga/commands-posix.c b/qga/commands-posix.c
> >index f6f3e3c..4887889 100644
> >--- a/qga/commands-posix.c
> >+++ b/qga/commands-posix.c
> >@@ -1875,6 +1875,90 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
> >      return processed;
> >  }
> >+void qmp_guest_set_admin_password(bool crypted, const char *password,
> >+                                  Error **errp)
> >+{
> >+    Error *local_err = NULL;
> >+    char *passwd_path = NULL;
> >+    pid_t pid;
> >+    int status;
> >+    int datafd[2] = { -1, -1 };
> >+    char *acctpw = g_strdup_printf("root:%s\n", password);
> >+    size_t acctpwlen = strlen(acctpw);
> >+
> >+    if (strchr(password, '\n')) {
> >+        error_setg(errp, "forbidden characters in new password");
> >+        goto out;
> >+    }
> >+
> >+    passwd_path = g_find_program_in_path("chpasswd");
> >+
> >+    if (!passwd_path) {
> >+        error_setg(errp, "cannot find 'passwd' program in PATH");
> >+        goto out;
> >+    }
> >+
> >+    if (pipe(datafd) < 0) {
> >+        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);
> >+
> >+        if (crypted) {
> >+            execle(passwd_path, "chpasswd", "-e", NULL, environ);
> >+        } else {
> >+            execle(passwd_path, "chpasswd", NULL, environ);
> >+        }
> >+        _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], acctpw, acctpwlen) != acctpwlen) {
> >+        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) {
> >+        error_propagate(errp, local_err);
> >+        goto out;
> >+    }
> >+
> >+    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 admin password");
> >+        goto out;
> >+    }
> >+
> >+out:
> >+    g_free(acctpw);
> >+    g_free(passwd_path);
> >+    if (datafd[0] != -1) {
> >+        close(datafd[0]);
> >+    }
> >+    if (datafd[1] != -1) {
> >+        close(datafd[1]);
> >+    }
> >+}
> >+
> >  #else /* defined(__linux__) */
> >  void qmp_guest_suspend_disk(Error **errp)
> >@@ -1910,6 +1994,12 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
> >      return -1;
> >  }
> >+void qmp_guest_set_admin_password(bool crypted, const char *password,
> >+                                  Error **errp)
> >+{
> >+    error_set(errp, QERR_UNSUPPORTED);
> >+}
> >+
> >  #endif
> >  #if !defined(CONFIG_FSFREEZE)
> >diff --git a/qga/commands-win32.c b/qga/commands-win32.c
> >index 3bcbeae..56854d5 100644
> >--- a/qga/commands-win32.c
> >+++ b/qga/commands-win32.c
> >@@ -446,6 +446,12 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
> >      return -1;
> >  }
> >+void qmp_guest_set_admin_password(bool crypted, const char *password,
> >+                                  Error **errp)
> >+{
> >+    error_set(errp, QERR_UNSUPPORTED);
> >+}
> >+
> >  /* add unsupported commands to the blacklist */
> >  GList *ga_command_blacklist_init(GList *blacklist)
> >  {
> >diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
> >index 376e79f..25118e2 100644
> >--- a/qga/qapi-schema.json
> >+++ b/qga/qapi-schema.json
> >@@ -738,3 +738,22 @@
> >  ##
> >  { 'command': 'guest-get-fsinfo',
> >    'returns': ['GuestFilesystemInfo'] }
> >+
> >+##
> >+# @guest-set-admin-password
> >+#
> >+# @crypted: true if password is already crypt()d, false if raw
> >+# @password: the new password entry
> >+#
> >+# If the @crypted flag is true, it is the callers responsibility
> >+# to ensure the correct crypt() encryption scheme is used. This
> >+# command does not attempt to interpret or report on the encryption
> >+# scheme. Refer to the documentation of the guest operating system
> >+# in question to determine what is supported.
> >+#
> >+# Returns: Nothing on success.
> >+#
> >+# Since 2.3
> >+##
> >+{ 'command': 'guest-set-admin-password',
> >+  'data': { 'crypted': 'bool', 'password': 'str' } }
> While implementing such functionality for Windows NT we can suffer from
> particular problem:
> -The password must be passed to WinApi function as a plain text, so we would
> need entire the encryption mechanism if we used ctypted: true

In that scenario, I'd suggest the windows impl of the command simply report
an error if the flag was crypted==true. QEMU shouldn't try to interpret the
data in any way IMHO, so that'd rule out decryption of any kind. In any
case, 'crypt' as a concept is all about doing a one-way hash so you can't
decrypt regardless.

Regards,
Daniel
diff mbox

Patch

diff --git a/qga/commands-posix.c b/qga/commands-posix.c
index f6f3e3c..4887889 100644
--- a/qga/commands-posix.c
+++ b/qga/commands-posix.c
@@ -1875,6 +1875,90 @@  int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
     return processed;
 }
 
+void qmp_guest_set_admin_password(bool crypted, const char *password,
+                                  Error **errp)
+{
+    Error *local_err = NULL;
+    char *passwd_path = NULL;
+    pid_t pid;
+    int status;
+    int datafd[2] = { -1, -1 };
+    char *acctpw = g_strdup_printf("root:%s\n", password);
+    size_t acctpwlen = strlen(acctpw);
+
+    if (strchr(password, '\n')) {
+        error_setg(errp, "forbidden characters in new password");
+        goto out;
+    }
+
+    passwd_path = g_find_program_in_path("chpasswd");
+
+    if (!passwd_path) {
+        error_setg(errp, "cannot find 'passwd' program in PATH");
+        goto out;
+    }
+
+    if (pipe(datafd) < 0) {
+        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);
+
+        if (crypted) {
+            execle(passwd_path, "chpasswd", "-e", NULL, environ);
+        } else {
+            execle(passwd_path, "chpasswd", NULL, environ);
+        }
+        _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], acctpw, acctpwlen) != acctpwlen) {
+        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) {
+        error_propagate(errp, local_err);
+        goto out;
+    }
+
+    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 admin password");
+        goto out;
+    }
+
+out:
+    g_free(acctpw);
+    g_free(passwd_path);
+    if (datafd[0] != -1) {
+        close(datafd[0]);
+    }
+    if (datafd[1] != -1) {
+        close(datafd[1]);
+    }
+}
+
 #else /* defined(__linux__) */
 
 void qmp_guest_suspend_disk(Error **errp)
@@ -1910,6 +1994,12 @@  int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
     return -1;
 }
 
+void qmp_guest_set_admin_password(bool crypted, const char *password,
+                                  Error **errp)
+{
+    error_set(errp, QERR_UNSUPPORTED);
+}
+
 #endif
 
 #if !defined(CONFIG_FSFREEZE)
diff --git a/qga/commands-win32.c b/qga/commands-win32.c
index 3bcbeae..56854d5 100644
--- a/qga/commands-win32.c
+++ b/qga/commands-win32.c
@@ -446,6 +446,12 @@  int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
     return -1;
 }
 
+void qmp_guest_set_admin_password(bool crypted, const char *password,
+                                  Error **errp)
+{
+    error_set(errp, QERR_UNSUPPORTED);
+}
+
 /* add unsupported commands to the blacklist */
 GList *ga_command_blacklist_init(GList *blacklist)
 {
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 376e79f..25118e2 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -738,3 +738,22 @@ 
 ##
 { 'command': 'guest-get-fsinfo',
   'returns': ['GuestFilesystemInfo'] }
+
+##
+# @guest-set-admin-password
+#
+# @crypted: true if password is already crypt()d, false if raw
+# @password: the new password entry
+#
+# If the @crypted flag is true, it is the callers responsibility
+# to ensure the correct crypt() encryption scheme is used. This
+# command does not attempt to interpret or report on the encryption
+# scheme. Refer to the documentation of the guest operating system
+# in question to determine what is supported.
+#
+# Returns: Nothing on success.
+#
+# Since 2.3
+##
+{ 'command': 'guest-set-admin-password',
+  'data': { 'crypted': 'bool', 'password': 'str' } }