diff options
31 files changed, 8416 insertions, 172 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b42debc..f25850e 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}) @@ -36,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) @@ -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-<PROVIDER>=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/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 <jdennis@redhat.com> +% 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. 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 " + "<name,value> pairs called \"options\" passed an array of " + "option names and option values. The <name,value> 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" + "<OrgID>:<LocalID> \n" + "<LocalID> 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/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/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() + diff --git a/openlmi-mof-register b/openlmi-mof-register index d7918e0..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> [mof] [...] [reg] + printf "Usage: $0 [ --just-mofs ] [ -n namespace ] [ -c cimom ] + CMD <mof> [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,30 +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 [ -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 @@ -116,6 +129,7 @@ while getopts "$optspec" optchar; do *) if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then echo "Unknown option --${OPTARG}" >&2 + exit 1 fi ;; esac @@ -123,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; @@ -130,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 @@ -137,6 +159,7 @@ done shift $(($OPTIND - 1)) namespace=${namespace:-$default_namespace} +cimom=${cimom:-all} if [ $# -lt 2 ]; then @@ -149,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 ]; @@ -156,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 @@ -164,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) 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/python/openlmi/common/IndicationManager.py b/src/python/openlmi/common/IndicationManager.py index ba0919d..a17703f 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'^(?P<prefix>lmi:' r'(?P<class_name>[a-z0-9_]+):)(?P<filter_id>.*)$', re.IGNORECASE) FILTER_DEFAULTS = { + "SourceNamespace" : "root/cimv2", "SourceNamespaces" : ["root/cimv2"], - "QueryLanguage" : "CIM:CQL" + "QueryLanguage" : "CIM:CQL" } @cmpi_logging.trace_function @@ -511,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) @@ -526,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: @@ -541,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: @@ -560,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 @@ -570,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 @@ -603,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/python/openlmi/common/JobManager.py b/src/python/openlmi/common/JobManager.py index 5ed7830..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): @@ -495,6 +518,23 @@ class Job(object): 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): """ Return indication that describes the post-execution values of the @@ -524,15 +564,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 @@ -651,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): """ @@ -701,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): @@ -1139,7 +1169,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 +1314,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]) diff --git a/src/realmd/CMakeLists.txt b/src/realmd/CMakeLists.txt new file mode 100644 index 0000000..cd6f6c8 --- /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 60_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 <konkret/konkret.h> +#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 <konkret/konkret.h> +#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 <konkret/konkret.h> +#include <string.h> +#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 <konkret/konkret.h> +#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 <konkret/konkret.h> +#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 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + version="1.1" + inkscape:version="0.48.4 r9939" + sodipodi:docname="class_diagram.svg"> + <defs + id="defs4"> + <marker + inkscape:stockid="Arrow2Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow2Mend" + style="overflow:visible;"> + <path + id="path3871" + style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" + d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " + transform="scale(0.6) rotate(180) translate(0,0)" /> + </marker> + <marker + inkscape:stockid="Arrow2Lend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow2Lend" + style="overflow:visible;"> + <path + id="path3865" + style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" + d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " + transform="scale(1.1) rotate(180) translate(1,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="2.8" + inkscape:cx="235.3264" + inkscape:cy="611.39239" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="1920" + inkscape:window-height="1002" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:snap-grids="true" + inkscape:snap-bbox="true"> + <inkscape:grid + type="xygrid" + id="grid3006" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g3035"> + <g + id="g3030"> + <g + id="g3022" + transform="matrix(1,0,0,0.97395887,0,10.489247)"> + <rect + y="402.36218" + x="350.39706" + height="25" + width="120" + id="rect3008" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.41732287;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + </g> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 350.39707,417.36218 120,0" + id="path3014" + inkscape:connector-curvature="0" /> + </g> + <text + sodipodi:linespacing="125%" + id="text3010" + y="412.41025" + x="375.69394" + style="font-size:8px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans" + xml:space="preserve"><tspan + y="412.41025" + x="375.69394" + id="tspan3012" + sodipodi:role="line">ComputerSystem</tspan></text> + </g> + <g + id="g3085" + transform="translate(-0.3488455,0)"> + <g + transform="translate(-221,2e-6)" + id="g3030-5"> + <g + id="g3022-6" + transform="matrix(1,0,0,0.97395887,0,10.489247)"> + <rect + y="402.36218" + x="350.39706" + height="25" + width="120" + id="rect3008-5" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.41732287;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + </g> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 350.39707,417.36218 120,0" + id="path3014-2" + inkscape:connector-curvature="0" /> + </g> + <text + sodipodi:linespacing="125%" + id="text3010-2" + y="412.41025" + x="189.21738" + style="font-size:8px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans" + xml:space="preserve"><tspan + y="412.41025" + x="189.21738" + id="tspan3012-9" + sodipodi:role="line">RealmdService</tspan></text> + </g> + <path + style="fill:#000000;fill-opacity:1;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 350,417.36218 -100,0" + id="path3093" + inkscape:connector-type="polyline" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-size:8px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans" + x="270.92188" + y="412.43054" + id="text3863" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3865" + x="270.92188" + y="412.43054">HostedService</tspan></text> + <g + id="g3018" + transform="translate(-0.3488455,69.844081)"> + <g + transform="translate(-221,2e-6)" + id="g3030-5-6"> + <g + id="g3022-6-4" + transform="matrix(1,0,0,0.97395887,0,10.489247)"> + <rect + y="402.36218" + x="350.39706" + height="25" + width="120" + id="rect3008-5-7" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.41732287;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + </g> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 350.39707,417.36218 120,0" + id="path3014-2-0" + inkscape:connector-curvature="0" /> + </g> + <text + sodipodi:linespacing="125%" + id="text3010-2-2" + y="412.41025" + x="189.21738" + style="font-size:8px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans" + xml:space="preserve"><tspan + y="412.41025" + x="189.21738" + id="tspan3012-9-8" + sodipodi:role="line">RealmdRealm</tspan></text> + </g> + <g + id="g3062" + transform="translate(-0.3488455,139.68809)"> + <g + transform="translate(-221,2e-6)" + id="g3030-5-6-3"> + <g + id="g3022-6-4-4" + transform="matrix(1,0,0,0.97395887,0,10.489247)"> + <rect + y="402.36218" + x="350.39706" + height="25" + width="120" + id="rect3008-5-7-3" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.41732287;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + </g> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 350.39707,417.36218 120,0" + id="path3014-2-0-8" + inkscape:connector-curvature="0" /> + </g> + <text + sodipodi:linespacing="125%" + id="text3010-2-2-5" + y="412.41025" + x="189.21738" + style="font-size:8px;font-style:normal;font-weight:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans" + xml:space="preserve"><tspan + y="412.41025" + x="189.21738" + id="tspan3012-9-8-4" + sodipodi:role="line">RealmdKerberosRealm</tspan></text> + </g> + <text + xml:space="preserve" + style="font-size:8px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans" + x="199.47266" + y="447.2489" + id="text3961" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3963" + x="199.47266" + y="447.2489">ServiceAffectsElement</tspan></text> + <path + style="fill:none;stroke:#ff0000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 189.04822,470.39873 0,-43.67629" + id="path4498" + inkscape:connector-type="polyline" + inkscape:connector-curvature="3" + inkscape:connection-start="#g3018" + inkscape:connection-start-point="d4" + inkscape:connection-end="#g3085" + inkscape:connection-end-point="d4" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-end:url(#Arrow2Mend)" + d="m 189.04822,542.42392 0,-43.67622" + id="path4500" + inkscape:connector-type="polyline" + inkscape:connector-curvature="3" /> + </g> +</svg> 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] <action> <arg> ... + + %%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 <konkret/konkret.h> + +#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 <glib.h> +#include <dbus/dbus.h> +#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 <stdio.h> +#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 <glib.h> + +#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 <strings.h> +#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 <stdarg.h> +#include <stdbool.h> + +#include <konkret/konkret.h> +#include <glib.h> + +#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 <stefw@gnome.org> + */ + +#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__ */ 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) diff --git a/src/software/openlmi/software/cimom_entry.py b/src/software/openlmi/software/cimom_entry.py index ad944c8..70df071 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,26 +114,84 @@ 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 -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, ns, classes, first_activation) + env, fltr, class_name, class_path, first_activation) + +def deactivate_filter(env, fltr, class_name, class_path, last_activation): + """ + CIMOM callback. -def deactivate_filter(env, fltr, ns, classes, last_activation): + 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, ns, classes, last_activation) + 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): 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: |