Custorm feeder for NetworkManager

In this article I would like to explain elementary way to create custom SecretAgent for NetworkManager. Program will communicate via DBus with NetworkManager. It would give password prompted by user, when NetworkManager asks for it. Once NetworkManager asks for password, my program will show dialog with name of wireless network and question about password. After giving password, program will send it to NetworkManager, so connection could be established.

Program will be very simple and don’t support all features of NetworkManager. Firstly, it don’t remember passwords prompted by user. Secondly, it don’t return error, when error occurring. It only supports one method of SecretAgent interface – GetSecrets.

Let’s start at beginning

What DBus is?

Dbus is service of modern GNU/Linux distribution. It allows processes to communicate. It supports sending messages process in the same session and registered as system service(or kernel service). We would like to use system service called NetworkManager.

DBus is object-oriented. It supports methods, signals, properties. All these thinks are assembled to interface, interfaces are assembled into paths and paths are grouped to objects(ex. services).

What NetworkManager is?

NetworkManager is system service, which storing connections settings and supports connecting to networks.

For purpose of this article, we will use org.freedekstop.NetworkManager(at this name NetworkManager is spoken), /org/freedesktop/NetworkManager/AgentManager paths and org.freedesktop.NetworkManager.AgentManager. We will use these thinks only for Register method. We will also implements method GetSecrets of org.freedesktop.NetworkManager.SecretAgent interface.

Which will be connected with our project?

Our program will use libgreattao. We will write one single application to rule all desktop environment, virtual console and shell.

Let’s go!

Firstly, we need to initialize libgreattao. To do this, we will call tao_initialize, giving program name(mandatory) as first argument, help text as second, pointer to argument’s count(mandatory) as third, as fourth pointer to array of char’s array(mandatory).

Next step is connecting to system bus of DBus. We will achieve this by step displayed below:


bus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);

But firstly, we need to declare two variables. First is called bus_session and it should been declared in global scope. Second is called error and it should been declared in main function scope.


DBusConnection *bus_connection;

and


DBusError error;

We must, also, register to AgentManager. We can done this by invoking Register method of org.freedesktop.NetworkManager.AgentManager, which can be found at path /org/freedekstop/NetworkManager/AgentManager, which is handled by object org.freedekstop.NetworkManager

  DBusMessage *msg;
  DBusError bus_error;
   
  msg = dbus_message_new_method_call("org.freedesktop.NetworkManager",
      "/org/freedesktop/NetworkManager/AgentManager", "org.freedesktop.NetworkManager.AgentManager",
      "Register"
    );

After creating a message, we can attach an arguments to it. Register catching one argument – agent’s identificator. It has string type and containing from 3 to 255 characters, but without colons. In our application it will looks like below:

const char *service_name = "org.taolib.nmpassword";

Below is displayed, how attach arguments to message:

dbus_message_append_args(msg, DBUS_TYPE_STRING, &service_name, DBUS_TYPE_INVALID); 

DBUS_TYPE_INVALID points, that there’s no more arguments. It’s obligatory!

At we face to face with most important part of DBus based programs – sending messages and waiting for replies:

dbus_connection_send_with_reply_and_block(bus_connection, msg, -1, &bus_error);
 
if (dbus_error_is_set(&bus_error)) {
   
  show_message_and_abort(bus_error.message);
}

I won’t introduce body of show_message_and_abort function. It doesn’t contain DBus related code. You can see, how it works, by looking at sources. Minus one meaning very long. I don’t know how order this function to wait infinity, so I give them maximum(for x86) value as unsigned int. Function dbus_connection_send_with_reply_and_block will waits for reply message omitting everything else(other messages will waits in poll).

Please, don’t forget about initializing error structure, called bus_error. At the end of our function, we will freeing error message:

dbus_error_free(&bus_error);

At now, we need add support for org.freedesktop.NetworkManager.SecretAgent interfacve. First step is give information, we were listening on this interface. Entire duty is closed in add_signal_support(I know name was misleading):

char *buffer;
int length;
DBusError error;
 
length = sizeof("interface=''")
            + strlen(interface);
buffer = (char *) malloc(length);
 
snprintf(buffer, length, "interface='%s'",      
     interface);
dbus_error_init(&error);
 
dbus_bus_add_match(bus_connection, buffer, &error);
 
if (dbus_error_is_set(&error))
{
     show_message(error.message);
}
 
dbus_error_free(&error);
 
free(buffer);

In first step we counting amount of required space for allocating buffer for character array for dbus_bus_add_match function. Sizeof will return size of character array(including character of code 0, so we don’t plus one). In second step we fill array of characters, using snprintg. In next steps we initializing error, invoking dbus_bus_add_match, checking for error occurrence.

In this moment we do most important thing – create a timer. This timer will be calling messages handling function with 100 ms interval.

timer = tao_add_timer(100, (void (*)(void *))nm_dbus_loop, (void*)bus_connection);

Timer like libgreattao windows are pointers of void, because they aren’t suspected to being used by application. Our message handling function is calling nm_dbus_loop. It looks like below:

