Hi, currently /etc/shadow is edited manually when needed (i.e. when setting the password for an user), and it is not changed when removing users. Import the upstream shadow.aug (currently in their development serie, but not part of any released version yet), and use it only when the augeas version is less than a potential 1.2.1 (covering also the case when the new version is just 1.3.0). Pino Toscano (5): daemon: make aug_close cleanup available for all daemon: add a way to check for the version of augeas appliance: daemon: import and use upstream shadow lens sysprep: remove (now obsolete) comment about /etc/shadow lens customize: use augeas to change passwords appliance/Makefile.am | 3 +- appliance/guestfs_shadow.aug | 72 ++++++++++++++++ customize/password.ml | 64 +++++++------- daemon/augeas.c | 137 +++++++++++++++++++++++++++++- daemon/daemon.h | 19 ++++- daemon/guestfsd.c | 11 +++ daemon/lvm-filter.c | 16 ---- sysprep/sysprep_operation_user_account.ml | 4 - 8 files changed, 268 insertions(+), 58 deletions(-) create mode 100644 appliance/guestfs_shadow.aug -- 1.9.3
Pino Toscano
2014-Sep-04 15:18 UTC
[Libguestfs] [PATCH 1/5] daemon: make aug_close cleanup available for all
Just code motion, no behaviour changes. --- daemon/daemon.h | 3 +++ daemon/guestfsd.c | 11 +++++++++++ daemon/lvm-filter.c | 16 ---------------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/daemon/daemon.h b/daemon/daemon.h index d90b3e7..b9e7402 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -176,6 +176,7 @@ extern void cleanup_free (void *ptr); extern void cleanup_free_string_list (void *ptr); extern void cleanup_unlink_free (void *ptr); extern void cleanup_close (void *ptr); +extern void cleanup_aug_close (void *ptr); /*-- in names.c (auto-generated) --*/ extern const char *function_names[]; @@ -426,11 +427,13 @@ is_zero (const char *buffer, size_t size) __attribute__((cleanup(cleanup_free_string_list))) #define CLEANUP_UNLINK_FREE __attribute__((cleanup(cleanup_unlink_free))) #define CLEANUP_CLOSE __attribute__((cleanup(cleanup_close))) +#define CLEANUP_AUG_CLOSE __attribute__((cleanup(cleanup_aug_close))) #else #define CLEANUP_FREE #define CLEANUP_FREE_STRING_LIST #define CLEANUP_UNLINK_FREE #define CLEANUP_CLOSE +#define CLEANUP_AUG_CLOSE #endif #endif /* GUESTFSD_DAEMON_H */ diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c index 321544f..34a47ab 100644 --- a/daemon/guestfsd.c +++ b/daemon/guestfsd.c @@ -47,6 +47,8 @@ # include <printf.h> #endif +#include <augeas.h> + #include "sockets.h" #include "c-ctype.h" #include "ignore-value.h" @@ -1511,3 +1513,12 @@ cleanup_close (void *ptr) if (fd >= 0) close (fd); } + +void +cleanup_aug_close (void *ptr) +{ + augeas *aug = * (augeas **) ptr; + + if (aug != NULL) + aug_close (aug); +} diff --git a/daemon/lvm-filter.c b/daemon/lvm-filter.c index 3bab9bf..3b117c5 100644 --- a/daemon/lvm-filter.c +++ b/daemon/lvm-filter.c @@ -33,22 +33,6 @@ #include "daemon.h" #include "actions.h" -#ifdef HAVE_ATTRIBUTE_CLEANUP -#define CLEANUP_AUG_CLOSE __attribute__((cleanup(cleanup_aug_close))) - -static void -cleanup_aug_close (void *ptr) -{ - augeas *aug = * (augeas **) ptr; - - if (aug != NULL) - aug_close (aug); -} - -#else -#define CLEANUP_AUG_CLOSE -#endif - GUESTFSD_EXT_CMD(str_lvm, lvm); GUESTFSD_EXT_CMD(str_cp, cp); GUESTFSD_EXT_CMD(str_rm, rm); -- 1.9.3
Pino Toscano
2014-Sep-04 15:18 UTC
[Libguestfs] [PATCH 2/5] daemon: add a way to check for the version of augeas
Query augeas for its version when required, i.e. only once when using the new augeas_is_version function. --- daemon/augeas.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ daemon/daemon.h | 16 +++++++- 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/daemon/augeas.c b/daemon/augeas.c index 74f3ba7..8dc6a7c 100644 --- a/daemon/augeas.c +++ b/daemon/augeas.c @@ -24,10 +24,46 @@ #include <unistd.h> #include <augeas.h> +#include <pcre.h> #include "daemon.h" #include "actions.h" #include "optgroups.h" +#include "xstrtol.h" + +#ifdef HAVE_ATTRIBUTE_CLEANUP +#define CLEANUP_PCRE_FREE __attribute__((cleanup(cleanup_pcre_free))) + +static void +cleanup_pcre_free (void *ptr) +{ + pcre *re = * (pcre **) ptr; + + if (re != NULL) + pcre_free (re); +} + +#else +#define CLEANUP_PCRE_FREE +#endif + +#define FPRINTF_AUGEAS_ERROR(aug,fs,...) \ + do { \ + int code = aug_error (aug); \ + if (code == AUG_ENOMEM) \ + reply_with_error (fs ": augeas out of memory", ##__VA_ARGS__); \ + else { \ + const char *message = aug_error_message (aug); \ + const char *minor = aug_error_minor_message (aug); \ + const char *details = aug_error_details (aug); \ + fprintf (stderr, fs ": %s%s%s%s%s", ##__VA_ARGS__, \ + message, \ + minor ? ": " : "", minor ? minor : "", \ + details ? ": " : "", details ? details : ""); \ + } \ + } while (0) + +int augeas_version; /* The Augeas handle. We maintain a single handle per daemon, which * is all that is necessary and reduces the complexity of the API @@ -35,6 +71,86 @@ */ static augeas *aug = NULL; +void +aug_read_version (void) +{ + CLEANUP_AUG_CLOSE augeas *ah = NULL; + int r; + const char *str; + CLEANUP_PCRE_FREE pcre *re = NULL; + const char *errptr; + int erroffset; + size_t len; +#define N_MATCHES 4 + int vec[N_MATCHES * 4]; + unsigned long int major = 0, minor = 0, patch = 0; + + if (augeas_version != 0) + return; + + /* Optimization: do not load the files nor the lenses, since we are + * only interested in the version. + */ + ah = aug_init ("/", NULL, AUG_NO_ERR_CLOSE | AUG_NO_LOAD | AUG_NO_STDINC); + if (!ah) { + FPRINTF_AUGEAS_ERROR (ah, "augeas initialization failed"); + return; + } + + if (aug_error (ah) != AUG_NOERROR) { + FPRINTF_AUGEAS_ERROR (ah, "aug_init"); + return; + } + + r = aug_get (ah, "/augeas/version", &str); + if (r != 1) { + FPRINTF_AUGEAS_ERROR (ah, "aug_get"); + return; + } + + re = pcre_compile ("(\\d+)\\.(\\d+)(\\.(\\d+))?", + 0, &errptr, &erroffset, NULL); + if (re == NULL) { + fprintf (stderr, "cannot compile the augeas version regexp\n"); + return; + } + + len = strlen (str); + r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof (vec) / sizeof (vec[0])); + if (r == PCRE_ERROR_NOMATCH) { + fprintf (stderr, "cannot match the version string in '%s'\n", str); + return; + } + + if (r > 1) { + if (xstrtoul (&str[vec[2]], NULL, 10, &major, NULL) != LONGINT_OK) { + fprintf (stderr, "could not parse '%*s' as integer\n", + vec[3]-vec[2], &str[vec[2]]); + return; + } + } + if (r > 2) { + if (xstrtoul (&str[vec[4]], NULL, 10, &minor, NULL) != LONGINT_OK) { + fprintf (stderr, "could not parse '%*s' as integer\n", + vec[5]-vec[4], &str[vec[4]]); + return; + } + } + if (r > 4) { + if (xstrtoul (&str[vec[8]], NULL, 10, &patch, NULL) != LONGINT_OK) { + fprintf (stderr, "could not parse '%*s' as integer\n", + vec[9]-vec[8], &str[vec[8]]); + return; + } + } + + if (verbose) + fprintf (stderr, "augeas version: %ld.%ld.%ld\n", major, minor, patch); + + augeas_version = (int) ((major << 16) | (minor << 8) | patch); +#undef N_MATCHES +} + /* Clean up the augeas handle on daemon exit. */ void aug_finalize (void) __attribute__((destructor)); void diff --git a/daemon/daemon.h b/daemon/daemon.h index b9e7402..0ccbc9e 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -228,8 +228,22 @@ extern void copy_lvm (void); /*-- in zero.c --*/ extern void wipe_device_before_mkfs (const char *device); -/*-- in augeas.c, hivex.c, journal.c --*/ +/*-- in augeas.c --*/ +extern void aug_read_version (void); extern void aug_finalize (void); + +/* The version of augeas, saved as: + * (MAJOR << 16) | (MINOR << 8) | PATCH + */ +extern int augeas_version; +static inline int +augeas_is_version (int major, int minor, int patch) +{ + aug_read_version (); /* Lazy version reading. */ + return augeas_version >= ((major << 16) | (minor << 8) | patch); +} + +/*-- hivex.c, journal.c --*/ extern void hivex_finalize (void); extern void journal_finalize (void); -- 1.9.3
Pino Toscano
2014-Sep-04 15:18 UTC
[Libguestfs] [PATCH 3/5] appliance: daemon: import and use upstream shadow lens
Import the upstream lens for the shadow file, just with a different identifier and not matching /etc/shadow by default. Instead, apply a transformation to have it match /etc/shadow only if the version of augeas is at least 1.2.1 [1]. [1] While the last upstream version is 1.2.0, all the development seems to happen in master, so whatever the next version is going to be numbered (e.g. 1.2.1 or 1.3.0), the check will be fine anyway. --- appliance/Makefile.am | 3 +- appliance/guestfs_shadow.aug | 72 ++++++++++++++++++++++++++++++++++++++++++++ daemon/augeas.c | 21 ++++++++++++- 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 appliance/guestfs_shadow.aug diff --git a/appliance/Makefile.am b/appliance/Makefile.am index bb0c0e8..b8377af 100644 --- a/appliance/Makefile.am +++ b/appliance/Makefile.am @@ -75,13 +75,14 @@ packagelist: packagelist.in Makefile cmp -s $@ $@-t || mv $@-t $@ rm -f $@-t -supermin.d/daemon.tar.gz: ../daemon/guestfsd guestfsd.suppressions guestfs_lvm_conf.aug +supermin.d/daemon.tar.gz: ../daemon/guestfsd guestfsd.suppressions guestfs_lvm_conf.aug guestfs_shadow.aug rm -f $@ $@-t rm -rf tmp-d mkdir -p tmp-d$(DAEMON_SUPERMIN_DIR) tmp-d/etc tmp-d/usr/share/guestfs ln ../daemon/guestfsd tmp-d$(DAEMON_SUPERMIN_DIR)/guestfsd ln $(srcdir)/guestfsd.suppressions tmp-d/etc/guestfsd.suppressions ln $(srcdir)/guestfs_lvm_conf.aug tmp-d/usr/share/guestfs/guestfs_lvm_conf.aug + ln $(srcdir)/guestfs_shadow.aug tmp-d/usr/share/guestfs/guestfs_shadow.aug ( cd tmp-d && tar zcf - * ) > $@-t rm -r tmp-d mv $@-t $@ diff --git a/appliance/guestfs_shadow.aug b/appliance/guestfs_shadow.aug new file mode 100644 index 0000000..2fbf455 --- /dev/null +++ b/appliance/guestfs_shadow.aug @@ -0,0 +1,72 @@ +(* + Module: Shadow + Parses /etc/shadow + + Author: Lorenzo M. Catucci <catucci@ccd.uniroma2.it> + + Original Author: Free Ekanayaka <free@64studio.com> + + About: Reference + + - man 5 shadow + - man 3 getspnam + + About: License + This file is licensed under the LGPL v2+, like the rest of Augeas. + + About: + + Each line in the shadow files represents the additional shadow-defined attributes + for the corresponding user, as defined in the passwd file. + +*) + +module Guestfs_Shadow + + autoload xfm + +(************************************************************************ + * USEFUL PRIMITIVES + *************************************************************************) + +let eol = Util.eol +let comment = Util.comment +let empty = Util.empty +let dels = Util.del_str + +let colon = Sep.colon + +let word = Rx.word +let integer = Rx.integer + +let sto_to_col = Passwd.sto_to_col +let sto_to_eol = Passwd.sto_to_eol + +(************************************************************************ + * Group: ENTRIES + *************************************************************************) + +(* View: entry *) +let entry = [ key word + . colon + . [ label "password" . sto_to_col? . colon ] + . [ label "lastchange_date" . store integer? . colon ] + . [ label "minage_days" . store integer? . colon ] + . [ label "maxage_days" . store integer? . colon ] + . [ label "warn_days" . store integer? . colon ] + . [ label "inactive_days" . store integer? . colon ] + . [ label "expire_date" . store integer? . colon ] + . [ label "flag" . store integer? ] + . eol ] + +(************************************************************************ + * LENS + *************************************************************************) + +let lns = (comment|empty|entry) * + +let filter + = incl "/shadow" + . Util.stdexcl + +let xfm = transform lns filter diff --git a/daemon/augeas.c b/daemon/augeas.c index 8dc6a7c..988deed 100644 --- a/daemon/augeas.c +++ b/daemon/augeas.c @@ -189,7 +189,7 @@ do_aug_init (const char *root, int flags) } /* Pass AUG_NO_ERR_CLOSE so we can display detailed errors. */ - aug = aug_init (buf, NULL, flags | AUG_NO_ERR_CLOSE); + aug = aug_init (buf, "/usr/share/guestfs/", flags | AUG_NO_ERR_CLOSE); if (!aug) { reply_with_error ("augeas initialization failed"); @@ -203,6 +203,25 @@ do_aug_init (const char *root, int flags) return -1; } + if (!augeas_is_version (1, 2, 1)) { + int r = aug_transform (aug, "guestfs_shadow", "/etc/shadow", + 0 /* = included */); + if (r == -1) { + AUGEAS_ERROR ("aug_transform"); + aug_close (aug); + aug = NULL; + return -1; + } + + /* If aug_load was implicitly called, reload the handle. */ + if ((flags & AUG_NO_LOAD) == 0) { + if (aug_load (aug) == -1) { + AUGEAS_ERROR ("aug_load"); + return -1; + } + } + } + return 0; } -- 1.9.3
Pino Toscano
2014-Sep-04 15:18 UTC
[Libguestfs] [PATCH 4/5] sysprep: remove (now obsolete) comment about /etc/shadow lens
We are either using the upstream lens, or our copy of it, to handle /etc/shadow, so now removing entries from it works. --- sysprep/sysprep_operation_user_account.ml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sysprep/sysprep_operation_user_account.ml b/sysprep/sysprep_operation_user_account.ml index cdb0bfd..872d8bc 100644 --- a/sysprep/sysprep_operation_user_account.ml +++ b/sysprep/sysprep_operation_user_account.ml @@ -93,10 +93,6 @@ let user_account_perform ~verbose ~quiet g root side_effects username; None in g#aug_rm userpath; - (* XXX Augeas doesn't yet have a lens for /etc/shadow, so the - * next line currently does nothing, but should start to - * work in a future version. - *) g#aug_rm (sprintf "/files/etc/shadow/%s" username); g#aug_rm (sprintf "/files/etc/group/%s" username); match home_dir with -- 1.9.3
Pino Toscano
2014-Sep-04 15:18 UTC
[Libguestfs] [PATCH 5/5] customize: use augeas to change passwords
Make use of augeas to load and edit /etc/shadow, now that we have (either from upstream or by ourselves) a lens handling it. --- customize/password.ml | 64 +++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/customize/password.ml b/customize/password.ml index 84af0c3..3437bf0 100644 --- a/customize/password.ml +++ b/customize/password.ml @@ -87,42 +87,36 @@ let rec set_linux_passwords ~prog ?password_crypto g root passwords | None -> default_crypto ~prog g root | Some c -> c in - (* XXX Would like to use Augeas here, but Augeas doesn't support - * /etc/shadow (as of 1.1.0). - *) + g#aug_init "/" 0; + let users = Array.to_list (g#aug_ls "/files/etc/shadow") in + List.iter ( + fun userpath -> + let user + let i = String.rindex userpath '/' in + String.sub userpath (i+1) (String.length userpath -i-1) in + try + (* Each line is: "user:[!!]password:..." + * !! at the front of the password field means the account is locked. + *) + let selector = Hashtbl.find passwords user in + let pwfield + match selector with + | { pw_locked = locked; + pw_password = Password password } -> + (if locked then "!!" else "") ^ encrypt password crypto + | { pw_locked = locked; + pw_password = Random_password } -> + let password = make_random_password () in + printf (f_"Setting random password of %s to %s\n%!") + user password; + (if locked then "!!" else "") ^ encrypt password crypto + | { pw_locked = true; pw_password = Disabled_password } -> "!!*" + | { pw_locked = false; pw_password = Disabled_password } -> "*" in + g#aug_set (userpath ^ "/password") pwfield + with Not_found -> () + ) users; + g#aug_save (); - let shadow = Array.to_list (g#read_lines "/etc/shadow") in - let shadow - List.map ( - fun line -> - try - (* Each line is: "user:[!!]password:..." - * !! at the front of the password field means the account is locked. - * 'i' points to the first colon, 'j' to the second colon. - *) - let i = String.index line ':' in - let user = String.sub line 0 i in - let selector = Hashtbl.find passwords user in - let j = String.index_from line (i+1) ':' in - let rest = String.sub line j (String.length line - j) in - let pwfield - match selector with - | { pw_locked = locked; - pw_password = Password password } -> - (if locked then "!!" else "") ^ encrypt password crypto - | { pw_locked = locked; - pw_password = Random_password } -> - let password = make_random_password () in - printf (f_"Setting random password of %s to %s\n%!") - user password; - (if locked then "!!" else "") ^ encrypt password crypto - | { pw_locked = true; pw_password = Disabled_password } -> "!!*" - | { pw_locked = false; pw_password = Disabled_password } -> "*" in - user ^ ":" ^ pwfield ^ rest - with Not_found -> line - ) shadow in - - g#write "/etc/shadow" (String.concat "\n" shadow ^ "\n"); (* In virt-sysprep /.autorelabel will label it correctly. *) g#chmod 0 "/etc/shadow" -- 1.9.3
Pino Toscano
2014-Sep-04 15:26 UTC
[Libguestfs] [PATCH 4/5] sysprep: remove comment and notes about /etc/shadow lens
We are either using the upstream lens, or our copy of it, to handle /etc/shadow, so now removing entries from it works. --- sysprep/sysprep_operation_user_account.ml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sysprep/sysprep_operation_user_account.ml b/sysprep/sysprep_operation_user_account.ml index cdb0bfd..3ed1b6e 100644 --- a/sysprep/sysprep_operation_user_account.ml +++ b/sysprep/sysprep_operation_user_account.ml @@ -93,10 +93,6 @@ let user_account_perform ~verbose ~quiet g root side_effects username; None in g#aug_rm userpath; - (* XXX Augeas doesn't yet have a lens for /etc/shadow, so the - * next line currently does nothing, but should start to - * work in a future version. - *) g#aug_rm (sprintf "/files/etc/shadow/%s" username); g#aug_rm (sprintf "/files/etc/group/%s" username); match home_dir with @@ -118,10 +114,6 @@ The \"root\" account is not removed. See the I<--user-accounts> parameter for a way to specify how to remove only some users, or to not remove some others."); - pod_notes = Some (s_"\ -Currently this does not remove the user accounts from -C</etc/shadow>. This is because there is no lens for -the shadow password file in Augeas."); extra_args = [ { extra_argspec = "--user-accounts", Arg.String set_users, s_"users" ^ " " ^ s_"Users to remove/keep"; extra_pod_argval = Some "USERS"; -- 1.9.3
Richard W.M. Jones
2014-Sep-05 12:05 UTC
Re: [Libguestfs] [PATCH 1/5] daemon: make aug_close cleanup available for all
On Thu, Sep 04, 2014 at 05:18:27PM +0200, Pino Toscano wrote:> Just code motion, no behaviour changes. > --- > daemon/daemon.h | 3 +++ > daemon/guestfsd.c | 11 +++++++++++ > daemon/lvm-filter.c | 16 ---------------- > 3 files changed, 14 insertions(+), 16 deletions(-) > > diff --git a/daemon/daemon.h b/daemon/daemon.h > index d90b3e7..b9e7402 100644 > --- a/daemon/daemon.h > +++ b/daemon/daemon.h > @@ -176,6 +176,7 @@ extern void cleanup_free (void *ptr); > extern void cleanup_free_string_list (void *ptr); > extern void cleanup_unlink_free (void *ptr); > extern void cleanup_close (void *ptr); > +extern void cleanup_aug_close (void *ptr); > > /*-- in names.c (auto-generated) --*/ > extern const char *function_names[]; > @@ -426,11 +427,13 @@ is_zero (const char *buffer, size_t size) > __attribute__((cleanup(cleanup_free_string_list))) > #define CLEANUP_UNLINK_FREE __attribute__((cleanup(cleanup_unlink_free))) > #define CLEANUP_CLOSE __attribute__((cleanup(cleanup_close))) > +#define CLEANUP_AUG_CLOSE __attribute__((cleanup(cleanup_aug_close))) > #else > #define CLEANUP_FREE > #define CLEANUP_FREE_STRING_LIST > #define CLEANUP_UNLINK_FREE > #define CLEANUP_CLOSE > +#define CLEANUP_AUG_CLOSE > #endif > > #endif /* GUESTFSD_DAEMON_H */ > diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c > index 321544f..34a47ab 100644 > --- a/daemon/guestfsd.c > +++ b/daemon/guestfsd.c > @@ -47,6 +47,8 @@ > # include <printf.h> > #endif > > +#include <augeas.h> > + > #include "sockets.h" > #include "c-ctype.h" > #include "ignore-value.h" > @@ -1511,3 +1513,12 @@ cleanup_close (void *ptr) > if (fd >= 0) > close (fd); > } > + > +void > +cleanup_aug_close (void *ptr) > +{ > + augeas *aug = * (augeas **) ptr; > + > + if (aug != NULL) > + aug_close (aug); > +} > diff --git a/daemon/lvm-filter.c b/daemon/lvm-filter.c > index 3bab9bf..3b117c5 100644 > --- a/daemon/lvm-filter.c > +++ b/daemon/lvm-filter.c > @@ -33,22 +33,6 @@ > #include "daemon.h" > #include "actions.h" > > -#ifdef HAVE_ATTRIBUTE_CLEANUP > -#define CLEANUP_AUG_CLOSE __attribute__((cleanup(cleanup_aug_close))) > - > -static void > -cleanup_aug_close (void *ptr) > -{ > - augeas *aug = * (augeas **) ptr; > - > - if (aug != NULL) > - aug_close (aug); > -} > - > -#else > -#define CLEANUP_AUG_CLOSE > -#endif > - > GUESTFSD_EXT_CMD(str_lvm, lvm); > GUESTFSD_EXT_CMD(str_cp, cp); > GUESTFSD_EXT_CMD(str_rm, rm); > -- > 1.9.3ACK. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://people.redhat.com/~rjones/virt-df/
Richard W.M. Jones
2014-Sep-05 12:08 UTC
Re: [Libguestfs] [PATCH 2/5] daemon: add a way to check for the version of augeas
On Thu, Sep 04, 2014 at 05:18:28PM +0200, Pino Toscano wrote:> Query augeas for its version when required, i.e. only once when using > the new augeas_is_version function. > --- > daemon/augeas.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > daemon/daemon.h | 16 +++++++- > 2 files changed, 131 insertions(+), 1 deletion(-) > > diff --git a/daemon/augeas.c b/daemon/augeas.c > index 74f3ba7..8dc6a7c 100644 > --- a/daemon/augeas.c > +++ b/daemon/augeas.c > @@ -24,10 +24,46 @@ > #include <unistd.h> > > #include <augeas.h> > +#include <pcre.h> > > #include "daemon.h" > #include "actions.h" > #include "optgroups.h" > +#include "xstrtol.h" > + > +#ifdef HAVE_ATTRIBUTE_CLEANUP > +#define CLEANUP_PCRE_FREE __attribute__((cleanup(cleanup_pcre_free))) > + > +static void > +cleanup_pcre_free (void *ptr) > +{ > + pcre *re = * (pcre **) ptr; > + > + if (re != NULL) > + pcre_free (re); > +} > + > +#else > +#define CLEANUP_PCRE_FREE > +#endif > + > +#define FPRINTF_AUGEAS_ERROR(aug,fs,...) \ > + do { \ > + int code = aug_error (aug); \ > + if (code == AUG_ENOMEM) \ > + reply_with_error (fs ": augeas out of memory", ##__VA_ARGS__); \ > + else { \ > + const char *message = aug_error_message (aug); \ > + const char *minor = aug_error_minor_message (aug); \ > + const char *details = aug_error_details (aug); \ > + fprintf (stderr, fs ": %s%s%s%s%s", ##__VA_ARGS__, \ > + message, \ > + minor ? ": " : "", minor ? minor : "", \ > + details ? ": " : "", details ? details : ""); \ > + } \ > + } while (0) > + > +int augeas_version; > > /* The Augeas handle. We maintain a single handle per daemon, which > * is all that is necessary and reduces the complexity of the API > @@ -35,6 +71,86 @@ > */ > static augeas *aug = NULL; > > +void > +aug_read_version (void) > +{ > + CLEANUP_AUG_CLOSE augeas *ah = NULL; > + int r; > + const char *str; > + CLEANUP_PCRE_FREE pcre *re = NULL; > + const char *errptr; > + int erroffset; > + size_t len; > +#define N_MATCHES 4 > + int vec[N_MATCHES * 4]; > + unsigned long int major = 0, minor = 0, patch = 0; > + > + if (augeas_version != 0) > + return; > + > + /* Optimization: do not load the files nor the lenses, since we are > + * only interested in the version. > + */ > + ah = aug_init ("/", NULL, AUG_NO_ERR_CLOSE | AUG_NO_LOAD | AUG_NO_STDINC); > + if (!ah) { > + FPRINTF_AUGEAS_ERROR (ah, "augeas initialization failed"); > + return; > + } > + > + if (aug_error (ah) != AUG_NOERROR) { > + FPRINTF_AUGEAS_ERROR (ah, "aug_init"); > + return; > + } > + > + r = aug_get (ah, "/augeas/version", &str); > + if (r != 1) { > + FPRINTF_AUGEAS_ERROR (ah, "aug_get"); > + return; > + }Wow, that is tedious. Be nice if Augeas had an API call to get the version fields directly ... I would be tempted to use sscanf to parse up the version number here. The code would be a lot shorter and simpler. But yes, ACK, although sscanf better. Rich.> + re = pcre_compile ("(\\d+)\\.(\\d+)(\\.(\\d+))?", > + 0, &errptr, &erroffset, NULL); > + if (re == NULL) { > + fprintf (stderr, "cannot compile the augeas version regexp\n"); > + return; > + } > + > + len = strlen (str); > + r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof (vec) / sizeof (vec[0])); > + if (r == PCRE_ERROR_NOMATCH) { > + fprintf (stderr, "cannot match the version string in '%s'\n", str); > + return; > + } > + > + if (r > 1) { > + if (xstrtoul (&str[vec[2]], NULL, 10, &major, NULL) != LONGINT_OK) { > + fprintf (stderr, "could not parse '%*s' as integer\n", > + vec[3]-vec[2], &str[vec[2]]); > + return; > + } > + } > + if (r > 2) { > + if (xstrtoul (&str[vec[4]], NULL, 10, &minor, NULL) != LONGINT_OK) { > + fprintf (stderr, "could not parse '%*s' as integer\n", > + vec[5]-vec[4], &str[vec[4]]); > + return; > + } > + } > + if (r > 4) { > + if (xstrtoul (&str[vec[8]], NULL, 10, &patch, NULL) != LONGINT_OK) { > + fprintf (stderr, "could not parse '%*s' as integer\n", > + vec[9]-vec[8], &str[vec[8]]); > + return; > + } > + } > + > + if (verbose) > + fprintf (stderr, "augeas version: %ld.%ld.%ld\n", major, minor, patch); > + > + augeas_version = (int) ((major << 16) | (minor << 8) | patch); > +#undef N_MATCHES > +} > + > /* Clean up the augeas handle on daemon exit. */ > void aug_finalize (void) __attribute__((destructor)); > void > diff --git a/daemon/daemon.h b/daemon/daemon.h > index b9e7402..0ccbc9e 100644 > --- a/daemon/daemon.h > +++ b/daemon/daemon.h > @@ -228,8 +228,22 @@ extern void copy_lvm (void); > /*-- in zero.c --*/ > extern void wipe_device_before_mkfs (const char *device); > > -/*-- in augeas.c, hivex.c, journal.c --*/ > +/*-- in augeas.c --*/ > +extern void aug_read_version (void); > extern void aug_finalize (void); > + > +/* The version of augeas, saved as: > + * (MAJOR << 16) | (MINOR << 8) | PATCH > + */ > +extern int augeas_version; > +static inline int > +augeas_is_version (int major, int minor, int patch) > +{ > + aug_read_version (); /* Lazy version reading. */ > + return augeas_version >= ((major << 16) | (minor << 8) | patch); > +} > + > +/*-- hivex.c, journal.c --*/ > extern void hivex_finalize (void); > extern void journal_finalize (void); > > -- > 1.9.3 > > _______________________________________________ > Libguestfs mailing list > Libguestfs@redhat.com > https://www.redhat.com/mailman/listinfo/libguestfs-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org
Richard W.M. Jones
2014-Sep-05 12:11 UTC
Re: [Libguestfs] [PATCH 3/5] appliance: daemon: import and use upstream shadow lens
On Thu, Sep 04, 2014 at 05:18:29PM +0200, Pino Toscano wrote:> Import the upstream lens for the shadow file, just with a different > identifier and not matching /etc/shadow by default. Instead, apply a > transformation to have it match /etc/shadow only if the version of > augeas is at least 1.2.1 [1]. > > [1] While the last upstream version is 1.2.0, all the development seems > to happen in master, so whatever the next version is going to be > numbered (e.g. 1.2.1 or 1.3.0), the check will be fine anyway.ACK. Sooner we can get rid of this hack the better though ... Rich.> appliance/Makefile.am | 3 +- > appliance/guestfs_shadow.aug | 72 ++++++++++++++++++++++++++++++++++++++++++++ > daemon/augeas.c | 21 ++++++++++++- > 3 files changed, 94 insertions(+), 2 deletions(-) > create mode 100644 appliance/guestfs_shadow.aug > > diff --git a/appliance/Makefile.am b/appliance/Makefile.am > index bb0c0e8..b8377af 100644 > --- a/appliance/Makefile.am > +++ b/appliance/Makefile.am > @@ -75,13 +75,14 @@ packagelist: packagelist.in Makefile > cmp -s $@ $@-t || mv $@-t $@ > rm -f $@-t > > -supermin.d/daemon.tar.gz: ../daemon/guestfsd guestfsd.suppressions guestfs_lvm_conf.aug > +supermin.d/daemon.tar.gz: ../daemon/guestfsd guestfsd.suppressions guestfs_lvm_conf.aug guestfs_shadow.aug > rm -f $@ $@-t > rm -rf tmp-d > mkdir -p tmp-d$(DAEMON_SUPERMIN_DIR) tmp-d/etc tmp-d/usr/share/guestfs > ln ../daemon/guestfsd tmp-d$(DAEMON_SUPERMIN_DIR)/guestfsd > ln $(srcdir)/guestfsd.suppressions tmp-d/etc/guestfsd.suppressions > ln $(srcdir)/guestfs_lvm_conf.aug tmp-d/usr/share/guestfs/guestfs_lvm_conf.aug > + ln $(srcdir)/guestfs_shadow.aug tmp-d/usr/share/guestfs/guestfs_shadow.aug > ( cd tmp-d && tar zcf - * ) > $@-t > rm -r tmp-d > mv $@-t $@ > diff --git a/appliance/guestfs_shadow.aug b/appliance/guestfs_shadow.aug > new file mode 100644 > index 0000000..2fbf455 > --- /dev/null > +++ b/appliance/guestfs_shadow.aug > @@ -0,0 +1,72 @@ > +(* > + Module: Shadow > + Parses /etc/shadow > + > + Author: Lorenzo M. Catucci <catucci@ccd.uniroma2.it> > + > + Original Author: Free Ekanayaka <free@64studio.com> > + > + About: Reference > + > + - man 5 shadow > + - man 3 getspnam > + > + About: License > + This file is licensed under the LGPL v2+, like the rest of Augeas. > + > + About: > + > + Each line in the shadow files represents the additional shadow-defined attributes > + for the corresponding user, as defined in the passwd file. > + > +*) > + > +module Guestfs_Shadow > + > + autoload xfm > + > +(************************************************************************ > + * USEFUL PRIMITIVES > + *************************************************************************) > + > +let eol = Util.eol > +let comment = Util.comment > +let empty = Util.empty > +let dels = Util.del_str > + > +let colon = Sep.colon > + > +let word = Rx.word > +let integer = Rx.integer > + > +let sto_to_col = Passwd.sto_to_col > +let sto_to_eol = Passwd.sto_to_eol > + > +(************************************************************************ > + * Group: ENTRIES > + *************************************************************************) > + > +(* View: entry *) > +let entry = [ key word > + . colon > + . [ label "password" . sto_to_col? . colon ] > + . [ label "lastchange_date" . store integer? . colon ] > + . [ label "minage_days" . store integer? . colon ] > + . [ label "maxage_days" . store integer? . colon ] > + . [ label "warn_days" . store integer? . colon ] > + . [ label "inactive_days" . store integer? . colon ] > + . [ label "expire_date" . store integer? . colon ] > + . [ label "flag" . store integer? ] > + . eol ] > + > +(************************************************************************ > + * LENS > + *************************************************************************) > + > +let lns = (comment|empty|entry) * > + > +let filter > + = incl "/shadow" > + . Util.stdexcl > + > +let xfm = transform lns filter > diff --git a/daemon/augeas.c b/daemon/augeas.c > index 8dc6a7c..988deed 100644 > --- a/daemon/augeas.c > +++ b/daemon/augeas.c > @@ -189,7 +189,7 @@ do_aug_init (const char *root, int flags) > } > > /* Pass AUG_NO_ERR_CLOSE so we can display detailed errors. */ > - aug = aug_init (buf, NULL, flags | AUG_NO_ERR_CLOSE); > + aug = aug_init (buf, "/usr/share/guestfs/", flags | AUG_NO_ERR_CLOSE); > > if (!aug) { > reply_with_error ("augeas initialization failed"); > @@ -203,6 +203,25 @@ do_aug_init (const char *root, int flags) > return -1; > } > > + if (!augeas_is_version (1, 2, 1)) { > + int r = aug_transform (aug, "guestfs_shadow", "/etc/shadow", > + 0 /* = included */); > + if (r == -1) { > + AUGEAS_ERROR ("aug_transform"); > + aug_close (aug); > + aug = NULL; > + return -1; > + } > + > + /* If aug_load was implicitly called, reload the handle. */ > + if ((flags & AUG_NO_LOAD) == 0) { > + if (aug_load (aug) == -1) { > + AUGEAS_ERROR ("aug_load"); > + return -1; > + } > + } > + } > + > return 0; > } > > -- > 1.9.3 > > _______________________________________________ > Libguestfs mailing list > Libguestfs@redhat.com > https://www.redhat.com/mailman/listinfo/libguestfs-- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com virt-df lists disk usage of guests without needing to install any software inside the virtual machine. Supports Linux and Windows. http://people.redhat.com/~rjones/virt-df/
Richard W.M. Jones
2014-Sep-05 12:13 UTC
Re: [Libguestfs] [PATCH 5/5] customize: use augeas to change passwords
On Thu, Sep 04, 2014 at 05:18:31PM +0200, Pino Toscano wrote:> Make use of augeas to load and edit /etc/shadow, now that we have > (either from upstream or by ourselves) a lens handling it. > --- > customize/password.ml | 64 +++++++++++++++++++++++---------------------------- > 1 file changed, 29 insertions(+), 35 deletions(-) > > diff --git a/customize/password.ml b/customize/password.ml > index 84af0c3..3437bf0 100644 > --- a/customize/password.ml > +++ b/customize/password.ml > @@ -87,42 +87,36 @@ let rec set_linux_passwords ~prog ?password_crypto g root passwords > | None -> default_crypto ~prog g root > | Some c -> c in > > - (* XXX Would like to use Augeas here, but Augeas doesn't support > - * /etc/shadow (as of 1.1.0). > - *) > + g#aug_init "/" 0; > + let users = Array.to_list (g#aug_ls "/files/etc/shadow") in > + List.iter ( > + fun userpath -> > + let user > + let i = String.rindex userpath '/' in > + String.sub userpath (i+1) (String.length userpath -i-1) in > + try > + (* Each line is: "user:[!!]password:..." > + * !! at the front of the password field means the account is locked. > + *) > + let selector = Hashtbl.find passwords user in > + let pwfield > + match selector with > + | { pw_locked = locked; > + pw_password = Password password } -> > + (if locked then "!!" else "") ^ encrypt password crypto > + | { pw_locked = locked; > + pw_password = Random_password } -> > + let password = make_random_password () in > + printf (f_"Setting random password of %s to %s\n%!") > + user password; > + (if locked then "!!" else "") ^ encrypt password crypto > + | { pw_locked = true; pw_password = Disabled_password } -> "!!*" > + | { pw_locked = false; pw_password = Disabled_password } -> "*" in > + g#aug_set (userpath ^ "/password") pwfield > + with Not_found -> () > + ) users; > + g#aug_save ();So in fact Augeas doesn't model the '!!' (locked) field, it just includes it in the /files/etc/shadow/<user>/password? ACK. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org