From 8836aa123cd11df359dfbb7b36da146490dbdfa3 Mon Sep 17 00:00:00 2001 From: Tomas Smetana Date: Wed, 24 Apr 2013 13:00:54 +0200 Subject: New provider: RealmD --- CMakeLists.txt | 1 + README | 111 +- mof/60_LMI_Realmd.mof | 519 +++++ src/CMakeLists.txt | 4 + src/realmd/CMakeLists.txt | 50 + src/realmd/LMI_HostedRealmdServiceProvider.c | 233 +++ src/realmd/LMI_RealmdKerberosRealmProvider.c | 627 ++++++ src/realmd/LMI_RealmdRealmProvider.c | 326 ++++ src/realmd/LMI_RealmdServiceProvider.c | 631 ++++++ src/realmd/LMI_ServiceAffectsRealmdRealmProvider.c | 252 +++ src/realmd/README | 221 +++ src/realmd/VERSION | 1 + src/realmd/doc/class_diagram.svg | 273 +++ src/realmd/doc/examples/realmd-cim | 245 +++ src/realmd/rdcp_dbus.c | 2050 ++++++++++++++++++++ src/realmd/rdcp_dbus.h | 74 + src/realmd/rdcp_error.c | 123 ++ src/realmd/rdcp_error.h | 38 + src/realmd/rdcp_realmdrealm.h | 310 +++ src/realmd/rdcp_util.c | 311 +++ src/realmd/rdcp_util.h | 116 ++ src/realmd/realm-dbus-constants.h | 66 + 22 files changed, 6581 insertions(+), 1 deletion(-) create mode 100644 mof/60_LMI_Realmd.mof create mode 100644 src/realmd/CMakeLists.txt create mode 100644 src/realmd/LMI_HostedRealmdServiceProvider.c create mode 100644 src/realmd/LMI_RealmdKerberosRealmProvider.c create mode 100644 src/realmd/LMI_RealmdRealmProvider.c create mode 100644 src/realmd/LMI_RealmdServiceProvider.c create mode 100644 src/realmd/LMI_ServiceAffectsRealmdRealmProvider.c create mode 100644 src/realmd/README create mode 100644 src/realmd/VERSION create mode 100644 src/realmd/doc/class_diagram.svg create mode 100644 src/realmd/doc/examples/realmd-cim create mode 100644 src/realmd/rdcp_dbus.c create mode 100644 src/realmd/rdcp_dbus.h create mode 100644 src/realmd/rdcp_error.c create mode 100644 src/realmd/rdcp_error.h create mode 100644 src/realmd/rdcp_realmdrealm.h create mode 100644 src/realmd/rdcp_util.c create mode 100644 src/realmd/rdcp_util.h create mode 100644 src/realmd/realm-dbus-constants.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b42debc..100059f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ option(WITH-SERVICE "Build service provider" ON) option(WITH-ACCOUNT "Build account provider" ON) option(WITH-HARDWARE "Build hardware provider" ON) option(WITH-LOGICALFILE "Build logical file provider" ON) +option(WITH-REALMD "Build RealmD provider" ON) # Set path to custom cmake modules set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) diff --git a/README b/README index 7e17e45..f1cf2a8 100644 --- a/README +++ b/README @@ -51,10 +51,15 @@ Following providers are part of this sub-project: Allows to install, remove, list and update packages. Requires python 2.6+ and yum. +* RealmD + This is a CIM interface for the RealmD daemon which allows for the Kerberos + and Active Directory realms enrollment + ******************************************************************************* * Build Dependencies * ******************************************************************************* For all providers: + - cmake - konkretcmpi-devel - sblim-cmpi-devel @@ -71,7 +76,9 @@ Provider specific dependencies: * Service - chkconfig - systemd or upstart or SysVinit - +* RealmD + - glib2-devel + - dbus-devel ******************************************************************************* * Compilation and installation * @@ -87,3 +94,105 @@ $ make You can disable specific provider by adding -DWITH-=0 to cmake line. + +******************************************************************************* +* Development Tips * +******************************************************************************* + +Understanding konkret code generation issues: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +konkret is a tool that reads in a mof file and generates C code. For +every XXX class it will generate a XXX.h and XXXProvider.c file. The +code generation occurs due to CMake macros provided by the +openlmi-providers-devel package. konkret needs to run any time you +modify the mof file. It *always* generates a new XXX.h file because +that's where definitions based on the contents of the mof file are +located. If there is no XXXProvider.c file it will also generate +it. This is a "stub" file in which you will fill in with your +implementation. If XXXProvider.c exits it will not overwrite it, +however it always overwrites the XXX.h file. + +Do not put anything into the XXX.h file you'll need to retain. + +After editing the mof file the make targets will cause konkret to run +again. You'll get brand new XXX.h files. But your old XXXProvider.c +files may no longer have the correct definitions (e.g. prototypes) +found in the XXX.h file so you may need to hand edit by copying the +function prototype from the XXX.h file into your XXXProvider.c file. + +If you've written definitions that logically belong in XXX.h but don't +want them nuked the next time konkret runs my solution was to put them +in someother .h file that's included by the XXXProvider.c file. + +Initializing class instances: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The way konkret works is to emit specialized inline functions to +initialize each member of a class. If the class is subclassed you get +different initializers depending on whether the property is in the +parent class or the subclass. You cannot call a parent class property +initializer in a subclass (yuck), you have to use the subclass +initializer for the property inherited from the parent class. This +creates a maintenance problem if the parent class changes, you have +find every place parent class properties are inialized and make +changes. To solve this problem I defined macros that initialize class +properties. The macro takes a "klass" parameter and token pastes it to +generate the class specific property manipulation function call. Using +these macros means anytime a class changes due to a change in the mof +file there is only one place where you need to change the code. These +macros are a good example of what logically belongs in the XXX.h file +but are separated out into a different .h file because konkret will +nuke anything you've added to a XXX.h file. + +Modifications to the provider: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +During development if the mof file changes you have to make Pegasus +reload the mof. It's not sufficient to retart cimserver, you have to +unregister the provider and register it again for Pegasus to see the +mof changes. Thus you would use the openlmi-mof-register command above +except pass the unregister option, followed by the above command, e.g. + +% openlmi-mof-register unregister /usr/share/openlmi-providers/LMI_Realmd.mof /usr/share/openlmi-providers/LMI_Realmd.reg +% openlmi-mof-register register /usr/share/openlmi-providers/LMI_Realmd.mof /usr/share/openlmi-providers/LMI_Realmd.reg + +If all you've done during devopment is modify the provider (not it's +mof definition) then all you need to do is: + +% sudo cimserver -s +% sudo make install +% sudo cimserver + +How do I run the Pegasus CIMOM so I can see debug statements? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +% sudo cimserver daemon=false forceProviderProcesses=false + +How do I use GDB to debug my provider? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create the following .gdbinit file where XXX is where you want to break. + +<<<<<<<<<< +set breakpoint pending on +b XXX +r daemon=false forceProviderProcesses=false +>>>>>>>>>> + +then run gdb like this: + +% sudo gdb cimserver + +How do I trace what Pegasus is doing? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +% cimserver daemon=false forceProviderProcesses=false logLevel=TRACE traceLevel=5 traceFacility=File traceComponents=All + +The trace file is written to: + +/var/lib/Pegasus/cache/trace/cimserver.trc + +More information about cimserver tracing can be found in the +"OpenPegasus Tracing User Guide" PDF. Google the title to get a +current URL. diff --git a/mof/60_LMI_Realmd.mof b/mof/60_LMI_Realmd.mof new file mode 100644 index 0000000..2063fc6 --- /dev/null +++ b/mof/60_LMI_Realmd.mof @@ -0,0 +1,519 @@ +[ Description ( + "Access to the Realmd Service. " + "Realmd is used to discover realms available for joining as well as " + "providing a mechanism for joining and leaving a realm."), + Provider("cmpi:cmpiLMI_Realmd") ] +class LMI_RealmdService : CIM_Service +{ + [Description ( + "The name of the provider. This is not normally displayed " + "to the user, but may be useful for diagnostics or debugging.")] + string RealmdName; + + [Description ( + "The version of the provider. This is not normally used in " + "logic, but may be useful for diagnostics or debugging.")] + string RealmdVersion; + + [Description ( + "The locale used for messages.")] + // FIXME: we should support CIM_LocalizationCapabilities but there is no way query supported locales. + string Locale; + + [Description ( + "A list of known, enrolled or discovered realms. All realms " + "that this provider knows about are listed here. As realms " + "are discovered they are added to this list.")] + string Realms[]; + + [Description ( + + "Discover realms for the given target. The input target is " + "usually a domain or realm name, perhaps typed by a user. If an " + "empty target string is provided the realm provider should try " + "to discover a default realm if possible (eg: from DHCP).\n " + "\n" + "The behavior of the method may be modified via optional " + " pairs called \"options\" passed an array of " + "option names and option values. The pair is " + "formed by indexing into the name array and finding it's value " + "at the same index in the value array.\n " + "\n" + "The currently defined options are:\n " + "\n" + "\"client-software\": a string containing the client software " + "identifier that the returned realms should match.\n" + "\n" + "\"server-software\": a string containing the client software " + "identifier that the returned realms should match.\n" + )] + + uint32 Discover( + [In, Description ( + "What realms to discover")] + string Target, + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionValues array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionNames[], + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionNames array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionValues[], + [In ( false ), Out, Description ( + "Array of references to discovered realms")] + LMI_RealmdRealm REF DiscoveredRealms[]); + + // Proof of concept simplfied API starts here + + [Description ( + "The name of the domain that this computer is a member of " + "or NULL if not a member of any domain.")] + string Domain; + + [Description ( + "Join the computer to a domain.")] + uint32 JoinDomain( + [In, Description ( + "The name of the domain to join.")] + string Domain, + [In, Description ( + "The administrative user who is authorizing joining the domain. " + "Or NULL for a one time password based join.")] + string User, + [In, Description ( + "Either NULL for an automatic join, a one time password, or the " + "password for the administrative user in the User parameter.")] + string Password, + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionValues array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionNames[], + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionNames array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionValues[]); + + [Description ( + "Make the computer leave its joined domain.")] + uint32 LeaveDomain( + [In, Description ( + "The name of the domain to join.")] + string Domain, + [In, Description ( + "The administrative user who is authorizing joining the domain. " + "Or NULL for a one time password based join.")] + string User, + [In, Description ( + "Either NULL for an automatic join, a one time password, or the " + "password for the administrative user in the User parameter.")] + string Password, + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionValues array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionNames[], + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionNames array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionValues[]); +}; + +[ Description ( + "Represents one realm. " + + "Contains generic information about a realm, and useful properties " + "for introspecting what kind of realm this is and how to work with " + "the realm. " + + "Use LMI_RealmdService.Discover() to get access to help populate the " + "LMI_RealmdService.Realms property. " + + "Different realms support various ways to configure them on the " + "system. LMI_RealmdRealm.Configured property to determine if a realm " + "is configured. If it is configured the property will be set to class " + "used to configure it. " + + "To configure a realm use the method on the LMIRealmdRealm subclass " + "designed for that purpose, for example the " + "LMI_RealmdKerberosRealm.Join() method. " + + "To deconfigure a realm from the current system, you can use the " + "Deconfigure() method. "), + Provider("cmpi:cmpiLMI_Realmd") ] +class LMI_RealmdRealm : CIM_LogicalElement +{ + + [Key, Override ( "InstanceID" ), + Description ( + "Within the scope of the instantiating Namespace, " + "InstanceID opaquely and uniquely identifies an instance " + "of this class. In order to ensure uniqueness within the " + "NameSpace, the value of InstanceID shall be constructed " + "using the following \'preferred\' algorithm: \n" + ": \n" + " will be DBus object path correlated to this instance.")] + string InstanceID; + + [Key, Description ( "The scoping System\'s CCN." ), + MaxLen ( 256 ), + Propagated ( "CIM_System.CreationClassName" )] + string SystemCreationClassName; + + [Key, Description ( "The scoping System\'s Name." ), + MaxLen ( 256 ), + Propagated ( "CIM_System.Name" )] + string SystemName; + + [Description ( + "Name of the realm, " + "appropriate for display to end users where necessary.")] + string RealmName; + + [Description ( + "If this property is NULL then the realm is not configured." + "Otherwise the realm is configured and the property contains " + "a string which is the interface that represents how it was " + "configured, e.g. \"KerberosMembership\".")] + string Configured; + + [Description ( + "Indicates the types of operations this realm is capable of." + "Current possible values are: \"Kerberos\", \"KerberosMembership\".")] + string SupportedInterfaces[]; + + [Description ( + "Extra detail information expressed as (name,value) pairs. " + "This array is correlated with the DetailValues array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed."), + ArrayType ( "Indexed" )] + string DetailNames[]; + [Description ( + "Extra detail information expressed as (name,value) pairs. " + "This array is correlated with the DetailNames array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed."), + ArrayType ( "Indexed" )] + string DetailValues[]; + + [Description ( + "Software packages that are required in order for a join to " + "succeed. These are either simple strings like \"sssd\" " + "or strings with an operator and version number like \"sssd >= 1.9.0\" " + "These values are specific to the packaging system that is being run.")] + string RequiredPackages[]; + + [Description ( + "Supported formats for login to this realm. This is only " + "relevant once the realm has been enrolled. The formats " + "will contain a \"%U\" in the string, which indicates where the " + "user name should be placed. The formats may contain a \"%D\" in " + "the string which indicates where a domain name should be placed. " + "The first format in the list is the preferred format for login names.")] + string LoginFormats[]; + + [Description ( + "The policy for logging into this computer using this realm. " + "The policy can be changed using the ChangeLoginPolicy() method. " + "The following policies are predefined. Not all providers support " + "all these policies and there may be provider specific policies or " + "multiple policies represented in the string: " + "\"allow-any-login\": allow login by any authenticated user present in this realm. " + "\"allow-permitted-logins\": only allow the logins permitted in the PermittedLogins property. " + "\"deny-any-login\": don't allow any logins via authenticated users of this realm.")] + string LoginPolicy; + + [Description ( + "The list of permitted authenticated users allowed to login " + "into this computer. This is only relevant if the LoginPolicy property " + "contains the \"allow-permitted-logins\" string.")] + string PermittedLogins[]; + + [Description ( + "Change the login policy and/or permitted logins for this realm. " + "Not all realms support the all the various login policies. An " + "error will be returned if the new login policy is not supported. " + "You may specify a NULL value for the login_policy argument which " + "will cause no change in the policy itself. If the policy is changed, " + "it will be reflected in the LoginPolicy property. " + "The permitted_add and permitted_remove arguments represent lists of " + "login names that should be added and removed from the PermittedLogins property.")] + uint32 ChangeLoginPolicy( + [In, Description ( + "the new login policy or NULL")] + string LoginPolicy, + [In, Description ( + "a list of logins to permit")] + string PermittedAdd[], + [In, Description ( + "a list of logins to not permit")] + string PermittedRemove[]); + + [Description ( + "Deconfigure: deconfigure this realm" + "\n" + "Deconfigure this realm from the local machine with standard " + "default behavior. " + "\n" + "The behavior of this method depends on the which configuration " + "interface is present in the Configured property. It does not " + "always delete membership accounts in the realm, but just " + "reconfigures the local machine so it no longer is configured " + "for the given realm. In some cases the implementation may try " + "to update membership accounts, but this is not guaranteed." + "\n" + "Various configuration interfaces may support more specific ways " + "to deconfigure a realm in a specific way, such as the " + "KerberosMembership.Leave() method.")] + uint32 Deconfigure(); + +}; + + +[ Description ( + "Credentials supported for joining. " + "\n" + "Various kinds of credentials that are supported when calling the " + "Join() method. " + "\n" + "Each credential is represented by a type, and an owner. The type " + "denotes which kind of credential is passed to the method. The " + "owner indicates to the client how to prompt the user or obtain " + "the credential, and to the service how to use the credential. " + "\n" + + "The various types are: " + "\"ccache\": " + "The credentials should contain an array of octets containing" + "the data from a kerberos credential cache file. " + "The data must be passed in the Data parameter, the Name & Password parameters must be NULL. " + "\n" + "\"password\": " + "The credentials should contain a pair of strings representing " + "a name and password. The name may contain a realm in the " + "standard kerberos format. If a realm is missing, it will " + "default to this realm. " + "The name must be passed in the Name parameter, the password must be passed " + "in the Password parameter, the Data parameter must be NULL. " + "\n" + "\"secret\": " + "The credentials should contain a string secret. This is " + "usually used for one time passwords. " + "The data must be passed in the Data parameter, the Name & Password parameters must be NULL. " + "\n" + "\"automatic\": " + "The credentials should contain an empty string. Using " + "\"automatic\" indicates that default or system credentials are " + "to be used. " + "The Name, Password & Data parameters must be NULL. " + "\n" + "The various owners are: " + "\n" + "\"administrator\": " + "The credentials belong to a kerberos user principal. " + "The caller may use this as a hint to prompt the user " + "for administrative credentials. " + "\n" + "\"user\": " + "The credentials belong to a kerberos user principal. The " + "caller may use this as a hint to prompt the user for his " + "(possibly non-administrative) credentials. " + "\n" + "\"computer\": " + "The credentials belong to a computer account. " + "\n" + "\"none\": " + "The credentials have an unspecified owner, such as a one time " + "secret."), + Provider("cmpi:cmpiLMI_Realmd") ] +class LMI_RealmdKerberosRealm : LMI_RealmdRealm +{ + [Description ( + "The kerberos name for this realm. This is usually in upper " + "case.")] + string RealmName; + + [Description ( + "The DNS domain name for this realm.")] + string DomainName; + + [Description ( + "The common administrator name for this type of realm. This " + "can be used by clients as a hint when prompting the user for " + "administrative authentication.")] + string SuggestedAdministrator; + + [Description ( + "This array is correlated with the SupportedJoinCredentialOwners array. " + + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (type,owner) tuple " + "can be constructed. The set of tuples formed by correlating " + "the two arrays define the supported combinations for the Join " + "method."), + ValueMap { "1", "2", "3", "4"}, + Values { "ccache", "password", "secrect", "automatic" }, + ArrayType ( "Indexed" )] + uint32 SupportedJoinCredentialTypes[]; + + [Description ( + "This array is correlated with the SupportedJoinCredentialTypes array. " + + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (type,owner) tuple " + "can be constructed. The set of tuples formed by correlating " + "the two arrays define the supported combinations for the Join " + "method."), + ValueMap { "1", "2", "3", "4"}, + Values { "administrator", "user", "computer", "none" }, + ArrayType ( "Indexed" )] + uint32 SupportedJoinCredentialOwners[]; + + [Description ( + "This array is correlated with the SupportedLeaveCredentialOwners array. " + + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (type,owner) tuple " + "can be constructed. The set of tuples formed by correlating " + "the two arrays define the supported combinations for the Leave " + "method."), + ValueMap { "1", "2", "3", "4"}, + Values { "ccache", "password", "secrect", "automatic" }, + ArrayType ( "Indexed" )] + uint32 SupportedLeaveCredentialTypes[]; + + [Description ( + "This array is correlated with the SupportedLeaveCredentialTypes array. " + + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (type,owner) tuple " + "can be constructed. The set of tuples formed by correlating " + "the two arrays define the supported combinations for the Leave " + "method."), + ValueMap { "1", "2", "3", "4"}, + Values { "administrator", "user", "computer", "none" }, + ArrayType ( "Indexed" )] + uint32 SupportedLeaveCredentialOwners[]; + + // FIXME - The Data parameter should be uint8 array with the octetstring qualifier + // but the octetstring qualier doesn't seem to do anything and you end up with + // an array of CMPIValue's with one octet in each, this is highly inefficent and awkward. + + [Description ( + "")] + uint32 Join( + [In, Description ( + "Credential type, see LMI_RealmdKerberosRealm description"), + ValueMap { "1", "2", "3", "4"}, + Values { "ccache", "password", "secrect", "automatic" }] + uint32 Type, + [In, Description ( + "Credential owner, see LMI_RealmdKerberosRealm description"), + ValueMap { "1", "2", "3", "4"}, + Values { "administrator", "user", "computer", "none" }] + uint32 Owner, + [In, Description ( + "The name may contain a realm in the standard kerberos format. " + "If a realm is missing, it will default to this realm. " + "Used when the Type is password.")] + string Name, + [In, Description ( + "Authentication password. " + "Used when the Type is password.")] + string Password, + [In, Description ( + "Binary data when the Type is ccache or secret"), + OctetString] + uint8 Data[], + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionValues array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionNames[], + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionNames array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionValues[]); + + [Description ( + "")] + uint32 Leave( + [In, Description ( + "Credential type, see LMI_RealmdKerberosRealm description"), + ValueMap { "1", "2", "3", "4"}, + Values { "ccache", "password", "secrect", "automatic" }] + uint32 Type, + [In, Description ( + "Credential owner, see LMI_RealmdKerberosRealm description"), + ValueMap { "1", "2", "3", "4"}, + Values { "administrator", "user", "computer", "none" }] + uint32 Owner, + [In, Description ( + "The name may contain a realm in the standard kerberos format. " + "If a realm is missing, it will default to this realm. " + "Used when the Type is password.")] + string Name, + [In, Description ( + "Authentication password. " + "Used when the Type is password.")] + string Password, + [In, Description ( + "Binary data when the Type is ccache or secret"), + OctetString] + uint8 Data[], + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionValues array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionNames[], + [In, ArrayType ( "Indexed" ), Description ( + "This array is correlated with the OptionNames array. " + "Each entry is related to the entries in the other array " + "located at the same index. In this way a (name,value) tuple " + "can be constructed.")] + string OptionValues[]); +}; + +[ Association, + Provider("cmpi:cmpiLMI_Realmd") ] +class LMI_HostedRealmdService: CIM_HostedService +{ + [ Override("Antecedent"), + Description("The hosting System") ] + CIM_ComputerSystem REF Antecedent; + + [ Override("Dependent"), + Description("The Central Instance of realm management") ] + LMI_RealmdService REF Dependent; +}; + +[ Association, + Provider("cmpi:cmpiLMI_Realmd") ] +class LMI_ServiceAffectsRealmdRealm: CIM_ServiceAffectsElement +{ + [ Override("AffectingElement"), + Description("The Central Instance of realm management") ] + LMI_RealmdService REF AffectingElement; + + [ Override("AffectedElement"), + Description("The managed Identity") ] + LMI_RealmdRealm REF AffectedElement; +}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6bf4b85..0cd11d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,3 +40,7 @@ endif (WITH-HARDWARE) if (WITH-LOGICALFILE) add_subdirectory(logicalfile) endif (WITH-LOGICALFILE) + +if (WITH-REALMD) + add_subdirectory(realmd) +endif (WITH-REALMD) diff --git a/src/realmd/CMakeLists.txt b/src/realmd/CMakeLists.txt new file mode 100644 index 0000000..380dd5c --- /dev/null +++ b/src/realmd/CMakeLists.txt @@ -0,0 +1,50 @@ +pkg_check_modules(DBUS1 dbus-1 REQUIRED) +pkg_check_modules(GLIB2 glib-2.0 REQUIRED) + +set(PROVIDER_NAME Realmd) +set(LIBRARY_NAME cmpiLMI_${PROVIDER_NAME}) +set(MOF LMI_Realmd.mof) + + +set(provider_SRCS + LMI_HostedRealmdServiceProvider.c + LMI_RealmdKerberosRealmProvider.c + LMI_RealmdRealmProvider.c + LMI_RealmdServiceProvider.c + LMI_ServiceAffectsRealmdRealmProvider.c + rdcp_dbus.c + rdcp_error.c + rdcp_util.c +) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + +konkretcmpi_generate(${MOF} + CIM_PROVIDERS + CIM_HEADERS +) + +add_library(${LIBRARY_NAME} SHARED + ${provider_SRCS} + ${CIM_PROVIDERS} + ${CIM_HEADERS} +) + +# FIXME - /usr/include/openlmi shouldn't be hardcoded, needed for globals.h +include_directories(${CMAKE_CURRENT_BINARY_DIR} + ${CMPI_INCLUDE_DIR} + ${DBUS1_INCLUDE_DIRS} + ${GLIB2_INCLUDE_DIRS} + ) + +target_link_libraries(${LIBRARY_NAME} + openlmicommon + ${KONKRETCMPI_LIBRARIES} + ${DBUS1_LIBRARIES} + ${GLIB2_LIBRARIES} + ) + +# Create registration file +cim_registration(${PROVIDER_NAME} ${LIBRARY_NAME} ${MOF} share/openlmi-providers) + +install(TARGETS ${LIBRARY_NAME} DESTINATION lib${LIB_SUFFIX}/cmpi/) diff --git a/src/realmd/LMI_HostedRealmdServiceProvider.c b/src/realmd/LMI_HostedRealmdServiceProvider.c new file mode 100644 index 0000000..08732c1 --- /dev/null +++ b/src/realmd/LMI_HostedRealmdServiceProvider.c @@ -0,0 +1,233 @@ +#include +#include "LMI_HostedRealmdService.h" +#include "CIM_ComputerSystem.h" +#include "rdcp_util.h" +#include "globals.h" + +static const CMPIBroker* _cb; + +static void LMI_HostedRealmdServiceInitialize() +{ +} + +static CMPIStatus LMI_HostedRealmdServiceCleanup( + CMPIInstanceMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_HostedRealmdServiceEnumInstanceNames( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + return KDefaultEnumerateInstanceNames( + _cb, mi, cc, cr, cop); +} + +static CMPIStatus LMI_HostedRealmdServiceEnumInstances( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + CMPIStatus status; + CIM_ComputerSystemRef computer_sys_ref; + LMI_RealmdServiceRef realmd_service_ref; + LMI_HostedRealmdService hosted_realmd_service; + CMPIObjectPath *computer_sys_op = NULL; + + const char *name_space = KNameSpace(cop); + const char *host_name = get_system_name(); + + CMSetStatus(&status, CMPI_RC_OK); + + LMI_InitComputerSystemKeys(CIM_ComputerSystemRef, &computer_sys_ref, + name_space, host_name); + + LMI_InitRealmdServiceKeys(LMI_RealmdServiceRef, &realmd_service_ref, name_space, host_name); + + computer_sys_op = LMI_RealmdServiceRef_ToObjectPath(&realmd_service_ref, &status); + computer_sys_op->ft->setClassName(computer_sys_op, + get_system_creation_class_name()); + + LMI_HostedRealmdService_Init(&hosted_realmd_service, _cb, name_space); + LMI_HostedRealmdService_SetObjectPath_Antecedent(&hosted_realmd_service, + computer_sys_op); + LMI_HostedRealmdService_Set_Dependent(&hosted_realmd_service, + &realmd_service_ref); + + KReturnInstance(cr, hosted_realmd_service); + + return status; +} + +static CMPIStatus LMI_HostedRealmdServiceGetInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + return KDefaultGetInstance( + _cb, mi, cc, cr, cop, properties); +} + +static CMPIStatus LMI_HostedRealmdServiceCreateInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_HostedRealmdServiceModifyInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci, + const char**properties) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_HostedRealmdServiceDeleteInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_HostedRealmdServiceExecQuery( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* lang, + const char* query) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_HostedRealmdServiceAssociationCleanup( + CMPIAssociationMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_HostedRealmdServiceAssociators( + CMPIAssociationMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* assocClass, + const char* resultClass, + const char* role, + const char* resultRole, + const char** properties) +{ + return KDefaultAssociators( + _cb, + mi, + cc, + cr, + cop, + LMI_HostedRealmdService_ClassName, + assocClass, + resultClass, + role, + resultRole, + properties); +} + +static CMPIStatus LMI_HostedRealmdServiceAssociatorNames( + CMPIAssociationMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* assocClass, + const char* resultClass, + const char* role, + const char* resultRole) +{ + return KDefaultAssociatorNames( + _cb, + mi, + cc, + cr, + cop, + LMI_HostedRealmdService_ClassName, + assocClass, + resultClass, + role, + resultRole); +} + +static CMPIStatus LMI_HostedRealmdServiceReferences( + CMPIAssociationMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* assocClass, + const char* role, + const char** properties) +{ + return KDefaultReferences( + _cb, + mi, + cc, + cr, + cop, + LMI_HostedRealmdService_ClassName, + assocClass, + role, + properties); +} + +static CMPIStatus LMI_HostedRealmdServiceReferenceNames( + CMPIAssociationMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* assocClass, + const char* role) +{ + return KDefaultReferenceNames( + _cb, + mi, + cc, + cr, + cop, + LMI_HostedRealmdService_ClassName, + assocClass, + role); +} + +CMInstanceMIStub( + LMI_HostedRealmdService, + LMI_HostedRealmdService, + _cb, + LMI_HostedRealmdServiceInitialize()) + +CMAssociationMIStub( + LMI_HostedRealmdService, + LMI_HostedRealmdService, + _cb, + LMI_HostedRealmdServiceInitialize()) + +KONKRET_REGISTRATION( + "root/cimv2", + "LMI_HostedRealmdService", + "LMI_HostedRealmdService", + "instance association"); diff --git a/src/realmd/LMI_RealmdKerberosRealmProvider.c b/src/realmd/LMI_RealmdKerberosRealmProvider.c new file mode 100644 index 0000000..5dc90d2 --- /dev/null +++ b/src/realmd/LMI_RealmdKerberosRealmProvider.c @@ -0,0 +1,627 @@ +#include +#include "LMI_RealmdKerberosRealm.h" +#include "globals.h" +#include "rdcp_error.h" +#include "rdcp_dbus.h" +#include "rdcp_util.h" +#include "rdcp_realmdrealm.h" + +static const CMPIBroker* _cb = NULL; + +CMPIStatus LMI_RealmdKerberosRealmRef_InitFromDBusPath( + LMI_RealmdKerberosRealmRef* self, + const CMPIBroker* cb, + const char* ns, + const char* dbus_path) +{ + CMPIStatus status; + + CMSetStatus(&status, CMPI_RC_OK); + + LMI_RealmdRealmInitKeys(LMI_RealmdKerberosRealmRef, self, dbus_path); + + return status; +} + +CMPIStatus LMI_RealmdKerberosRealm_InitFromDBusPath( + LMI_RealmdKerberosRealm* self, + const CMPIBroker* cb, + const char* ns, + const char* dbus_path) +{ + CMPIStatus status; + GError *g_error = NULL; + GVariant *realm_props = NULL; + GVariant *kerberos_props = NULL; + GVariant *kerberos_membership_props = NULL; + + CMSetStatus(&status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + return handle_g_error(&g_error, _cb, &status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + } + + GET_DBUS_PROPERIES_OR_EXIT(realm_props, dbus_path, + REALM_DBUS_REALM_INTERFACE, &status); + GET_DBUS_PROPERIES_OR_EXIT(kerberos_props, dbus_path, + REALM_DBUS_KERBEROS_INTERFACE, &status); + + LMI_RealmdRealmInitKeys(LMI_RealmdKerberosRealm, self, dbus_path); + LMI_InitFromDBusRealmProps(LMI_RealmdKerberosRealm, self, realm_props); + LMI_InitFromDBusKerberosRealmProps(LMI_RealmdKerberosRealm, self, kerberos_props); + + if (SupportsDBusInterface(realm_props, REALM_DBUS_KERBEROS_MEMBERSHIP_INTERFACE)) { + GET_DBUS_PROPERIES_OR_EXIT(kerberos_membership_props, dbus_path, + REALM_DBUS_KERBEROS_MEMBERSHIP_INTERFACE, &status); + + LMI_InitFromDBusKerberosMembershipProps(LMI_RealmdKerberosRealm, self, + kerberos_membership_props); + } + + exit: + G_VARIANT_FREE(realm_props); + G_VARIANT_FREE(kerberos_props); + G_VARIANT_FREE(kerberos_membership_props); + + return status; +} + + +static void LMI_RealmdKerberosRealmInitialize() +{ +} + +static CMPIStatus LMI_RealmdKerberosRealmCleanup( + CMPIInstanceMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_RealmdKerberosRealmEnumInstanceNames( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + return KDefaultEnumerateInstanceNames( + _cb, mi, cc, cr, cop); +} + +static CMPIStatus LMI_RealmdKerberosRealmEnumInstances( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + CMPIStatus status; + GError *g_error = NULL; + GVariant *provider_props = NULL; + GVariant *realm_props = NULL; + GVariantIter *iter = NULL; + gchar *realm_obj_path; + const char *name_space = KNameSpace(cop); + + CMSetStatus(&status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + return handle_g_error(&g_error, _cb, &status, CMPI_RC_ERR_FAILED, + "rdcp_dbus_initialize failed"); + } + + GET_DBUS_PROPERIES_OR_EXIT(provider_props, REALM_DBUS_SERVICE_PATH, + REALM_DBUS_PROVIDER_INTERFACE, &status); + + g_variant_lookup(provider_props, "Realms", "ao", &iter); + while (g_variant_iter_next(iter, "&o", &realm_obj_path)) { + LMI_RealmdKerberosRealm realmd_realm; + + GET_DBUS_PROPERIES_OR_EXIT(realm_props, realm_obj_path, + REALM_DBUS_REALM_INTERFACE, &status); + if (!SupportsDBusInterface(realm_props, REALM_DBUS_KERBEROS_INTERFACE)) { + G_VARIANT_FREE(realm_props); + continue; + } + G_VARIANT_FREE(realm_props); + + status = LMI_RealmdKerberosRealm_InitFromDBusPath(&realmd_realm, _cb, + name_space, realm_obj_path); + if (status.rc != CMPI_RC_OK) { + goto exit; + } + + KReturnInstance(cr, realmd_realm); + } + + exit: + G_VARIANT_ITER_FREE(iter); + G_VARIANT_FREE(provider_props); + G_VARIANT_FREE(realm_props); + + return status; +} + +static CMPIStatus LMI_RealmdKerberosRealmGetInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + return KDefaultGetInstance( + _cb, mi, cc, cr, cop, properties); +} + +static CMPIStatus LMI_RealmdKerberosRealmCreateInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdKerberosRealmModifyInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci, + const char** properties) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdKerberosRealmDeleteInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdKerberosRealmExecQuery( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* lang, + const char* query) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +CMInstanceMIStub( + LMI_RealmdKerberosRealm, + LMI_RealmdKerberosRealm, + _cb, + LMI_RealmdKerberosRealmInitialize()) + +static CMPIStatus LMI_RealmdKerberosRealmMethodCleanup( + CMPIMethodMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_RealmdKerberosRealmInvokeMethod( + CMPIMethodMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* meth, + const CMPIArgs* in, + CMPIArgs* out) +{ + return LMI_RealmdKerberosRealm_DispatchMethod( + _cb, mi, cc, cr, cop, meth, in, out); +} + +CMMethodMIStub( + LMI_RealmdKerberosRealm, + LMI_RealmdKerberosRealm, + _cb, + LMI_RealmdKerberosRealmInitialize()) + +KUint32 LMI_RealmdKerberosRealm_ChangeLoginPolicy( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdKerberosRealmRef* self, + const KString* LoginPolicy, + const KStringA* PermittedAdd, + const KStringA* PermittedRemove, + CMPIStatus* status) +{ + KUint32 result = KUINT32_INIT; + + KSetStatus(status, ERR_NOT_SUPPORTED); + return result; +} + +KUint32 LMI_RealmdKerberosRealm_Deconfigure( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdKerberosRealmRef* self, + CMPIStatus* status) +{ + KUint32 result = KUINT32_INIT; + + KSetStatus(status, ERR_NOT_SUPPORTED); + return result; +} + +KEXTERN KUint32 LMI_RealmdKerberosRealm_Join( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdKerberosRealmRef* self, + const KUint32* Type, + const KUint32* Owner, + const KString* Name, + const KString* Password, + const KUint8A* Data, + const KStringA* OptionNames, + const KStringA* OptionValues, + CMPIStatus* status) +{ + GError *g_error = NULL; + KUint32 result = KUINT32_INIT; + const char *cred_type = NULL; + const char *cred_owner = NULL; + gchar *data = NULL; + gsize data_len; + gchar *dbus_path = NULL; + GVariant *credentials = NULL; + GVariant *data_variant = NULL; + GVariant *options = NULL; + + KUint32_Set(&result, LMI_REALMD_RESULT_SUCCESS); + CMSetStatus(status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!Type->exists || Type->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "Type parameter absent"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!Owner->exists || Owner->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "Owner parameter absent"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!dbus_path_from_instance_id(self->InstanceID.chars, &dbus_path, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, + "dbus_path_from_instance_id() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + switch(Owner->value) { + case LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_administrator: + case LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_user: + case LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_computer: + case LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_none: + break; + default: + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "Invalid Owner parameter"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + cred_type = SupportedJoinCredentialTypes_enum_to_name(Type->value); + cred_owner = SupportedJoinCredentialOwners_enum_to_name(Owner->value); + + switch(Type->value) { + case LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_ccache: + if ((Name->exists && !Name->null) || (Password->exists && !Password->null)) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Name & Password parameters must be NULL when Type is ccache"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + if (!Data->exists || Data->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Data parameter must be provided when Type is ccache"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if ((data = get_data_from_KUint8A(Data, &data_len)) == NULL) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_FAILED, + "unabled to allocate memory"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + data_variant = g_variant_new_from_data(G_VARIANT_TYPE ("ay"), + data, data_len, + TRUE, g_free, (gpointer) data); + + credentials = g_variant_new("(ssv)", cred_type, cred_owner, data_variant); + break; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_password: + if (!Name->exists || Name->null || !Password->exists || Password->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Name & Password parameters must be provided when Type is password"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + if (Data->exists && !Data->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Data parameter must be NULL when Type is password"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + credentials = g_variant_new("(ssv)", cred_type, cred_owner, + g_variant_new("(ss)", Name->chars, Password->chars)); + + break; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_secrect: + if ((Name->exists && !Name->null) || (Password->exists && !Password->null)) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Name & Password parameters must be NULL when Type is secret"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + if (!Data->exists || Data->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Data parameter must be provided when Type is secret"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if ((data = get_data_from_KUint8A(Data, &data_len)) == NULL) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_FAILED, + "unabled to allocate memory"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + credentials = g_variant_new("(ssv)", cred_type, cred_owner, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, + data, data_len, 1)); + break; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_automatic: + if ((Name->exists && !Name->null) || (Password->exists && !Password->null) || + (Data->exists && !Data->null)) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Name, Password & Data parameters must be NULL when Type is secret"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + credentials = g_variant_new ("(ssv)", cred_type, cred_owner, + g_variant_new_string ("")); + + break; + default: + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "Invalid Type parameter"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + + if (!build_g_variant_options_from_KStringA(OptionNames, OptionValues, &options, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, + "failed to convert options to gvariant"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!dbus_join_call(system_bus, dbus_path, credentials, options, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, "dbus_join_call() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + + exit: + + g_free(data); + G_VARIANT_FREE(credentials); + G_VARIANT_FREE(data_variant); + G_VARIANT_FREE(options); + + return result; +} + +KEXTERN KUint32 LMI_RealmdKerberosRealm_Leave( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdKerberosRealmRef* self, + const KUint32* Type, + const KUint32* Owner, + const KString* Name, + const KString* Password, + const KUint8A* Data, + const KStringA* OptionNames, + const KStringA* OptionValues, + CMPIStatus* status) +{ + GError *g_error = NULL; + KUint32 result = KUINT32_INIT; + const char *cred_type = NULL; + const char *cred_owner = NULL; + gchar *data = NULL; + gsize data_len; + gchar *dbus_path = NULL; + GVariant *credentials = NULL; + GVariant *data_variant = NULL; + GVariant *options = NULL; + + KUint32_Set(&result, LMI_REALMD_RESULT_SUCCESS); + CMSetStatus(status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!Type->exists || Type->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "Type parameter absent"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!Owner->exists || Owner->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "Owner parameter absent"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!dbus_path_from_instance_id(self->InstanceID.chars, &dbus_path, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, + "dbus_path_from_instance_id() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + switch(Owner->value) { + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_administrator: + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_user: + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_computer: + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_none: + break; + default: + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "Invalid Owner parameter"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + cred_type = SupportedLeaveCredentialTypes_enum_to_name(Type->value); + cred_owner = SupportedLeaveCredentialOwners_enum_to_name(Owner->value); + + switch(Type->value) { + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_ccache: + if ((Name->exists && !Name->null) || (Password->exists && !Password->null)) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Name & Password parameters must be NULL when Type is ccache"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + if (!Data->exists || Data->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Data parameter must be provided when Type is ccache"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if ((data = get_data_from_KUint8A(Data, &data_len)) == NULL) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_FAILED, + "unabled to allocate memory"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + data_variant = g_variant_new_from_data(G_VARIANT_TYPE ("ay"), + data, data_len, + TRUE, g_free, (gpointer) data); + + credentials = g_variant_new("(ssv)", cred_type, cred_owner, data_variant); + break; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_password: + if (!Name->exists || Name->null || !Password->exists || Password->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Name & Password parameters must be provided when Type is password"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + if (Data->exists && !Data->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Data parameter must be NULL when Type is password"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + credentials = g_variant_new("(ssv)", cred_type, cred_owner, + g_variant_new("(ss)", Name->chars, Password->chars)); + + break; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_secrect: + if ((Name->exists && !Name->null) || (Password->exists && !Password->null)) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Name & Password parameters must be NULL when Type is secret"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + if (!Data->exists || Data->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Data parameter must be provided when Type is secret"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if ((data = get_data_from_KUint8A(Data, &data_len)) == NULL) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_FAILED, + "unabled to allocate memory"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + credentials = g_variant_new("(ssv)", cred_type, cred_owner, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, + data, data_len, 1)); + break; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_automatic: + if ((Name->exists && !Name->null) || (Password->exists && !Password->null) || + (Data->exists && !Data->null)) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Name, Password & Data parameters must be NULL when Type is secret"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + credentials = g_variant_new ("(ssv)", cred_type, cred_owner, + g_variant_new_string ("")); + + break; + default: + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "Invalid Type parameter"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + + if (!build_g_variant_options_from_KStringA(OptionNames, OptionValues, &options, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, + "failed to convert options to gvariant"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!dbus_leave_call(system_bus, dbus_path, credentials, options, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, "dbus_leave_call() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + + exit: + + g_free(data); + G_VARIANT_FREE(credentials); + G_VARIANT_FREE(data_variant); + G_VARIANT_FREE(options); + + return result; +} + + +KONKRET_REGISTRATION( + "root/cimv2", + "LMI_RealmdKerberosRealm", + "LMI_RealmdKerberosRealm", + "instance method"); diff --git a/src/realmd/LMI_RealmdRealmProvider.c b/src/realmd/LMI_RealmdRealmProvider.c new file mode 100644 index 0000000..d8e0a18 --- /dev/null +++ b/src/realmd/LMI_RealmdRealmProvider.c @@ -0,0 +1,326 @@ +#include +#include +#include "LMI_RealmdRealm.h" +#include "globals.h" +#include "rdcp_error.h" +#include "rdcp_dbus.h" +#include "rdcp_util.h" +#include "rdcp_realmdrealm.h" + +static const CMPIBroker* _cb = NULL; + +CMPIStatus LMI_RealmdRealmRef_InitFromDBusPath( + LMI_RealmdRealmRef* self, + const CMPIBroker* cb, + const char* ns, + const char* dbus_path) +{ + CMPIStatus status; + + CMSetStatus(&status, CMPI_RC_OK); + + LMI_RealmdRealmInitKeys(LMI_RealmdRealmRef, self, dbus_path); + + return status; +} + +CMPIStatus LMI_RealmdRealm_InitFromDBusPath( + LMI_RealmdRealm* self, + const CMPIBroker* cb, + const char* ns, + const char* dbus_path) +{ + CMPIStatus status; + GError *g_error = NULL; + GVariant *realm_props = NULL; + + CMSetStatus(&status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + return handle_g_error(&g_error, _cb, &status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + } + + GET_DBUS_PROPERIES_OR_EXIT(realm_props, dbus_path, + REALM_DBUS_REALM_INTERFACE, &status); + + LMI_RealmdRealmInitKeys(LMI_RealmdRealm, self, dbus_path); + LMI_InitFromDBusRealmProps(LMI_RealmdRealm, self, realm_props); + + exit: + G_VARIANT_FREE(realm_props); + + return status; +} + +static void LMI_RealmdRealmInitialize() +{ +} + +static CMPIStatus LMI_RealmdRealmCleanup( + CMPIInstanceMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_RealmdRealmEnumInstanceNames( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + return KDefaultEnumerateInstanceNames( + _cb, mi, cc, cr, cop); +} + +static CMPIStatus LMI_RealmdRealmEnumInstances( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + CMPIStatus status; + GError *g_error = NULL; + GVariant *provider_props = NULL; + GVariantIter *iter = NULL; + gchar *realm_obj_path; + const char *name_space = KNameSpace(cop); + + CMSetStatus(&status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + return handle_g_error(&g_error, _cb, &status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + } + + GET_DBUS_PROPERIES_OR_EXIT(provider_props, REALM_DBUS_SERVICE_PATH, + REALM_DBUS_PROVIDER_INTERFACE, &status); + + g_variant_lookup(provider_props, "Realms", "ao", &iter); + while (g_variant_iter_next(iter, "&o", &realm_obj_path)) { + LMI_RealmdRealm realmd_realm; + + status = LMI_RealmdRealm_InitFromDBusPath(&realmd_realm, _cb, + name_space, realm_obj_path); + if (status.rc != CMPI_RC_OK) { + goto exit; + } + KReturnInstance(cr, realmd_realm); + } + + exit: + G_VARIANT_ITER_FREE(iter); + G_VARIANT_FREE(provider_props); + + return status; +} + +static CMPIStatus LMI_RealmdRealmGetInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + CMPIStatus status; + GError *g_error = NULL; + LMI_RealmdRealmRef realmdrealm_ref; + gchar *dbus_path = NULL; + LMI_RealmdRealm realmd_realm; + + CMSetStatus(&status, CMPI_RC_OK); + + KReturnIf(LMI_RealmdRealmRef_InitFromObjectPath(&realmdrealm_ref, _cb, cop)); + + if (!dbus_path_from_instance_id(realmdrealm_ref.InstanceID.chars, &dbus_path, &g_error)) { + return handle_g_error(&g_error, _cb, &status, CMPI_RC_ERR_FAILED, + "dbus_path_from_instance_id() failed"); + } + + KReturnIf(LMI_RealmdRealm_InitFromDBusPath(&realmd_realm, _cb, KNameSpace(cop), dbus_path)); + + KReturnInstance(cr, realmd_realm); + + return status; +} + +static CMPIStatus LMI_RealmdRealmCreateInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdRealmModifyInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci, + const char** properties) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdRealmDeleteInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdRealmExecQuery( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* lang, + const char* query) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +CMInstanceMIStub( + LMI_RealmdRealm, + LMI_RealmdRealm, + _cb, + LMI_RealmdRealmInitialize()) + +static CMPIStatus LMI_RealmdRealmMethodCleanup( + CMPIMethodMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_RealmdRealmInvokeMethod( + CMPIMethodMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* meth, + const CMPIArgs* in, + CMPIArgs* out) +{ + return LMI_RealmdRealm_DispatchMethod( + _cb, mi, cc, cr, cop, meth, in, out); +} + +CMMethodMIStub( + LMI_RealmdRealm, + LMI_RealmdRealm, + _cb, + LMI_RealmdRealmInitialize()) + +KUint32 LMI_RealmdRealm_ChangeLoginPolicy( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdRealmRef* self, + const KString* LoginPolicy, + const KStringA* PermittedAdd, + const KStringA* PermittedRemove, + CMPIStatus* status) +{ + GError *g_error = NULL; + KUint32 result = KUINT32_INIT; + gchar *dbus_path = NULL; + const gchar *login_policy = NULL; + GVariant *permitted_add = NULL; + GVariant *permitted_remove = NULL; + GVariant *options = NULL; + + KUint32_Set(&result, LMI_REALMD_RESULT_SUCCESS); + CMSetStatus(status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!LoginPolicy->exists || LoginPolicy->null) { + login_policy = ""; + } else { + login_policy = LoginPolicy->chars; + } + + if (!PermittedAdd->exists || PermittedAdd->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "PermittedAdd parameter absent"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!PermittedRemove->exists || PermittedRemove->null) { + CMSetStatusWithChars(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, "PermittedRemove parameter absent"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!dbus_path_from_instance_id(self->InstanceID.chars, &dbus_path, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, + "dbus_path_from_instance_id() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!build_g_variant_string_array_from_KStringA(PermittedAdd, &permitted_add, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, + "failed to convert PermittedAdd to gvariant array"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!build_g_variant_string_array_from_KStringA(PermittedRemove, &permitted_remove, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, + "failed to convert PermittedRemove to gvariant array"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + /* For now we don't pass any options so just create an empty dictionary */ + options = g_variant_new_array(G_VARIANT_TYPE ("{sv}"), NULL, 0); + + if (!dbus_change_login_policy_call(system_bus, dbus_path, login_policy, + permitted_add, permitted_remove, + options, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, "dbus_change_login_policy_call() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + + exit: + + G_VARIANT_FREE(permitted_add); + G_VARIANT_FREE(permitted_remove); + G_VARIANT_FREE(options); + + return result; +} + +KUint32 LMI_RealmdRealm_Deconfigure( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdRealmRef* self, + CMPIStatus* status) +{ + KUint32 result = KUINT32_INIT; + + KSetStatus(status, ERR_NOT_SUPPORTED); + return result; +} + +KONKRET_REGISTRATION( + "root/cimv2", + "LMI_RealmdRealm", + "LMI_RealmdRealm", + "instance method"); diff --git a/src/realmd/LMI_RealmdServiceProvider.c b/src/realmd/LMI_RealmdServiceProvider.c new file mode 100644 index 0000000..01fc0b8 --- /dev/null +++ b/src/realmd/LMI_RealmdServiceProvider.c @@ -0,0 +1,631 @@ +#include +#include "LMI_RealmdService.h" +#include "globals.h" +#include "rdcp_error.h" +#include "rdcp_dbus.h" +#include "rdcp_util.h" +#include "rdcp_realmdrealm.h" + +static const CMPIBroker* _cb = NULL; + +/** + * get_joined_domain: + * + * @provider_props Realmd service provider properties + * + * Determine if the host is joined to a domain and if so return the domain name. + * + * Returns: domain name if found, NULL otherwise. Must be freed with g_free. + */ +static gchar * +get_joined_domain(GVariant *provider_props) +{ + CMPIStatus status; + GError *g_error = NULL; + GVariant *realm_props = NULL; + GVariant *kerberos_props = NULL; + GVariantIter *iter = NULL; + gchar *realm_obj_path = NULL; + gchar *configured_interface = NULL; + gchar *domain_name = NULL; + + CMSetStatus(&status, CMPI_RC_OK); + + g_variant_lookup(provider_props, "Realms", "ao", &iter); + while (g_variant_iter_next(iter, "&o", &realm_obj_path)) { + GET_DBUS_PROPERIES_OR_EXIT(realm_props, realm_obj_path, + REALM_DBUS_REALM_INTERFACE, &status); + if (g_variant_lookup(realm_props, "Configured", "&s", &configured_interface)) { + if (strlen(configured_interface)) { + if (strcmp(configured_interface, REALM_DBUS_KERBEROS_MEMBERSHIP_INTERFACE) == 0) { + GET_DBUS_PROPERIES_OR_EXIT(kerberos_props, realm_obj_path, + REALM_DBUS_KERBEROS_INTERFACE, &status); + if (g_variant_lookup(kerberos_props, "DomainName", "&s", &domain_name)) { + goto exit; + } + G_VARIANT_FREE(kerberos_props); + } + } + } + G_VARIANT_FREE(realm_props); + } + + exit: + G_VARIANT_ITER_FREE(iter); + G_VARIANT_FREE(realm_props); + G_VARIANT_FREE(kerberos_props); + + return domain_name ? g_strdup(domain_name) : NULL; +} + + + +static void LMI_RealmdServiceInitialize() +{ +} + +static CMPIStatus LMI_RealmdServiceCleanup( + CMPIInstanceMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_RealmdServiceEnumInstanceNames( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + return KDefaultEnumerateInstanceNames( + _cb, mi, cc, cr, cop); +} + +static CMPIStatus LMI_RealmdServiceEnumInstances( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + CMPIStatus status; + GError *g_error = NULL; + LMI_RealmdService lmi_realmd_service; + const char *name_space = KNameSpace(cop); + const char *host_name = get_system_name(); + CMPICount i; + GVariant *provider_props = NULL; + GVariantIter *iter; + gsize n_items; + gchar *realm_obj_path; + gchar *name = NULL; + gchar *version = NULL; + gchar *joined_domain = NULL; + + CMSetStatus(&status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + return handle_g_error(&g_error, _cb, &status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + } + + LMI_InitRealmdServiceKeys(LMI_RealmdService, &lmi_realmd_service, name_space, host_name); + + GET_DBUS_PROPERIES_OR_EXIT(provider_props, REALM_DBUS_SERVICE_PATH, + REALM_DBUS_PROVIDER_INTERFACE, &status); + + g_variant_lookup(provider_props, "Realms", "ao", &iter); + n_items = g_variant_iter_n_children(iter); + LMI_RealmdService_Init_Realms(&lmi_realmd_service, n_items); + for (i = 0; g_variant_iter_next(iter, "&o", &realm_obj_path); i++) { +#ifdef RDCP_DEBUG + printf("path[%d]=%s\n", i, realm_obj_path); +#endif + LMI_RealmdService_Set_Realms(&lmi_realmd_service, i, realm_obj_path); + } + + if (g_variant_lookup(provider_props, "Name", "&s", &name)) { + LMI_RealmdService_Set_RealmdName(&lmi_realmd_service, name); + } + + if (g_variant_lookup(provider_props, "Version", "&s", &version)) { + LMI_RealmdService_Set_RealmdVersion(&lmi_realmd_service, version); + } + + if ((joined_domain = get_joined_domain(provider_props))) { + LMI_RealmdService_Set_Domain(&lmi_realmd_service, joined_domain); + } + + KReturnInstance(cr, lmi_realmd_service); + + exit: + G_VARIANT_ITER_FREE(iter); + G_VARIANT_FREE(provider_props); + g_free(joined_domain); + + return status; +} + +static CMPIStatus LMI_RealmdServiceGetInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + return KDefaultGetInstance( + _cb, mi, cc, cr, cop, properties); +} + +static CMPIStatus LMI_RealmdServiceCreateInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdServiceModifyInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci, + const char** properties) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdServiceDeleteInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_RealmdServiceExecQuery( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* lang, + const char* query) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +CMInstanceMIStub( + LMI_RealmdService, + LMI_RealmdService, + _cb, + LMI_RealmdServiceInitialize()) + +static CMPIStatus LMI_RealmdServiceMethodCleanup( + CMPIMethodMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_RealmdServiceInvokeMethod( + CMPIMethodMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* meth, + const CMPIArgs* in, + CMPIArgs* out) +{ + return LMI_RealmdService_DispatchMethod( + _cb, mi, cc, cr, cop, meth, in, out); +} + +CMMethodMIStub( + LMI_RealmdService, + LMI_RealmdService, + _cb, + LMI_RealmdServiceInitialize()) + +KUint32 LMI_RealmdService_RequestStateChange( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdServiceRef* self, + const KUint16* RequestedState, + KRef* Job, + const KDateTime* TimeoutPeriod, + CMPIStatus* status) +{ + KUint32 result = KUINT32_INIT; + + KSetStatus(status, ERR_NOT_SUPPORTED); + return result; +} + +KUint32 LMI_RealmdService_StartService( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdServiceRef* self, + CMPIStatus* status) +{ + KUint32 result = KUINT32_INIT; + + KSetStatus(status, ERR_NOT_SUPPORTED); + return result; +} + +KUint32 LMI_RealmdService_StopService( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdServiceRef* self, + CMPIStatus* status) +{ + KUint32 result = KUINT32_INIT; + + KSetStatus(status, ERR_NOT_SUPPORTED); + return result; +} + +KUint32 LMI_RealmdService_ChangeAffectedElementsAssignedSequence( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdServiceRef* self, + const KRefA* ManagedElements, + const KUint16A* AssignedSequence, + KRef* Job, + CMPIStatus* status) +{ + KUint32 result = KUINT32_INIT; + + KSetStatus(status, ERR_NOT_SUPPORTED); + return result; +} + +KUint32 LMI_RealmdService_Discover( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdServiceRef* self, + const KString* Target, + const KStringA* OptionNames, + const KStringA* OptionValues, + KRefA* DiscoveredRealms, + CMPIStatus* status) +{ + GError *g_error = NULL; + KUint32 result = KUINT32_INIT; + GVariant *options = NULL; + gint32 relevance = 0; + gchar **paths = NULL; + gchar *path, **pp; + CMPICount i, n_paths; + + KUint32_Set(&result, LMI_REALMD_RESULT_SUCCESS); + CMSetStatus(status, CMPI_RC_OK); + + if (!rdcp_dbus_initialize(&g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!build_g_variant_options_from_KStringA(OptionNames, OptionValues, &options, &g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, + "failed to convert options to gvariant"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!dbus_discover_call(system_bus, Target->chars, options, + &relevance, &paths, &g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, "dbus_discover_call() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + +#ifdef RDCP_DEBUG + print_paths(paths, "%s: target=%s, paths:", __FUNCTION__, Target->chars); +#endif + + for (pp = paths, path = *pp++, n_paths = 0; path; path = *pp++, n_paths++); + + if (!KRefA_Init(DiscoveredRealms, cb, n_paths)) { + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + for (pp = paths, path = *pp++, i = 0; path; path = *pp++, i++) { + LMI_RealmdRealmRef realmdrealm_ref; + CMPIObjectPath *realmdrealm_op; + + + *status = LMI_RealmdRealmRef_InitFromDBusPath(&realmdrealm_ref, cb, + LMI_RealmdServiceRef_NameSpace((LMI_RealmdServiceRef*)self), path); + if (status->rc != CMPI_RC_OK) { + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if ((realmdrealm_op = LMI_RealmdRealmRef_ToObjectPath(&realmdrealm_ref, status)) == NULL) { + goto exit; + } + if (!KRefA_Set(DiscoveredRealms, i, realmdrealm_op)) { + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + } + + exit: + + G_VARIANT_FREE(options); + g_strfreev(paths); + + return result; +} + +// FIXME +static gboolean +get_credential_supported_owner(GVariant *supported, const gchar *cred_type, const gchar **cred_owner_return) +{ + GVariantIter iter; + const gchar *type; + const gchar *owner; + + g_variant_iter_init (&iter, supported); + while (g_variant_iter_loop (&iter, "(&s&s)", &type, &owner)) { + if (g_str_equal (cred_type, type)) { + *cred_owner_return = owner; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +is_credential_supported (GVariant *supported, const gchar *cred_type, const gchar *cred_owner) +{ + GVariantIter iter; + const gchar *type; + const gchar *owner; + + g_variant_iter_init(&iter, supported); + while (g_variant_iter_loop (&iter, "(&s&s)", &type, &owner)) { + if (g_str_equal(cred_type, type) && + g_str_equal(cred_owner, owner)) { + return TRUE; + } + } + + return FALSE; +} + +KUint32 LMI_RealmdService_Join_Leave_Domain( + bool join, + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdServiceRef* self, + const KString* Domain, + const KString* User, + const KString* Password, + const KStringA* OptionNames, + const KStringA* OptionValues, + CMPIStatus* status) +{ + const gchar *method_name = NULL; + const gchar *supported_credentials_property = NULL; + GError *g_error = NULL; + KUint32 result = KUINT32_INIT; + gint32 relevance = 0; + gchar **paths = NULL; + gchar *dbus_path, **pp; + CMPICount n_paths; + const gchar *cred_type = NULL; + const gchar *cred_owner = NULL; + GVariant *supported_creds = NULL; + GVariant *realm_props = NULL; + GVariant *kerberos_membership_props = NULL; + GVariant *credentials = NULL; + GVariant *options = NULL; + + KUint32_Set(&result, LMI_REALMD_RESULT_SUCCESS); + CMSetStatus(status, CMPI_RC_OK); + + /* Assure we can communicate with DBus */ + if (!rdcp_dbus_initialize(&g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (join) { + method_name = "Join"; + supported_credentials_property = "SupportedJoinCredentials"; + } else { + method_name = "Leave"; + supported_credentials_property = "SupportedLeaveCredentials"; + } + + /* Call Discover to obtain list of DBus object paths for domain */ + if (!build_g_variant_options_from_KStringA(OptionNames, OptionValues, &options, &g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, + "failed to convert options to gvariant"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!dbus_discover_call(system_bus, Domain->chars, options, + &relevance, &paths, &g_error)) { + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, "dbus_discover_call() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + +#ifdef RDCP_DEBUG + print_paths(paths, "%s: target=%s, paths:", __FUNCTION__, Domain->chars); +#endif + + for (pp = paths, dbus_path = *pp++, n_paths = 0; dbus_path; dbus_path = *pp++, n_paths++); + + if (n_paths < 1) { + SetCMPIStatus(cb, status, CMPI_RC_ERR_FAILED, "Domain (%s) does not exist", Domain->chars); + KUint32_Set(&result, LMI_REALMD_RESULT_NO_SUCH_DOMAIN); + goto exit; + } + + dbus_path = paths[0]; + + /* Lookup the realm properties so we can determine the supported DBus interfaces */ + GET_DBUS_PROPERIES_OR_EXIT(realm_props, dbus_path, + REALM_DBUS_REALM_INTERFACE, status); + if (!SupportsDBusInterface(realm_props, REALM_DBUS_KERBEROS_MEMBERSHIP_INTERFACE)) { + SetCMPIStatus(cb, status, CMPI_RC_ERR_FAILED, "Domain (%s) does not support joining or leaving", + Domain->chars); + KUint32_Set(&result, LMI_REALMD_RESULT_DOMAIN_DOES_NOT_SUPPORT_JOINING); + goto exit; + } + + GET_DBUS_PROPERIES_OR_EXIT(kerberos_membership_props, dbus_path, + REALM_DBUS_KERBEROS_MEMBERSHIP_INTERFACE, status); + + if (!g_variant_lookup(kerberos_membership_props, supported_credentials_property, "@a(ss)", + &supported_creds)) { + SetCMPIStatus(cb, status, CMPI_RC_ERR_FAILED, + "Domain (%s) did not supply supported %s credentials", + Domain->chars, method_name); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + + if (!User->exists || User->null) { + /* No User */ + if (!Password->exists || Password->null) { + /* No User, No Password: automatic */ + cred_type = "automatic"; + if (!get_credential_supported_owner(supported_creds, cred_type, &cred_owner)) { + SetCMPIStatus(cb, status, CMPI_RC_ERR_FAILED, + "Domain (%s) does not support automatic %s credentials", + Domain->chars, method_name); + KUint32_Set(&result, LMI_REALMD_RESULT_DOMAIN_DOES_NOT_SUPPORT_PROVIDED_CREDENTIALS); + goto exit; + } + + credentials = g_variant_new ("(ssv)", cred_type, cred_owner, + g_variant_new_string ("")); + + } else { + /* No User, Password: one time password using secret */ + cred_type = "secret"; + if (!get_credential_supported_owner(supported_creds, cred_type, &cred_owner)) { + SetCMPIStatus(cb, status, CMPI_RC_ERR_FAILED, + "Domain (%s) does not support secret %s credentials", + Domain->chars, method_name); + KUint32_Set(&result, LMI_REALMD_RESULT_DOMAIN_DOES_NOT_SUPPORT_PROVIDED_CREDENTIALS); + goto exit; + } + credentials = g_variant_new("(ssv)", cred_type, cred_owner, + g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + Password->chars, + strlen(Password->chars), 1)); + } + } else { + /* User */ + if (!Password->exists || Password->null) { + /* User, No Password: invalid combination */ + SetCMPIStatus(cb, status, CMPI_RC_ERR_INVALID_PARAMETER, + "Must provide a password when User is provided"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } else { + /* User, Password: password auth */ + cred_type = "password"; + cred_owner = "administrator"; + if (!is_credential_supported(supported_creds, cred_type, cred_owner)) { + SetCMPIStatus(cb, status, CMPI_RC_ERR_FAILED, + "Domain (%s) does not support password with administrator ownership credentials", + Domain->chars); + KUint32_Set(&result, LMI_REALMD_RESULT_DOMAIN_DOES_NOT_SUPPORT_PROVIDED_CREDENTIALS); + goto exit; + } + credentials = g_variant_new("(ssv)", cred_type, cred_owner, + g_variant_new("(ss)", User->chars, Password->chars)); + + } + } + + if (join) { + if (!dbus_join_call(system_bus, dbus_path, credentials, options, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, "dbus_join_call() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + } else { + if (!dbus_leave_call(system_bus, dbus_path, credentials, options, &g_error)) { + handle_g_error(&g_error, cb, status, CMPI_RC_ERR_FAILED, "dbus_leave_call() failed"); + KUint32_Set(&result, LMI_REALMD_RESULT_FAILED); + goto exit; + } + } + + + exit: + + G_VARIANT_FREE(supported_creds); + G_VARIANT_FREE(realm_props); + G_VARIANT_FREE(kerberos_membership_props); + G_VARIANT_FREE(credentials); + G_VARIANT_FREE(options); + g_strfreev(paths); + + return result; +} +KEXTERN KUint32 LMI_RealmdService_JoinDomain( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdServiceRef* self, + const KString* Domain, + const KString* User, + const KString* Password, + const KStringA* OptionNames, + const KStringA* OptionValues, + CMPIStatus* status) +{ + return LMI_RealmdService_Join_Leave_Domain(true, cb, mi, context, self, + Domain, User, Password, + OptionNames, OptionValues, + status); +} + +KEXTERN KUint32 LMI_RealmdService_LeaveDomain( + const CMPIBroker* cb, + CMPIMethodMI* mi, + const CMPIContext* context, + const LMI_RealmdServiceRef* self, + const KString* Domain, + const KString* User, + const KString* Password, + const KStringA* OptionNames, + const KStringA* OptionValues, + CMPIStatus* status) +{ + return LMI_RealmdService_Join_Leave_Domain(false, cb, mi, context, self, + Domain, User, Password, + OptionNames, OptionValues, + status); +} + +KONKRET_REGISTRATION( + "root/cimv2", + "LMI_RealmdService", + "LMI_RealmdService", + "instance method"); diff --git a/src/realmd/LMI_ServiceAffectsRealmdRealmProvider.c b/src/realmd/LMI_ServiceAffectsRealmdRealmProvider.c new file mode 100644 index 0000000..70d65f4 --- /dev/null +++ b/src/realmd/LMI_ServiceAffectsRealmdRealmProvider.c @@ -0,0 +1,252 @@ +#include +#include "LMI_ServiceAffectsRealmdRealm.h" +#include "rdcp_util.h" +#include "globals.h" +#include "rdcp_error.h" +#include "rdcp_dbus.h" +#include "rdcp_util.h" +#include "rdcp_realmdrealm.h" + +static const CMPIBroker* _cb; + +static void LMI_ServiceAffectsRealmdRealmInitialize() +{ +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmCleanup( + CMPIInstanceMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmEnumInstanceNames( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + return KDefaultEnumerateInstanceNames( + _cb, mi, cc, cr, cop); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmEnumInstances( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + CMPIStatus status; + GError *g_error = NULL; + GVariant *provider_props = NULL; + GVariantIter *iter = NULL; + gchar *realm_obj_path; + LMI_RealmdServiceRef realmd_service_ref; + LMI_ServiceAffectsRealmdRealm service_affects; + const char *name_space = KNameSpace(cop); + const char *host_name = get_system_name(); + + CMSetStatus(&status, CMPI_RC_OK); + + LMI_InitRealmdServiceKeys(LMI_RealmdServiceRef, &realmd_service_ref, name_space, host_name); + + if (!rdcp_dbus_initialize(&g_error)) { + return handle_g_error(&g_error, _cb, &status, CMPI_RC_ERR_FAILED, "rdcp_dbus_initialize failed"); + } + + GET_DBUS_PROPERIES_OR_EXIT(provider_props, REALM_DBUS_SERVICE_PATH, + REALM_DBUS_PROVIDER_INTERFACE, &status); + + g_variant_lookup(provider_props, "Realms", "ao", &iter); + while (g_variant_iter_next(iter, "&o", &realm_obj_path)) { + LMI_RealmdRealmRef realmd_realm_ref; + + status = LMI_RealmdRealmRef_InitFromDBusPath(&realmd_realm_ref, _cb, name_space, realm_obj_path); + if (status.rc != CMPI_RC_OK) { + goto exit; + } + + LMI_ServiceAffectsRealmdRealm_Init(&service_affects, _cb, name_space); + LMI_ServiceAffectsRealmdRealm_Set_AffectedElement(&service_affects, &realmd_realm_ref); + LMI_ServiceAffectsRealmdRealm_Set_AffectingElement(&service_affects, &realmd_service_ref); + LMI_ServiceAffectsRealmdRealm_Init_ElementEffects(&service_affects, 1); + LMI_ServiceAffectsRealmdRealm_Set_ElementEffects(&service_affects, 0, + LMI_ServiceAffectsRealmdRealm_ElementEffects_Manages); + + KReturnInstance(cr, service_affects); + } + + exit: + G_VARIANT_ITER_FREE(iter); + G_VARIANT_FREE(provider_props); + + return status; +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmGetInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char** properties) +{ + return KDefaultGetInstance( + _cb, mi, cc, cr, cop, properties); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmCreateInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmModifyInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const CMPIInstance* ci, + const char**properties) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmDeleteInstance( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmExecQuery( + CMPIInstanceMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* lang, + const char* query) +{ + CMReturn(CMPI_RC_ERR_NOT_SUPPORTED); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmAssociationCleanup( + CMPIAssociationMI* mi, + const CMPIContext* cc, + CMPIBoolean term) +{ + CMReturn(CMPI_RC_OK); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmAssociators( + CMPIAssociationMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* assocClass, + const char* resultClass, + const char* role, + const char* resultRole, + const char** properties) +{ + return KDefaultAssociators( + _cb, + mi, + cc, + cr, + cop, + LMI_ServiceAffectsRealmdRealm_ClassName, + assocClass, + resultClass, + role, + resultRole, + properties); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmAssociatorNames( + CMPIAssociationMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* assocClass, + const char* resultClass, + const char* role, + const char* resultRole) +{ + return KDefaultAssociatorNames( + _cb, + mi, + cc, + cr, + cop, + LMI_ServiceAffectsRealmdRealm_ClassName, + assocClass, + resultClass, + role, + resultRole); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmReferences( + CMPIAssociationMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* assocClass, + const char* role, + const char** properties) +{ + return KDefaultReferences( + _cb, + mi, + cc, + cr, + cop, + LMI_ServiceAffectsRealmdRealm_ClassName, + assocClass, + role, + properties); +} + +static CMPIStatus LMI_ServiceAffectsRealmdRealmReferenceNames( + CMPIAssociationMI* mi, + const CMPIContext* cc, + const CMPIResult* cr, + const CMPIObjectPath* cop, + const char* assocClass, + const char* role) +{ + return KDefaultReferenceNames( + _cb, + mi, + cc, + cr, + cop, + LMI_ServiceAffectsRealmdRealm_ClassName, + assocClass, + role); +} + +CMInstanceMIStub( + LMI_ServiceAffectsRealmdRealm, + LMI_ServiceAffectsRealmdRealm, + _cb, + LMI_ServiceAffectsRealmdRealmInitialize()) + +CMAssociationMIStub( + LMI_ServiceAffectsRealmdRealm, + LMI_ServiceAffectsRealmdRealm, + _cb, + LMI_ServiceAffectsRealmdRealmInitialize()) + +KONKRET_REGISTRATION( + "root/cimv2", + "LMI_ServiceAffectsRealmdRealm", + "LMI_ServiceAffectsRealmdRealm", + "instance association"); diff --git a/src/realmd/README b/src/realmd/README new file mode 100644 index 0000000..dec6866 --- /dev/null +++ b/src/realmd/README @@ -0,0 +1,221 @@ +Realmd CIM Provider + +Building +======== + +Prerequisites: +-------------- + +To build you'll need these packages installed: + +cmake +openlmi-providers-devel +konkretcmpi + + +To install and run you'll need at a minimum: +-------------------------------------------- + +tog-pegasus +openlmi-providers + + +This project uses the same build mechanism as openlmi-provider which +is based on cmake. It's also important specify the same cmake +configuration parameters enforced by RPM. + +My short term solution is to use the following shell script. I add the +CFLAGS override to turn on options useful for debugging during +development, you may wish to omit that. + +<<<<<<<<<< +#!/bin/sh + +export CFLAGS='-g -O0 -DRDCP_DEBUG' + +/usr/bin/cmake -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_INSTALL_PREFIX:PATH=/usr -DINCLUDE_INSTALL_DIR:PATH=/usr/include -DLIB_INSTALL_DIR:PATH=/usr/lib -DSYSCONF_INSTALL_DIR:PATH=/etc -DSHARE_INSTALL_PREFIX:PATH=/usr/share -DBUILD_SHARED_LIBS:BOOL=ON . + +if [ $? -eq 0 ]; then + make +fi +>>>>>>>>>> + +Installing: +----------- + +% sudo make install + +This copies the mof file, the registration file and the loadable +module to their destination. Then you must register your module with +the Pegasus CIMOM. Note: pegasus MUST be running! + +% openlmi-mof-register register /usr/share/openlmi-providers/LMI_Realmd.mof /usr/share/openlmi-providers/LMI_Realmd.reg + +Development Tips: +----------------- + +Understanding konkret code generation issues: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +konkret is a tool that reads in a mof file and generates C code. For +every XXX class it will generate a XXX.h and XXXProvider.c file. The +code generation occurs due to CMake macros provided by the +openlmi-providers-devel package. konkret needs to run any time you +modify the mof file. It *always* generates a new XXX.h file because +that's where definitions based on the contents of the mof file are +located. If there is no XXXProvider.c file it will also generate +it. This is a "stub" file in which you will fill in with your +implementation. If XXXProvider.c exits it will not overwrite it, +however it always overwrites the XXX.h file. + +Do not put anything into the XXX.h file you'll need to retain. + +After editing the mof file the make targets will cause konkret to run +again. You'll get brand new XXX.h files. But your old XXXProvider.c +files may no longer have the correct definitions (e.g. prototypes) +found in the XXX.h file so you may need to hand edit by copying the +function prototype from the XXX.h file into your XXXProvider.c file. + +If you've written definitions that logically belong in XXX.h but don't +want them nuked the next time konkret runs my solution was to put them +in someother .h file that's included by the XXXProvider.c file. + +Initializing class instances: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The way konkret works is to emit specialized inline functions to +initialize each member of a class. If the class is subclassed you get +different initializers depending on whether the property is in the +parent class or the subclass. You cannot call a parent class property +initializer in a subclass (yuck), you have to use the subclass +initializer for the property inherited from the parent class. This +creates a maintenance problem if the parent class changes, you have +find every place parent class properties are inialized and make +changes. To solve this problem I defined macros that initialize class +properties. The macro takes a "klass" parameter and token pastes it to +generate the class specific property manipulation function call. Using +these macros means anytime a class changes due to a change in the mof +file there is only one place where you need to change the code. These +macros are a good example of what logically belongs in the XXX.h file +but are separated out into a different .h file because konkret will +nuke anything you've added to a XXX.h file. + +Modifications to the provider: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +During development if the mof file changes you have to make Pegasus +reload the mof. It's not sufficient to retart cimserver, you have to +unregister the provider and register it again for Pegasus to see the +mof changes. Thus you would use the openlmi-mof-register command above +except pass the unregister option, followed by the above command, e.g. + +% openlmi-mof-register unregister /usr/share/openlmi-providers/LMI_Realmd.mof /usr/share/openlmi-providers/LMI_Realmd.reg +% openlmi-mof-register register /usr/share/openlmi-providers/LMI_Realmd.mof /usr/share/openlmi-providers/LMI_Realmd.reg + +If all you've done during devopment is modify the provider (not it's +mof definition) then all you need to do is: + +% sudo cimserver -s +% sudo make install +% sudo cimserver + +How do I run the Pegasus CIMOM so I can see debug statements? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +% sudo cimserver daemon=false forceProviderProcesses=false + +How do I use GDB to debug my provider? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create the following .gdbinit file where XXX is where you want to break. + +<<<<<<<<<< +set breakpoint pending on +b XXX +r daemon=false forceProviderProcesses=false +>>>>>>>>>> + +then run gdb like this: + +% sudo gdb cimserver + +How do I trace what Pegasus is doing? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +% cimserver daemon=false forceProviderProcesses=false logLevel=TRACE traceLevel=5 traceFacility=File traceComponents=All + +The trace file is written to: + +/var/lib/Pegasus/cache/trace/cimserver.trc + +More information about cimserver tracing can be found in the +"OpenPegasus Tracing User Guide" PDF. Google the title to get a +current URL. + +FAQ +=== + +Q: What does the rdcp acronym stand for? + +A: Realmd CIM Provider. The "rd" is for Realmd, the "c" is for CIM and + the "p" is for Provider + +Q: What decisions influenced your DBus implementation strategy? + +A: There are essentially two supported DBus API's. A very high level + GDBus and a very low level libdbus. + + GDBus requires you to utilize Gnome's GObject pseudo + object-orientated framework. If you're not familar with it it's a + fairly steep learning curve to become proficient. Also if you're + not proficient in GObject programming it's hard to comprehend code + which utilizes it thus putting normal C developers at a + disadvantage. However GDBus gives you a lot of nice support, one of + the nice features is everything is based on GVariants, a powerful + data structure. + + On the other hand libdbus is very low level, there is very little + support for the necessary DBus operations. However it is used + extensively by other projects so it's not an aberration to use it, + it's pure C code making it easier to understand and it doesn't pull + in the whole GObject system. But it's a lot of work to use. + + I took a compromise approach. I didn't have the time to become + proficient with GObject and I felt the GObject based code was + difficult for C programmers without GObject experience to read + and modify. However I recognized the value of expressing most + things in terms of GVariants, a hallmark of GDBus. So I wrote some + utility code that supports serializing GVariants into and out of + libdbus. This allowed me to use the powerful GVariant without + having to get involved with GObjects and GDBus. If we ever decide + to port the code to GDBus it shold be fairly straight forward + because we're already using GVariant's as a fundamental type. This + seemed to represent a reasonable compromise between libdus and + GDBus, it avoids the pseudo object-orientated framework of GObject + in favor of vanilla C code but retains the powerful use of + GVariants. I guess only time will tell if it was a smart choice or + not. + +ToDo +==== + +Implement locale in RealmdService. + +Utilize CMPI Logging instead of debug printf statements (currently +controlled by the RDCP_DEBUG compile time flag). + +Generate indications when realms are added or removed from the Realms +property of the LMI_RealmdService. + +Any blocking operations should not block the CIMOM. +(e.g. communicating with DBus). I think the right way to do this is +via the CMPI threading support, but this needs further +investigation. Other openlmi developers would be a good resource for +this issue. + +We call DBus to get object properties a lot. There is I believe +support for DBus clients which caches object properties and listens on +the properties change signal to refresh the properties cache. Access +to the properties are then performed via the local properties cache +rather than the via RPC. Obviously this is much more efficient, we +should support it. diff --git a/src/realmd/VERSION b/src/realmd/VERSION new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/src/realmd/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/src/realmd/doc/class_diagram.svg b/src/realmd/doc/class_diagram.svg new file mode 100644 index 0000000..1b962d3 --- /dev/null +++ b/src/realmd/doc/class_diagram.svg @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + ComputerSystem + + + + + + + + + RealmdService + + + HostedService + + + + + + + + RealmdRealm + + + + + + + + + RealmdKerberosRealm + + ServiceAffectsElement + + + + diff --git a/src/realmd/doc/examples/realmd-cim b/src/realmd/doc/examples/realmd-cim new file mode 100644 index 0000000..90ae9d5 --- /dev/null +++ b/src/realmd/doc/examples/realmd-cim @@ -0,0 +1,245 @@ +#!/usr/bin/python + +import sys +import os +import optparse +import urlparse +import pywbem + +#---------------------------------------------------------------------- + +def do_list(conn, options, args): + realms = conn.EnumerateInstances('LMI_RealmdKerberosRealm') + + print "%d realms" % (len(realms)) + + for realm in realms: + if options.verbose > 1: + # Very verbose, dump all properties + property_names = sorted(realm.keys()) + for name in property_names: + value = realm[name] + print " %s: %s" % (name, value) + + print realm['RealmName'] + print " type: kerberos" + print " realm-name: %s" % realm['RealmName'] + print " domain-name: %s" % realm['DomainName'] + + is_configured = True + configured = realm['Configured'] + if not configured: + configured = "no" + is_configured = False + elif configured == "KerberosMembership": + configured = "kerberos-member" + + print " configured: %s" % configured + + for detail in zip(realm['DetailNames'], realm['DetailValues']): + print " %s: %s" % (detail[0], detail[1]) + + if is_configured: + print " login-formats: %s" % ", ".join(realm['LoginFormats']) + print " login-policy: %s" % realm['LoginPolicy'] + print " permitted-logins: %s" % ", ".join(realm['PermittedLogins']) + + print + +def do_join(conn, options, args): + # Validate arguments + if len(args) != 3: + raise ValueError("You must supply exacly 3 arguments (user, password, domain)") + + user, password, domain = args + + if (options.verbose): + print "Joining domain: %s" % domain + + try: + realmd_service_instance_name = conn.EnumerateInstanceNames('LMI_RealmdService')[0] + except Exception, e: + raise ValueError("could not obtain realmd service") + + try: + retval, outparams = conn.InvokeMethod("JoinDomain", realmd_service_instance_name, + Domain=domain, + User=user, + Password=password) + except Exception, e: + raise ValueError("Join failed (%s)" % (e)) + + + +def do_leave(conn, options, args): + # Validate arguments + if len(args) != 3: + raise ValueError("You must supply exacly 3 arguments (user, password, domain)") + + user, password, domain = args + + if (options.verbose): + print "Leave domain: %s" % domain + + try: + realmd_service_instance_name = conn.EnumerateInstanceNames('LMI_RealmdService')[0] + except Exception, e: + raise ValueError("could not obtain realmd service") + + try: + retval, outparams = conn.InvokeMethod("LeaveDomain", realmd_service_instance_name, + Domain=domain, + User=user, + Password=password) + except Exception, e: + raise ValueError("Leave failed (%s)" % (e)) + + + +def do_discover(conn, options, args): + # Validate arguments + if len(args) != 1: + raise ValueError("You must supply exactly 1 domain.") + + domain = args[0] + + if (options.verbose): + print "Discovering domain: %s" % domain + + try: + realmd_service_instance_name = conn.EnumerateInstanceNames('LMI_RealmdService')[0] + except Exception, e: + raise ValueError("could not obtain realmd service") + + try: + retval, outparams = conn.InvokeMethod("Discover", realmd_service_instance_name, + Target=domain) + except Exception, e: + raise ValueError("Join failed (%s)" % (e)) + + realm_refs = outparams['DiscoveredRealms'] + + print "%d Discovered" % len(realm_refs) + for realm_ref in realm_refs: + #print realm_ref + realm = conn.GetInstance(realm_ref) + print realm['RealmName'] + print " Name: %s" % realm['RealmName'] + print " Configured: %s" % realm['Configured'] + print " Supported Interfaces: %s" % ", ".join(realm['SupportedInterfaces']) + for detail in zip(realm['DetailNames'], realm['DetailValues']): + print " %s: %s" % (detail[0], detail[1]) + print " login-formats: %s" % ", ".join(realm['LoginFormats']) + print " login-policy: %s" % realm['LoginPolicy'] + print " permitted-logins: %s" % ", ".join(realm['PermittedLogins']) + + + +#---------------------------------------------------------------------- + +def main(): + + actions = {'list': do_list, + 'discover': do_discover, + 'join': do_join, + 'leave': do_leave, + } + + usage =''' + %%prog [options] ... + + %%prog [options] list + %%prog [options] discover domain + %%prog [options] join user password domain + %%prog [options] leave user password domain + + Available Actions: %(actions)s + ''' % {'actions': ", ".join(sorted(actions.keys()))} + + # Set-up defaults + + default_cimom_port = 5989 + default_url = os.environ.get("LMI_CIMOM_URL", "https://localhost:5989") + + parsed_default_url = urlparse.urlparse(default_url) + + if parsed_default_url.port: + default_port = parsed_default_url.port or default_cimom_port + + default_username = os.environ.get("LMI_CIMOM_USERNAME", "root") + default_password = os.environ.get("LMI_CIMOM_PASSWORD", "") + + # Set-up arg parser + parser = optparse.OptionParser(usage=usage) + + parser.add_option('-c', '--url', dest='url', default=default_url, + help='CIMOM URL or hostname to connect to') + + parser.add_option('-u', '--username', dest='username', default=default_username, + help='Username for CIMOM authentication') + + parser.add_option('-p', '--password', dest='password', default=default_password, + help='Password for CIMOM authentication') + + parser.add_option('-v', '--verbose', dest='verbose', default=0, + action='count', + help='Turn on verbose output, increases verbosity level by one each time specified') + + options, args = parser.parse_args() + + # Validate arguments + + try: + action = args.pop(0) + except IndexError: + print >>sys.stderr, "You must supply an action to execute" + parser.print_help() + return 1 + + try: + action_func = actions[action] + except KeyError: + print >>sys.stderr, "Unknown action (%s)" % (action) + parser.print_help() + return 1 + + # Get CIMOM URL + + parsed_url = urlparse.urlparse(options.url) + + if not parsed_url.netloc: + # Handle case where URL was bare hostname + parsed_url = urlparse.urlparse('//' + options.url) + + scheme = 'https' + hostname = parsed_url.hostname + port = parsed_url.port or default_port + + url = urlparse.urlunparse((scheme, "%s:%d" % (hostname, port), '', None, None, None)) + + # Connect to CIMOM + + if (options.verbose): + print "Connecting to: %s" % url + + try: + conn = pywbem.WBEMConnection(url, (options.username, options.password)) + except Exception, e: + print >>sys.stderr, "Unable to connect to %s (%s)" % (options.url, e) + return 1 + + + # Execute action + + try: + action_func(conn, options, args) + except Exception, e: + print >>sys.stderr, "%s failed: %s" % (action, e) + return 1 + + return 0 + +#---------------------------------------------------------------------- + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/realmd/rdcp_dbus.c b/src/realmd/rdcp_dbus.c new file mode 100644 index 0000000..b05e5c9 --- /dev/null +++ b/src/realmd/rdcp_dbus.c @@ -0,0 +1,2050 @@ +#include + +#include "rdcp_error.h" +#include "realm-dbus-constants.h" + +#include "rdcp_dbus.h" +#include "rdcp_util.h" + +/*----------------------------------------------------------------------------*/ + +DBusConnection* system_bus = NULL; + +/*----------------------------------------------------------------------------*/ +#ifdef TRACE_VARIANT +static const char * +dbus_type_to_string(int type); +#endif + +static GError * +dbus_error_to_gerror(DBusError *dbus_error); + +static const char* +dbus_msg_type_to_string (int message_type); + +static void +dbus_message_print_indent(GString *string, int depth); + +static void +dbus_message_print_hex(GString *string, unsigned char *bytes, unsigned int len, int depth); + +static void +dbus_message_print_byte_array(GString *string, DBusMessageIter *iter, int depth); + +static void +dbus_message_print_iter(GString *string, DBusMessageIter *iter, int depth); + +static GString * +dbus_message_print_string(DBusMessage *message, GString *string, dbus_bool_t show_header); + +static gchar * +dbus_message_print(DBusMessage *message, GString *string, dbus_bool_t show_header); + +static gboolean +append_g_variant_to_dbus_msg_iter(DBusMessageIter *iter, GVariant *value, GError **g_error); + +static gboolean +append_g_variant_to_dbus_message(DBusMessage *message, GVariant *g_variant, GError **g_error); + +static gboolean +dbus_method_append_args_tuple(DBusMessage *message, GVariant *args, GError **g_error); + +static gboolean +marshal_dbus_string_variant(DBusMessageIter *iter, const char *value, GError **g_error); + +static gboolean +marshal_dbus_dict_string_entry(DBusMessageIter *array, const char *name, const char *value, GError **g_error); + +static gboolean +dbus_iter_to_variant(DBusMessageIter *iter, GVariant **g_variant_return, GError **g_error); + +static gboolean +dbus_message_to_g_variant(DBusMessage *msg, GVariant **g_variant_return, GError **g_error); + +static gboolean +dbus_method_reply_to_g_variant_tuple(DBusMessage *msg, GVariant **g_variant_return, GError **g_error); + +gboolean +get_dbus_string_property(DBusConnection *bus, const char *object_path, + const char* interface, const char *property, + char **value_return, GError **g_error); +gboolean +get_dbus_properties(DBusConnection *bus, const char *object_path, + const char* interface, GVariant **properties_return, + GError **g_error); + +static gboolean +dbus_discover_marshal(const char* target, GVariant *options, + DBusMessage **msg_return, GError **g_error); + +static gboolean +dbus_discover_unmarshal(DBusMessage *reply, gint32 *relevance_return, gchar ***paths_return, GError **g_error); + +/*----------------------------------------------------------------------------*/ + +#define RETURN_DBUS_ERROR(gerror, dbus_error) \ +{ \ + if (gerror) { \ + *gerror = dbus_error_to_gerror(&dbus_error); \ + } \ + dbus_error_free(&dbus_error); \ + return FALSE; \ +} + +GError * +dbus_error_to_gerror(DBusError *dbus_error) +{ + GError *gerror = NULL; + + if (dbus_error == NULL) { + g_set_error(&gerror, RDCP_ERROR, RDCP_ERROR_DBUS, + "dbus error not provided"); + } else { + g_set_error(&gerror, RDCP_ERROR, RDCP_ERROR_DBUS, + "dbus error (%s): %s", dbus_error->name, dbus_error->message); + } + + return gerror; +} + +/*----------------------------------------------------------------------------*/ + +/* + * The code to print a DBus message is based upon DBUS code in + * dbus/tools/dbus-print-message.c which is GPL. + */ + +static const char* +dbus_msg_type_to_string (int message_type) +{ + switch (message_type) + { + case DBUS_MESSAGE_TYPE_SIGNAL: + return "signal"; + case DBUS_MESSAGE_TYPE_METHOD_CALL: + return "method call"; + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + return "method return"; + case DBUS_MESSAGE_TYPE_ERROR: + return "error"; + default: + return "(unknown message type)"; + } +} + +#define INDENT_STRING " " +#define INDENT_WIDTH sizeof(INDENT_STRING) +#define PAGE_WIDTH 80 + +static void +dbus_message_print_indent(GString *string, int depth) +{ + while (depth-- > 0) + g_string_append(string, INDENT_STRING); +} + +static void +dbus_message_print_hex(GString *string, unsigned char *bytes, unsigned int len, int depth) +{ + unsigned int i, columns; + + g_string_append_printf(string, "array of bytes [\n"); + + dbus_message_print_indent (string, depth + 1); + + /* Each byte takes 3 cells (two hexits, and a space), except the last one. */ + columns = (PAGE_WIDTH - ((depth + 1) * INDENT_WIDTH)) / 3; + + if (columns < 8) + columns = 8; + + i = 0; + + while (i < len) { + g_string_append_printf(string, "%02x", bytes[i]); + i++; + + if (i != len) { + if (i % columns == 0) { + g_string_append(string, "\n"); + dbus_message_print_indent(string, depth + 1); + } else { + g_string_append(string, " "); + } + } + } + + g_string_append(string, "\n"); + dbus_message_print_indent(string, depth); + g_string_append(string, "]\n"); +} + +#define DEFAULT_SIZE 100 + +static void +dbus_message_print_byte_array(GString *string, DBusMessageIter *iter, int depth) +{ + unsigned char *bytes = malloc (DEFAULT_SIZE + 1); + unsigned int len = 0; + unsigned int max = DEFAULT_SIZE; + dbus_bool_t all_ascii = TRUE; + int current_type; + + while ((current_type = dbus_message_iter_get_arg_type (iter)) != DBUS_TYPE_INVALID) { + unsigned char val; + + dbus_message_iter_get_basic (iter, &val); + bytes[len] = val; + len++; + + if (val < 32 || val > 126) + all_ascii = FALSE; + + if (len == max) { + max *= 2; + bytes = realloc(bytes, max + 1); + } + + dbus_message_iter_next (iter); + } + + if (all_ascii) { + bytes[len] = '\0'; + g_string_append_printf(string, "array of bytes \"%s\"\n", bytes); + } else { + dbus_message_print_hex(string, bytes, len, depth); + } + + free (bytes); +} + +static void +dbus_message_print_iter(GString *string, DBusMessageIter *iter, int depth) +{ + int type; + + while ((type = dbus_message_iter_get_arg_type (iter)) != DBUS_TYPE_INVALID) { + + dbus_message_print_indent(string, depth); + + switch (type) { + case DBUS_TYPE_BOOLEAN: { + dbus_bool_t val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "boolean %s\n", val ? "true" : "false"); + } break; + + case DBUS_TYPE_BYTE: { + unsigned char val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "byte %d\n", val); + } break; + + case DBUS_TYPE_INT16: { + dbus_int16_t val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "int16 %" G_GINT16_FORMAT "\n", val); + } break; + + case DBUS_TYPE_UINT16: { + dbus_uint16_t val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "uint16 %" G_GUINT16_FORMAT "\n", val); + } break; + + case DBUS_TYPE_INT32: { + dbus_int32_t val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "int32 %" G_GINT32_FORMAT "\n", val); + } break; + + case DBUS_TYPE_UINT32: { + dbus_uint32_t val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "uint32 %" G_GUINT32_FORMAT "\n", val); + } break; + + case DBUS_TYPE_INT64: { + dbus_int64_t val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "int64 %" G_GINT64_FORMAT "\n", val); + } break; + + case DBUS_TYPE_UINT64: { + dbus_uint64_t val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "uint64 %" G_GUINT64_FORMAT "\n", val); + } break; + + case DBUS_TYPE_DOUBLE: { + double val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "double %g\n", val); + } break; + + case DBUS_TYPE_STRING: { + char *val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "string \"%s\"\n", val); + } break; + + case DBUS_TYPE_OBJECT_PATH: { + char *val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "object path \"%s\"\n", val); + } break; + + case DBUS_TYPE_SIGNATURE: { + char *val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "signature \"%s\"\n", val); + } break; + + case DBUS_TYPE_UNIX_FD: { + dbus_uint32_t val; + dbus_message_iter_get_basic (iter, &val); + g_string_append_printf(string, "Unix FD %" G_GUINT32_FORMAT "\n", val); + } break; + + case DBUS_TYPE_ARRAY: { + int current_type; + DBusMessageIter subiter; + + dbus_message_iter_recurse (iter, &subiter); + + current_type = dbus_message_iter_get_arg_type (&subiter); + + if (current_type == DBUS_TYPE_BYTE) { + dbus_message_print_byte_array(string, &subiter, depth); + break; + } + + g_string_append(string, "array [\n"); + while (current_type != DBUS_TYPE_INVALID) { + dbus_message_print_iter(string, &subiter, depth+1); + + dbus_message_iter_next (&subiter); + current_type = dbus_message_iter_get_arg_type (&subiter); + + if (current_type != DBUS_TYPE_INVALID) + g_string_append(string, ","); + } + dbus_message_print_indent(string, depth); + g_string_append(string, "]\n"); + } break; + + case DBUS_TYPE_VARIANT: { + DBusMessageIter subiter; + + dbus_message_iter_recurse (iter, &subiter); + + g_string_append(string, "variant "); + dbus_message_print_iter(string, &subiter, depth+1); + } break; + + case DBUS_TYPE_STRUCT: { + int current_type; + DBusMessageIter subiter; + + dbus_message_iter_recurse (iter, &subiter); + + g_string_append(string, "struct {\n"); + while ((current_type = dbus_message_iter_get_arg_type (&subiter)) != DBUS_TYPE_INVALID) { + dbus_message_print_iter(string, &subiter, depth+1); + dbus_message_iter_next (&subiter); + if (dbus_message_iter_get_arg_type (&subiter) != DBUS_TYPE_INVALID) + g_string_append(string, ","); + } + dbus_message_print_indent(string, depth); + g_string_append(string, "}\n"); + } break; + + case DBUS_TYPE_DICT_ENTRY: { + DBusMessageIter subiter; + + dbus_message_iter_recurse (iter, &subiter); + + g_string_append(string, "dict entry(\n"); + dbus_message_print_iter(string, &subiter, depth+1); + dbus_message_iter_next (&subiter); + dbus_message_print_iter(string, &subiter, depth+1); + dbus_message_print_indent(string, depth); + g_string_append(string, ")\n"); + } break; + + default: + g_string_append_printf(string, " (unknown arg type '%c')\n", type); + break; + } + dbus_message_iter_next(iter); + } +} + +/** + * dbus_message_print_string: + * @message The DBusMessage to format into a string. + * @string If non-NULL appends to this GString. + * @show_header If #TRUE the message header will be included. + * + * Formats a DBusMessage into a string. + * + * Returns: A GString which must be freed with g_string_free() + */ +static GString * +dbus_message_print_string(DBusMessage *message, GString *string, dbus_bool_t show_header) +{ + DBusMessageIter iter; + const char *sender; + const char *destination; + int message_type; + + g_return_val_if_fail (message != NULL, NULL); + + if (string == NULL) { + string = g_string_new(NULL); + } + + message_type = dbus_message_get_type (message); + sender = dbus_message_get_sender (message); + destination = dbus_message_get_destination (message); + + if (show_header) { + g_string_append_printf(string, "%s sender=%s -> dest=%s", + dbus_msg_type_to_string (message_type), + sender ? sender : "(null sender)", + destination ? destination : "(null destination)"); + + switch (message_type) { + case DBUS_MESSAGE_TYPE_METHOD_CALL: + case DBUS_MESSAGE_TYPE_SIGNAL: + g_string_append_printf(string, " serial=%u path=%s; interface=%s; member=%s\n", + dbus_message_get_serial (message), + dbus_message_get_path (message), + dbus_message_get_interface (message), + dbus_message_get_member (message)); + break; + + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + g_string_append_printf(string, " reply_serial=%u\n", + dbus_message_get_reply_serial (message)); + break; + + case DBUS_MESSAGE_TYPE_ERROR: + g_string_append_printf(string, " error_name=%s reply_serial=%u\n", + dbus_message_get_error_name (message), + dbus_message_get_reply_serial (message)); + break; + + default: + g_string_append(string, "\n"); + break; + } + } + + dbus_message_iter_init(message, &iter); + dbus_message_print_iter(string, &iter, 1); + + return string; +} + +/** + * dbus_message_print_string: + * @message The DBusMessage to format into a string. + * @string If non-NULL appends to this GString. + * @show_header If #TRUE the message header will be included. + * + * Formats a DBusMessage into a string. + * + * Returns: A simple malloc'ed string which must be freed with g_free(). + */ +static gchar * +dbus_message_print(DBusMessage *message, GString *string, dbus_bool_t show_header) +{ + g_return_val_if_fail (message != NULL, NULL); + + return g_string_free(dbus_message_print_string(message, NULL, show_header), FALSE); +} + +/*----------------------------------------------------------------------------*/ + +/** + * append_g_variant_to_dbus_msg_iter: + * @g_error initialized to error info when FALSE is returned. + * + * Helper routine for append_g_variant_to_dbus_message(). + * Performs the recusive descent into the GVariant appending + * values to the DBusMessage as it goes. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ +static gboolean +append_g_variant_to_dbus_msg_iter(DBusMessageIter *iter, GVariant *value, GError **g_error) +{ + GVariantClass class; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + class = g_variant_classify(value); + + switch (class) { + case G_VARIANT_CLASS_BOOLEAN: { + dbus_bool_t v = g_variant_get_boolean(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &v); + } break; + case G_VARIANT_CLASS_BYTE: { + guint8 v = g_variant_get_byte(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &v); + } break; + case G_VARIANT_CLASS_INT16: { + gint16 v = g_variant_get_int16 (value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &v); + } break; + case G_VARIANT_CLASS_UINT16: { + guint16 v = g_variant_get_uint16(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &v); + } break; + case G_VARIANT_CLASS_INT32: { + gint32 v = g_variant_get_int32(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &v); + } break; + case G_VARIANT_CLASS_UINT32: { + guint32 v = g_variant_get_uint32(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &v); + } break; + case G_VARIANT_CLASS_INT64: { + gint64 v = g_variant_get_int64(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_INT64, &v); + } break; + case G_VARIANT_CLASS_UINT64: { + guint64 v = g_variant_get_uint64(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &v); + } break; + case G_VARIANT_CLASS_HANDLE: { + gint32 v = g_variant_get_handle(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &v); + } break; + case G_VARIANT_CLASS_DOUBLE: { + gdouble v = g_variant_get_double(value); + dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE, &v); + } break; + case G_VARIANT_CLASS_STRING: { + const gchar *v = g_variant_get_string(value, NULL); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &v); + } break; + case G_VARIANT_CLASS_OBJECT_PATH: { + const gchar *v = g_variant_get_string(value, NULL); + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &v); + } break; + case G_VARIANT_CLASS_SIGNATURE: { + const gchar *v = g_variant_get_string(value, NULL); + dbus_message_iter_append_basic(iter, DBUS_TYPE_SIGNATURE, &v); + } break; + case G_VARIANT_CLASS_VARIANT: { + DBusMessageIter sub; + GVariant *child; + + child = g_variant_get_child_value(value, 0); + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, + g_variant_get_type_string(child), + &sub); + if (!append_g_variant_to_dbus_msg_iter(&sub, child, g_error)) { + G_VARIANT_FREE(child); + goto fail; + } + dbus_message_iter_close_container(iter, &sub); + G_VARIANT_FREE(child); + } break; + case G_VARIANT_CLASS_MAYBE: { + GVariant *child; + + if (!g_variant_n_children(value)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "cannot serialize an empty GVariant MAYBE"); + goto fail; + } else { + child = g_variant_get_child_value(value, 0); + if (!append_g_variant_to_dbus_msg_iter(iter, child, g_error)) { + G_VARIANT_FREE(child); + goto fail; + } + G_VARIANT_FREE(child); + } + } break; + case G_VARIANT_CLASS_ARRAY: { + DBusMessageIter dbus_iter; + const gchar *type_string; + gsize n, i; + GVariant *child; + + type_string = g_variant_get_type_string(value); + type_string++; /* skip the 'a' */ + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + type_string, &dbus_iter); + + n = g_variant_n_children(value); + + for (i = 0; i < n; i++) { + child = g_variant_get_child_value(value, i); + if (!append_g_variant_to_dbus_msg_iter(&dbus_iter, child, g_error)) { + G_VARIANT_FREE(child); + goto fail; + } + G_VARIANT_FREE(child); + } + + dbus_message_iter_close_container(iter, &dbus_iter); + } break; + case G_VARIANT_CLASS_TUPLE: { + DBusMessageIter dbus_iter; + gsize n, i; + GVariant *child; + + dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, + NULL, &dbus_iter); + + n = g_variant_n_children(value); + + for (i = 0; i < n; i++) { + child = g_variant_get_child_value(value, i); + if (!append_g_variant_to_dbus_msg_iter(&dbus_iter, child, g_error)) { + G_VARIANT_FREE(child); + goto fail; + } + G_VARIANT_FREE(child); + } + + dbus_message_iter_close_container(iter, &dbus_iter); + + } break; + case G_VARIANT_CLASS_DICT_ENTRY: { + DBusMessageIter dbus_iter; + GVariant *key, *val; + + dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, + NULL, &dbus_iter); + key = g_variant_get_child_value(value, 0); + if (!append_g_variant_to_dbus_msg_iter(&dbus_iter, key, g_error)) { + G_VARIANT_FREE(key); + goto fail; + } + G_VARIANT_FREE(key); + + val = g_variant_get_child_value(value, 1); + if (!append_g_variant_to_dbus_msg_iter(&dbus_iter, val, g_error)) { + G_VARIANT_FREE(val); + goto fail; + } + G_VARIANT_FREE(val); + + dbus_message_iter_close_container(iter, &dbus_iter); + } break; + default: { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "Error serializing GVariant with class '%c' to a D-Bus message", + class); + goto fail; + } break; + } + + return TRUE; + + fail: + return FALSE; +} + +/** + * append_g_variant_to_dbus_message: + * @message DBus message currently being built + * @g_variant The GVariant item to be appended to @message + * @g_error initialized to error info when FALSE is returned. + * + * Given a DBusMessage append the contents of the provied @g_variant to the message. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ +static gboolean +append_g_variant_to_dbus_message(DBusMessage *message, GVariant *g_variant, GError **g_error) +{ + DBusMessageIter iter; + + g_return_val_if_fail (message != NULL, FALSE); + g_return_val_if_fail (g_variant != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + dbus_message_iter_init_append(message, &iter); + if (!append_g_variant_to_dbus_msg_iter(&iter, g_variant, g_error)) { + return FALSE; + } + return TRUE; +} + +/** + * dbus_method_append_args_tuple: + * @message DBus message currently being built + * @args A GVariant tuple containing the method parameters to + * be appended to @message + * @g_error initialized to error info when FALSE is returned. + * + * Append the method parameters to a DBus method message. @args + * is a GVariant tuple representing the parameter list. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ +static gboolean +dbus_method_append_args_tuple(DBusMessage *message, GVariant *args, GError **g_error) +{ + DBusMessageIter iter; + gsize n, i; + GVariant *arg; + + g_return_val_if_fail (message != NULL, FALSE); + g_return_val_if_fail (args != NULL && g_variant_is_of_type(args, G_VARIANT_TYPE_TUPLE), FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + if ((n = g_variant_n_children(args))) { + + dbus_message_iter_init_append(message, &iter); + + for (i = 0; i < n; i++) { + arg = g_variant_get_child_value(args, i); + if (!append_g_variant_to_dbus_msg_iter(&iter, arg, g_error)) { + G_VARIANT_FREE(arg); + return FALSE; + } + G_VARIANT_FREE(arg); + } + } + + return TRUE; +} + +/** + * marshal_dbus_string_variant: + * @iter iterator into which the string variant will be inserted + * @value string value to insert as variant + * @g_error initialized to error info when FALSE is returned. + * + * Add a string variant while marshaling DBus protocol. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ +static gboolean +marshal_dbus_string_variant(DBusMessageIter *iter, const char *value, GError **g_error) +{ + DBusMessageIter variant; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "s", &variant)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "cannot open dbus variant string container, value=\"%s\"", value); + return FALSE; + } + + if (!dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &value)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "cannot append dbus variant string value, value=\"%s\"", value); + return FALSE; + } + + if (!dbus_message_iter_close_container(iter, &variant)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "cannot close dbus variant container, value=\"%s\"", value); + return FALSE; + } + + return TRUE; +} + +/** + * marshal_dbus_dict_string_entry: + * @array dictionary array into which entry is inserted + * @name entry's key + * @value entry's value + * @g_error initialized to error info when FALSE is returned. + * + * Adds a dictionary entry into an dictionary array whose key is a + * string and whose value is also a string while marshaling DBus protocol. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ +static gboolean +marshal_dbus_dict_string_entry(DBusMessageIter *array, const char *name, const char *value, GError **g_error) +{ + DBusMessageIter entry; + + g_return_val_if_fail (array != NULL, FALSE); + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + if (!dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL, &entry)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "cannot open dbus dict entry container for option <%s=%s>", name, value); + return FALSE; + } + + if (!dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "cannot append option name for option <%s=%s>", name, value); + return FALSE; + } + + if (!marshal_dbus_string_variant(&entry, value, g_error)) { + return FALSE; + } + + if (!dbus_message_iter_close_container(array, &entry)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "cannot close dbus dict entry container for option <%s=%s>", name, value); + return FALSE; + } + + return TRUE; +} + + +/*----------------------------------------------------------------------------*/ + + +/** + * dbus_iter_to_variant: + * @msg DBusMessage which will be converted to a GVariant + * @g_variant_return Pointer to location where GVariant will be returned, + * will be NULL if error occurs + * @g_error initialized to error info when FALSE is returned. + * + * Helper routine for dbus_message_to_g_variant(), + * Performs the recusive descent into the DBusMessage appending + * values to the GVariant as it goes. + * + * Returns: return TRUE if successful, @g_variant_return will be non-NULL. + * FALSE if error with @g_error initialized, @g_variant_return will be NULL. + */ +static gboolean +dbus_iter_to_variant(DBusMessageIter *iter, GVariant **g_variant_return, GError **g_error) +{ + gboolean result = TRUE; + int arg_type; + GVariant *g_variant = NULL; + char *signature = NULL; + GVariantBuilder builder; + DBusMessageIter sub; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (g_variant_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *g_variant_return = NULL; + + arg_type = dbus_message_iter_get_arg_type(iter); + +#ifdef TRACE_VARIANT + signature = dbus_message_iter_get_signature(iter); + printf("dbus_iter_to_variant enter: type=%s signature=%s\n", + dbus_type_to_string(arg_type), signature); + g_free(signature); + signature = NULL; +#endif + + switch (arg_type) { + case DBUS_TYPE_BOOLEAN: { + dbus_bool_t value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_boolean(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant boolean value=%d", value); + result = FALSE; + } + + } break; + case DBUS_TYPE_BYTE: { + guint8 value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_byte(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant byte value=%uc", value); + result = FALSE; + } + + } break; + case DBUS_TYPE_INT16: { + gint16 value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_int16(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant int16 value=%" G_GINT16_FORMAT, value); + result = FALSE; + } + + } break; + case DBUS_TYPE_UINT16: { + guint16 value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_uint16(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant uint16 value=%" G_GUINT16_FORMAT, value); + result = FALSE; + } + + } break; + case DBUS_TYPE_INT32: { + gint32 value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_int32(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant int32 value=%" G_GINT32_FORMAT, value); + result = FALSE; + } + + } break; + case DBUS_TYPE_UINT32: { + guint32 value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_uint32(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant uint32 value=%" G_GUINT32_FORMAT, value); + result = FALSE; + } + + } break; + case DBUS_TYPE_INT64: { + gint64 value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_int64(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant int64 value=%" G_GINT64_FORMAT, value); + result = FALSE; + } + + } break; + case DBUS_TYPE_UINT64: { + guint64 value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_uint64(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant uint64 value=%" G_GUINT64_FORMAT, value); + result = FALSE; + } + + } break; + case DBUS_TYPE_DOUBLE: { + gdouble value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_double(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant double value=%f", value); + result = FALSE; + } + + } break; + case DBUS_TYPE_STRING: { + gchar *value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_string(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant string value=\"%s\"", value); + result = FALSE; + } + + } break; + case DBUS_TYPE_OBJECT_PATH: { + gchar *value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_object_path(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant object path value=\"%s\"", value); + result = FALSE; + } + + } break; + case DBUS_TYPE_SIGNATURE: { + gchar *value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_signature(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant signature value=\"%s\"", value); + result = FALSE; + } + + } break; + case DBUS_TYPE_UNIX_FD: { + guint32 value; + + dbus_message_iter_get_basic(iter, &value); + if ((g_variant = g_variant_new_uint32(value)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant file descriptor value=%u", value); + result = FALSE; + } + + } break; + case DBUS_TYPE_ARRAY: { + GVariant *item; + + signature = dbus_message_iter_get_signature(iter); + + g_variant_builder_init(&builder, G_VARIANT_TYPE(signature)); + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + if (!dbus_iter_to_variant(&sub, &item, g_error)) { + g_variant_builder_clear(&builder); + result = FALSE; + goto exit; + } +#ifdef TRACE_VARIANT + { + gchar *variant_as_string = g_variant_print(item, TRUE); + printf("array item=%s\n", variant_as_string); + g_free(variant_as_string); + } +#endif + g_variant_builder_add_value(&builder, item); + dbus_message_iter_next(&sub); + } + + if ((g_variant = g_variant_builder_end(&builder)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant array"); + result = FALSE; + } + + } break; + case DBUS_TYPE_VARIANT: { + GVariant *item; + + signature = dbus_message_iter_get_signature(iter); + g_variant_builder_init(&builder, G_VARIANT_TYPE(signature)); + dbus_message_iter_recurse(iter, &sub); + + if (!dbus_iter_to_variant(&sub, &item, g_error)) { + g_variant_builder_clear(&builder); + result = FALSE; + goto exit; + } +#ifdef TRACE_VARIANT + { + gchar *variant_as_string = g_variant_print(item, TRUE); + printf("variant item=%s\n", variant_as_string); + g_free(variant_as_string); + } +#endif + + g_variant_builder_add_value(&builder, item); + + if ((g_variant = g_variant_builder_end(&builder)) == NULL) { + gchar *variant_as_string = g_variant_print(item, FALSE); + + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant variant for value=%s", variant_as_string); + g_free(variant_as_string); + result = FALSE; + } + } break; + case DBUS_TYPE_STRUCT: { + GVariant *item; + + signature = dbus_message_iter_get_signature(iter); + g_variant_builder_init(&builder, G_VARIANT_TYPE(signature)); + dbus_message_iter_recurse(iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + if (!dbus_iter_to_variant(&sub, &item, g_error)) { + g_variant_builder_clear(&builder); + result = FALSE; + goto exit; + } +#ifdef TRACE_VARIANT + { + gchar *variant_as_string = g_variant_print(item, TRUE); + printf("struct item=%s\n", variant_as_string); + g_free(variant_as_string); + } +#endif + g_variant_builder_add_value(&builder, item); + dbus_message_iter_next(&sub); + } + + if ((g_variant = g_variant_builder_end(&builder)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to create GVariant struct"); + result = FALSE; + } + + } break; + case DBUS_TYPE_DICT_ENTRY: { + GVariant *key, *value; + + dbus_message_iter_recurse(iter, &sub); + signature = dbus_message_iter_get_signature(iter); + + if (!dbus_iter_to_variant(&sub, &key, g_error)) { + g_prefix_error(g_error, "unable to create GVariant dict_entry key: "); + result = FALSE; + goto exit; + } + + dbus_message_iter_next(&sub); + + if (!dbus_iter_to_variant(&sub, &value, g_error)) { + g_prefix_error(g_error, "unable to create GVariant dict_entry value: "); + result = FALSE; + goto exit; + } +#ifdef TRACE_VARIANT + { + gchar *key_variant_as_string = g_variant_print(key, TRUE); + gchar *value_variant_as_string = g_variant_print(key, TRUE); + printf("dict_entry key=%s value=%s\n", g_variant_print(key, TRUE), g_variant_print(value, TRUE)); + g_free(key_variant_as_string); + g_free(value_variant_as_string); + } +#endif + + g_variant = g_variant_new_dict_entry(key, value); + + } break; + default: { + signature = dbus_message_iter_get_signature(iter); + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unknown DBus type=%d, signature=%s", arg_type, signature); + result = FALSE; + break; + } + } + + exit: + if (signature) dbus_free(signature); + *g_variant_return = g_variant; + +#ifdef TRACE_VARIANT + { + gchar *variant_as_string = NULL; + if (g_variant) { + variant_as_string = g_variant_print(g_variant, TRUE); + } else { + variant_as_string = "NULL"; + } + printf("dbus_iter_to_variant returns %s, variant=%s\n", + result ? "TRUE" : "FALSE", variant_as_string); + if (g_variant) { + g_free(variant_as_string); + } + } +#endif + + return result; +} + +/** + * dbus_message_to_g_variant: + * @msg DBusMessage which will be converted to a GVariant + * @g_variant_return Pointer to location where GVariant will be returned, + * will be NULL if error occurs + * @g_error initialized to error info when FALSE is returned. + * + * Converts a DBusMessage to a GVariant. + * + * Returns: return TRUE if successful, @g_variant_return will be non-NULL. + * FALSE if error with @g_error initialized, @g_variant_return will be NULL. + */ +static gboolean +dbus_message_to_g_variant(DBusMessage *msg, GVariant **g_variant_return, GError **g_error) +{ + DBusMessageIter iter; + + g_return_val_if_fail (msg != NULL, FALSE); + g_return_val_if_fail (g_variant_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *g_variant_return = NULL; + +#ifdef RDCP_DBUS_DEBUG + { + gchar *msg_as_string = dbus_message_print(msg, NULL, FALSE); + printf("dbus_message_to_g_variant: msg=\n%s", msg_as_string); + g_free(msg_as_string); + } +#endif + + if (!dbus_message_iter_init(msg, &iter)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "could not create iterator to parse DBus message"); + return FALSE; + } + + if (!dbus_iter_to_variant(&iter, g_variant_return, g_error)) { + g_prefix_error(g_error, "unable to convert dbus_message to GVariant: "); + return FALSE; + } + +#ifdef RDCP_DBUS_DEBUG + { + gchar *variant_as_string = NULL; + if (*g_variant_return) { + variant_as_string = g_variant_print(*g_variant_return, TRUE); + } else { + variant_as_string = "NULL"; + } + printf("dbus_message_to_g_variant returns variant=%s\n", variant_as_string); + if (*g_variant_return) { + g_free(variant_as_string); + } + } +#endif + + return TRUE; +} + +/** + * dbus_method_reply_to_g_variant_tuple: + * @msg DBus message reply which will be converted to a tuple of GVariant's + * @g_variant_return Pointer to location where GVariant will be returned, + * will be NULL if error occurs + * @g_error initialized to error info when FALSE is returned. + * + * A DBus method reply contains a sequence of zero or more OUT parameters. + * Parse the method reply and build a GVariant tuple whose members are + * the OUT parameters. Each tuple member will also be a GVariant. + * + * Returns: return TRUE if successful, @g_variant_return will be non-NULL. + * FALSE if error with @g_error initialized, @g_variant_return will be NULL. + */ +static gboolean +dbus_method_reply_to_g_variant_tuple(DBusMessage *msg, GVariant **g_variant_return, GError **g_error) +{ + DBusMessageIter iter; + GVariantBuilder builder; + + g_return_val_if_fail (msg != NULL, FALSE); + g_return_val_if_fail (g_variant_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *g_variant_return = NULL; + +#ifdef RDCP_DBUS_DEBUG + { + gchar *msg_as_string = dbus_message_print(msg, NULL, FALSE); + printf("dbus_method_reply_to_g_variant_tuple: msg=\n%s", msg_as_string); + g_free(msg_as_string); + } +#endif + + g_variant_builder_init(&builder, G_VARIANT_TYPE_TUPLE); + + if (!dbus_message_iter_init(msg, &iter)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "could not create iterator to parse DBus message"); + return FALSE; + } + + while (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INVALID) { + GVariant *g_variant = NULL; + + if (!dbus_iter_to_variant(&iter, &g_variant, g_error)) { + g_prefix_error(g_error, "unable to convert dbus_message to GVariant: "); + return FALSE; + } + + g_variant_builder_add_value(&builder, g_variant); + + dbus_message_iter_next (&iter); + } + + if ((*g_variant_return = g_variant_builder_end(&builder)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to build GVariant options array"); + return FALSE; + } + + +#ifdef RDCP_DBUS_DEBUG + { + gchar *variant_as_string = NULL; + if (*g_variant_return) { + variant_as_string = g_variant_print(*g_variant_return, TRUE); + } else { + variant_as_string = "NULL"; + } + printf("dbus_method_reply_to_g_variant_tuple returns variant=%s\n", variant_as_string); + if (*g_variant_return) { + g_free(variant_as_string); + } + } +#endif + + return TRUE; +} + +/*----------------------------------------------------------------------------*/ + +/** + * get_dbus_string_property: + * @bus The DBus connection on which the query will be performed + * @object_path The DBus object path identifying the object + * @interface The DBus interface which provides the property + * @property The name of the proptery on the interface + * @value_return Pointer to where string value will be returned. + * Must be freed with g_free(), will be NULL if error occurs. + * @g_error initialized to error info when FALSE is returned. + * + * Retrieve a string valued property from an DBus object. + * + * Returns: return TRUE if successful, @value_return will be non-NULL. + * FALSE if error with @g_error initialized, @value_return will be NULL. + */ +gboolean +get_dbus_string_property(DBusConnection *bus, const char *object_path, + const char* interface, const char *property, + char **value_return, GError **g_error) +{ + const char *method = "Get"; + DBusMessage* msg = NULL; + DBusMessage* reply = NULL; + DBusError dbus_error; + const char *interface_ptr = interface; + const char *property_ptr = property; + DBusMessageIter iter, variant; + char *value = NULL; + char *signature; + + g_return_val_if_fail (bus != NULL, FALSE); + g_return_val_if_fail (object_path != NULL, FALSE); + g_return_val_if_fail (interface != NULL, FALSE); + g_return_val_if_fail (property != NULL, FALSE); + g_return_val_if_fail (value_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *value_return = NULL; + dbus_error_init(&dbus_error); + + if ((msg = dbus_message_new_method_call(REALM_DBUS_BUS_NAME, object_path, + DBUS_INTERFACE_PROPERTIES, method)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to create" + "DBus %s.%s() message, object_path=%s, interface=%s, property=%s", + DBUS_INTERFACE_PROPERTIES, method, object_path, interface, property); + return FALSE; + } + + if (!dbus_message_append_args(msg, + DBUS_TYPE_STRING, &interface_ptr, + DBUS_TYPE_STRING, &property_ptr, + DBUS_TYPE_INVALID)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to add args to " + "DBus %s.%s() message, object_path=%s, interface=%s, property=%s", + DBUS_INTERFACE_PROPERTIES, method, object_path, interface, property); + dbus_message_unref(msg); + return FALSE; + } + + if ((reply = dbus_connection_send_with_reply_and_block(bus, msg, -1, &dbus_error)) == NULL) { + dbus_message_unref(msg); + RETURN_DBUS_ERROR(g_error, dbus_error); + } + dbus_message_unref(msg); + + if (!dbus_message_has_signature(reply, "v")) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "expected variant in DBus %s.%s() reply, object_path=%s, interface=%s, property=%s", + DBUS_INTERFACE_PROPERTIES, method, object_path, interface, property); + dbus_message_unref(reply); + return FALSE; + } + + if (!dbus_message_iter_init(reply, &iter)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "could not create iterator to parse " + "DBus %s.%s() reply, object_path=%s, interface=%s, property=%s", + DBUS_INTERFACE_PROPERTIES, method, object_path, interface, property); + dbus_message_unref(reply); + return FALSE; + } + + dbus_message_iter_recurse(&iter, &variant); + signature = dbus_message_iter_get_signature(&variant); + if (!g_str_equal(signature, DBUS_TYPE_STRING_AS_STRING)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "expected string type variant but got \"%s\" signature instead for " + "DBus %s.%s() reply, object_path=%s, interface=%s, property=%s", + signature, DBUS_INTERFACE_PROPERTIES, method, object_path, interface, property); + dbus_free(signature); + dbus_message_unref(reply); + return FALSE; + } + dbus_free(signature); + dbus_message_iter_get_basic(&variant, &value); + *value_return = g_strdup(value); + + dbus_message_unref(reply); + return TRUE; +} + +/** + * get_dbus_properties: + * @bus The DBus connection on which the query will be performed + * @object_path The DBus object path identifying the object + * @interface The DBus interface which provides the property + * @properties_return Pointer to where a GVariant containing all + * the interface's properties for the object will be returned. + * Must be freed with g_variant_unref(), will be NULL if error occurs. + * @g_error initialized to error info when FALSE is returned. + * + * Retrieve all the interface properties from an DBus object. + * Returned as a GVariant dictionary. Use g_variant_lookup() to + * obtain a specific property in the dictionary. + * + * Returns: return TRUE if successful, @value_return will be non-NULL. + * FALSE if error with @g_error initialized, @value_return will be NULL. + */ +gboolean +get_dbus_properties(DBusConnection *bus, const char *object_path, + const char* interface, GVariant **properties_return, + GError **g_error) +{ + const char *method = "GetAll"; + DBusMessage* msg = NULL; + DBusMessage* reply = NULL; + DBusError dbus_error; + const char *interface_ptr = interface; + + g_return_val_if_fail (bus != NULL, FALSE); + g_return_val_if_fail (object_path != NULL, FALSE); + g_return_val_if_fail (interface != NULL, FALSE); + g_return_val_if_fail (properties_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *properties_return = NULL; + dbus_error_init(&dbus_error); + + if ((msg = dbus_message_new_method_call(REALM_DBUS_BUS_NAME, object_path, + DBUS_INTERFACE_PROPERTIES, method)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to create" + "DBus %s.%s() message, object_path=%s, interface=%s", + DBUS_INTERFACE_PROPERTIES, method, object_path, interface); + return FALSE; + } + + if (!dbus_message_append_args(msg, + DBUS_TYPE_STRING, &interface_ptr, + DBUS_TYPE_INVALID)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to add args to " + "DBus %s.%s() message, object_path=%s, interface=%s", + DBUS_INTERFACE_PROPERTIES, method, object_path, interface); + dbus_message_unref(msg); + return FALSE; + } + + if ((reply = dbus_connection_send_with_reply_and_block(bus, msg, -1, &dbus_error)) == NULL) { + dbus_message_unref(msg); + RETURN_DBUS_ERROR(g_error, dbus_error); + } + dbus_message_unref(msg); + + if (!dbus_message_to_g_variant(reply, properties_return, g_error)) { + dbus_message_unref(reply); + return FALSE; + } + + + dbus_message_unref(reply); + return TRUE; +} + +/*----------------------------------------------------------------------------*/ + +/** + * dbus_discover_marshal: + * @target what to discover + * @options dictionary of option {key,values} + * @msg_return if successful DBus message returned here, + * if error then this will be NULL. + * @g_error initialized to error info when FALSE is returned. + * + * Marshal a realm Discover method call. + * + * Returns: return TRUE if successful, @msg_return will point to DBusMessage, + * FALSE if error with @g_error initialized. @msg_return will be NULL. + */ +static gboolean +dbus_discover_marshal(const char* target, GVariant *options, + DBusMessage **msg_return, GError **g_error) +{ + const char *method = "Discover"; + DBusMessage *msg = NULL; + DBusMessageIter iter; + + g_return_val_if_fail (target != NULL, FALSE); + g_return_val_if_fail (options != NULL, FALSE); + g_return_val_if_fail (msg_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *msg_return = NULL; + + if ((msg = dbus_message_new_method_call(REALM_DBUS_BUS_NAME, REALM_DBUS_SERVICE_PATH, + REALM_DBUS_PROVIDER_INTERFACE, method)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to create dbus method call %s.%s() message, object_path=%s", + REALM_DBUS_PROVIDER_INTERFACE, method, REALM_DBUS_SERVICE_PATH); + return FALSE; + } + + dbus_message_iter_init_append (msg, &iter); /* void return */ + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &target)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to add target parameter (%s)", target); + dbus_message_unref(msg); + return FALSE; + } + + if (!append_g_variant_to_dbus_message(msg, options, g_error)) { + g_prefix_error(g_error, "unable to append GVariant options dictionary into %s.%s() message", + REALM_DBUS_PROVIDER_INTERFACE, method); + dbus_message_unref(msg); + return FALSE; + } + + *msg_return = msg; + return TRUE; +} + +/** + * dbus_discover_unmarshal: + * @reply DBus method reply from Discover call + * @relevance_return Pointer to returned relevance value + * @paths_return Pointer to an array of object path strings, + * must be freed with g_strfreev(). + * @g_error initialized to error info when FALSE is returned. + * + * Parses the DBus reply message from the Discover call and unpacks + * the output paramters in the *_return parameters of this function. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ + +static gboolean +dbus_discover_unmarshal(DBusMessage *reply, gint32 *relevance_return, gchar ***paths_return, GError **g_error) +{ + GVariant *g_variant_reply = NULL; + + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (relevance_return != NULL, FALSE); + g_return_val_if_fail (paths_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + if (!dbus_method_reply_to_g_variant_tuple(reply, &g_variant_reply, g_error)) { + gchar *reply_string = dbus_message_print(reply, NULL, FALSE); + + g_prefix_error(g_error, "unable convert reply (%s) to GVariant tuple: ", reply_string); + g_free(reply_string); + dbus_message_unref(reply); + return FALSE; + } + + g_variant_get(g_variant_reply, "(i^ao)", relevance_return, paths_return); + G_VARIANT_FREE(g_variant_reply); + + return TRUE; +} + +/** + * dbus_discover_call: + * @target what to discover + * @options dictionary of option {key,values} + * @relevance_return Pointer to returned relevance value + * @paths_return Pointer to an array of object path strings, + * must be freed with g_strfreev(). + * @g_error initialized to error info when FALSE is returned. + * + * Marshal a realm Discover method call, call it synchronously, + * unmarsh it's reply and return the OUT parameters. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized + */ +gboolean +dbus_discover_call(DBusConnection *bus, const char *target, GVariant *options, + gint32 *relevance_return, gchar ***paths_return, GError **g_error) +{ + DBusError dbus_error; + DBusMessage *msg = NULL; + DBusMessage* reply = NULL; + + g_return_val_if_fail (bus != NULL, FALSE); + g_return_val_if_fail (target != NULL, FALSE); + g_return_val_if_fail (options != NULL, FALSE); + g_return_val_if_fail (relevance_return != NULL, FALSE); + g_return_val_if_fail (paths_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + dbus_error_init(&dbus_error); + if (!dbus_discover_marshal(target, options, &msg, g_error)) { + RETURN_DBUS_ERROR(g_error, dbus_error); + } + + if ((reply = dbus_connection_send_with_reply_and_block(bus, msg, -1, &dbus_error)) == NULL) { + dbus_message_unref(msg); + RETURN_DBUS_ERROR(g_error, dbus_error); + } + dbus_message_unref(msg); + + if (!dbus_discover_unmarshal(reply, relevance_return, paths_return, g_error)) { + dbus_message_unref(reply); + return FALSE; + } + dbus_message_unref(reply); + return TRUE; +} + +/*----------------------------------------------------------------------------*/ + +/** + * dbus_change_login_policy_marshal: + * @dbus_path The DBus object path of the object supporting the Realm + * interface on which the call will be made. + * @login_policy The new login policy, or an empty string. + * @permitted_add: An array of logins to permit. + * @permitted_remove: An array of logins to not permit. + * @options dictionary of option {key,values} + * @msg_return if successful DBus message returned here, + * if error then this will be NULL. + * @g_error initialized to error info when FALSE is returned. + * + * Marshal a realm ChangeLoginPolicy method call. + * + * Returns: return TRUE if successful, @msg_return will point to DBusMessage, + * FALSE if error with @g_error initialized. @msg_return will be NULL. + */ +static gboolean +dbus_change_login_policy_marshal(const gchar *dbus_path, const char *login_policy, + GVariant *permitted_add, GVariant *permitted_remove, + GVariant *options, DBusMessage **msg_return, GError **g_error) +{ + const char *method = "ChangeLoginPolicy"; + DBusMessage *msg = NULL; + DBusMessageIter iter; + + g_return_val_if_fail (dbus_path != NULL, FALSE); + g_return_val_if_fail (login_policy != NULL, FALSE); + g_return_val_if_fail (permitted_add != NULL, FALSE); + g_return_val_if_fail (permitted_remove != NULL, FALSE); + g_return_val_if_fail (options != NULL, FALSE); + g_return_val_if_fail (msg_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *msg_return = NULL; + + if ((msg = dbus_message_new_method_call(REALM_DBUS_BUS_NAME, dbus_path, + REALM_DBUS_REALM_INTERFACE, method)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to create dbus method call %s.%s() message, object_path=%s", + REALM_DBUS_PROVIDER_INTERFACE, method, REALM_DBUS_SERVICE_PATH); + return FALSE; + } + + dbus_message_iter_init_append (msg, &iter); /* void return */ + + if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &login_policy)) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to add login_policy parameter (%s)", login_policy); + dbus_message_unref(msg); + return FALSE; + } + + if (!append_g_variant_to_dbus_message(msg, permitted_add, g_error)) { + g_prefix_error(g_error, "unable to append GVariant permitted_add dictionary into %s.%s() message", + REALM_DBUS_PROVIDER_INTERFACE, method); + dbus_message_unref(msg); + return FALSE; + } + + if (!append_g_variant_to_dbus_message(msg, permitted_remove, g_error)) { + g_prefix_error(g_error, "unable to append GVariant permitted_remove dictionary into %s.%s() message", + REALM_DBUS_PROVIDER_INTERFACE, method); + dbus_message_unref(msg); + return FALSE; + } + + if (!append_g_variant_to_dbus_message(msg, options, g_error)) { + g_prefix_error(g_error, "unable to append GVariant options dictionary into %s.%s() message", + REALM_DBUS_PROVIDER_INTERFACE, method); + dbus_message_unref(msg); + return FALSE; + } + + *msg_return = msg; + return TRUE; +} + +/** + * dbus_change_login_policy_unmarshal: + * @reply DBus method reply from ChangeLoginPolicy call + * @g_error initialized to error info when FALSE is returned. + * + * Parses the DBus reply message from the ChangeLoginPolicy call. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ + +static gboolean +dbus_change_login_policy_unmarshal(DBusMessage *reply, GError **g_error) +{ + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + return TRUE; +} + +/** + * dbus_change_login_policy_call: + * @dbus_path The DBus object path of the object supporting the Realm + * interface on which the call will be made. + * @login_policy The new login policy, or an empty string. + * @permitted_add: An array of logins to permit. + * @permitted_remove: An array of logins to not permit. + * @options dictionary of option {key,values} + * @g_error initialized to error info when FALSE is returned. + * + * Marshal a realm ChangeLoginPolicy method call, call it synchronously, + * unmarsh it's reply and return the OUT parameters. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized + */ +gboolean +dbus_change_login_policy_call(DBusConnection *bus, const gchar *dbus_path, const char *login_policy, + GVariant *permitted_add, GVariant *permitted_remove, + GVariant *options, GError **g_error) +{ + DBusError dbus_error; + DBusMessage *msg = NULL; + DBusMessage* reply = NULL; + + g_return_val_if_fail (bus != NULL, FALSE); + g_return_val_if_fail (dbus_path != NULL, FALSE); + g_return_val_if_fail (login_policy != NULL, FALSE); + g_return_val_if_fail (permitted_add != NULL, FALSE); + g_return_val_if_fail (permitted_remove != NULL, FALSE); + g_return_val_if_fail (options != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + dbus_error_init(&dbus_error); + if (!dbus_change_login_policy_marshal(dbus_path, login_policy, + permitted_add, permitted_remove, + options, &msg, g_error)) { + RETURN_DBUS_ERROR(g_error, dbus_error); + } + + if ((reply = dbus_connection_send_with_reply_and_block(bus, msg, -1, &dbus_error)) == NULL) { + dbus_message_unref(msg); + RETURN_DBUS_ERROR(g_error, dbus_error); + } + dbus_message_unref(msg); + + if (!dbus_change_login_policy_unmarshal(reply, g_error)) { + dbus_message_unref(reply); + return FALSE; + } + dbus_message_unref(reply); + return TRUE; +} + +/*----------------------------------------------------------------------------*/ + +/** + * dbus_join_leave_marshal: + * @dbus_path The DBus object path of the object supporting the kerberos + * membership interface on which the Join/Leave call will be made. + * @credentials A GVariant encoding the credentials according the the credential + * type. See the Realmd DBus interface for specifics. + * @options dictionary of option {key,values} + * @msg_return if successful DBus message returned here, + * if error then this will be NULL. + * @g_error initialized to error info when FALSE is returned. + * + * Since the Join() & Leave() methods share identical signatures (differing + * only in their method name) we use a common routine. + * + * Returns: return TRUE if successful, @msg_return will point to DBusMessage, + * FALSE if error with @g_error initialized. @msg_return will be NULL. + */ +static gboolean +dbus_join_leave_marshal(const char *method, const gchar* dbus_path, + GVariant *credentials, GVariant *options, + DBusMessage **msg_return, GError **g_error) +{ + DBusMessage *msg = NULL; + DBusMessageIter iter; + + g_return_val_if_fail (method != NULL, FALSE); + g_return_val_if_fail (dbus_path != NULL, FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + g_return_val_if_fail (options != NULL, FALSE); + g_return_val_if_fail (msg_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *msg_return = NULL; + + if ((msg = dbus_message_new_method_call(REALM_DBUS_BUS_NAME, dbus_path, + REALM_DBUS_KERBEROS_MEMBERSHIP_INTERFACE, method)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "failed to create dbus method call %s.%s() message, object_path=%s", + REALM_DBUS_PROVIDER_INTERFACE, method, REALM_DBUS_SERVICE_PATH); + return FALSE; + } + + dbus_message_iter_init_append (msg, &iter); /* void return */ + + if (!append_g_variant_to_dbus_message(msg, credentials, g_error)) { + g_prefix_error(g_error, "unable to append GVariant credentials into %s.%s() message", + REALM_DBUS_PROVIDER_INTERFACE, method); + dbus_message_unref(msg); + return FALSE; + } + + if (!append_g_variant_to_dbus_message(msg, options, g_error)) { + g_prefix_error(g_error, "unable to append GVariant options dictionary into %s.%s() message", + REALM_DBUS_PROVIDER_INTERFACE, method); + dbus_message_unref(msg); + return FALSE; + } + + *msg_return = msg; + return TRUE; +} + +/** + * dbus_join_leave_unmarshal: + * @reply DBus method reply from Join/Leave call + * @g_error initialized to error info when FALSE is returned. + * + * Since the Join() & Leave() methods share identical signatures (differing + * only in their method name) we use a common routine. + * + * Parses the DBus reply message from the Join/Leave call. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ + +static gboolean +dbus_join_leave_unmarshal(DBusMessage *reply, GError **g_error) +{ + + g_return_val_if_fail (reply != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + return TRUE; +} + +/** + * dbus_join_leave_call: + * @dbus_path The DBus object path of the object supporting the kerberos + * membership interface on which the Join/Leave call will be made. + * @credentials A GVariant encoding the credentials according the the credential + * type. See the Realmd DBus interface for specifics. + * @options dictionary of option {key,values} + * @g_error initialized to error info when FALSE is returned. + * + * Since the Join() & Leave() methods share identical signatures (differing + * only in their method name) we use a common routine. + * + * Marshal a realm Join/Leave method call, call it synchronously, + * unmarsh it's reply and return the OUT parameters. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized + */ +gboolean +dbus_join_leave_call(const char *method, DBusConnection *bus, const gchar *dbus_path, + GVariant *credentials, GVariant *options, GError **g_error) +{ + DBusError dbus_error; + DBusMessage *msg = NULL; + DBusMessage* reply = NULL; + + g_return_val_if_fail (method != NULL, FALSE); + g_return_val_if_fail (bus != NULL, FALSE); + g_return_val_if_fail (dbus_path != NULL, FALSE); + g_return_val_if_fail (credentials != NULL, FALSE); + g_return_val_if_fail (options != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + dbus_error_init(&dbus_error); + if (!dbus_join_leave_marshal(method, dbus_path, credentials, options, &msg, g_error)) { + RETURN_DBUS_ERROR(g_error, dbus_error); + } + + if ((reply = dbus_connection_send_with_reply_and_block(bus, msg, -1, &dbus_error)) == NULL) { + dbus_message_unref(msg); + RETURN_DBUS_ERROR(g_error, dbus_error); + } + dbus_message_unref(msg); + + if (!dbus_join_leave_unmarshal(reply, g_error)) { + dbus_message_unref(reply); + return FALSE; + } + dbus_message_unref(reply); + return TRUE; +} + + +/*----------------------------------------------------------------------------*/ + +/** + * dbus_join_call: + * @dbus_path The DBus object path of the object supporting the kerberos + * membership interface on which the Join call will be made. + * @credentials A GVariant encoding the credentials according the the credential + * type. See the Realmd DBus interface for specifics. + * @options dictionary of option {key,values} + * @g_error initialized to error info when FALSE is returned. + * + * Marshal a realm Join method call, call it synchronously, + * unmarsh it's reply and return the OUT parameters. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized + */ +gboolean +dbus_join_call(DBusConnection *bus, const gchar *dbus_path, + GVariant *credentials, GVariant *options, GError **g_error) +{ + + return dbus_join_leave_call("Join", bus, dbus_path, credentials, + options, g_error); + +} + + +/** + * dbus_leave_call: + * @dbus_path The DBus object path of the object supporting the kerberos + * membership interface on which the Leave call will be made. + * @credentials A GVariant encoding the credentials according the the credential + * type. See the Realmd DBus interface for specifics. + * @options dictionary of option {key,values} + * @g_error initialized to error info when FALSE is returned. + * + * Marshal a realm Leave method call, call it synchronously, + * unmarsh it's reply and return the OUT parameters. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized + */ +gboolean +dbus_leave_call(DBusConnection *bus, const gchar *dbus_path, + GVariant *credentials, GVariant *options, GError **g_error) +{ + + return dbus_join_leave_call("Leave", bus, dbus_path, credentials, + options, g_error); + +} + + +/*----------------------------------------------------------------------------*/ +/** + * get_short_dbus_interface_name + * @interface fully qualified DBus interface name + * + * Given a DBus interface name return a friendly short name + * appropriate for users to see. Currently the known short names are: + * + * * "Kerberos" + * * "KerberosMembership" + * * "Realm" + * * "Provider" + * * "Service" + * + * If the interface is not recognized the portion of the interface + * following the last period (".") will be returned. If there is no + * period in the interface name then the entire interface string is returned. + * If the interface is NULL then "(null)" is returned. + * + * Returns: pointer to string, must free with g_free() + */ + +char * +get_short_dbus_interface_name(const char *interface) +{ + char *token = NULL; + + if (interface == NULL) { + return g_strdup("(null)"); + } + + if (strcmp(interface, REALM_DBUS_KERBEROS_INTERFACE) == 0) { + return g_strdup("Kerberos"); + } + if (strcmp(interface, REALM_DBUS_KERBEROS_MEMBERSHIP_INTERFACE) == 0) { + return g_strdup("KerberosMembership"); + } + if (strcmp(interface, REALM_DBUS_REALM_INTERFACE) == 0) { + return g_strdup("Realm"); + } + if (strcmp(interface, REALM_DBUS_PROVIDER_INTERFACE) == 0) { + return g_strdup("Provider"); + } + if (strcmp(interface, REALM_DBUS_SERVICE_INTERFACE) == 0) { + return g_strdup("Service"); + } + /* Return string which begins after last period */ + if ((token = rindex(interface, '.'))) { + token++; /* skip "." char */ + return g_strdup(token); + } else { + return g_strdup(interface); + } + +} + +/*----------------------------------------------------------------------------*/ + +/** + * rdcp_dbus_initialize: + * @g_error initialized to error info when FALSE is returned. + * + * Initializes the dbus module. + * + * - opens a connection the System Bus, exported as #system_bus + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized. + */ + +gboolean +rdcp_dbus_initialize(GError **g_error) +{ + DBusError dbus_error = DBUS_ERROR_INIT; + + dbus_error_init(&dbus_error); + + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + if (!system_bus) { + if ((system_bus = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error)) == NULL) { + *g_error = dbus_error_to_gerror(&dbus_error); + g_prefix_error(g_error, "could not connect to System DBus"); + return FALSE; + } + } + + return TRUE; +} diff --git a/src/realmd/rdcp_dbus.h b/src/realmd/rdcp_dbus.h new file mode 100644 index 0000000..d050071 --- /dev/null +++ b/src/realmd/rdcp_dbus.h @@ -0,0 +1,74 @@ +#ifndef __RDCP_DBUS_H__ +#define __RDCP_DBUS_H__ + +#include +#include +#include "realm-dbus-constants.h" + +#define VERBOSE +//#define TRACE_VARIANT + +/*----------------------------------------------------------------------------*/ + +extern DBusConnection* system_bus; + +/*----------------------------------------------------------------------------*/ + +gboolean +get_dbus_string_property(DBusConnection *bus, const char *object_path, + const char* interface, const char *property, + char **value_return, GError **g_error); +gboolean +get_dbus_properties(DBusConnection *bus, const char *object_path, + const char* interface, GVariant **properties_return, + GError **g_error); + +gboolean +dbus_discover_call(DBusConnection *bus, const char *target, GVariant *options, + gint32 *relevance_return, gchar ***paths_return, GError **g_error); + +gboolean +dbus_change_login_policy_call(DBusConnection *bus, const gchar *dbus_path, const char *login_policy, + GVariant *permitted_add, GVariant *permitted_remove, + GVariant *options, GError **g_error); +gboolean +dbus_join_call(DBusConnection *bus, const gchar *dbus_path, + GVariant *credentials, GVariant *options, GError **g_error); + +gboolean +dbus_leave_call(DBusConnection *bus, const gchar *dbus_path, + GVariant *credentials, GVariant *options, GError **g_error); + +char * +get_short_dbus_interface_name(const char *interface); + +gboolean +rdcp_dbus_initialize(GError **g_error); + +#ifdef RDCP_DEBUG +#define PRINT_DBUS_PROPERTIES(dbus_props, dbus_path, dbus_interface) \ + print_properties(dbus_props, "%s: Properties for %s, interface=%s", \ + __FUNCTION__, dbus_path, dbus_interface); +#else +#define PRINT_DBUS_PROPERTIES(dbus_properties, dbus_path, dbus_interface) +#endif + +#define GET_DBUS_PROPERIES_OR_EXIT(dbus_props, dbus_path, dbus_interface, status) \ +{ \ + if (dbus_props != NULL) { \ + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, \ + "get_dbus_properties failed, dbus_props was non-NULL (%s:%d)", \ + __FILE__, __LINE__); \ + goto exit; \ + } \ + if (!get_dbus_properties(system_bus, dbus_path, dbus_interface, &dbus_props, &g_error)) { \ + handle_g_error(&g_error, _cb, status, CMPI_RC_ERR_FAILED, \ + "get_dbus_properties failed, path=%s interface=%s", \ + dbus_path, dbus_interface); \ + goto exit; \ + } \ + \ + PRINT_DBUS_PROPERTIES(dbus_props, dbus_path, dbus_interface); \ +} + +#endif /* __RDCP_DBUS_H__ */ diff --git a/src/realmd/rdcp_error.c b/src/realmd/rdcp_error.c new file mode 100644 index 0000000..93d7ac3 --- /dev/null +++ b/src/realmd/rdcp_error.c @@ -0,0 +1,123 @@ +#include +#include "rdcp_util.h" +#include "rdcp_error.h" + +GQuark +rdcp_error_quark (void) +{ + static volatile gsize once = 0; + static GQuark quark = 0; + + if (g_once_init_enter(&once)) { + quark = g_quark_from_static_string("rdcp-error"); + g_once_init_leave(&once, 1); + } + + return quark; +} + +const char * +rdcp_error_code_to_string(rdcp_error_codes ec) +{ + switch(ec) { + case RDCP_ERROR_INTERNAL: return "RDCP_ERROR_INTERNAL"; + case RDCP_ERROR_INVALID_ARG: return "RDCP_ERROR_INVALID_ARG"; + case RDCP_ERROR_INVALID_INSTANCE_ID: return "RDCP_ERROR_INVALID_INSTANCE_ID"; + case RDCP_ERROR_DBUS: return "RDCP_ERROR_DBUS"; + default: return "unknown error code"; + } +} + +/*----------------------------------------------------------------------------*/ + +/** + * handle_g_error: + * @g_error pointer to non-NULL GError pointer describing problem + * @mb CMPI message broker + * @st CMPI status result + * @rc CMPI return code + * @format printf-style format string, may be #NULL if no additional + * message is desired. + * + * Sets @st status to the @rc return code and an optional printf + * styles formatted message which is prepended to the error message + * contained in the g_error. It frees the g_error. + * + * Returns: the @st status passed in. + */ +CMPIStatus +handle_g_error(GError **g_error, const CMPIBroker* mb, CMPIStatus* st, CMPIrc rc, + const gchar *format, ...) +{ + CMPIStatus failsafe_status; + GString *message; + va_list va; + + CMSetStatus(&failsafe_status, CMPI_RC_ERR_FAILED); + g_return_val_if_fail (g_error != NULL && *g_error != NULL, failsafe_status); + g_return_val_if_fail (st != NULL, failsafe_status); + + message = g_string_sized_new(DEFAULT_STATUS_MSG_SIZE); + g_string_append_printf(message, "%s: ", ORGID); + + if (format) { + va_start(va, format); + g_string_append_vprintf(message, format, va); + va_end(va); + g_string_append(message, ": "); + } + + g_string_append_printf(message, "(%s(%d)) ", + rdcp_error_code_to_string((*g_error)->code), + (*g_error)->code); + g_string_append(message, (*g_error)->message); + g_error_free(*g_error); + *g_error = NULL; + + CMSetStatusWithChars(mb, st, rc, message->str); + g_string_free(message, TRUE); + + return *st; +} + + +/** + * SetCMPIStatus: + * @mb CMPI message broker + * @st CMPI status result + * @rc CMPI return code + * @format printf-style format string, may be #NULL if no additional + * message is desired. + * + * Sets @st status to the @rc return code and an optional printf + * style formatted message. + * + * Returns: the @st status passed in. + */ +CMPIStatus +SetCMPIStatus(const CMPIBroker* mb, CMPIStatus* st, CMPIrc rc, + const gchar *format, ...) +{ + CMPIStatus failsafe_status; + GString *message = NULL; + va_list va; + + CMSetStatus(&failsafe_status, CMPI_RC_ERR_FAILED); + g_return_val_if_fail (st != NULL, failsafe_status); + + if (format) { + message = g_string_sized_new(DEFAULT_STATUS_MSG_SIZE); + g_string_append_printf(message, "%s: ", ORGID); + + va_start(va, format); + g_string_append_vprintf(message, format, va); + va_end(va); + + CMSetStatusWithChars(mb, st, rc, message->str); + g_string_free(message, TRUE); + } else { + CMSetStatus(st, rc); + } + + return *st; +} diff --git a/src/realmd/rdcp_error.h b/src/realmd/rdcp_error.h new file mode 100644 index 0000000..ff60d5b --- /dev/null +++ b/src/realmd/rdcp_error.h @@ -0,0 +1,38 @@ +#ifndef __RDCP_ERROR_H__ +#define __RDCP_ERROR_H__ + +#include + +#define RDCP_ERROR (rdcp_error_quark ()) + +GQuark rdcp_error_quark (void) G_GNUC_CONST; + +typedef enum { + RDCP_ERROR_INTERNAL = 1, + RDCP_ERROR_INVALID_ARG, + RDCP_ERROR_INVALID_INSTANCE_ID, + RDCP_ERROR_DBUS, +} rdcp_error_codes; + +const char * +rdcp_error_code_to_string(rdcp_error_codes ec); + +#define LMI_REALMD_RESULT_SUCCESS 0 +#define LMI_REALMD_RESULT_FAILED 1 +#define LMI_REALMD_RESULT_NO_SUCH_DOMAIN 2 +#define LMI_REALMD_RESULT_DOMAIN_DOES_NOT_SUPPORT_PROVIDED_CREDENTIALS 3 +#define LMI_REALMD_RESULT_DOMAIN_DOES_NOT_SUPPORT_JOINING 4 + +#define DEFAULT_STATUS_MSG_SIZE 128 + +CMPIStatus +handle_g_error(GError **g_error, const CMPIBroker* cb, CMPIStatus* status, CMPIrc rc, + const gchar *format, ...) + __attribute__ ((format (printf, 5, 6))); + +CMPIStatus +SetCMPIStatus(const CMPIBroker* mb, CMPIStatus* st, CMPIrc rc, + const gchar *format, ...) + __attribute__ ((format (printf, 4, 5))); + +#endif /* __RDCP_ERROR_H__ */ diff --git a/src/realmd/rdcp_realmdrealm.h b/src/realmd/rdcp_realmdrealm.h new file mode 100644 index 0000000..491bc73 --- /dev/null +++ b/src/realmd/rdcp_realmdrealm.h @@ -0,0 +1,310 @@ +#ifndef __RDCP_REALMDREALM_H__ +#define __RDCP_REALMDREALM_H__ + +#include +#include "LMI_RealmdRealm.h" +#include "LMI_RealmdKerberosRealm.h" + + +KINLINE LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_Enum +SupportedJoinCredentialTypes_name_to_enum(const char *name) +{ + if (strcasecmp(name, "ccache")) + return LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_ccache; + if (strcasecmp(name, "password")) + return LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_password; + if (strcasecmp(name, "secrect")) + return LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_secrect; + if (strcasecmp(name, "automatic")) + return LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_automatic; + return 0; +} + +KINLINE const char * +SupportedJoinCredentialTypes_enum_to_name(LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_Enum value) +{ + switch(value) { + case LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_ccache: + return "ccache"; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_password: + return "password"; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_secrect: + return "secrect"; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialTypes_automatic: + return "automatic"; + default: + return NULL; + } +} + +KINLINE LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_Enum +SupportedJoinCredentialOwners_name_to_enum(const char *name) +{ + if (strcasecmp(name, "administrator")) + return LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_administrator; + if (strcasecmp(name, "user")) + return LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_user; + if (strcasecmp(name, "computer")) + return LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_computer; + if (strcasecmp(name, "none")) + return LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_none; + return 0; +} + +KINLINE const char * +SupportedJoinCredentialOwners_enum_to_name(LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_Enum value) +{ + switch(value) { + case LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_administrator: + return "administrator"; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_user: + return "user"; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_computer: + return "computer"; + case LMI_RealmdKerberosRealm_SupportedJoinCredentialOwners_none: + return "none"; + default: + return NULL; + } +} + +KINLINE LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_Enum +SupportedLeaveCredentialTypes_name_to_enum(const char *name) +{ + if (strcasecmp(name, "ccache")) + return LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_ccache; + if (strcasecmp(name, "password")) + return LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_password; + if (strcasecmp(name, "secrect")) + return LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_secrect; + if (strcasecmp(name, "automatic")) + return LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_automatic; + return 0; +} + +KINLINE const char *SupportedLeaveCredentialTypes_enum_to_name(LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_Enum value) +{ + switch(value) { + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_ccache: + return "ccache"; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_password: + return "password"; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_secrect: + return "secrect"; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialTypes_automatic: + return "automatic"; + default: + return NULL; + } +} + +KINLINE LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_Enum +SupportedLeaveCredentialOwners_name_to_enum(const char *name) +{ + if (strcasecmp(name, "administrator")) + return LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_administrator; + if (strcasecmp(name, "user")) + return LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_user; + if (strcasecmp(name, "computer")) + return LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_computer; + if (strcasecmp(name, "none")) + return LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_none; + return 0; +} + +KINLINE const char * +SupportedLeaveCredentialOwners_enum_to_name(LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_Enum value) +{ + switch(value) { + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_administrator: + return "administrator"; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_user: + return "user"; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_computer: + return "computer"; + case LMI_RealmdKerberosRealm_SupportedLeaveCredentialOwners_none: + return "none"; + default: + return NULL; + } +} + +#define LMI_RealmdRealmInitKeys(klass, obj, dbus_path) \ +{ \ + gchar *instance_id = NULL; \ + const char *host_name = get_system_name(); \ + \ + klass##_Init(obj, cb, ns); \ + \ + instance_id = instance_id_from_dbus_path(dbus_path); \ + klass##_Set_InstanceID(obj, instance_id); \ + g_free(instance_id); \ + \ + klass##_Set_SystemCreationClassName(obj, get_system_creation_class_name()); \ + klass##_Set_SystemName(obj, host_name); \ +} + +KINLINE bool SupportsDBusInterface(GVariant *dbus_props, const char *dbus_interface) +{ + bool result = false; + GVariantIter *iter = NULL; + gchar *value; + + if (g_variant_lookup(dbus_props, "SupportedInterfaces", "as", &iter)) { + while (g_variant_iter_next(iter, "&s", &value)) { + if (strcmp(value, dbus_interface) == 0) { + result = true; + break; + } + } + G_VARIANT_ITER_FREE(iter); + } + return result; +} + +#define LMI_InitFromDBusRealmProps(klass, obj, dbus_props) \ +{ \ + gchar *value = NULL; \ + gchar *name = NULL; \ + gsize n_items; \ + GVariantIter *iter; \ + CMPICount i; \ + \ + if (g_variant_lookup(dbus_props, "Name", "&s", &value)) { \ + klass##_Set_RealmName(obj, value); \ + } \ + \ + if (g_variant_lookup(dbus_props, "Configured", "&s", &value)) { \ + if (strlen(value) == 0) { \ + klass##_Null_Configured(obj); \ + } else { \ + char *interface_name = get_short_dbus_interface_name(value); \ + klass##_Set_Configured(obj, interface_name); \ + g_free(interface_name); \ + } \ + } \ + \ + if (g_variant_lookup(dbus_props, "SupportedInterfaces", "as", &iter)) { \ + n_items = g_variant_iter_n_children(iter); \ + klass##_Init_SupportedInterfaces(obj, n_items); \ + for (i = 0; g_variant_iter_next(iter, "&s", &value); i++) { \ + char *interface_name = get_short_dbus_interface_name(value); \ + klass##_Set_SupportedInterfaces(obj, i, interface_name); \ + g_free(interface_name); \ + } \ + G_VARIANT_ITER_FREE(iter); \ + } \ + \ + if (g_variant_lookup(dbus_props, "Details", "a(ss)", &iter)) { \ + n_items = g_variant_iter_n_children(iter); \ + klass##_Init_DetailNames(obj, n_items); \ + klass##_Init_DetailValues(obj, n_items); \ + for (i = 0; g_variant_iter_next(iter, "(&s&s)", &name, &value); i++) { \ + klass##_Set_DetailNames(obj, i, name); \ + klass##_Set_DetailValues(obj, i, value); \ + } \ + G_VARIANT_ITER_FREE(iter); \ + } \ + \ + if (g_variant_lookup(dbus_props, "LoginFormats", "as", &iter)) { \ + n_items = g_variant_iter_n_children(iter); \ + klass##_Init_LoginFormats(obj, n_items); \ + for (i = 0; g_variant_iter_next(iter, "&s", &value); i++) { \ + klass##_Set_LoginFormats(obj, i, value); \ + } \ + G_VARIANT_ITER_FREE(iter); \ + } \ + \ + if (g_variant_lookup(dbus_props, "LoginPolicy", "&s", &value)) { \ + klass##_Set_LoginPolicy(obj, value); \ + } \ + \ + if (g_variant_lookup(dbus_props, "PermittedLogins", "as", &iter)) { \ + n_items = g_variant_iter_n_children(iter); \ + klass##_Init_PermittedLogins(obj, n_items); \ + for (i = 0; g_variant_iter_next(iter, "&s", &value); i++) { \ + klass##_Set_PermittedLogins(obj, i, value); \ + } \ + G_VARIANT_ITER_FREE(iter); \ + } \ +} + +#define LMI_InitFromDBusKerberosRealmProps(klass, obj, dbus_props) \ +{ \ + gchar *value = NULL; \ + \ + if (g_variant_lookup(dbus_props, "RealmName", "&s", &value)) { \ + klass##_Set_RealmName(obj, value); \ + } \ + \ + if (g_variant_lookup(dbus_props, "DomainName", "&s", &value)) { \ + klass##_Set_DomainName(obj, value); \ + } \ +} + +#define LMI_InitFromDBusKerberosMembershipProps(klass, obj, dbus_props) \ +{ \ + gchar *value = NULL; \ + gchar *type = NULL; \ + gchar *owner = NULL; \ + gsize n_items; \ + GVariantIter *iter; \ + CMPICount i; \ + \ + if (g_variant_lookup(dbus_props, "SuggestedAdministrator", "&s", &value)) { \ + klass##_Set_SuggestedAdministrator(obj, value); \ + } \ + \ + if (g_variant_lookup(dbus_props, "SupportedJoinCredentials", "a(ss)", &iter)) { \ + n_items = g_variant_iter_n_children(iter); \ + klass##_Init_SupportedJoinCredentialTypes(obj, n_items); \ + klass##_Init_SupportedJoinCredentialOwners(obj, n_items); \ + for (i = 0; g_variant_iter_next(iter, "(&s&s)", &type, &owner); i++) { \ + klass##_Set_SupportedJoinCredentialTypes(obj, i, \ + SupportedJoinCredentialTypes_name_to_enum(type)); \ + klass##_Set_SupportedJoinCredentialOwners(obj, i, \ + SupportedJoinCredentialOwners_name_to_enum(owner)); \ + } \ + G_VARIANT_ITER_FREE(iter); \ + } \ + \ + if (g_variant_lookup(dbus_props, "SupportedLeaveCredentials", "a(ss)", &iter)) { \ + n_items = g_variant_iter_n_children(iter); \ + klass##_Init_SupportedLeaveCredentialTypes(obj, n_items); \ + klass##_Init_SupportedLeaveCredentialOwners(obj, n_items); \ + for (i = 0; g_variant_iter_next(iter, "(&s&s)", &type, &owner); i++) { \ + klass##_Set_SupportedLeaveCredentialTypes(obj, i, \ + SupportedLeaveCredentialTypes_name_to_enum(type)); \ + klass##_Set_SupportedLeaveCredentialOwners(obj, i, \ + SupportedLeaveCredentialOwners_name_to_enum(owner)); \ + } \ + G_VARIANT_ITER_FREE(iter); \ + } \ +} + +CMPIStatus LMI_RealmdRealmRef_InitFromDBusPath( + LMI_RealmdRealmRef* self, + const CMPIBroker* cb, + const char* ns, + const char* dbus_path); + + +CMPIStatus LMI_RealmdRealm_InitFromDBusPath( + LMI_RealmdRealm* self, + const CMPIBroker* cb, + const char* ns, + const char* dbus_path); + +CMPIStatus LMI_RealmdKerberosRealmRef_InitFromDBusPath( + LMI_RealmdKerberosRealmRef* self, + const CMPIBroker* cb, + const char* ns, + const char* dbus_path); + +CMPIStatus LMI_RealmdKerberosRealm_InitFromDBusPath( + LMI_RealmdKerberosRealm* self, + const CMPIBroker* cb, + const char* ns, + const char* dbus_path); + +#endif /* __RDCP_REALMDREALM_H__ */ diff --git a/src/realmd/rdcp_util.c b/src/realmd/rdcp_util.c new file mode 100644 index 0000000..73ba17b --- /dev/null +++ b/src/realmd/rdcp_util.c @@ -0,0 +1,311 @@ +#include "rdcp_util.h" +#include "rdcp_dbus.h" + +#define VERBOSE + +void +print_properties (GVariant *properties, gchar *format, ...) +{ + va_list args; + GVariantClass class; + GVariantIter iter; + GVariant *value; + gchar *key; + gchar *value_as_string; + gsize n_children, i; + + if (format) { + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + } + + g_variant_iter_init (&iter, properties); + while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) { + class = g_variant_classify(value); + + if (class == G_VARIANT_CLASS_ARRAY) { + n_children = g_variant_n_children(value); + if (n_children == 0) { + printf(" %s: []\n", key); + + } else { + GVariant *child; + + printf(" %s: [\n", key); + for (i = 0; i < n_children; i++) { + child = g_variant_get_child_value(value, i); + value_as_string = g_variant_print(child, TRUE); + printf(" %s", value_as_string); + g_free(value_as_string); + G_VARIANT_FREE(child); + if (i < n_children-1) { + printf("\n"); + } else { + printf("]\n"); + } + } + } + } else { + value_as_string = g_variant_print(value, TRUE); + printf(" %s: %s\n", key, value_as_string); + g_free(value_as_string); + } + } + printf("\n"); +} + +void +print_paths(gchar **paths, gchar *format, ...) { + va_list args; + gchar *path, **pp; + int i, n_items; + + pp = paths; + for (path = *pp++, n_items = 0; path; path = *pp++, n_items++); + + if (format) { + va_start(args, format); + vprintf(format, args); + va_end(args); + } + + printf(" [%d paths:]\n", n_items); + + pp = paths; + for (path = *pp++, i = 0; path; path = *pp++, i++) { + printf(" path[%d]: %s\n", i, path); + } +} + +/*----------------------------------------------------------------------------*/ + +/** + * build_g_variant_options_from_KStringA + * @keys An array of dictionary keys. + * @values An array of dictionary values. + * @g_variant_return Pointer to location where GVariant will be returned, + * will be NULL if error. + * @g_error initialized to error info when FALSE is returned. + * + * Builds a GVariant dictionay of Realmd options. + * + * The keys and values in the dict are strings. The keys and values + * are passed as independent arrays, each value is paired with it's + * key by using the same index into keys and values array. It is an + * error if the length of the two arrays are not equal. If the length + * of the keys & values array is zero then the dictionary will be + * empty. + * + * If the option is not a string you must initialize the value for it + * as a string. The string value will be parsed and converted to the + * appropriate type for the option. Boolean values must be either + * "TRUE" or "FALSE" (case insensitive). + * + * Returns: return TRUE if successful, @g_variant_return will be non-NULL. + * FALSE if error with @g_error initialized, @g_variant_return will be NULL. + */ +gboolean +build_g_variant_options_from_KStringA(const KStringA *keys, const KStringA *values, + GVariant **g_variant_return, GError **g_error) +{ + GVariantBuilder builder; + GVariant *g_variant; + CMPICount i, count; + + g_return_val_if_fail (keys != NULL, FALSE); + g_return_val_if_fail (values != NULL, FALSE); + g_return_val_if_fail (g_variant_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *g_variant_return = NULL; + + if (keys->count != values->count) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_INVALID_ARG, + "length of keys array (%d) != length of values array (%d)", + keys->count, values->count); + return FALSE; + } + + count = keys->count; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + for (i = 0; i < count; i++) { + const char *key; + const char *value; + + key = KStringA_Get((KStringA *)keys, i); + value = KStringA_Get((KStringA *)values, i); + + if (g_str_equal(key, "assume-packages")) { + gboolean g_boolean; + + if (g_ascii_strcasecmp(value, "true") == 0) { + g_boolean = TRUE; + } else if (g_ascii_strcasecmp(value, "false") == 0) { + g_boolean = FALSE; + } else { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_INVALID_ARG, + "invalid value for assume-packages option (%s), must be TRUE or FALSE", value); + g_variant_builder_clear(&builder); + return FALSE; + } + g_variant_builder_add_parsed (&builder, "{%s, <%b>}", key, g_boolean); + } else { + g_variant_builder_add_parsed (&builder, "{%s, <%s>}", key, value); + } + } + + if ((g_variant = g_variant_builder_end(&builder)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to build GVariant options array"); + return FALSE; + } + + *g_variant_return = g_variant; + return TRUE; +} + +/** + * build_g_variant_string_array_from_KStringA: + * @values The KStringA source array + * @g_variant_return Pointer to location where GVariant will be returned, + * will be NULL if error. + * @g_error initialized to error info when FALSE is returned. + * + * Builds a GVariant array of strings "as" from a KStringA. + * + * Returns: return TRUE if successful, @g_variant_return will be non-NULL. + * FALSE if error with @g_error initialized, @g_variant_return will be NULL. + */ +gboolean +build_g_variant_string_array_from_KStringA(const KStringA *values, + GVariant **g_variant_return, GError **g_error) +{ + GVariantBuilder builder; + GVariant *g_variant; + CMPICount i, count; + + g_return_val_if_fail (values != NULL, FALSE); + g_return_val_if_fail (g_variant_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *g_variant_return = NULL; + + count = values->count; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_STRING_ARRAY); + + for (i = 0; i < count; i++) { + const char *value; + + value = KStringA_Get((KStringA *)values, i); + g_variant_builder_add (&builder, "s", value); + } + + if ((g_variant = g_variant_builder_end(&builder)) == NULL) { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_DBUS, + "unable to build GVariant options array"); + return FALSE; + } + + *g_variant_return = g_variant; + return TRUE; +} + +/*----------------------------------------------------------------------------*/ + +/** + * dbus_path_from_instance_id + * @instance_id A CIM Class InstanceID with a dbus_path encapsulated inside + * @dbus_path_return Pointer to location where DBus object path will be returned, + * will be NULL if error occurs. Must be freed with g_free() + * @g_error initialized to error info when FALSE is returned. + * + * CIM class instances bound to a DBus object embed the DBus object + * path into their InstanceID. This is one element contributing to the + * InstanceID's uniqueness. It also allows us to retrieve the DBus + * object path in order to operate on the matching DBus object. + * + * Returns: return TRUE if successful, FALSE if error with @g_error initialized + */ +gboolean +dbus_path_from_instance_id(const char *instance_id, gchar **dbus_path_return, GError **g_error) +{ + gchar *dbus_path = NULL; + + g_return_val_if_fail (instance_id != NULL, FALSE); + g_return_val_if_fail (dbus_path_return != NULL, FALSE); + g_return_val_if_fail (g_error == NULL || *g_error == NULL, FALSE); + + *dbus_path_return = NULL; + + if ((dbus_path = strchr(instance_id, ':')) != NULL) { + dbus_path++; /* skip ':' */ + *dbus_path_return = g_strdup(dbus_path); + return TRUE; + } else { + g_set_error(g_error, RDCP_ERROR, RDCP_ERROR_INVALID_INSTANCE_ID, + "could not locate DBus path in CIM InstanceID = \"%s\"", + instance_id); + return FALSE; + } +} + +/** + * instance_id_from_dbus_path + * @dbus_path The DBus object path to encode in the CIM InstanceID + * + * Returns: InstanceID, must be freed with g_free() + */ +gchar * +instance_id_from_dbus_path(const char *dbus_path) +{ + GString *g_instance_id = NULL; + + g_instance_id = g_string_new(NULL); + g_string_printf(g_instance_id, "%s:%s", ORGID, dbus_path); + return g_string_free(g_instance_id, FALSE); +} + +/** + * get_data_from_KUint8A + * + * @ka KUint8 Array containing binary data + * @size_return Length of the returned buffer is returned here if non-NULL. + * + * Binary data passed in a CMPI Uint8 array cannot be accessed as a + * pointer to the data. Instead each element of the array must be read + * and inserted into a contiguous buffer. + * + * Returns: data buffer, must be freed with g_free() + */ +gchar * +get_data_from_KUint8A(const KUint8A *ka, gsize *size_return) +{ + gsize n_octets; + gchar *buffer, *p; + CMPICount count, i; + KUint8 octet; + + count = ka->count; + n_octets = count; + + if ((buffer = g_malloc(n_octets)) == NULL) { + if (size_return) + *size_return = 0; + return NULL; + } + + for (i = 0, p = buffer; i < count; i++) { + octet = KUint8A_Get(ka, i); + *p++ = octet.value; + } + + if (size_return) + *size_return = n_octets; + + return buffer; +} diff --git a/src/realmd/rdcp_util.h b/src/realmd/rdcp_util.h new file mode 100644 index 0000000..aef255a --- /dev/null +++ b/src/realmd/rdcp_util.h @@ -0,0 +1,116 @@ +#ifndef __RDCP_UTIL_H__ +#define __RDCP_UTIL_H__ + +#include +#include + +#include +#include + +#include "rdcp_error.h" + +#define ORGID "LMI_Realmd" +#define REALMD_SERVICE_NAME "OpenLMI Realmd Service" + + +#define G_VARIANT_FREE(variant) \ +{ \ + if (variant) { \ + g_variant_unref(variant); \ + variant = NULL; \ + } \ +} + +#define G_VARIANT_ITER_FREE(iter) \ +{ \ + if (iter) { \ + g_variant_iter_free(iter); \ + iter = NULL; \ + } \ +} + +/** + * octetstring_parse + * @octetstring Pointer to octetstring data + * @data_len_return Pointer to uint32 value which receives the number + * of octets in the octetstring buffer. + * + * Given an octetstring, extract it's length and set the pointer to + * the data. + * + * When specified on elements of type array of uint8, the OctetString + * qualifier indicates that the entire array represents a single octet + * string. The first four array entries shall represent a length + * field, and any subsequent entries shall represent the octets in the + * octet string. The four uint8 values in the length field shall be + * interpreted as a 32-bit unsigned number where the first array entry + * is the most significant byte. The number represented by the length + * field shall be the number of octets in the octet string plus + * four. For example, the empty octet string is represented as { 0x00, + * 0x00, 0x00, 0x04 }. + + * + */ + +KINLINE unsigned char *octetstring_parse(unsigned char *octetstring, CMPIUint32 *data_len) +{ + unsigned char *data = NULL; + *data_len = 0; + if (octetstring) { + *data_len = (octetstring[0] << 24) | + (octetstring[1] << 16) | + (octetstring[2] << 8) | + (octetstring[3]); + *data_len -= 4; + data = octetstring + 4; + } + return data; +} + +#define LMI_InitRealmdServiceKeys(klass, obj, name_space, host_name) \ +{ \ + klass##_Init(obj, _cb, name_space); \ + klass##_Set_Name(obj, REALMD_SERVICE_NAME); \ + klass##_Set_SystemCreationClassName(obj, \ + get_system_creation_class_name()); \ + klass##_Set_SystemName(obj, host_name); \ + klass##_Set_CreationClassName(obj, \ + LMI_RealmdService_ClassName); \ + \ +} + + +#define LMI_InitComputerSystemKeys(klass, obj, name_space, host_name) \ +{ \ + klass##_Init(obj, _cb, name_space); \ + klass##_Set_Name(obj, host_name); \ + klass##_Set_CreationClassName(obj, \ + get_system_creation_class_name()); \ +} + +void +print_properties (GVariant *properties, gchar *format, ...) +__attribute__ ((format (printf, 2, 3))); + +void +print_paths(gchar **paths, gchar *format, ...) +__attribute__ ((format (printf, 2, 3))); + +gboolean +build_g_variant_options_from_KStringA(const KStringA *keys, const KStringA *values, + GVariant **g_variant_return, GError **g_error); + +gboolean +build_g_variant_string_array_from_KStringA(const KStringA *values, + GVariant **g_variant_return, GError **g_error); + +gboolean +dbus_path_from_instance_id(const char *instance_id, gchar **dbus_path_return, GError **g_error); + +gchar * +instance_id_from_dbus_path(const char *dbus_path); + +gchar * +get_data_from_KUint8A(const KUint8A *ka, gsize *size_return); + +#endif /* __RDCP_UTIL_H__ */ diff --git a/src/realmd/realm-dbus-constants.h b/src/realmd/realm-dbus-constants.h new file mode 100644 index 0000000..555c59b --- /dev/null +++ b/src/realmd/realm-dbus-constants.h @@ -0,0 +1,66 @@ +/* realmd -- Realm configuration service + * + * Copyright 2012 Red Hat Inc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * See the included COPYING file for more information. + * + * Author: Stef Walter + */ + +#ifndef __REALM_DBUS_CONSTANTS_H__ +#define __REALM_DBUS_CONSTANTS_H__ + +#define REALM_DBUS_BUS_NAME "org.freedesktop.realmd" +#define REALM_DBUS_SERVICE_PATH "/org/freedesktop/realmd" + +#define DBUS_PEER_INTERFACE "org.freedesktop.DBus.Peer" +#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties" +#define DBUS_INTROSPECTABLE_INTERFACE "org.freedesktop.DBus.Introspectable" + +#define REALM_DBUS_PROVIDER_INTERFACE "org.freedesktop.realmd.Provider" +#define REALM_DBUS_REALM_INTERFACE "org.freedesktop.realmd.Realm" +#define REALM_DBUS_KERBEROS_INTERFACE "org.freedesktop.realmd.Kerberos" +#define REALM_DBUS_KERBEROS_MEMBERSHIP_INTERFACE "org.freedesktop.realmd.KerberosMembership" +#define REALM_DBUS_SERVICE_INTERFACE "org.freedesktop.realmd.Service" + +#define REALM_DBUS_DIAGNOSTICS_SIGNAL "Diagnostics" + +#define REALM_DBUS_ERROR_INTERNAL "org.freedesktop.realmd.Error.Internal" +#define REALM_DBUS_ERROR_FAILED "org.freedesktop.realmd.Error.Failed" +#define REALM_DBUS_ERROR_BUSY "org.freedesktop.realmd.Error.Busy" +#define REALM_DBUS_ERROR_NOT_AUTHORIZED "org.freedesktop.realmd.Error.NotAuthorized" +#define REALM_DBUS_ERROR_CANCELLED "org.freedesktop.realmd.Error.Cancelled" +#define REALM_DBUS_ERROR_ALREADY_CONFIGURED "org.freedesktop.realmd.Error.AlreadyConfigured" +#define REALM_DBUS_ERROR_NOT_CONFIGURED "org.freedesktop.realmd.Error.NotConfigured" +#define REALM_DBUS_ERROR_AUTH_FAILED "org.freedesktop.realmd.Error.AuthenticationFailed" + +#define REALM_DBUS_DISCOVERY_DOMAIN "domain" +#define REALM_DBUS_DISCOVERY_KDCS "kerberos-kdcs" +#define REALM_DBUS_DISCOVERY_REALM "kerberos-realm" + +#define REALM_DBUS_NAME_CHARS "abcdefghijklnmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" + +#define REALM_DBUS_LOGIN_POLICY_ANY "allow-any-login" +#define REALM_DBUS_LOGIN_POLICY_PERMITTED "allow-permitted-logins" +#define REALM_DBUS_LOGIN_POLICY_DENY "deny-any-login" + +#define REALM_DBUS_OPTION_OPERATION "operation" +#define REALM_DBUS_OPTION_COMPUTER_OU "computer-ou" +#define REALM_DBUS_OPTION_SERVER_SOFTWARE "server-software" +#define REALM_DBUS_OPTION_CLIENT_SOFTWARE "client-software" +#define REALM_DBUS_OPTION_MEMBERSHIP_SOFTWARE "membership-software" +#define REALM_DBUS_OPTION_ASSUME_PACKAGES "assume-packages" + +#define REALM_DBUS_IDENTIFIER_ACTIVE_DIRECTORY "active-directory" +#define REALM_DBUS_IDENTIFIER_WINBIND "winbind" +#define REALM_DBUS_IDENTIFIER_FREEIPA "freeipa" +#define REALM_DBUS_IDENTIFIER_SSSD "sssd" +#define REALM_DBUS_IDENTIFIER_SAMBA "samba" +#define REALM_DBUS_IDENTIFIER_ADCLI "adcli" + +#endif /* __REALM_DBUS_CONSTANTS_H__ */ -- cgit From 4f93f9ff0181d78edb0ecd6f81e9a41d26c448b8 Mon Sep 17 00:00:00 2001 From: Tomas Smetana Date: Wed, 24 Apr 2013 13:08:49 +0200 Subject: Rename LMI_Realmd.mof to 60_LMI_Realmd.mof --- src/realmd/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realmd/CMakeLists.txt b/src/realmd/CMakeLists.txt index 380dd5c..cd6f6c8 100644 --- a/src/realmd/CMakeLists.txt +++ b/src/realmd/CMakeLists.txt @@ -3,7 +3,7 @@ pkg_check_modules(GLIB2 glib-2.0 REQUIRED) set(PROVIDER_NAME Realmd) set(LIBRARY_NAME cmpiLMI_${PROVIDER_NAME}) -set(MOF LMI_Realmd.mof) +set(MOF 60_LMI_Realmd.mof) set(provider_SRCS -- cgit From a10370b13e73184080146ead6899726f78423c43 Mon Sep 17 00:00:00 2001 From: Jan Safranek Date: Thu, 25 Apr 2013 10:58:24 +0200 Subject: Fixed LMI_ConcreteJob.GetError and .GetErrors to return correct CIM instance. We should return CIMInstance instead of CIMError (which has nothing to do with CIM_Error). --- src/python/openlmi/common/JobManager.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/python/openlmi/common/JobManager.py b/src/python/openlmi/common/JobManager.py index 5ed7830..f2d0c71 100644 --- a/src/python/openlmi/common/JobManager.py +++ b/src/python/openlmi/common/JobManager.py @@ -494,6 +494,23 @@ class Job(object): inst['PreCall'] = True return inst + @cmpi_logging.trace_method + def get_cim_error(self): + """ + Return job error as CIMInstance of CIM_Error. + :returns: CIMInstance of CIM_Error + """ + path = pywbem.CIMInstanceName( + classname="CIM_Error", + host=socket.gethostname(), + namespace=self.job_manager.namespace) + err = pywbem.CIMInstance( + classname="CIM_Error", + path=path) + err['CIMStatusCode'] = pywbem.Uint32(self.error[0]) + err['Message'] = self.error[1] + return err + @cmpi_logging.trace_method def get_post_call(self): """ @@ -524,15 +541,7 @@ class Job(object): if self.return_value is not None: inst['ReturnValue'] = str(self.return_value) if self.error is not None: - path = pywbem.CIMInstanceName( - classname="CIM_Error", - host=socket.gethostname(), - namespace=self.job_manager.namespace) - err = pywbem.CIMInstance( - classname="CIM_Error", - path=path) - err['CIMStatusCode'] = pywbem.Uint32(self.error[0]) - err['Message'] = self.error[1] + err = self.get_cim_error() inst['Error'] = [err, ] return inst @@ -1139,7 +1148,8 @@ class LMI_ConcreteJob(CIMProvider2): if job.error is None: errors = [] else: - errors = [job.error, ] + err = job.get_cim_error() + errors = [err, ] out_params = [ pywbem.CIMParameter( name='errors', @@ -1283,9 +1293,10 @@ class LMI_ConcreteJob(CIMProvider2): type='instance', is_array=False) else: + err = job.get_cim_error() error = pywbem.CIMParameter( name='error', - value=job.error, + value=err, type='instance') rval = self.Values.GetError.Success return (rval, [error]) -- cgit From 9e1db917fa4ac56b56343a796c41f3213f53a2af Mon Sep 17 00:00:00 2001 From: Tomas Smetana Date: Thu, 25 Apr 2013 15:34:07 +0200 Subject: Add the provider howto document --- doc/cim-provider-howto.md | 1213 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1213 insertions(+) create mode 100644 doc/cim-provider-howto.md diff --git a/doc/cim-provider-howto.md b/doc/cim-provider-howto.md new file mode 100644 index 0000000..da7fb0e --- /dev/null +++ b/doc/cim-provider-howto.md @@ -0,0 +1,1213 @@ +% OpenLMI CIM Provider HOWTO +% John Dennis +% 3/30/2013 + +# License # + +This document is licensed under the [Create Commons ShareAlike +license](http://creativecommons.org/licenses/by-sa/3.0/) + +You are free: + +To Share + : To copy, distribute and transmit the work + +To Remix + : To adapt the work + +Use commercially + + : To make commercial use of the work + +Under the following conditions: + +Attribution + + : You must attribute the work in the manner specified by the author + or licensor (but not in any way that suggests that they endorse + you or your use of the work). + +Share Alike + + : If you alter, transform, or build upon this work, you may + distribute the resulting work only under the same or similar + license to this one. + + +# Introduction # + +CIM stands for Common Information Model. CIM is one component of WBEM +(Web-Based Enterprise Management). WBEM is a technology suite allowing +one to remotely enumerate the computing resources in an enterprise, +query their state, modify their configuration and otherwise act upon +those resources. People often use the term CIM when they are actually +discussing WBEM. Technically CIM is only a schema and +specification. The suite of specifications and technologies providing +enterprise computer management based on the CIM model is WBEM. + +CIM is an open standard under the auspices of DMTF ([Distributed +Management Task Force](http://www.dmtf.org/)). WBEM Management tools +based on CIM allow IT administrators to manage diverse computing +resources in a heterogeneous environment. In addtion to CIM DMTF also +manages many of the specifications related to WBEM. + +If you've been asked to write a CIM provider this document will +introduce you to the relevant technologies and guide you through the +development process. + +## WBEM Components ## + +Although technically CIM is only a schema definition the common usage +of the term CIM is often taken to mean the collection of tools and +technologies enabling computer management, i.e. WBEM. Without the +overarching technology ecosystem that WBEM provides CIM would just be +a paper abstraction. Lets briefly explore how these independent +components fit together to form a WBEM management solution. + +A manged system runs a service called the CIMOM, sometimes referred to +as a broker. CIMOM stands for Common Information Model Object +Manager. Typically the CIMOM is connected to the internet so that it +can provide remote administration of the computer. The CIMOM will load +a set of providers. Each provider is a software module dedicated to +managing one type (i.e. class) of resource on the computer, for +example network interfaces. There may be multiple instances of that +resource class. The provider is responsible for managing all instances +of that resource class. The provider in addition to providing +information about a resource instance may optionally allow the +resource instance to be configured or acted upon. It is the CIMOM +which organizes all the providers on the system and grants a CIM +client access to those providers. There are many CIMOM implementations +available. Since all CIMOM's are supposed to follow the collection of +WBEM standards all CIM clients should be able to inter-operate with +each CIMOM. + +A CIM provider can be loaded into different CIMOM implementations if +both the CIMOM and the provider utilize a common programming API. CMPI +(Common Manageability Programming Interface) is the standardized API +all CIMOM's and provider's should be coded to. + +A CIM client is able to connect to a CIMOM and interact with the +resources (i.e. objects) being managed by the CIMOM. It is important +to note *a CIM client does not interact directly with a provider* +running in the CIMOM. Rather the CIMOM will expose to the CIM +client the objects made available to the CIMOM via it's set of loaded +providers. + +Communication between a CIM client and a CIMOM occurs via +standardized protocols. At the time of this writing only one protocol +is in wide use, [CIM Operations over +HTTP](http://www.dmtf.org/standards/wbem). This protocol establishes +HTTP headers and then passes the CIM payload as an XML document to +the CIMOM. Responses from the CIMOM are also encoded in XML. The +definition of the XML documents are defined in [Representation of CIM +in XML](http://www.dmtf.org/standards/wbem). The collection of +standards used by CIM clients and CIMOM brokers are known as WBEM +(Web-Based Enterprise Management). + +In a typical scenario a CIM client will make an authenticated +connection to a CIMOM and ask it to enumerate the set of objects +it's interested in. The CIM client may choose to enumerate all instances +of a specific class or it may utilize a query language to refine the +results. CIM objects are often related to one another via +Associations. Querying via associations is very similar to +performing a join in SQL. + +CIM objects have properties and methods. A property is a piece of data +and a method is a function you can call. A CIM client may examine the +properties of a returned object to determine it's health state, +configuration, status, etc. Configuration is an advanced topic +explained in greater depth in [Resource +Configuration](#resource-configuration). + +A lot of information has been presented in a short time, to help +clarify here is simple break down of how the components fit together. + +1. CIM client makes authenticated connection to a CIMOM. + +2. Using the HTTP protocol an XML document is passed from the CIM + client to the CIMOM. The XML document describes the requested CIM + operation. Formation of the XML document is performed by CIM client + API library routines. + +3. The CIMOM interprets the XML document and calls routines in it's + providers to service the request. + +4. The CIMOM communicates with it's providers using the CMPI C + language API. + +5. The CIMOM coalesces the information supplied by it's providers + and forms an XML document which will then be returned as the HTTP + response to the CIM client. + +6. Library routines in the CIM client parse the XML document and + return the information via a CIM client API. + +# What do I need to know to write a CIM provider? # + +CIM is an extraordinarily complex topic. Without some guidance one can +easily get lost in the wealth of material resulting in spinning your +wheels without making a lot of progress. In this section we try to +summarize some key aspects of CIM and direct you to information that +will help you complete your task while helping you stay clear of +material which is not relevant. + +Material deemed to be critical will be highlighted + +**In a single sentence like this.** + +## CIM Schema, MOF and Profiles ## + +### CIM Schema and MOF Syntax ### + +CIM models real world objects and their relationships. Those objects +are modeled via CIM classes. A CIM class has properties and +methods. The DMTF has defined a set of CIM classes which are meant to +be the building blocks for a CIM model, this is the [CIM +Schema](http://www.dmtf.org/standards/CIM). The CIM Schema is +expressed in the MOF [Managed Object +Format](http://www.dmtf.org/standards/cim) syntax. MOF files are used +to define provider interfaces. + +**You must be fluent in MOF, it is the language of CIM.** + +### Models and Profiles ### + +A model is a collection of schema elements designed to model a +computer system component which is to be managed. It defines the +schema classes used to represent the managed elements and their +relationships. At it's heart a model is pure CIM Schema but schema +alone is not sufficient to explain intended usages nor the rules for +how the schema elements interact. This expository material is +collected into a document called a profile. Profiles follow a +standardized format called the [Management Profile Specification +Template](http://www.dmtf.org/standards/profiles). DMTF has already +defined numerous profiles to cover common computer system elements, +these are collected in the [Management +Profiles](http://www.dmtf.org/standards/profiles) web page. For some +reason the primary CIM page on the DMTF website does not link to the +management profiles. This curious omission might cause you to miss the +critical aspect of CIM profiles. + +**Prior to starting a CIM provider peruse the [Management +Profiles](http://www.dmtf.org/standards/profiles) to determine if one +or more profiles already exist, if so you should implement that model.** + +## Are you creating a model or implementing an existing profile? ## + +If a profile already exists your job is tremendously simplified. The +profile lays out the exact classes you have to implement along with +the rules for how they interact. Chapter 9 of the profile is +especially useful because it illustrates expected use cases with +examples, this can greatly aid your comprehension of the model and +it's profile. If a profile exists it is not necessary to understand +the breath and depth of the CIM Schema, the necessary schema elements +have already been assembled for you. At this point you can move on to +the provider implementation tasks at this point using the profile as a +recipe. + +However if a profile does not yet exist for your provider you must +define one. Unfortunately this is a very challenging task, it demands +an in-depth understanding the CIM Schema as well as a working knowledge +of the existing profiles. You need a familiarity with the existing profiles +in order to understand the design patterns of CIM, otherwise your +provider will not function as expected. + +If you do find yourself in the position of having to author a +model/profile then you should read the [Using the +Schema](http://www2.informatik.hu-berlin.de/~xing/Lib/cim-tutorial/using/index.html) +and [Extending the +Schema](http://www2.informatik.hu-berlin.de/~xing/Lib/cim-tutorial/extend/index.html) +sections in this [tutorial +section](http://www2.informatik.hu-berlin.de/~xing/Lib/cim-tutorial/intro/components.html). It +will provide the conceptual framework for schema design decisions. + + +## CMPI and KonkretCMPI ## + +OpenLMI encourages the use of KonkretCMPI to aid provider +development. + +**You will want to read the** [KonkretCMPI +Documentation](http://konkretcmpi.org/KonkretCMPI.html) **to +understand the basic development process with KonkretCMPI.** + +You might be tempted after reading the KonkretCMPI documentation to +dive and begin writing a provider believing KonkretCMPI given you +all you need to know to complete the task. But the truth is you're not +writing to a KonkretCMPI API, instead you're writing your provider +using the CMPI API. Essentially what KonkretCMPI does is insulate you +from CMPI. KonkretCMPI gives you a layer over CMPI that provides a +nice level of abstraction and other utility support. The other primary +advantage of KonkretCMPI is it automatically generates all the +necessary stub functions needed to comply with CMPI. After KonkretCMPI +runs you need to add your implementation to the function stubs +KonkretCMPI generated for you. Overall this simplifies the development +process. + +However, if you don't have an understanding of CMPI from the outset +you'll likely find what KonkretCMPI generates confusing because it +will seem to exist in a vacuum. You won't necessarily understand how +or why all the code pieces generated by KonkretCMPI fit together. You +might find yourself asking were certain initialization is performed or +in what order, how to manage life cycle, how are errors handled, +etc. All of this is clearly spelled out in the CMPI spec. You'll +probably also discover what KonkretCMPI gives to is incomplete, not +everything you may need to do in your provider is covered by +KonkretCMPI. There is an excellent chance you'll need to call the CMPI +API directly for services not provided by KonkretCMPI. It's best to +think of KonkretCMPI as a CMPI helper, it is not a CMPI replacement +(i.e. wrapper around CMPI). + +**Therefore you should also read the** [CMPI +Specification](https://www2.opengroup.org/ogsys/catalog/C061) + +The CMPI specification is long, here is my suggestion for reading it +to get started. Read chapters 1-5, those chapters lay out the basic +model and groundwork. If you understand those concepts you're in good +shape. The remaining chapters list the function tables and detailed +descriptions of each function in it's respective table. You don't need +to know the details of each function, but it helps to know what is +available. I would suggest perusing the beginning of each of these +remaining chapters just to see what's available in table, you can skip +the per function detail. + +The CMPI specification describes the CMPI API at the raw C level. It +can be very verbose code which is ripe for simplification through +pre-processor macros. Standard macros are defined in +`/usr/include/cmpi/cmpimacs.h`. Most code will use those macros and as +such those macros are the *effective* CMPI API. You should be using +these standard CMPI macros and be familiar with them for those cases +where KonkretCMPI does not provide an alternative. + +**You should be familiar with the contents of `cmpimacs.h`.** + +## Tutorials ## + +At this point you now have enough background information to dive into +the [DMTF CIM +Tutorial](http://www.wbemsolutions.com/tutorials/DMTF/). This tutorial +will help clarify the concepts already presented and round out +material we have glossed over. The tutorial is brief and a bit +superficial, it may or may not satisfy your needs for the +comprehension of CIM. + +**Read the** [DMTF CIM +Tutorial](http://www.wbemsolutions.com/tutorials/DMTF/). + +On the other hand the [Learn +CIM](http://www2.informatik.hu-berlin.de/~xing/Lib/cim-tutorial/start.html) +is much more complete and goes into much more depth. Not everyone will +need the material here but many will find it helpful. A good strategy +is to skim the tutorial making note of the material it covers and then +later when confronted with a gap in your comprehension return to the +tutorial. + +## Tools and Packages ## + +### CIMOM's ### + + * [OpenPegasus](https://collaboration.opengroup.org/pegasus/) + OpenPegasus is an open-source implementation of the DMTF CIM and + WBEM standards. OpenPegasus is written in C++ and is designed to + be portable. It builds and runs on most versions of UNIX, + Linux, OpenVMS, and Microsoft Windows. + + RPM package name: tog-pegasus + + You may find the [OpenPegasus Administrator's Guide](http://cvs.opengroup.org/cgi-bin/viewcvs.cgi/*checkout*/pegasus/doc/Admin_Guide_Release.pdf) + useful for topics concerning installation, configuration, + authentication, etc. of OpenPegasus. + + * [SFCB](http://sourceforge.net/apps/mediawiki/sblim/index.php?title=Sfcb) + Small Footprint CIM Broker. SFCB is a CIM server for + resource-constrained and embedded environments. It is written in C + and designed to be modular and lightweight. + + RPM package name: sblim-sfcb + + +### Development Tools ### + + * [KonkretCMPI](http://konkretcmpi.org/) is used to generate C source code + for providers from a mof specification. + + RPM package name: konkretcmpi + + + * [CMake](http://www.cmake.org/) is the required build tool. Various + aspects of OpenLMI provider development and deployment are + automatically handled via custom CMake macros provided by OpenLMI. + + RPM package name: cmake + + * [PyWBEM](http://sourceforge.net/apps/mediawiki/pywbem/index.php?title=Main_Page) + PyWBEM is a Python library supporting CIM operations over HTTP using + the WBEM CIM-XML protocol. It is easy to use and master. PyWBEM also + provides a Python provider interface suitable for rapid development + of CIM providers. + + RPM package name: pywbem + + * [OpenLMI](https://fedorahosted.org/openlmi/) + OpenLMI maintains numerous development tools to ease the task of + CIM provider development, deployement and WBEM operations. + + RPM package name: openlmi-providers-devel + + +### Client Tools ### + + * [PyWBEM](http://sourceforge.net/apps/mediawiki/pywbem/index.php?title=Main_Page) + can be used for client scripting in Python. + + * [YAWN](http://sourceforge.net/apps/mediawiki/pywbem/index.php?title=YAWN) + Yet Another WBEM Navigator. YAWN runs in the Apache web server, it + is Python based and utilizes Apache's mod-python. It is a CIM + client tool in that it provides a way to browse CIM Schema and + exercise CIM providers from within your local web browser. + + RPM package name: yawn + + * [OpenLMI](https://fedorahosted.org/openlmi/) + OpenLMI maintain several client tools + + RPM package name: openlmi-tools + +# Writing a CIM provider for OpenLMI # + +## OpenLMI Development Conventions ## + +OpenLMI has established a number of development conventions which you will +want to observe. + +* The preferred CIMOM is + [OpenPegasus](https://collaboration.opengroup.org/pegasus/) + +* The preferred source code language for providers is C. + +* [KonkretCMPI](http://konkretcmpi.org/) is used to generate C source code + for providers. + +* [CMake](http://www.cmake.org/) is the build tool. Various aspects of + provider development and deployment are automatically handled via + custom CMake macros provided by OpenLMI. + +* [PyWBEM](http://sourceforge.net/apps/mediawiki/pywbem/index.php?title=Main_Page) + is used for client scripting in Python and implementing providers + when Python is the language of choice. + + +## Set-Up Your Environment ## + +This assumes you are working on a Fedora/RHEL/CentOS RPM based Linux +distribution, although the basic concepts remain the same some package +names and commands may differ slightly for other Linux distributions. + +### Install CIMOM ### + +Install the OpenPegasus package + +~~~~~~ +sudo yum install tog-pegasus +~~~~~~ + +Make sure OpenPegasus is running. **Note:** provider registration only +works when OpenPegasus is running. + +~~~~~~ +sudo systemctl start tog-pegasus.service +~~~~~~ + +If you want the OpenPegasus CIMOM service to automatically start after +a reboot: + +~~~~~~ +sudo systemctl enable tog-pegasus.service +~~~~~~ + +**Tip:** pegasus can easily be controlled via the `cimserver` command, +in fact the systemd service control mechanisms simply calls the +`cimserver` command. During development you may find it easier to +start, stop, and check the status of the OpenPegasus CIMOM service via +`cimserver` rather than by the `systemctl` infrastructure. + +### Install YAWN ### + +YAWN is not a requirement but being able to browse the CIM class +hierarchy and invoke your provider from within your web browser is so +handy most developers will want this. YAWN runs in the Apache web +server therefore you will need to have Apache (e.g. httpd) installed +and running to be able to browse at the following URL http://host/yawn +where host is the host name you've installed YAWN on. + +~~~~~~ +sudo yum install httpd yawn +~~~~~~ + +Then make sure the Apache httpd service is running. + +~~~~~~ +sudo systemctl start httpd.service +~~~~~~ + +Optionally enable Apache httpd to start after booting. + +~~~~~~ +sudo systemctl enable httpd.service +~~~~~~ + +See the section on [OpenPegasus authentication](#openpegasus-authentication) to understand the +username and password prompts required by YAWN when you first connect. + +### Install Client Tools ### + +OpenLMI Tools provides a CIM shell and a few other handy utilities. + +~~~~~~ +sudo yum install openlmi-tools +~~~~~~ + +Various WBEM command line utilities are provided by sblim-wbemcli + +~~~~~~ +sudo yum install sblim-wbemcli +~~~~~~ + +Python libraries which allow you to write simple Python scripts for +WBEM operations are provided by pywbem. + +~~~~~~ +sudo yum install pywbem +~~~~~~ + +## Install Provider Development tools ## + +You will need KonkretCMPI, CMake and tools provided by OpenLMI. + +~~~~~~ +sudo yum install cmake konkretcmpi openlmi-providers-devel +~~~~~~ + +## Begin Provider Development (C Language) ## + +During this discussion we're going to use `XXX` as the name of +your provider, you will need to substitute `XXX` for your provider +name. + +You must use CMake to take advantage of the OpenLMI development +support. It expects the following structure in your development +tree. + +~~~~~~ +CMakeLists.txt +mof/LMI_XXX.mof +~~~~~~ + +CMake will read the contents of CMakeLists.txt and produce a set of +native Makefiles. CMake macros provided by openlmi-providers-devel +will also set things up to invoke konkretcmpi to translate your +LMI_XXX.mof file into a set of C source code files. + +The defaults for CMake do not correspond to the defaults when CMake is +invoked when producing OpenLMI RPM's. The goal here is not so much to +match RPM but rather to produce a build that matches the expected +system conventions and the OpenLMI conventions. + +Since you probably won't be producing an RPM for your provider +initially it's best to make sure CMake is configured to generate +Makefiles that will build using the same conventions for building and +installing as is done in RPM. There are various ways to do this but +one simple technique is to define a shell script which invokes CMake +with the matching RPM configuration, for example: + +~~~~~~ +#!/bin/sh + +/usr/bin/cmake -DCMAKE_VERBOSE_MAKEFILE=ON \ + -DCMAKE_INSTALL_PREFIX:PATH=/usr \ + -DINCLUDE_INSTALL_DIR:PATH=/usr/include \ + -DLIB_INSTALL_DIR:PATH=/usr/lib \ + -DSYSCONF_INSTALL_DIR:PATH=/etc \ + -DSHARE_INSTALL_PREFIX:PATH=/usr/share \ + -DBUILD_SHARED_LIBS:BOOL=ON . + +if [ $? -eq 0 ]; then + make +fi +~~~~~~ + +During development it is useful to turn on debugging symbols and turn +off optimization which complicate debugging using `gdb`. If you add + +~~~~~~ +export CFLAGS='-g -O0' +~~~~~~ + +to the above script before invoking cmake it will add these options to +the Makefile. CMake has other mechanisms for producing debug builds +(i.e. build targets). If you prefer those CMake mechanisms that's fine +too but I found the above to be simple and expedient. + +You will need a valid CMakeLists.txt file that follows the OpenLMI +CMake conventions. Since OpenLMI is under active development the +contents of the example CMakeLists.txt file may evolve or a +CMakeLists.txt template may be included in the future but for the time +being the example CMakeLists.txt represents a viable starting point. + +**Note:** If you aren't familiar with CMake it can be difficult to +figure out how to do a few simple things. For example if your provider +is dependent upon another library how do you get things set up such +that the include files are found and the right libraries are added +when linking? In the following CMakeLists.txt example I've added a +dependency on `glib2` only for the purposes of illustration, if your +provider does not use `glib2` you won't need any of the items in the +CMakeLists.txt file with the string "glib" in it. But you can use +"glib" items as a model for what needs to be added for any dependency +you do have. Also note that `glib2` installs pkgconfig files which +define how to compile and link against `glib2`. Most library packages ship +with pkgconfig files, the `glib2` example assumes pkgconfig files are +available for the dependency, if your dependency does not provide +pkgconfig files you'll have to adjust accordingly. + +~~~~~~ +cmake_minimum_required(VERSION 2.6) +include(OpenLMIMacros) +find_package(CMPI REQUIRED) +find_package(KonkretCMPI REQUIRED) + +find_package(PkgConfig QUIET) +pkg_check_modules(GLIB2 glib-2.0 REQUIRED) + +add_subdirectory(mof) + +set(PROVIDER_NAME XXX) +set(LIBRARY_NAME cmpiLMI_${PROVIDER_NAME}) +set(MOF LMI_XXX.mof) + + +# Add all your .c source files here +set(provider_SRCS + LMI_XXXProvider.c +) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + +konkretcmpi_generate(${MOF} + CIM_PROVIDERS + CIM_HEADERS +) + +add_library(${LIBRARY_NAME} SHARED + ${provider_SRCS} + ${CIM_PROVIDERS} + ${CIM_HEADERS} +) + +# FIXME - /usr/include/openlmi shouldn't be hardcoded, needed for globals.h +# OpenLMI should provide a pkgconfig file +include_directories(${CMAKE_CURRENT_BINARY_DIR} + ${CMPI_INCLUDE_DIR} + ${GLIB2_INCLUDE_DIRS} + /usr/include/openlmi + ) + +target_link_libraries(${LIBRARY_NAME} + openlmicommon + ${KONKRETCMPI_LIBRARIES} + ${GLIB2_LIBRARIES} + ) + +# Create registration file +cim_registration(${PROVIDER_NAME} ${LIBRARY_NAME} ${MOF} share/openlmi-providers) + +install(TARGETS ${LIBRARY_NAME} DESTINATION lib${LIB_SUFFIX}/cmpi/) +~~~~~~ + +## Perform a Build ## + +Your mof/LMI_XXX.mof file will need to have been populated with +something to start with. At this point you may want to refer to the +[KonkretCMPI Documentation](http://konkretcmpi.org/KonkretCMPI.html) +to understand the basic development process with KonkretCMPI and +follow it's example tutorial. + +### Understanding KonkretCMPI Behavior ### + +The role of KonkretCMPI is to read a MOF file and generate C code +definitions and code stubs necessary to implement your +provider. KonkretCMPI generates 3 distinct sets of files: + +* .h C header files +* .c C implementation files +* .reg CIMOM registration files + +Anytime your mof file changes KonkretCMPI needs to run again to make +sure the generated files are in sync with the mof file contents. +If KonkretCMPI sees that you are missing any of the generated .c files it +will generate the .c files for you. However if KonkretCMPI finds an +existing .c file it will **not overwrite** the .c file. This is because +you will have likely added your custom provider implementation code to +these files and you don't want to lose your work. + +The header files are directly tied to the definitions found in the mof +file. It is essential those definitions be correct and reflect the +current contents of the mof file. Therefore KonkretCMPI **always +overwrites generated .h files**. + +**Tip:** Do not add any custom content to any of the generated .h +files, your content will be lost every time KonkretCMPI runs due to a +mof file change. If you did add any custom content to a generated .h +file you'll have to merge it back in, this is a pain. A better +approach is to put custom header style information in an independent +header file and include it in your .c file. + +**Note:** Because KonkretCMPI will not overwrite an existing .c file +you may need to merge function prototype information back into your .c +file. You typically only need to do this when a CIM method signature +is modified in the mof file or you've added a new CIM method. It's +much easier to merge function prototypes back into your .c file than +it is to move the .c file aside to allow KonkretCMPI to regenerate the +.c file and then subsequently needing to merge all your custom code +back into the newly generated .c file. + +**Tip:** It's easier to do your provider development if your mof file +is complete and exactly as you want it. This way you'll have fewer +issues with KonkretCMPI regenerating files, needing to unregister and +re-register your provider, or wondering why you can't see your updated +mof (because you forget to redo the registration). But sometimes it's +easier to develop your mof incrementally, you'll have to decide which +development strategy better suits your style and situation. + +## Installing and Registering Your Provider ## + +If all has gone well after typing `make` you will have compiled your +.c files and linked them into a provider module. Now you need to +install your provider so your CIMOM can load it. + +In order for your CIMOM to expose your provider it must be registered +with your CIMOM. This requires the following 3 files to have been +installed in the expected location in your file system. + +* provider module (i.e. your provider .so file) +* provider mof file +* provider registration file + +The following command will install these files + +~~~~~~ +sudo make install +~~~~~~ + +**Note:** This is one reason it's critical to invoke CMake with the +overridden defaults otherwise the installation directories will not +default to the system defaults. + +Now that the files are installed you must register your +provider. OpenLMI provides a utility script `openlmi-mof-register` to +simplify this task. **Note:** the `openlmi-mof-register` script is +also used to unregister a provider, more on that in a moment. + +Substitute `XXX` for the name of your provider. + +~~~~~~ +sudo openlmi-mof-register register \ + /usr/share/openlmi-providers/XXX.mof \ + /usr/share/openlmi-providers/XXX.reg +~~~~~~ + +**Note:** OpenPegasus must be running when you register or unregister +a provider. + +**Tip:** During your provider development cycle of edit/build/test you +do not need to re-register your provider if your mof file did not +change. It's sufficient to just install your updated provider .so +(e.g. `sudo make install`) and restart the CIMOM. However if your mof +file changed your CIMOM won't know about the mof changes because one +of the things registration does is import the mof definitions into the +CIMOM. After editing your mof you will need to unregister your old +provider and re-register it again. In summary only after a mof +modification you will need to do the following: + +~~~~~~ +sudo openlmi-mof-register unregister \ + /usr/share/openlmi-providers/XXX.mof \ + /usr/share/openlmi-providers/XXX.reg +sudo make install +sudo openlmi-mof-register register \ + /usr/share/openlmi-providers/XXX.mof \ + /usr/share/openlmi-providers/XXX.reg +~~~~~~ + +## Testing Your Provider ## + +Once your provider is installed and registered you will want to +exercise it. Here are some simple ways you can do that, which one you +choose is up to you, all boil down to invoking a CIM client against +your CIMOM. + +* Use YAWN in your web browser, open a URL +(e.g. `http://localhost/yawn) and navigate to one of your provider +classes. + +* Write a Python script using PyWBEM and run that script. + +* Use the OpenLMI shell + +## Development Debugging Tricks and Techniques ## + +### Starting, Stopping and Controling OpenPegasus ### + +Don't use the `systemctl` service command to stop and start +OpenPegasus, instead use the `cimserver` command, it's much easier, +plus you can specify one time configuration parameters useful for +debugging (see below). + +### Run OpenPegasus In The Foreground ### + +You can insert printf debugging statements in your code but you won't +see them on the console unless you run OpenPegasus in a special +way. Also, you will want to run OpenPegasus in the foreground, not +allow it to fork a daemon process or spawn child processes. Running +OpenPegasus in the following way is much friendlier during the +development cycle. + +~~~~~~ +sudo cimserver daemon=false forceProviderProcesses=false +~~~~~~ + +When run this way you'll see any printf's you've added, they will +appear in your console. When you're done with the current testing +cycle simply control-c and OpenPegasus will exit. A rapid development +cycle might look like this: + +~~~~~~ +# Edit your provider source code +make +sudo make install +sudo cimserver daemon=false forceProviderProcesses=false +# Test via YAWN or a script +~~~~~~ + +### Using the Debugger ### + +It's easy to set a breakpoint in your provider and have `gdb` break +there for you. Of course you'll want to have compiled with -g to turn +debugging symbols on and you'll probably also want to disable +optimization with -O0 so that single stepping in the debugger +follows your source code instead of jumping around. + +Create a .gdbinit file in your local directory. Let's say you want to +break on LMI_foobar + +~~~~~~ +set breakpoint pending on +b LMI_foobar +r daemon=false forceProviderProcesses=false +~~~~~~ + +For security reasons current versions of `gdb` require you to enable +reading the the local .gdbinit file, one solution is to add this to +your `~/.gdbinit` file: + +~~~~~~ +add-auto-load-safe-path . +set auto-load local-gdbinit on +~~~~~~ + +Then run OpenPegasus under `gdb` + +~~~~~~ +sudo gdb /usr/sbin/cimserver +~~~~~~ + +Exercise your provide in your preferred fashion and you should +break. Then debug in `gdb` as you would normally do. + +Of course like most things in life there are multiple ways of doing +things, the above is just one suggestion, you could use gdb to attach +to the running cimserver process or any number of other mechanisms, use +your programming skills and knowledge to find a methodology that works +best for you. + +### Controlling OpenPegasus Behavior ### + +[Advanced startup properties for CIMOM](http://publib.boulder.ibm.com/infocenter/iseries/v6r1m0/topic/rzatl/rzatladvstartup.htm) +provides useful information about available options to control +OpenPegasus behavior. + +### OpenPegasus Logging and Tracing ### + +OpenPegasus has a tracing facility. You can utilize the CMPI logging +commands to record debug and/or informational messages to the +OpenPegasus trace file instead of printf statements. This is a cleaner +solution once your provider more stable and you can dispense with +temporary printf statements. + +The CMPI logging and trace functions are `CMLogMessage` and +`CMTraceMessage` and are defined in `/usr/include/cmpi/cmpimacs.h`, +refer to that file for their usage. + +But more importantly when you're baffled about what OpenPegasus is +doing it can be invaluable to have full logging and trace information +at your disposal to peruse. The trace file is: + +~~~~~~ +/var/lib/Pegasus/cache/trace/cimserver.trc +~~~~~~ + +To ratchet up the verbosity of the trace information you may want to +run OpenPegasus like this: + +~~~~~~ +sudo cimserver daemon=false forceProviderProcesses=false \ + logLevel=TRACE traceLevel=5 traceFacility=File \ + traceComponents=All +~~~~~~ + +Detailed information about OpenPegasus tracing can be found in +[OpenPegasus Tracing User Guide](http://cvs.opengroup.org/cgi-bin/viewcvs.cgi/*checkout*/pegasus/doc/TracingUserGuide.pdf?rev=1.3) + + +### Dumping Method Parameters To the Console ### + +Sometimes it's nice to be able to see the CIM method parameters being +passed to your CIM method by dumping them to the console (of course +you could also run under `gdb` too and break on the +method). KonkretCMPI generates a Args_Print function in the generated +.h file you can call. + +To print method YYY args in konkret: + +In XXX_DispatchMethod() in XXX.h + +Add + +XXX_YYY_Args_Print(&args, stdout); + +*after* XXX_YYY_Args_InitFromArgs (otherwise args won't be initialized.) + +## Provider Development Tips ## + +## MOF Development Issues ## + +### Structures and Array of Structures as CIM Method Parameters ### + +There is no way to pass a complex object in a CIM method call (i.e. a +structure or class). CIM arrays are limited to simple scalar base +types (int, string, etc.). Thus there is no way to pass things like +(key,value) pairs directly. Instead one needs to define an array for +the key names, and an array for the values (of a specific base +type). To find the value of a key look up it's value at the same index +in the as it appears in the key array. The same holds true for any +array of structures, you have to decompose the structure members into +individual arrays and recombine them back together by indexing into +each array using the same index. Don't forget you'll need to declare +the array with the `ArrayType ( "Indexed" )` qualifier in the MOF +file. This is very reminiscent of programming in FORTRAN, ugh! + +## KonkretCMPI Oddities ## + +KonkretCMPI doc uses this example invocation (note KonkretCMPI +invocation is normally done via CMake macros) + +~~~~~~ +konkret -s KC_Widget -m Widget.mof KC_Widget=Widget +~~~~~~ + +But there is no man page describing what the args do in detail and the +-h option is very terse and omits describing the final arg and for a +long it was not clear to me what that arg was doing. The CMake macro +konkretcmpi_generate does not use the same arg list as what is +documented above which is also confusing. Apparently the form used in +the Konkret doc of `KC_Widget=Widget` is an alias mechanism which +modifies the class name as found in the MOF file (lhs) to an alternate +name (rhs) used in the generated C code. The type names, function +names, generated file names etc. will all use the rhs alias, otherwise +they will use the class name as found in the MOF. + +## OpenPegasus Authentication ## + +The [OpenPegasus Administrator's Guide](http://cvs.opengroup.org/cgi-bin/viewcvs.cgi/*checkout*/pegasus/doc/Admin_Guide_Release.pdf) +gives a brief overview of how OpenPegasus handles authentication. But +the following document which is installed along with the tog-pegasus +package on Red Hat systems gives a more comprehensive overview. + +~~~~~~ +/usr/share/doc/tog-pegasus-*/README.RedHat.Security +~~~~~~ + +The short story is root user authentication works for local +connections but is denied for network connections. If you've installed +YAWN then the user authentication prompt issued by YAWN appears to +OpenPegasus as a local user and root will work. However this is quite +insecure and should be avoided. Root authentication is possibly +justified in constrained cases such as during development where the +target machine is on an isolated local network (i.e. virtual +machines used for test and development). + +The preferred mechanism is to use the `pegasus` user account which is +created when tog-pegasus is installed. However there is no password +established for the `pegasus` user during install (this is a security +precaution) and you will need to set the `pegasus` user password +(requires root privileges) + +~~~~~~ +sudo passwd pegasus XXX +~~~~~~ + +where XXX is the `pegasus` password. After this is done you can +authenticate to OpenPegasus with the username `pegasus` and the +password you created. + +# Advanced CIM Topics # + +## Resource Configuration ## + +How one handles configuration of CIM elements is a surprisingly +complex topic and woefully under documented. If you're developing your +own profile you'll need to understand these topics. The best way to +learn about configuration approaches is to study the existing CIM +profiles and see how they are handled in the example profiles. + +One naive approach would be to provide a CIM method to set +configuration parameters on your CIM object. This is a traditional +approach in many programming API's. However the CIM Schema and +existing models often take a different approach utilizing the +following CIM classes and associations. This is probably worth entire +tutorial on it's own. Here is a brief introduction: + +The following classes are used as base classes to contain +configuration parameters. + +* `CIM_SettingData` +* `CIM_Capabilities` + +The following *association* classes are used to form links between +the `SettingData` and `Capability` derived classes. + +* `CIM_ElementSettingData` +* `CIM_ElementCapabilities` +* `CIM_SettingsDefineCapabilities` +* `CIM_SettingsDefineState` + +The way these can be combined is many fold. Naively you may assume you +would have only one `SettingData` instance where the entirety of the +configuration parameters are stored. But in fact you may have many +such instances joined in a web by associations, some indicating +current values, defaults values to be applied next, minimum values, +maximum values, etc. + +#### CIM_SettingData #### + +ChangeableType + : Has the following possible values + + * Not Changeable - Persistent + * Changeable - Transient + * Changeable - Persistent + * Not Changeable - Transient + +CIM_SettingData is linked via CIM_SettingsDefineState and +CIM_SettingsDefineCapabilities associations. + + +#### CIM_ElementSettingData #### + +CIM_ElementSettingData has the following properties: + +IsDefault + : Has the following possible values + + * Unknown + * Is Default + * Is Not Default + +IsCurrent + : Has the following possible values + + * Unknown + * Is Current + * Is Not Current + +IsNext + : Has the following possible values + + * Unknown + * Is Next + * Is Not Next + * Is Next For Single Use + +#### CIM_ElementCapabilities #### + +Characteristics[] + : Has the following possible simultaneous values + + * Default + * Current + +#### CIM_SettingsDefineCapabilities #### + +PropertyPolicy + : Has the following possible values + + * Independent + * Correlated + +ValueRole + : Has the following possible values + + * Default + * Optimal + * Mean + * Supported + +ValueRange + : Has the following possible values + + * Point + * Minimums + * Maximums + * Increments + +### Tying the Configuration Classes Together ### + +If you're looking at a CIM_ElementSettingData association the `IsDefault` +property will tell you if the group of configuration parameters +pointed by the SettingData reference are the default values. Likewise +the `IsCurrent` property tells you if the configuration parameters +pointed by the SettingData reference are current values or not. The +`IsNext` property tells you if the the configuration parameters +pointed by the SettingData reference will be applied the next time +configuration is applied and whether those parameters will permanently +persist. + +The CIM_ElementCapabilities association tells you if the +CIM_Capabilities pointed to by the association for a CIM_ManagedElement +(i.e. an object) are the defaults or the current values. + +The CIM_SettingsDefineCapabilities association tells you how to +interpret the SettingData being pointed to. There may be many +SettingData objects needed to fully specify the configuration. The +`PropertyPolicy` property tells you if you have to correlate the +SettingData values or if you can treat them independently. The +`ValueRole` property tells you what role the pointed to SettingData +plays, i.e. defaults, optimal, average, etc. The `ValueRange` property +tells you if the pointed to SettingData are a single set of values, +just the minimum values, just the maximum values, or represent the +increments each property value can be stepped by. + +In practice what the `ValueRole` property does is force you to have +many SettingData objects to specify the configuration for an +element. Let's say your CIM element has some properties that can only +be specified within a minimum and maximum range. You would then create +a SettingData containing the valid minimums and point to it via a +CIM_SettingsDefineCapabilities association. Likewise you would create +a SettingData containing the valid maximums and point to it via a +CIM_SettingsDefineCapabilities association. To ascertain the valid +range you have to query for CIM_SettingsDefineCapabilities where the +`ValueRange` property is Minimums, query for the Maximums and then +follow the association pointers to each respective Capabilities to form +the min/max range. By the same token a CIM_SettingsDefineCapabilities +whose `ValueRange` property is Point indicates a single set of values +rather than a range. Ultimately you have to find all the +CIM_SettingsDefineCapabilities objects bound to the element you want +to configure and interpret them. + +Are you confused yet? It's very convoluted and the possible +combinations are large. Don't you wish you could just call a method +and set the configuration parameters or query them? The best way to +wrap your head around all this is to study the various profiles +utilizing these classes, especially study the use case examples in +Chapter 9 of the profile, that will help solidify your understanding. + +# FAQ # + +**Q:** How do I make a CIM method a class method as opposed to a instance +method? + +**A:** An instance method is bound to the instance it is called from, in +object oriented languages the instance is often called "self" or +"this". This is the default method binding in CIM. However you can +specify class methods as well which are not bound to an +instance, to do this add the `Static` qualifier to the list of +qualifiers belonging to the method. + +# Vocabulary # + +CIM + + : [Common Information Model](http://www.dmtf.org/standards/cim) is + schema and associated specification which details how to represent + the elements of a computer system in order to manage those + elements. This yeilds a common and portable mechanism by which IT + administrators can manage their computing resources. CIM is + defined by the DMTF. + +CIM Schema + + : The [CIM Schema](http://www.dmtf.org/standards/CIM) is a + collection of predefined CIM classes which forms the building + blocks for modeling in CIM. The CIM Schema is expressed in MOF + (Managed Object Format) syntax. + +CIMOM + + : Common Information Model Object Manager. Sometimes referred to as a + broker the CIMOM is a network connected service running on a + managed computer which grants access to the CIM providers on + the managed computer. A CIM client connects to the CIMOM in + order to manage a specific resource on the managed computer. Those + resource instances are made available to the CIMOM by the + providers loaded by the CIMOM. + +CMPI + + : [Common Manageability Programming + Interface](http://www.opengroup.org/standards/enterprise-management). CMPI + is an open standard defined by the Open Group which defines the + programming API between a CIMOM and a CIM provider. In the + absence of CMPI each CIM provider would need to be coded to + the API of the CIMOM it was loaded into. CMPI allows a CIM + provider to be written once and utilized by different CIMOM + implementations. + +DMTF + + : [Distributed Management Task Force](http://www.dmtf.org/) is an + industry consortium defining open standards for computer system + management. + +KonkretCMPI + + : A tool used to aid development of CIM + providers. [KonkretCMPI](http://konkretcmpi.org/) reads a MOF + specification file and generates a set of C header files, C + program files and provider registration files. The primary purpose + of KonkretCMPI is to insulate a provider author from the CMPI API + by providing all the necessary "glue code" needed to adhere to the + CMPI specification. This allows the programmer to focus on the + particulars of the provider. + +MOF + + : [Managed Object Format](http://www.dmtf.org/standards/CIM) is the + syntax used to describe the CIM Schema. MOF files are used to + define provider interfaces. + +Provider + + : A software module which is loaded by the CIMOM broker running + locally on a managed system which provides information about a + type (i.e. class) of resource, for example network + interfaces. There may be multiple instances of that resource + class. The Provider is responsible for managing all instances + of that resource class. The Provider in addition to providing + information about a resource instance may optionally allow the + resource instance to be configured or acted upon. + +WBEM + + : [Web-Based Enterprise + Management](http://www.dmtf.org/standards/wbem). A collection of + standardized technologies providing unified management of + distributed computing environments based on CIM concepts. -- cgit From 59e8bf604c382b9269b4448f10223de631b40036 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Thu, 11 Apr 2013 13:39:43 +0200 Subject: fixed error handling upon method params check --- src/software/openlmi/software/core/InstallationService.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/openlmi/software/core/InstallationService.py b/src/software/openlmi/software/core/InstallationService.py index acc7ca6..77bfbad 100644 --- a/src/software/openlmi/software/core/InstallationService.py +++ b/src/software/openlmi/software/core/InstallationService.py @@ -605,7 +605,7 @@ def _check_target_and_collection(env, src_type, target, collection): except pywbem.CIMError as exc: raise InstallationError(values.Unspecified_Error, "Target must be either NULL or match managed" - " computer system: %s", str(exc)) + " computer system: %s" % str(exc)) if collection: try: SystemCollection.check_path(env, collection, "Collection") -- cgit From 272c051e8453d0a98771d8741e08478f18bbf638 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Thu, 18 Apr 2013 22:04:03 +0200 Subject: modified indication filters And moved software static filters to shared JobManager. Indication filters now reflect the name of indication class, which is instrumented by particular provider. This ensures, that cimom knows, which provider to call and does not bother the others. So instead of selecting indication instances from general class CIM_InstModification, it will be LMI_SoftwareInstModification. This is a common pattern for indication queries. Avoided another shortcoming of sfcbmof parser: /* comment */ something useful is also ignored :-( Static filters of JobManager made more generic for any provider to use. Software jobmanager is now using them. This reduces redundation of code. --- mof/70_LMI_SoftwareIndicationFilters.mof | 24 +++-- src/python/openlmi/common/IndicationManager.py | 3 +- src/python/openlmi/common/JobManager.py | 101 +++++++++++++--------- src/software/openlmi/software/cimom_entry.py | 7 +- src/software/openlmi/software/yumdb/jobmanager.py | 81 ++--------------- 5 files changed, 87 insertions(+), 129 deletions(-) diff --git a/mof/70_LMI_SoftwareIndicationFilters.mof b/mof/70_LMI_SoftwareIndicationFilters.mof index e3af599..b79917d 100644 --- a/mof/70_LMI_SoftwareIndicationFilters.mof +++ b/mof/70_LMI_SoftwareIndicationFilters.mof @@ -28,7 +28,7 @@ instance of CIM_IndicationFilter { SystemCreationClassName = "CIM_ComputerSystem"; SystemName = "kvm-fedora18"; Name = "LMI:LMI_SoftwareInstallationJob:PercentUpdated"; - Query = "SELECT * FROM CIM_InstModification WHERE " + Query = "SELECT * FROM LMI_SoftwareInstModification WHERE " "SourceInstance ISA LMI_SoftwareInstallationJob AND " "SourceInstance.CIM_ConcreteJob::PercentComplete <> " "PreviousInstance.CIM_ConcreteJob::PercentComplete"; @@ -47,12 +47,11 @@ instance of CIM_IndicationFilter { SystemName = "kvm-fedora18"; Name = "LMI:LMI_SoftwareInstallationJob:Succeeded"; QueryLanguage = "CIM:CQL"; - Query = "SELECT * FROM CIM_InstModification WHERE " + Query = "SELECT * FROM LMI_SoftwareInstModification WHERE " "SourceInstance ISA LMI_SoftwareInstallationJob AND " - "SourceInstance.CIM_ConcreteJob::JobState" - " = 17" - /* This is not supported by sfcb: - * " = CIM_ConcreteJob.JobState#'Completed'" */; + "SourceInstance.CIM_ConcreteJob::JobState = 17"; + /* This is not supported by sfcb: + * " = CIM_ConcreteJob.JobState#'Completed'" */ SourceNamespace = "root/cimv2"; SourceNamespaces = {"root/cimv2"}; }; @@ -62,12 +61,11 @@ instance of CIM_IndicationFilter { SystemCreationClassName = "CIM_ComputerSystem"; SystemName = "kvm-fedora18"; Name = "LMI:LMI_SoftwareInstallationJob:Failed"; - Query = "SELECT * FROM CIM_InstModification WHERE " + Query = "SELECT * FROM LMI_SoftwareInstModification WHERE " "SourceInstance ISA LMI_SoftwareInstallationJob AND " - "SourceInstance.CIM_ConcreteJob::JobState" - " = 10" - /* This is not supported by sfcb: - * "CIM_ConcreteJob.JobState#'Exception'" */; + "SourceInstance.CIM_ConcreteJob::JobState = 10"; + /* This is not supported by sfcb: + * "CIM_ConcreteJob.JobState#'Exception'" */ QueryLanguage = "CIM:CQL"; Description = "Modification of Operational Status for a " "Concrete Job to 'Complete' and 'OK'."; @@ -80,7 +78,7 @@ instance of CIM_IndicationFilter { SystemCreationClassName = "CIM_ComputerSystem"; SystemName = "kvm-fedora18"; Name = "LMI:LMI_SoftwareInstallationJob:Changed"; - Query = "SELECT * FROM CIM_InstModification WHERE " + Query = "SELECT * FROM LMI_SoftwareInstModification WHERE " "SourceInstance ISA LMI_SoftwareInstallationJob AND " "SourceInstance.CIM_ConcreteJob::JobState <> " "PreviousInstance.CIM_ConcreteJob::JobState"; @@ -95,7 +93,7 @@ instance of CIM_IndicationFilter { SystemCreationClassName = "CIM_ComputerSystem"; SystemName = "kvm-fedora18"; Name = "LMI:LMI_SoftwareInstallationJob:Created"; - Query = "SELECT * FROM CIM_InstCreation WHERE" + Query = "SELECT * FROM LMI_SoftwareInstCreation WHERE" " SourceInstance ISA LMI_SoftwareInstallationJob"; QueryLanguage = "CIM:CQL"; Description = "Creation of a ConcreteJob."; diff --git a/src/python/openlmi/common/IndicationManager.py b/src/python/openlmi/common/IndicationManager.py index ba0919d..7834378 100644 --- a/src/python/openlmi/common/IndicationManager.py +++ b/src/python/openlmi/common/IndicationManager.py @@ -35,8 +35,9 @@ RE_FILTER_NAME = re.compile(r'^(?Plmi:' r'(?P[a-z0-9_]+):)(?P.*)$', re.IGNORECASE) FILTER_DEFAULTS = { + "SourceNamespace" : "root/cimv2", "SourceNamespaces" : ["root/cimv2"], - "QueryLanguage" : "CIM:CQL" + "QueryLanguage" : "CIM:CQL" } @cmpi_logging.trace_function diff --git a/src/python/openlmi/common/JobManager.py b/src/python/openlmi/common/JobManager.py index f2d0c71..e78967c 100644 --- a/src/python/openlmi/common/JobManager.py +++ b/src/python/openlmi/common/JobManager.py @@ -47,10 +47,33 @@ import threading from Queue import Queue import pywbem import openlmi.common.cmpi_logging as cmpi_logging +from openlmi.common.IndicationManager import IndicationManager from pywbem.cim_provider2 import CIMProvider2 import socket import traceback +@cmpi_logging.trace_function +def register_filters(job_clsname, indication_manager=None): + """ + This function registers static indication filters at IndicationManager. + It should be called upon provider's initialization. + + :param job_clsname: (``String``) CIM class name for asynchonous jobs. + Will be part of filter queries. + :param indication_manager: If not given, global instance will be obtained. + """ + if indication_manager is None: + ind_manager = IndicationManager.get_instance() + filters = {} + query_args = { + "classname" : job_clsname, + "prefix" : indication_manager.nameprefix + } + for fltr_id, fltr_props in JobManager.IND_FILTERS.items(): + filters[fltr_id] = fltr_props.copy() + filters[fltr_id]['Query'] = fltr_props['Query'] % query_args + indication_manager.add_filters(job_clsname, filters) + # Too many instance attributes # pylint: disable-msg=R0902 class Job(object): @@ -660,6 +683,43 @@ class JobManager(object): IND_JOB_CHANGED = "Changed" IND_JOB_CREATED = "Created" + IND_FILTERS = { + IND_JOB_PERCENT_UPDATED: { + "Query" : "SELECT * FROM LMI_%(prefix)sInstModification WHERE " + "SourceInstance ISA %(classname)s AND " + "SourceInstance.CIM_ConcreteJob::PercentComplete <> " + "PreviousInstance.CIM_ConcreteJob::PercentComplete", + "Description" : "Modification of Percentage Complete for a " + "Concrete Job.", + }, + IND_JOB_SUCCEEDED: { + "Query" : "SELECT * FROM LMI_%(prefix)sInstModification WHERE " + "SourceInstance ISA %(classname)s AND " + "SourceInstance.CIM_ConcreteJob::JobState = 17", + "Description": "Modification of Job State for a " + "Concrete Job to 'Complete'.", + }, + IND_JOB_FAILED: { + "Query" : "SELECT * FROM LMI_%(prefix)sInstModification WHERE " + "SourceInstance ISA %(classname)s AND " + "SourceInstance.CIM_ConcreteJob::JobState = 10", + "Description": "Modification of Job State for a " + "Concrete Job to 'Exception'.", + }, + IND_JOB_CHANGED: { + "Query" : "SELECT * FROM LMI_%(prefix)sInstModification WHERE " + "SourceInstance ISA %(classname)s AND " + "SourceInstance.CIM_ConcreteJob::JobState <> " + "PreviousInstance.CIM_ConcreteJob::JobState", + "Description": "Modification of Job State for a ConcreteJob.", + }, + IND_JOB_CREATED: { + "Query" : "SELECT * FROM LMI_%(prefix)sInstCreation WHERE " + "SourceInstance ISA %(classname)s", + "Description": "Creation of a ConcreteJob.", + }, + } + @cmpi_logging.trace_method def __init__(self, name, namespace, indication_manager, timer_manager): """ @@ -710,46 +770,7 @@ class JobManager(object): """ Add all job-related ``IndicationFilters`` to indication manager. """ - filters = { - self.IND_JOB_PERCENT_UPDATED: { - "Query" : "SELECT * FROM CIM_InstModification WHERE " - "SourceInstance ISA %(classname)s AND " - "SourceInstance.CIM_ConcreteJob::PercentComplete <> " - "PreviousInstance.CIM_ConcreteJob::PercentComplete", - "Description" : "Modification of Percentage Complete for a " - "Concrete Job.", - }, - self.IND_JOB_SUCCEEDED: { - "Query" : "SELECT * FROM CIM_InstModification WHERE " - "SourceInstance ISA %(classname)s AND " - "SourceInstance.CIM_ConcreteJob::JobState = 17", - "Description": "Modification of Job State for a " - "Concrete Job to 'Complete'.", - }, - self.IND_JOB_FAILED: { - "Query" : "SELECT * FROM CIM_InstModification WHERE " - "SourceInstance ISA %(classname)s AND " - "SourceInstance.CIM_ConcreteJob::JobState = 10", - "Description": "Modification of Job State for a " - "Concrete Job to 'Exception'.", - }, - self.IND_JOB_CHANGED: { - "Query" : "SELECT * FROM CIM_InstModification WHERE " - "SourceInstance ISA %(classname)s AND " - "SourceInstance.CIM_ConcreteJob::JobState <> " - "PreviousInstance.CIM_ConcreteJob::JobState", - "Description": "Modification of Job State for a ConcreteJob.", - }, - self.IND_JOB_CREATED: { - "Query" : "SELECT * FROM CIM_InstCreation WHERE " - "SourceInstance ISA %(classname)s", - "Description": "Creation of a ConcreteJob.", - }, - } - # add class name - for f in filters.itervalues(): - f['Query'] = f['Query'] % {"classname" : self.job_classname } - self.indication_manager.add_filters(self.job_classname, filters) + register_filters(self.job_classname, self.indication_manager) @cmpi_logging.trace_method def get_providers(self): diff --git a/src/software/openlmi/software/cimom_entry.py b/src/software/openlmi/software/cimom_entry.py index ad944c8..d507a01 100644 --- a/src/software/openlmi/software/cimom_entry.py +++ b/src/software/openlmi/software/cimom_entry.py @@ -26,6 +26,7 @@ Entry module for OpenLMI Software providers. from multiprocessing import Queue from openlmi.common import cmpi_logging +from openlmi.common import JobManager from openlmi.common.IndicationManager import IndicationManager from openlmi.software.core import InstallationJob from openlmi.software.LMI_SoftwareIdentity import LMI_SoftwareIdentity @@ -113,7 +114,7 @@ def get_providers(env): # daemon. That means it does not have to be cleaned up. im = IndicationManager.get_instance( env, "Software", "root/cimv2", queue=Queue()) - jobmanager.register_filters(im) + JobManager.register_filters("LMI_SoftwareInstallationJob", im) return providers @@ -123,11 +124,11 @@ def authorize_filter(env, fltr, ns, classes, owner): def activate_filter (env, fltr, ns, classes, first_activation): IndicationManager.get_instance().activate_filter( - env, fltr, ns, classes, first_activation) + env, fltr, class_name, class_path, first_activation) def deactivate_filter(env, fltr, ns, classes, last_activation): IndicationManager.get_instance().deactivate_filter( - env, fltr, ns, classes, last_activation) + env, fltr, class_name, class_path, last_activation) def enable_indications(env): IndicationManager.get_instance().enable_indications(env) diff --git a/src/software/openlmi/software/yumdb/jobmanager.py b/src/software/openlmi/software/yumdb/jobmanager.py index 7283d05..977b38f 100644 --- a/src/software/openlmi/software/yumdb/jobmanager.py +++ b/src/software/openlmi/software/yumdb/jobmanager.py @@ -39,8 +39,8 @@ import threading import time import traceback -from openlmi.common import cmpi_logging from openlmi.common.IndicationManager import IndicationManager +from openlmi.common.JobManager import JobManager as JM from openlmi.software.yumdb import errors, jobs from openlmi.software.yumdb.util import trace_function @@ -56,69 +56,6 @@ MINIMUM_TIME_BEFORE_REMOVAL = 10 # replacement for cmpi_logging.logger LOG = None -IND_JOB_PERCENT_UPDATED = "PercentUpdated" -IND_JOB_SUCCEEDED = "Succeeded" -IND_JOB_FAILED = "Failed" -IND_JOB_CHANGED = "Changed" -IND_JOB_CREATED = "Created" - -IND_FILTERS = { - IND_JOB_PERCENT_UPDATED: { - "Query" : "SELECT * FROM CIM_InstModification WHERE " - "SourceInstance ISA %(classname)s AND " - "SourceInstance.CIM_ConcreteJob::PercentComplete <> " - "PreviousInstance.CIM_ConcreteJob::PercentComplete", - "Description" : "Modification of Percentage Complete for a " - "Concrete Job.", - }, - IND_JOB_SUCCEEDED: { - "Query" : "SELECT * FROM CIM_InstModification WHERE " - "SourceInstance ISA %(classname)s AND " - "SourceInstance ISA LMI_SoftwareInstallationJob AND " - "SourceInstance.CIM_ConcreteJob::JobState = 17", - # symbolic constants not supported by sfcb - #"CIM_ConcreteJob.JobState#'Completed'" - "Description": "Modification of Operational Status for a " - "Concrete Job to 'Complete' and 'OK'.", - }, - IND_JOB_FAILED: { - "Query" : "SELECT * FROM CIM_InstModification WHERE " - "SourceInstance ISA %(classname)s AND " - "SourceInstance.CIM_ConcreteJob::JobState = 10", - # symbolic constants not supported by sfcb - #"CIM_ConcreteJob.JobState#'Exception'", - "Description": "Modification of Operational Status for a " - "Concrete Job to 'Complete' and 'Error'.", - }, - IND_JOB_CHANGED: { - "Query" : "SELECT * FROM CIM_InstModification WHERE " - "SourceInstance ISA %(classname)s AND " - "SourceInstance.CIM_ConcreteJob::JobState <> " - "PreviousInstance.CIM_ConcreteJob::JobState", - "Description": "Modification of Job State for a ConcreteJob.", - }, - IND_JOB_CREATED: { - "Query" : "SELECT * FROM CIM_InstCreation WHERE " - "SourceInstance ISA %(classname)s", - "Description": "Creation of a ConcreteJob.", - }, -} - -@cmpi_logging.trace_function -def register_filters(indication_manager): - """ - This function registers static indication filters at IndicationManager. - It should be called upon provider's initialization. - """ - to_register = {} - for fltr_id, fltr in IND_FILTERS.items(): - if not indication_manager.is_registered(JOB_CLASSNAME, fltr_id): - fltr["Query"] = fltr["Query"] % {"classname" : JOB_CLASSNAME } - to_register[fltr_id] = fltr - if to_register: - indication_manager.add_filters(JOB_CLASSNAME, - to_register, ensure_installed=False) - # ***************************************************************************** # Decorators # ***************************************************************************** @@ -183,7 +120,7 @@ class JobIndicationSender(object): """ def __init__(self, indication_manager, job, - indications=IND_JOB_CHANGED, new=None): + indications=JM.IND_JOB_CHANGED, new=None): """ :param job (``YumJob``) Is job instance, which will be immediately snapshoted as old instance and later as a new one. @@ -258,11 +195,11 @@ class JobIndicationSender(object): "can not send any indication without id") if make_snapshot: self.snapshot() - if ( IND_JOB_CHANGED in self._indications + if ( JM.IND_JOB_CHANGED in self._indications and self._new_instance is None): raise errors.IndicationError("no snapshot made for modified job") for fltr_id in self._indications: - if fltr_id == IND_JOB_CREATED: + if fltr_id == JM.IND_JOB_CREATED: LOG.debug("sending instance creation indication for job %s", self._job) self._indication_manager.send_instcreation( @@ -368,7 +305,7 @@ class JobManager(threading.Thread): LOG.debug('job %s enqued for YumWorker to handle', job) heapq.heappush(self._job_queue, job) if getattr(job, 'async', False) is True: - ind = self._prepare_indication_for(job, IND_JOB_CREATED) + ind = self._prepare_indication_for(job, JM.IND_JOB_CREATED) self._async_jobs[job.jobid] = job ind.send() self._job_enqueued.notify() @@ -557,7 +494,7 @@ class JobManager(threading.Thread): 'can not finish not started job "%s"' % job) if getattr(job, 'async', False): ind = self._prepare_indication_for(job, - (IND_JOB_CHANGED, IND_JOB_PERCENT_UPDATED)) + (JM.IND_JOB_CHANGED, JM.IND_JOB_PERCENT_UPDATED)) job.finish(result, result_data) if getattr(job, 'async', False): if job.delete_on_completion: @@ -566,9 +503,9 @@ class JobManager(threading.Thread): self._schedule_event(schedule_at, job.jobid, self.ACTION_REMOVE) if result == job.RESULT_SUCCESS: - ind.add_indication_ids(IND_JOB_SUCCEEDED) + ind.add_indication_ids(JM.IND_JOB_SUCCEEDED) elif result == job.RESULT_ERROR: - ind.add_indication_ids(IND_JOB_FAILED) + ind.add_indication_ids(JM.IND_JOB_FAILED) ind.send(True) else: LOG.debug("sending reply for %s: (%s, %s)", job, @@ -598,7 +535,7 @@ class JobManager(threading.Thread): if job is not None: if getattr(job, "async", False): ind = self._prepare_indication_for(job, - (IND_JOB_CHANGED, IND_JOB_PERCENT_UPDATED)) + (JM.IND_JOB_CHANGED, JM.IND_JOB_PERCENT_UPDATED)) job.start() ind.send(True) else: -- cgit From d95bfa9e736e9471fa337c8a992516b98d4eb0f4 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Tue, 23 Apr 2013 11:33:12 +0200 Subject: updated doc comments for cimom callbacks cimom callbacks for filter enablement have misleading param names and miss proper doc --- src/python/openlmi/common/IndicationManager.py | 57 ++++++++++++++++++++-- src/software/openlmi/software/cimom_entry.py | 66 ++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 9 deletions(-) diff --git a/src/python/openlmi/common/IndicationManager.py b/src/python/openlmi/common/IndicationManager.py index 7834378..a17703f 100644 --- a/src/python/openlmi/common/IndicationManager.py +++ b/src/python/openlmi/common/IndicationManager.py @@ -512,10 +512,19 @@ class IndicationManager(singletonmixin.Singleton): self.ensure_filters_installed(class_name=class_name) @cmpi_logging.trace_method - def authorize_filter(self, _env, fltr, _ns, _classes, _owner): + def authorize_filter(self, _env, fltr, _class_name, _op, _owner): """ AuthorizeFilter callback from CIMOM. Call this method from appropriate - CIMOM callback. + CIMOM callback + + It asks us to verify whether this filter is allowed. + + :param fltr: Contains the filter that must be authorized. + :param _class_name: (``String``) Contains the class name extracted + from the filter FROM clause. + :param _op: The name of the class for which monitoring is required. + Only the namespace part is set if className is a process indication. + :param _owner The owner argument is the destination owner. """ with self._access_lock: res = self._get_matching_filter(fltr) @@ -527,10 +536,24 @@ class IndicationManager(singletonmixin.Singleton): return False @cmpi_logging.trace_method - def activate_filter(self, _env, fltr, _ns, _classes, first_activation): + def activate_filter(self, _env, fltr, _class_name, _class_path, + first_activation): """ ActivateFilter callback from CIMOM. Call this method from appropriate CIMOM callback. + + It ask us to begin monitoring a resource. The function shall begin + monitoring the resource according to the filter express only. + + :param fltr: The filter argument contains the filter specification + for this subscription to become active. + :param _class_name: (``String``) The class name extracted from the filter + FROM clause. + :param _class_path: (``CIMInstanceName``) The name of the class for + which monitoring is required. Only the namespace part is set if + eventType is a process indication. + :param first_activation: (``bool``) Set to true if this is the first + filter for className. """ with self._access_lock: if not first_activation: @@ -542,10 +565,23 @@ class IndicationManager(singletonmixin.Singleton): make_filter_name(res[0], res[1]), fltr) @cmpi_logging.trace_method - def deactivate_filter(self, _env, fltr, _ns, _classes, last_activation): + def deactivate_filter(self, _env, fltr, _class_name, _class_path, + last_activation): """ DeactivateFilter callback from CIMOM. Call this method from appropriate CIMOM callback. + + Informs us that monitoring using this filter should stop. + + :param fltr: The filter argument contains the filter specification for + this subscription to become active. + :param class_name: (``String``) The class name extracted from the filter + FROM clause. + :param class_path: (``CIMInstanceName``) class_path The name of the + class for which monitoring is required. Only the namespace part is + set if className is a process indication. + :last_activation: (``bool``) Set to true if this is the last filter for + className. """ with self._access_lock: if not last_activation: @@ -561,6 +597,11 @@ class IndicationManager(singletonmixin.Singleton): """ EnableIndications callback from CIMOM. Call this method from appropriate CIMOM callback. + + Tells us that indications can now be generated. The MB is now prepared + to process indications. The function is normally called by the MB after + having done its intialization and processing of persistent subscription + requests. """ with self._access_lock: self._enabled = True @@ -571,6 +612,11 @@ class IndicationManager(singletonmixin.Singleton): """ EnableIndications callback from CIMOM. Call this method from appropriate CIMOM callback. + + Tells us that we should stop generating indications. MB will not accept + any indications until enabled again. The function is normally called + when the MB is shutting down indication services either temporarily or + permanently. """ with self._access_lock: self._enabled = False @@ -604,7 +650,8 @@ class IndicationManager(singletonmixin.Singleton): ind['SourceInstance'] = instance ind['SourceInstanceHost'] = socket.gethostname() ind['SourceInstanceModelPath'] = str(instance.path) - ind['IndicationFilterName'] = make_filter_name(instance.classname, filter_id) + ind['IndicationFilterName'] = make_filter_name( + instance.classname, filter_id) ind['PerceivedSeverity'] = self.SEVERITY_INFO cmpi_logging.logger.info("Sending indication %s for %s" % diff --git a/src/software/openlmi/software/cimom_entry.py b/src/software/openlmi/software/cimom_entry.py index d507a01..70df071 100644 --- a/src/software/openlmi/software/cimom_entry.py +++ b/src/software/openlmi/software/cimom_entry.py @@ -118,22 +118,80 @@ def get_providers(env): return providers -def authorize_filter(env, fltr, ns, classes, owner): +def authorize_filter(env, fltr, class_name, op, owner): + """ + CIMOM callback. + + It asks us to verify whether this filter is allowed. + + :param fltr: (``String``) Contains the filter that must be authorized. + :param class_name: (``String``) Contains the class name extracted + from the filter FROM clause. + :param op: The name of the class for which monitoring is required. + Only the namespace part is set if className is a process indication. + :param owner The owner argument is the destination owner. + """ IndicationManager.get_instance().authorize_filter( - env, fltr, ns, classes, owner) + env, fltr, class_name, op, owner) -def activate_filter (env, fltr, ns, classes, first_activation): +def activate_filter(env, fltr, class_name, class_path, first_activation): + """ + CIMOM callback. + + It ask us to begin monitoring a resource. The function shall begin + monitoring the resource according to the filter express only. + + :param fltr: (``String``) The filter argument contains the filter + specification for this subscription to become active. + :param class_name: (``String``) The class name extracted from the filter + FROM clause. + :param class_path: (``CIMInstanceName``) The name of the class for which + monitoring is required. Only the namespace part is set if eventType + is a process indication. + :param first_activation: (``bool``) Set to true if this is the first + filter for className. + """ IndicationManager.get_instance().activate_filter( env, fltr, class_name, class_path, first_activation) -def deactivate_filter(env, fltr, ns, classes, last_activation): +def deactivate_filter(env, fltr, class_name, class_path, last_activation): + """ + CIMOM callback. + + Informs us that monitoring using this filter should stop. + + :param fltr: (``String``) The filter argument contains the filter + specification for this subscription to become active. + :param class_name: (``String``) The class name extracted from the filter + FROM clause. + :param class_path: (``CIMInstanceName``) class_path The name of the class + for which monitoring is required. Only the namespace part is set + if className is a process indication. + :last_activation: (``bool``) Set to true if this is the last filter for + className. + """ IndicationManager.get_instance().deactivate_filter( env, fltr, class_name, class_path, last_activation) def enable_indications(env): + """ + CIMOM callback. + + Tells us that indications can now be generated. The MB is now prepared + to process indications. The function is normally called by the MB after + having done its intialization and processing of persistent subscription + requests. + """ IndicationManager.get_instance().enable_indications(env) def disable_indications(env): + """ + CIMOM callback. + + Tells us that we should stop generating indications. MB will not accept any + indications until enabled again. The function is normally called when the + MB is shutting down indication services either temporarily or permanently. + """ IndicationManager.get_instance().disable_indications(env) def can_unload(_env): -- cgit From cfcbeb29b9944e2676cc03a85e9c7c5a7e619fa5 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Tue, 23 Apr 2013 16:23:18 +0200 Subject: Revert "Merge branch 'software-indications' of ssh://git.fedorahosted.org/git/openlmi-providers into software-indications" This reverts commit 0f9da4ac91a0acb2c53d19ca1ec345c07a5043fb, reversing changes made to 217acf2c624b99db7335acb0aa50d92efcb368a8. --- src/software/openlmi/software/core/InstallationService.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/openlmi/software/core/InstallationService.py b/src/software/openlmi/software/core/InstallationService.py index 77bfbad..acc7ca6 100644 --- a/src/software/openlmi/software/core/InstallationService.py +++ b/src/software/openlmi/software/core/InstallationService.py @@ -605,7 +605,7 @@ def _check_target_and_collection(env, src_type, target, collection): except pywbem.CIMError as exc: raise InstallationError(values.Unspecified_Error, "Target must be either NULL or match managed" - " computer system: %s" % str(exc)) + " computer system: %s", str(exc)) if collection: try: SystemCollection.check_path(env, collection, "Collection") -- cgit From ac68269f2a2c6e072a038d758e45438a0c8a6c69 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Wed, 24 Apr 2013 05:54:53 +0200 Subject: fixed output parameter name of GetError method --- src/software/openlmi/software/LMI_SoftwareInstallationJob.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/openlmi/software/LMI_SoftwareInstallationJob.py b/src/software/openlmi/software/LMI_SoftwareInstallationJob.py index ba0867f..a5fc269 100644 --- a/src/software/openlmi/software/LMI_SoftwareInstallationJob.py +++ b/src/software/openlmi/software/LMI_SoftwareInstallationJob.py @@ -375,6 +375,6 @@ class LMI_SoftwareInstallationJob(CIMProvider2): error = InstallationJob.job2error(job) out_params = [] if error is not None: - param = pywbem.CIMParameter('Errors', type='instance', value=error) + param = pywbem.CIMParameter('Error', type='instance', value=error) out_params.append(param) return (self.values.GetErrors.Success, out_params) -- cgit From 3126884ffe36e05fc4785b4cfa980a34fcae1439 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Fri, 26 Apr 2013 08:34:33 +0200 Subject: added script for online modification of pegasus repo This allows to create and delete instances and classes specified in mof files on-line. Complements cimmof application, which can not do "delete". With this we are able to unregister static filters and classes from pegasus on package removal - if the pegasus is running :-(. --- openlmi-cimmof | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100755 openlmi-cimmof diff --git a/openlmi-cimmof b/openlmi-cimmof new file mode 100755 index 0000000..cbec8d4 --- /dev/null +++ b/openlmi-cimmof @@ -0,0 +1,340 @@ +#!/usr/bin/env python +# Copyright (C) 2012-2013 Red Hat, Inc. All rights reserved. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +""" +Allows to modify Pegasus repository with declarations in mof files. +Pegasus must be running for this script to work. It depends on cimmof +binary, which is online compilator of MOF files for pegasus. + +It works in this way: + 1. cimmof is called on input mof files + 2. its output (xml) is then parsed by pywbem functions producing + CIM objects (instances of ``pywbem.CIMClass`` and + ``pywbem.CIMInstance``) + 3. these objects are then used in calls to + ``{Create,Modify}{Instance,Class}`` + +*Note* that only Class and Instance declarations are supported. + - This is due to limitations in pywbem parser. + - Although this could be avoided by calling wbemexec on generated XML. +""" + +import argparse +import re +import logging +import subprocess +import sys +import pywbem +import xml.dom.minidom as dom + +DEFAULT_NAMESPACE = "root/cimv2" +DEFAULT_CIMMOF = "cimmof" + +RE_COMMENT = re.compile(r'\s*\s*', re.DOTALL) + +logging.basicConfig(level=logging.ERROR, + format="%(levelname)s - %(message)s") +LOG = logging.getLogger(__name__) + +def die(msg, *args, **kwargs): + """ + Exit with error printed to stderr. + """ + LOG.error(msg, *args, **kwargs) + sys.exit(1) + +def xml_cleanup(xml_str): + """ + Return xml string without comments and whitespaces. + """ + # remove comments + without_comments = "".join(RE_COMMENT.split(xml_str)) + # remove whitespaces + return "".join(l.strip() for l in without_comments.split("\n")) + +def get_objects_from_mofs(cimmof, namespace, *mofs): + """ + Call cimmof binary with mofs as input and obtain class/instance + declarations in XML. + + Return list of pywbem CIM abstractions for each declaration. + """ + cmd = [cimmof, '--xml', '-n', namespace] + objects = [] + for mof in mofs: + process = subprocess.Popen(cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=sys.stderr) + (out, _) = process.communicate(mof.read()) + parsed_dom = dom.parseString(xml_cleanup(out)) + # we cannot use pywbem.parse_cim because it does not support + # DECLARATION element, but we can parse individual values + for decl in parsed_dom.getElementsByTagName('VALUE.OBJECT'): + # pywbem first makes tupletree from dom: + # (name, attributes, children) + # and from it generates pywbem CIM abstractions + (_name, _attrs, obj) = pywbem.tupleparse.parse_value_object( + pywbem.dom_to_tupletree(decl)) + objects.append(obj) + return objects + +def get_instance_path(conn, namespace, instance, classes): + """ + Obtains a class declaration from cimom for given instance and builds + a path from it. + + :param conn is a wbem connection + :param namespace (``str``) is a target namespace of instance + :param instance (``CIMInstance``) contains class name and is used to get + values of key properties. Is modified by adding path to it. + :param classes (``dict``) is a cache of classes obtained from cimom. + Its items are in form: ``(classname, CIMClass)``. + + Return ``CIMInstanceName``. + """ + if not instance.classname in classes: + classes[instance.classname] = conn.GetClass(instance.classname, + IncludeQualifiers=True, + namespace=namespace) + cls = classes[instance.classname] + keys = [p.name for p in cls.properties.values() if "Key" in p.qualifiers] + path = pywbem.CIMInstanceName(instance.classname, namespace=namespace) + for key in keys: + if not key in instance: + die("instance of %s is missing key property \"%s\"", + instance.classname, key) + path[key] = instance[key] + instance.path = path + return path + +def create_class(conn, cls, namespace, classes, allow_update=False): + """ + Create class or modify it if already present. + + :param classes: (``dict``) a cache of classes obtained from cimom. + :param allow_update: (``bool``) whether to modify existing class. + """ + try: + if not cls.classname in classes: + classes[cls.classname] = conn.GetClass(cls.classname, + IncludeQualifiers=True, + namespace=namespace) + except pywbem.CIMError as err: + if err.args[0] != pywbem.CIM_ERR_NOT_FOUND: + raise + if cls.classname in classes: + if not allow_update: + LOG.error("class %s already exists", cls.classname) + else: + conn.ModifyClass(cls, namespace=namespace) + LOG.info("modified class %s", cls.classname) + else: + conn.CreateClass(cls, namespace=namespace) + LOG.info("created class %s", cls.classname) + +def create_instance(conn, inst, namespace, classes, allow_update=False): + """ + Create instance or modify it if already present. + + :param classes: (``dict``) a cache of classes obtained from cimom. + :param allow_update: (``bool``) whether to modify existing instance. + """ + path = get_instance_path(conn, namespace, inst, classes) + present = None + try: + present = conn.GetInstance(path) + except pywbem.CIMError as err: + if err.args[0] != pywbem.CIM_ERR_NOT_FOUND: + raise + if present is not None: + try: + if allow_update: + conn.ModifyInstance(inst) + LOG.info("modified instance for path %s", path) + else: + LOG.error("instance %s already exists", path) + except pywbem.CIMError as err: + if err.args[0] == pywbem.CIM_ERR_NOT_SUPPORTED: + LOG.error("ModifyInstance() is not supported for class %s," + " please remove the instance first", + inst.classname) + else: + raise + + else: + conn.CreateInstance(inst) + LOG.info("created instance for path %s", path) + +def reorder_objects(cmd, objects): + """ + Reorder classes and instances so that dependent objects are handled + later. + + Classes can depend between each other in two ways: + 1. one inherits from another + 2. one refers to another (associations) + + The first case can be solved by counting number of parents, that are + also to be removed. Class with highest number will be created as last. + + The second one applies only to associations, which can not refer + to each other. Let's just append them after non-associations. + + :param cmd: (``str``) can be "create" or "delete". In latter case the + result is reversed, so the dependent classes/instances are removed + as first. + + *Note* this does not handle dependencies between instances. + """ + cls_list = [] + assoc_list = [] + cls_dict = set( c.classname.lower() + for c in objects if isinstance(c, pywbem.CIMClass)) + # (class name, number of superclasses in cls_dict) + cls_deps = pywbem.NocaseDict() + inst_list = [] + for obj in objects: + if isinstance(obj, pywbem.CIMClass): + if 'association' in obj.qualifiers: + assoc_list.append(obj) + else: + cls_list.append(obj) + cls_deps[obj.classname] = 0 + parent = obj.superclass + while parent in cls_dict: + cls_deps[obj.classname] += 1 + parent = cls_dict[parent].classname + else: # no specific reordering of instances + inst_list.append(obj) + key_func = lambda c: (cls_deps[c.classname], c.classname) + cls_list = sorted(cls_list, key=key_func) + assoc_list = sorted(assoc_list, key=key_func) + + result = cls_list + assoc_list + inst_list + if cmd == "delete": + result.reverse() + return result + +def push_to_repo(namespace, cmd, objects, allow_update=False): + """ + Create or delete desired objects in Pegasus repository. + + :param cmd: (``string``) is one of { 'create' | 'delete' } + :param objects: (``list``) is a list of pywbem CIM abstractions + created from mofs. They will be operated upon. + """ + if not isinstance(namespace, basestring): + raise TypeError("namespace must be string") + if not cmd in ('create', 'delete'): + raise ValueError('cmd must be either "create" or "delete"') + classes = pywbem.NocaseDict() + conn = pywbem.PegasusUDSConnection() + objects = reorder_objects(cmd, objects) + for obj in objects: + try: + if cmd == "create": + if isinstance(obj, pywbem.CIMClass): + create_class(conn, obj, namespace, classes, allow_update) + elif isinstance(obj, pywbem.CIMInstance): + create_instance(conn, obj, namespace, classes, + allow_update) + else: + LOG.error("unsupported object for creation: %s", + obj.__class__.__name__) + + else: + try: + if isinstance(obj, pywbem.CIMClass): + conn.DeleteClass(obj.classname, namespace=namespace) + LOG.info("deleted class %s", obj.classname) + elif isinstance(obj, pywbem.CIMInstance): + path = get_instance_path(conn, namespace, obj, classes) + conn.DeleteInstance(path) + LOG.info("deleted instance %s", path) + else: + LOG.error("unsupported object for deletion: %s", + obj.__class__.__name__) + except pywbem.CIMError as err: + if err.args[0] == pywbem.CIM_ERR_NOT_FOUND: + LOG.warn("%s not present in repository", + obj if isinstance(obj, pywbem.CIMClass) + else path) + else: + raise + + except pywbem.CIMError as err: + if err.args[0] in (pywbem.CIM_ERR_INVALID_PARAMETER, ): + LOG.warn("failed to %s %s: %s", cmd, obj, err) + else: + raise + +def parse_cmd_line(): + """ + Parse command line and return options. + """ + parser = argparse.ArgumentParser( + usage="%(prog)s [options] {create,delete} mof [mof ...]", + description="Allows to create/delete instances and classes" + " declared in MOF files. It operates only on Pegasus broker" + " that needs to be up and running.") + parser.add_argument('--cimmof', default=DEFAULT_CIMMOF, + help="Path to cimmof binary to use.") + #parser.add_argument('--xml', action='store_true', default=False, + #help="Do not execute any action on cimom, just print the" + #" xml to stdout.") + parser.add_argument('-n', '--namespace', default=DEFAULT_NAMESPACE, + help="Target CIM Repository namespace.") + parser.add_argument('-v', '--verbose', action='store_true', + default=False, help="Be more verbosive on output.") + + mof_parser = argparse.ArgumentParser(add_help=False) + mof_parser.add_argument('mof', nargs='+', + type=argparse.FileType('r'), + default=sys.stdin, + help="Mof files containing declarations of classes and instances" + " to be installed or removed from Pegasus broker.") + + command = parser.add_subparsers(title="Operation commands", + dest="command", + help="Operation on declarations.") + create_cmd = command.add_parser('create', parents=[mof_parser], + help='Create instances and classes listed in mof files.') + create_cmd.add_argument('-u', '--allow-update', + action="store_true", default=False, + help="Allow update of class declaration if it already exists.") + command.add_parser('delete', parents=[mof_parser], + help="Delete instances and classes listed in mof files.") + + args = parser.parse_args() + return args + +def main(): + """ + The main functionality of script. + """ + args = parse_cmd_line() + if args.verbose: + LOG.setLevel(logging.INFO) + # parse mofs and build list of pywbem objects + objs = get_objects_from_mofs(args.cimmof, args.namespace, *args.mof) + if not objs: + die("no declarations found!") + push_to_repo(args.namespace, args.command, objs, + getattr(args, 'allow_update', False)) + +if __name__ == '__main__': + main() + -- cgit From dc80290506189ef80da0c1d8c0e733574ab7eef0 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Fri, 26 Apr 2013 08:36:17 +0200 Subject: openlmi-cimmof used by openlmi-mof-register Also appended for installation to libexec with cmake. --- CMakeLists.txt | 1 + openlmi-mof-register | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 100059f..f25850e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ add_subdirectory(tools) install(PROGRAMS openlmi-mof-register DESTINATION bin) install(PROGRAMS openlmi-register-pegasus DESTINATION libexec) +install(PROGRAMS openlmi-cimmof DESTINATION libexec) install(FILES cmake/modules/OpenLMIMacros.cmake DESTINATION share/cmake/Modules) install(FILES cmake/modules/FindCMPI.cmake DESTINATION share/cmake/Modules) install(FILES cmake/modules/FindKonkretCMPI.cmake DESTINATION share/cmake/Modules) diff --git a/openlmi-mof-register b/openlmi-mof-register index d7918e0..521c967 100755 --- a/openlmi-mof-register +++ b/openlmi-mof-register @@ -94,6 +94,11 @@ function unregister() /usr/bin/systemctl reload-or-try-restart sblim-sfcb.service fi + if [ -x $(dirname $0)/openlmi-cimmof ]; then + $(dirname $0)/openlmi-cimmof -n $namespace delete ${mofs[@]} + else + /usr/libexec/openlmi-cimmof -n $namespace delete ${mofs[@]} + fi if [ -n "$reg" -a $HAS_PEGASUS -eq 1 ]; then for provider in $(sed -n 's/ *location: *//p' "$reg" | sort | uniq); -- cgit From 338851a8fe5b9dfec1e9695602d501e1fe918912 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Fri, 26 Apr 2013 08:15:48 +0200 Subject: registration script now allows to specify cimom Until now openlmi-mof-register operated on both sfcbd and tog-pegasus if present on system. This fails when we target namespace present only in one of them (like "root/interop" for sfcbd or "root/PG_InterOp" for tog-pegasus). This patch adds "-c" option allowing to specify target cimom. For example: openlmi-mof-register -n root/PG_InterOp -c tog-pegasus \ --just-mofs unregister mof/70_LMI_SoftwareIndicationFilters.mof Removes instances of indication filters only from tog-pegasus. --- openlmi-mof-register | 83 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/openlmi-mof-register b/openlmi-mof-register index 521c967..b2b1790 100755 --- a/openlmi-mof-register +++ b/openlmi-mof-register @@ -24,11 +24,15 @@ default_namespace="root/cimv2" function usage() { - printf "Usage: $0 [ --just-mofs ] [ -n namespace ] CMD [mof] [...] [reg] + printf "Usage: $0 [ --just-mofs ] [ -n namespace ] [ -c cimom ] + CMD [mof] [...] [reg] CMD is one of [ register, unregister ] Default namespace is $default_namespace, which can be changed with '-n' option. + Supported cimoms are sfcbd and tog-pegasus. Without \"-c\" argument, the + operation is processed for any cimom present on system (all of them). + --just-mofs option causes that all arguments after CMD will be treated as mof files - no registration file is expected. @@ -46,14 +50,14 @@ function register() shift fi mofs="$@" - if [ $HAS_SFCBD -eq 1 ]; + if [ $HAS_SFCBD -eq 1 ] && echo $cimom | grep -q 'all\|sfcbd'; then - /usr/bin/sfcbstage ${reg:+-r} $reg $mofs + /usr/bin/sfcbstage -n $namespace ${reg:+-r} $reg $mofs /usr/bin/sfcbrepos -f /usr/bin/systemctl reload-or-try-restart sblim-sfcb.service fi - if [ $HAS_PEGASUS -eq 1 ]; + if [ $HAS_PEGASUS -eq 1 ] && echo $cimom | grep -q 'all\|tog-pegasus'; then /usr/sbin/cimserver --status > /dev/null 2>&1 if [ $? -eq 0 ]; @@ -64,7 +68,7 @@ function register() fi $CIMMOF -uc $mofs - if [ $JUST_MOFS = 0 ]; then + if [ $JUST_MOFS -eq 0 ]; then if [ -x $(dirname $0)/openlmi-register-pegasus ]; then cat "$reg" | $(dirname $0)/openlmi-register-pegasus | $CIMMOF -uc -n root/PG_Interop @@ -81,35 +85,39 @@ function unregister() reg=$1 shift fi - # convert mofs to `basename mof` - declare -a mofs=("$@") - for ((i=0; i<${#mofs[@]}; i++)); do - mofs[$i]=$(basename "${mofs[$i]}") - done - if [ $HAS_SFCBD -eq 1 ]; + declare -a mofs=("$@") + if [ $HAS_SFCBD -eq 1 ] && echo $cimom | grep -q 'all\|sfcbd'; then - /usr/bin/sfcbunstage ${reg:+-r} $(basename "$reg") ${mofs[@]} + # convert mofs to `basename mof` + declare -a bmofs + for ((i=0; i<${#mofs[@]}; i++)); do + bmofs[$i]=$(basename "${mofs[$i]}") + done + /usr/bin/sfcbunstage -n $namespace ${reg:+-r} $(basename "$reg") ${bmofs[@]} /usr/bin/sfcbrepos -f /usr/bin/systemctl reload-or-try-restart sblim-sfcb.service fi - if [ -x $(dirname $0)/openlmi-cimmof ]; then - $(dirname $0)/openlmi-cimmof -n $namespace delete ${mofs[@]} - else - /usr/libexec/openlmi-cimmof -n $namespace delete ${mofs[@]} - fi - if [ -n "$reg" -a $HAS_PEGASUS -eq 1 ]; + if [ $HAS_PEGASUS -eq 1 ] && echo $cimom | grep -q 'all\|tog-pegasus'; then - for provider in $(sed -n 's/ *location: *//p' "$reg" | sort | uniq); - do - /usr/bin/cimprovider -d -m ${provider} && /usr/bin/cimprovider -r -m ${provider} - done + if [ -x $(dirname $0)/openlmi-cimmof ]; then + $(dirname $0)/openlmi-cimmof -n $namespace delete ${mofs[@]} + else + /usr/libexec/openlmi-cimmof -n $namespace delete ${mofs[@]} + fi + if [ -n "$reg" ]; + then + for provider in $(sed -n 's/ *location: *//p' "$reg" | sort | uniq); + do + /usr/bin/cimprovider -d -m ${provider} && /usr/bin/cimprovider -r -m ${provider} + done + fi fi } JUST_MOFS=0 -optspec=":hn:-:" +optspec=":hn:c:-:" while getopts "$optspec" optchar; do case "$optchar" in @@ -121,6 +129,7 @@ while getopts "$optspec" optchar; do *) if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then echo "Unknown option --${OPTARG}" >&2 + exit 1 fi ;; esac @@ -128,6 +137,13 @@ while getopts "$optspec" optchar; do n) namespace="$OPTARG" ;; + c) + cimom="$OPTARG" + if [ "$cimom" != "sfcbd" -a "$cimom" != "tog-pegasus" ]; then + echo "Not supported cimom: $cimom" >&2 + exit 1 + fi + ;; h) usage; exit 0; @@ -135,6 +151,7 @@ while getopts "$optspec" optchar; do *) if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then echo "Non-option argument: '-${OPTARG}'" >&2 + exit 1 fi ;; esac @@ -142,6 +159,7 @@ done shift $(($OPTIND - 1)) namespace=${namespace:-$default_namespace} +cimom=${cimom:-all} if [ $# -lt 2 ]; then @@ -154,6 +172,10 @@ then HAS_SFCBD=1 else HAS_SFCBD=0 + if [ $cimom != "sfcbd" ]; then + echo "Sfcbd not detected on system!" >&2 + exit 1 + fi fi if [ -e /usr/sbin/cimserver ]; @@ -161,6 +183,10 @@ then HAS_PEGASUS=1 else HAS_PEGASUS=0 + if [ $cimom != "tog-pegasus" ]; then + echo "Pegasus not detected on system!" >&2 + exit 1 + fi fi # TODO: check if at least one server is installed @@ -169,9 +195,14 @@ shift # parse the reg and mofs - use $@ and remove the last item declare -a ARGS=("$@") -LEN=$(( ${#ARGS[@]} -1 )) -REG=${ARGS[$LEN]} -MOFS=(${ARGS[@]:0:$(($LEN))}) +if [ $JUST_MOFS -eq 0 ]; +then + LEN=$(( ${#ARGS[@]} -1 )) + REG=${ARGS[$LEN]} + MOFS=(${ARGS[@]:0:$(($LEN))}) +else + MOFS=("$@") +fi case $CMD in register) -- cgit