DBusMessage* msg;
struct password_prompt *prompt;
void *window;
 
  dbus_connection_read_write(bus_connection, 0);
  msg = dbus_connection_pop_message(bus_connection);
   
   
  if (msg) {
    window = NULL;
     
    if (dbus_message_is_method_call(msg, "org.freedesktop.NetworkManager.SecretAgent", "GetSecrets")) {
     
  prepare_return_secret(msg, &window);
    }
     
    if (!window) {
     
  dbus_message_unref(msg);
    }
  }

As second argument of dbus_connection_read_write we give 0. What that meaning? It means that this function won’t block. This function are only decorator – you can remove it and I forget about this. This function can be used to detect bus connection broken, because in this case returns FALSE.

We retrieve message(NULL means no message in poll) to check this is not invocation of our DBus method. In next step we check password prompt is created – if not, we remove message. In other case, message are necessary to get network ssid and to create response message.

At now most hard thing. We must read ssid of network and question flags. NetworkManager are sending question flags, which in example inform that keyring can be interactive(communicate with user).

First argument of our DBus method is network settings, second is configuration path(not as a file – it points to path in NetworkManager). Third and fourth arguments are not known to me – I don’t use it. The last argument is flags.

At the beginning of our function we declare some variables:

DBusMessageIter container, item, a, character, b;
int buffer_position;
char buffer[1024];
char ok;
char *key;
struct nm_settings *prompt;
unsigned int flags;

DBusMessageIter are iterators. It remembers on which argument we do last action.

First, what we doing is setting first iterator to first argument. It hast array type, so we will checks elements of this array. We called dbus_message_iter_recurse(&container, &a); In next step we checks elements of current element. It was caused that this table are dictionary and dictionary contains elements of type DBUS_DICT_ENTRY. This kind of elements assembles two elements – key and value. We are searching string 802-11-wireless for assign dict-value to iterator called item. In next step, we are searching for string ssid for setting iterator to value of dictionary. The case is very complicated – ssid aren’t stored as string, but as variant of array of bytes. We need to call recurse twice.

if (strcmp(key, "ssid") == 0) {
     dbus_message_iter_next(&a);
     dbus_message_iter_recurse(&a, &b);
     dbus_message_iter_recurse(&b, &character);
      
     buffer_position = 0;
     do {
   dbus_message_iter_get_basic(&character, &buffer[buffer_position]);
   ++buffer_position;
     } while (dbus_message_iter_next(&character));
   }

Each retreived byte we add to our buffer. In next step (ssid) we checks that ssid was retreived correctyly. If not, we exits from function.

To obtain flags(the fifth argument), we need to call recurse two times. If first org second flag are set, the NetworkManager allowed to communicate with user. In other case, we exits from function.

At the end of function, we create new window and sets window variable to newly created window.

Most interesting thing is, what cause, when user type password. In this case return_secret will be called.

Return_secret function acts as mirror of previous function, because it create message, so it calls dbus_message_iter_init_append instead of dbus_message_iter_init. Both function’s signature’s are identical. Only difference is in behavior – dbus_message_iter_init_append will write arguments instead of reading them. We will also use dbus_message_iter_append_basic – we will use this function to writes strings. We need also dbus_message_iter_open_container, which are mirror function of dbus_message_iter_recurse. Signature in DBus are character’s array. We create signature by calling dbus_message_iter_open_container.

dbus_message_iter_open_container(&container1, DBUS_TYPE_ARRAY,
                     DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
                     DBUS_TYPE_STRING_AS_STRING
                     DBUS_TYPE_ARRAY_AS_STRING
                     DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
                     DBUS_TYPE_STRING_AS_STRING
                     DBUS_TYPE_VARIANT_AS_STRING
                     DBUS_DICT_ENTRY_END_CHAR_AS_STRING
                     DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &container2);

Third argument is part of signature. Our function will return array of dictionaries, which key as string and value as dictionary. Inner dictionary are assembled with string as key and variant as value. We will set content of variant by string. In this way settings in NetworkManager are stored and our application must follow that way.

After filled of container, we must close it. Strings are not assembled type. We will use dbus_message_iter_close_container, which waiting for pointer to initialized iterator and pointer to iterator. First argument are parent iterator and second are child iterator.

As a result of calling our function, we will retrieve:

[{"802-11-wireless" => [{"security" => variant("802-11-wireless-security")}],  "802-11-wireless-security" => [{"key-mgmt" => variant("wpa-psk"), "psk" => variant(our_password)}]

I must mentoin, that variant are used to storing variables of different kind.

Please, remember about downloading latest version of libgreattao. Instructions about how do this, you can find on SourceForge.

Code of application you can download from:
My home site

In preparation are applet for connecting with networks. It also uses libgreattao. Currently libgreatao doesn’t support tray.

2 thoughts on “Custorm feeder for NetworkManager

  1. slawomir2014lachu Post author

    For those, who likes to automatic connection with network, i wrote this script:
    starttimers
    waitforwindows 0 1 1 /Objects/Attributes
    =window passwordwnd /Objects/Attributes[1]
    setinput $passwordwnd /attributes/password “our_password”
    run $passwordwnd /attributes/password
    exit 0

    Please, write go somewhere and invoke from bash
    while true
    do
    path_to_tao_network_manager_password –tao-shell-execute-file path_to_out_application_script
    done

    This script will give password(our_password) to NetworkManager, when it asks for it. We use bash to invoke our script, because libgreattao’s shell doesn’t support loops.

    Like

    Reply

Leave a comment