diff --git a/TODO b/TODO index ef0485a..611e7c8 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,9 @@ - XML import/export? -- compat with kwalletmanager --- Will require conversion to different struct model. + +- Write* and Create*/New* methods +-- return relevant entry as native item +-- confirm i'm checking the result code for all of them. + +- TESTS diff --git a/blob_funcs.go b/blob_funcs.go index 63ab00b..0a2c912 100644 --- a/blob_funcs.go +++ b/blob_funcs.go @@ -1,5 +1,9 @@ package gokwallet +import ( + "github.com/godbus/dbus/v5" +) + /* NewBlob returns a Blob. It requires a RecurseOpts (you can use DefaultRecurseOpts, call NewRecurseOpts, or provide your own RecurseOpts struct). @@ -37,7 +41,15 @@ func NewBlob(f *Folder, keyName string, recursion *RecurseOpts) (blob *Blob, err // Update fetches a Blob's Blob.Value. func (b *Blob) Update() (err error) { - // TODO. + var v dbus.Variant + + if err = b.Dbus.Call( + DbusWMReadEntry, 0, b.folder.wallet.handle, b.folder.Name, b.Name, b.folder.wallet.wm.AppID, + ).Store(&v); err != nil { + return + } + + b.Value = v.Value().([]byte) return } diff --git a/consts.go b/consts.go index 7a3bdf2..40c1e00 100644 --- a/consts.go +++ b/consts.go @@ -6,13 +6,15 @@ const ( DbusFailure int32 = 1 ) +type kwalletdEnumType int32 + // KwalletD Dbus enums for WalletItem types. const ( - kwalletdEnumTypeUnknown int32 = iota // UnknownItem (0) - kwalletdEnumTypePassword // Password (1) - kwalletdEnumTypeStream // Blob (2) - kwalletdEnumTypeMap // Map (3) - kwalletdEnumTypeUnused = 0xffff // 65535 + KwalletdEnumTypeUnknown kwalletdEnumType = iota // UnknownItem (0) + KwalletdEnumTypePassword // Password (1) + KwalletdEnumTypeStream // Blob (2) + KwalletdEnumTypeMap // Map (3) + KwalletdEnumTypeUnused = 0xffff // 65535 ) // KWalletD Dbus interfaces. @@ -147,7 +149,7 @@ const ( Deprecated: use DbusWMEntriesList instead. */ - DbusWMReadEntryList string = DbusInterfaceWM + ".readEntryList" + // DbusWMReadEntryList string = DbusInterfaceWM + ".readEntryList" // DbusWMReadMap returns a Map from a Folder (as a byteslice). DbusWMReadMap string = DbusInterfaceWM + ".readMap" @@ -157,7 +159,7 @@ const ( Deprecated: use DbusWMMapList instead. */ - DbusWMReadMapList string = DbusInterfaceWM + ".readMapList" + // DbusWMReadMapList string = DbusInterfaceWM + ".readMapList" // DbusWMReadPassword returns a Password from a Folder (as a byteslice). DbusWMReadPassword string = DbusInterfaceWM + ".readPassword" @@ -167,7 +169,7 @@ const ( Deprecated: use DbusWMPasswordList instead. */ - DbusWMReadPasswordList string = DbusInterfaceWM + ".readPasswordList" + // DbusWMReadPasswordList string = DbusInterfaceWM + ".readPasswordList" // DbusWMReconfigure is [FUNCTION UNKNOWN/UNDOCUMENTED; TODO? NOT IMPLEMENTED.] // DbusWMReconfigure string = DbusInterfaceWM + ".reconfigure" diff --git a/errs.go b/errs.go index da36db7..2b5d190 100644 --- a/errs.go +++ b/errs.go @@ -23,4 +23,6 @@ var ( ErrNoCreate error = errors.New("failed to create an object") // ErrNoDisconnect can occur if trying to disconnect a Wallet from a WalletManager/application and a failure occurs. ErrNoDisconnect error = errors.New("failed to disconnect wallet from application") + // ErrInvalidMap will get triggered if a populated map[string]string (even an empty one) is expected but a nil is received. + ErrInvalidMap error = errors.New("invalid map; cannot be nil") ) diff --git a/folder_funcs.go b/folder_funcs.go index 77b9cdc..3b0ed4d 100644 --- a/folder_funcs.go +++ b/folder_funcs.go @@ -44,6 +44,78 @@ func NewFolder(w *Wallet, name string, recursion *RecurseOpts) (folder *Folder, return } +// HasEntry specifies if a Folder has an entry (WalletItem item) by the give entryName. +func (f *Folder) HasEntry(entryName string) (hasEntry bool, err error) { + + if err = f.Dbus.Call( + DbusWMHasEntry, 0, f.wallet.handle, f.Name, entryName, f.wallet.wm.AppID, + ).Store(&hasEntry); err != nil { + return + } + + return +} + +/* + KeyNotExist returns true if a key/entry name entryName does *not* exist. + Essentially the same as Folder.HasEntry, but whereas Folder.HasEntry requires the parent wallet + to be open/unlocked, Folder.KeyNotExist does not require this. +*/ +func (f *Folder) KeyNotExist(entryName string) (doesNotExist bool, err error) { + + if err = f.Dbus.Call( + DbusWMKeyNotExist, 0, f.wallet.Name, f.Name, entryName, + ).Store(&doesNotExist); err != nil { + return + } + + return +} + +// ListEntries lists all entries (WalletItem items) in a Folder (regardless of type) by name. +func (f *Folder) ListEntries() (entryNames []string, err error) { + + if err = f.Dbus.Call( + DbusWMEntryList, 0, f.wallet.handle, f.Name, f.wallet.wm.AppID, + ).Store(&entryNames); err != nil { + return + } + + return +} + +// RemoveEntry removes a WalletItem from a Folder given its entryName (key). +func (f *Folder) RemoveEntry(entryName string) (err error) { + + var rslt int32 + + if err = f.Dbus.Call( + DbusWMRemoveEntry, 0, f.wallet.handle, f.Name, entryName, f.wallet.wm.AppID, + ).Store(&rslt); err != nil { + return + } + + err = resultCheck(rslt) + + return +} + +// RenameEntry renames a WalletItem in a Folder from entryName to newEntryName. +func (f *Folder) RenameEntry(entryName, newEntryName string) (err error) { + + var rslt int32 + + if err = f.Dbus.Call( + DbusWMRenameEntry, 0, f.wallet.handle, f.Name, entryName, newEntryName, f.wallet.wm.AppID, + ).Store(&rslt); err != nil { + return + } + + err = resultCheck(rslt) + + return +} + // Update runs all of the configured Update[type] methods for a Folder, depending on Folder.Recurse configuration. func (f *Folder) Update() (err error) { @@ -172,7 +244,7 @@ func (f *Folder) UpdateBlobs() (err error) { f.BinaryData = make(map[string]*Blob, len(mapKeys)) for _, k := range mapKeys { - if isBlob, err = f.isType(k, kwalletdEnumTypeStream); err != nil { + if isBlob, err = f.isType(k, KwalletdEnumTypeStream); err != nil { errs = append(errs, err) err = nil continue @@ -218,7 +290,7 @@ func (f *Folder) UpdateUnknowns() (err error) { f.Unknown = make(map[string]*UnknownItem, len(mapKeys)) for _, k := range mapKeys { - if isUnknown, err = f.isType(k, kwalletdEnumTypeUnknown); err != nil { + if isUnknown, err = f.isType(k, KwalletdEnumTypeUnknown); err != nil { errs = append(errs, err) err = nil continue @@ -240,8 +312,90 @@ func (f *Folder) UpdateUnknowns() (err error) { return } -// isType checks if a certain key keyName is of type typeCheck (via kwalletdEnumType*). -func (f *Folder) isType(keyName string, typeCheck int32) (isOfType bool, err error) { +// WriteBlob adds or replaces a Blob to/in a Folder. +func (f *Folder) WriteBlob(entryName string, entryValue []byte) (err error) { + + if err = f.WriteEntry(entryName, KwalletdEnumTypeStream, entryValue); err != nil { + return + } + + return +} + +/* + WriteEntry is used for adding/replacing a WalletItem as a general interface. + If possible, you'll want to use a item-type-specific method (e.g. Folder.WritePassword) as this one is a little unwieldy to use. + entryType must be the relevant KwalletdEnumType* constant (do not use KwalletdEnumTypeUnused). +*/ +func (f *Folder) WriteEntry(entryName string, entryType kwalletdEnumType, entryValue []byte) (err error) { + + var rslt int32 + + if entryType == KwalletdEnumTypeUnused { + err = ErrNoCreate + return + } + + if err = f.Dbus.Call( + DbusWMWriteEntry, 0, f.wallet.handle, f.Name, entryName, entryValue, int32(entryType), f.wallet.wm.AppID, + ).Store(&rslt); err != nil { + return + } + + err = resultCheck(rslt) + + return +} + +// WriteMap adds or replaces a Map to/in a Folder. +func (f *Folder) WriteMap(entryName string, entryValue map[string]string) (err error) { + + var rslt int32 + var b []byte + + if b, err = mapToBytes(entryValue); err != nil { + return + } + + if err = f.Dbus.Call( + DbusWMWriteMap, 0, f.wallet.handle, f.Name, entryName, b, f.wallet.wm.AppID, + ).Store(&rslt); err != nil { + return + } + + err = resultCheck(rslt) + + return +} + +// WritePassword adds or replaces a Password to/in a Folder. +func (f *Folder) WritePassword(entryName, entryValue string) (err error) { + + var rslt int32 + + if err = f.Dbus.Call( + DbusWMWritePassword, 0, f.wallet.handle, f.Name, entryName, entryValue, f.wallet.wm.AppID, + ).Store(&rslt); err != nil { + return + } + + err = resultCheck(rslt) + + return +} + +// WriteUnknown adds or replaces an UnknownItem to/in a Folder. +func (f *Folder) WriteUnknown(entryName string, entryValue []byte) (err error) { + + if err = f.WriteEntry(entryName, KwalletdEnumTypeUnknown, entryValue); err != nil { + return + } + + return +} + +// isType checks if a certain key keyName is of type typeCheck (via KwalletdEnumType*). +func (f *Folder) isType(keyName string, typeCheck kwalletdEnumType) (isOfType bool, err error) { var entryType int32 @@ -251,5 +405,9 @@ func (f *Folder) isType(keyName string, typeCheck int32) (isOfType bool, err err return } + if int32(typeCheck) == entryType { + isOfType = true + } + return } diff --git a/go.mod b/go.mod index bcf7110..9b8411b 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,4 @@ module r00t2.io/gokwallet go 1.17 -require ( - github.com/godbus/dbus/v5 v5.0.6 - r00t2.io/goutils v1.1.0 -) +require github.com/godbus/dbus/v5 v5.0.6 diff --git a/go.sum b/go.sum index 166ba77..aeec758 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,2 @@ -github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/jszwec/csvutil v1.5.0/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4YcJjGBkg= -r00t2.io/goutils v1.1.0 h1:vJqEWvdX0TKDyT+hTgws0wA1dc/F8JkQKw5cDHz0wH0= -r00t2.io/goutils v1.1.0/go.mod h1:CMK3RGnMSyjDSfYxeFQl/oJTkkUMS1jhSTdGTkAPpQw= -r00t2.io/sysutils v0.0.0-20210224054841-55ac47c86928/go.mod h1:XzJkBF6SHAODEszJlOcjtGoTHwYnZZNmseA6PyOujes= diff --git a/map_funcs.go b/map_funcs.go index 864b0e4..7d78993 100644 --- a/map_funcs.go +++ b/map_funcs.go @@ -37,7 +37,17 @@ func NewMap(f *Folder, keyName string, recursion *RecurseOpts) (m *Map, err erro // Update fetches a Map's Map.Value. func (m *Map) Update() (err error) { - // TODO. + var b []byte + + if err = m.Dbus.Call( + DbusWMReadMap, 0, m.folder.wallet.handle, m.folder.Name, m.Name, m.folder.wallet.wm.AppID, + ).Store(&b); err != nil { + return + } + + if m.Value, _, err = bytesToMap(b); err != nil { + return + } return } diff --git a/multierror_funcs.go b/multierror_funcs.go index 1f3bfa6..b5d96b4 100644 --- a/multierror_funcs.go +++ b/multierror_funcs.go @@ -35,6 +35,7 @@ func NewErrors(errs ...error) (err error) { return } +// Error returns a string representation of a MultiError (to conform with the error interface). func (e *MultiError) Error() (errStr string) { var numErrs int diff --git a/password_funcs.go b/password_funcs.go index 2400894..5d788a2 100644 --- a/password_funcs.go +++ b/password_funcs.go @@ -37,7 +37,15 @@ func NewPassword(f *Folder, keyName string, recursion *RecurseOpts) (password *P // Update fetches a Password's Password.Value. func (p *Password) Update() (err error) { - // TODO. + var b []byte + + if err = p.Dbus.Call( + DbusWMReadPassword, 0, p.folder.wallet.handle, p.folder.Name, p.Name, p.folder.wallet.wm.AppID, + ).Store(&b); err != nil { + return + } + + p.Value = string(b) return } diff --git a/types.go b/types.go index 54a3bbf..a6901f3 100644 --- a/types.go +++ b/types.go @@ -184,15 +184,14 @@ type Blob struct { /* UnknownItem is a secret item of unknown classification, so there isn't exactly a good way of determining a type for UnknownItem.Value. - As such, its dbus.ObjectPath is used. - TODO: There may be a method to fetch the raw bytes of the object (such as one would use with Blob) in the future. + As such, its UnknownItem.Value is just raw bytes. */ type UnknownItem struct { *DbusObject // Name is the name of this UnknownItem. Name string `json:"name"` // Value is the Dbus path of this UnknownItem. - Value dbus.ObjectPath `json:"value"` + Value []byte `json:"value"` // Recurse contains the relevant RecurseOpts. Recurse *RecurseOpts `json:"recurse_opts"` // wm is the parent WalletManager that UnknownItem.folder.wallet was fetched from. diff --git a/unknownitem_funcs.go b/unknownitem_funcs.go index be1af3e..7baf517 100644 --- a/unknownitem_funcs.go +++ b/unknownitem_funcs.go @@ -1,5 +1,9 @@ package gokwallet +import ( + "github.com/godbus/dbus/v5" +) + /* NewUnknownItem returns an UnknownItem. It requires a RecurseOpts (you can use DefaultRecurseOpts, call NewRecurseOpts, or provide your own RecurseOpts struct). @@ -37,7 +41,15 @@ func NewUnknownItem(f *Folder, keyName string, recursion *RecurseOpts) (unknown // Update fetches an UnknownItem's UnknownItem.Value. func (u *UnknownItem) Update() (err error) { - // TODO. + var v dbus.Variant + + if err = u.Dbus.Call( + DbusWMReadEntry, 0, u.folder.wallet.handle, u.folder.Name, u.Name, u.folder.wallet.wm.AppID, + ).Store(&v); err != nil { + return + } + + u.Value = v.Value().([]byte) return } diff --git a/utils.go b/utils.go index b4fa7b9..a450678 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,10 @@ package gokwallet import ( + "bytes" + "encoding/binary" + "strings" + "github.com/godbus/dbus/v5" ) @@ -59,3 +63,111 @@ func bytemapKeys(variant dbus.Variant) (keyNames []string) { return } + +// bytesToMap takes a byte slice and returns a map[string]string based on a Dbus QMap struct(ure). +func bytesToMap(raw []byte) (m map[string]string, numEntries uint32, err error) { + + var buf *bytes.Reader + var kLen uint32 + var vLen uint32 + var k []byte + var v []byte + + /* + I considered using: + - https://github.com/lunixbochs/struc + - https://github.com/roman-kachanovsky/go-binary-pack + - https://github.com/go-restruct/restruct + + The second hasn't been updated in quite some time, the first or third would have been a headache due to the variable length, + and ultimately I felt it was silly to add a dependency for only a single piece of data (Map). + So sticking to stdlib. + */ + + buf = bytes.NewReader(raw) + + if err = binary.Read(buf, binary.BigEndian, &numEntries); err != nil { + return + } + + m = make(map[string]string, numEntries) + + for i := uint32(0); i < numEntries; i++ { + if err = binary.Read(buf, binary.BigEndian, &kLen); err != nil { + return + } + + k = make([]byte, kLen) + + if err = binary.Read(buf, binary.BigEndian, &k); err != nil { + return + } + + if err = binary.Read(buf, binary.BigEndian, &vLen); err != nil { + return + } + + v = make([]byte, vLen) + + if err = binary.Read(buf, binary.BigEndian, &v); err != nil { + return + } + + // QMap does this infuriating thing where it separates each character with a null byte. So we need to strip them out. + k = bytes.ReplaceAll(k, []byte{0x0}, []byte{}) + v = bytes.ReplaceAll(v, []byte{0x0}, []byte{}) + + m[string(k)] = string(v) + } + + return +} + +// mapToBytes performs the inverse of bytesToMap. +func mapToBytes(m map[string]string) (raw []byte, err error) { + + var numEntries uint32 + var buf *bytes.Buffer + var kLen uint32 + var vLen uint32 + var kB []byte + var vB []byte + + if m == nil { + err = ErrInvalidMap + return + } + + numEntries = uint32(len(m)) + + buf = &bytes.Buffer{} + + if err = binary.Write(buf, binary.BigEndian, &numEntries); err != nil { + return + } + + for k, v := range m { + kB = []byte(strings.Join(strings.Split(k, ""), "\x00")) + vB = []byte(strings.Join(strings.Split(v, ""), "\x00")) + kLen = uint32(len(kB)) + vLen = uint32(len(vB)) + + if err = binary.Write(buf, binary.BigEndian, &kLen); err != nil { + return + } + if err = binary.Write(buf, binary.BigEndian, &k); err != nil { + return + } + + if err = binary.Write(buf, binary.BigEndian, &vLen); err != nil { + return + } + if err = binary.Write(buf, binary.BigEndian, &v); err != nil { + return + } + } + + raw = buf.Bytes() + + return +} diff --git a/wallet_funcs.go b/wallet_funcs.go index 8e1d0d8..09205df 100644 --- a/wallet_funcs.go +++ b/wallet_funcs.go @@ -223,6 +223,18 @@ func (w *Wallet) ForceClose() (err error) { return } +// HasFolder indicates if a Wallet has a Folder in it named folderName. +func (w *Wallet) HasFolder(folderName string) (hasFolder bool, err error) { + + if err = w.Dbus.Call( + DbusWMHasFolder, 0, w.handle, folderName, w.wm.AppID, + ).Store(&hasFolder); err != nil { + return + } + + return +} + // IsOpen returns whether a Wallet is open ("unlocked") or not (as well as updates Wallet.IsOpen). func (w *Wallet) IsOpen() (isOpen bool, err error) { @@ -286,6 +298,28 @@ func (w *Wallet) Open() (err error) { return } +/* + RemoveFolder removes a Folder folderName from a Wallet. + Note that this will also remove all WalletItems in the given Folder. +*/ +func (w *Wallet) RemoveFolder(folderName string) (err error) { + + var success bool + + if err = w.Dbus.Call( + DbusWMRemoveFolder, 0, w.handle, folderName, w.wm.AppID, + ).Store(&success); err != nil { + return + } + + if !success { + err = ErrOperationFailed + return + } + + return +} + // Update fetches/updates all Folder objects in a Wallet. func (w *Wallet) Update() (err error) {