Blog (76)
Komentarze (5.6k)
Recenzje (0)
@nintyfanWłasny podajnik hasła dla NetworkManagera

Własny podajnik hasła dla NetworkManagera

W tym wpisie chciałbym przedstawić podstawy tego, jak zrobić własny SecretAgent dla NetworkManager-a. Program będzie się komunikować po DBus-ie z NetworkManager-em, by podać mu hasło wpisane przez użytkownika. Za każdym razem, gdy NetworkManager zapyta o hasło, program wyświetli okno z nazwą sieci bezprzewodowej i pytaniem o hasło. Po wprowadzeniu hasła, program prześle je do NetworkManager-a, przez co powinno nastąpić połączenie z siecią.

Program jest prosty i nie wspiera wszystkiego. Przede wszystkim, to nie pamięta on wpisanych haseł. Po drugie, to nie zwraca błędu dla NetworkManagera w przypadku napotkania błędu. Wspiera on tylko jedną metodę interfejsu SecretAgent - GetSecrets.

Ale zacznijmy od początku

Czym jest DBUS?

DBus to usługa systemów GNU/Linux, która pozwala procesom się komunikować. Wspierana jest komunikacja w ramach jednej sesji lub systemem(kernel lub usługi systemowe). Aplikacja może się połączyć do szyny sesji lub systemowej. Na szynie systemowej jest kernel, jak i usłgui systemowe. My będziemy korzystać z usługi systemowej NetworkManager.

DBus jest zorientowany obiektowo. Obsługuje metody, sygnały, właściwości. Wszystko to jest zgrupowane w interfejsy, interfejsy są zgrupowane w ścieżki, a ścieżki w obiekty(np. usługi).

Czym jest NetworkManager?

NetworkManager to usługa systemowa przechowująca ustawienia sieci, a także obsługująca łączenie się z sieciami.

Dla naszych potrzeb będziemy korzystać z obiektu org.freedesktop.NetworkManager(to tam NetworkManager nasłuchuje), ścieżki /org/freedesktop/NetworkManager/AgentManager i interfejsu org.freedesktop.NetworkManager.AgentManager(tylko dla metody register). Będziemy natomiast implementować metodę GetSecrets interfejsu org.freedekstop.NetworkManager.SecretAgent.

Z czym to powiążemy?

Nasz program będzie korzystać z libgreattao(napiszemy jeden program, by rządzić wszystkimi środowiskami graficznymi!).

Zaczynamy!

Pierwsze, co musimy zrobić , to zainicjować libgreattao. Dlatego w funkcji main wywołujemy tao_initialize, przekazując jako pierwszy argument nazwę programu(obligatoryjnie), jako drugi tekst pomocy, jako trzeci wskaźnik na ilość argumentów(obligatoryjnie), jako czwarty wskaźnik na tablicę łańcuchów znaków(obligatoryjnie).

Następnym krokiem jest połączenie się do szyny systemowej DBus-a. Zrobimy to w ten sposób:

[code=C] bus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error); [/code]

Ale najpierw trzeba zadeklarować dwie zmienne. Pierwszą będzie bus_connection i będzie ona globalna, a druga error i będzie na stosie funkcji main.

[code=C]DBusConnection *bus_connection;[/code]

i

[code=C]DBusError error;[/code]

Jeżeli bus_connection będzie null-em, to kończymy program z błędem, zwracając liczbę różną od 0 w main-ie.

Jednak przed wywołaniem [code=C]dbus_bus_get(DBUS_BUS_SYSTEM, &error);[/code] trzeba zainicjować zmienną przechowującą błąd:

[code=C]dbus_error_init(&error);[/code]

Teraz należy się zarejestrować do AgentManagert-a NetworkManager-a. Robimy to wywołując metodę Register interfejsu org.freedekstop.NetworkManager.AgentManager, który jest pod ścieżką /org/freedekstop/NetworkManager/AgentManager, a  siedzi w obiekcie org.freedesktop.NetworkManager.

Tworzenie nagłówka wiadomości wygląda następująco:

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

[/code]

Po utworzeniu wiadomości, można przypisać jej argumenty. Tak się składa, że Register przyjmuje jeden argument - identyfikator agenta. Jest on typu string i składa on się z minimum trzech znaków, a maksymalnie 255, jednak bez dwukropków. W naszej aplikacji wygląda to następująco:


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

Teraz należy podać argumenty:


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

DBUS_TYPE_INVALID wskazuje, że nie ma więcej argumentów. Trezeba go podać.

Teraz najważniejsza część - wysłanie komunikatu i oczekiwanie na odpowiedź.

[code=C] 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); } [/code]

Funkcją show_message_and_abort się nie zajmiemy, ponieważ nie jest związana z DBus-em. Możesz zobaczyć, jak działa, przeglądając źródła. Minus jeden oznacza bardzo długo. Nie wiedziałem, jak nakazać jej czekać w nieskończoność, dlatego podałem największą (dla x86) możliwą wartość w unsigned int, czyli -1. Funkcja dbus_connection_send_with_reply_and_block będzie oczekiwać na komunikat odpowiedzi, pomijając pozostałe(pozostają one w kolejce).

Nie zapomnij oczywiście o zainicjowaniu struktury błędu bus_error. Na końcu naszej funkcji należy zwolnić komunikat błędu.

[code=C] dbus_error_free(&bus_error); [/code]

Teraz należy dodać obsługę interfejsu org.freedekstop.NetworkManager.SecretAgent. Pierwszym krokiem jest poinformowanie, że będziemy nasłuchiwać na tym interfejsie. Całość jest zamknięta w add_signal_support(wiem - nazwa myląca):

[code=C] 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); [/code]

Najpierw obliczamy ilość potrzebnego miejsca na zaalokowanie miejsca na ciąg znaków dla funkcji dbus_bus_add_match. Sizeof zwróci długość ciągu znaków(wraz z zerem, więc nie musimy dodawać jeden). Następnie tworzymy ciąg znaków za pomocą snprintf. Inicjalizujemy error, a następnie wywołujemy omawianą funkcję, po czym sprawdzamy czy struktura błędu sygnalizuje błąd.

Teraz najważniejsze - tworzymy timer. Timer będzie wywoływać funkcję obsługi komunikatów DBus-a co 100 ms.

[code=C] timer = tao_add_timer(100, (void (*)(void *))nm_dbus_loop, (void*)bus_connection); [/code]

Timer, podobnie jak okna w libgreattao są wskaźnikiem typu void, bo nie są przeznaczone do wykorzystania wewnętrznie w aplikacji. Naszą funkcją obsługi komunikatów jest nm_dbus_loop. Wygląda ona następująco:

[code=C] 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); } } [/code]

Jako drugi argument funkcji dbus_connection_read_write przekazujemy 0, co oznacza, że nie będzie blokować. Funkcja jest tylko ozdobnikiem - można ją usunąć, a ja zapomniałem. Ta funkcja jednak będzie przydatna w dalszym rozwoju aplikacji, bo zwraca FALSE w przypadku, gdy połączenie zostanie zerwane.

My pobieramy komunikat(NULL w przypadku braku w kolejce), by sprawdzić czy nie jest to wywołanie naszej funkcji DBus-a. Następnie sprawdzamy czy zostało utworzone okienko z zapytaniem o hasło - jeżeli nie, to kasujemy komunikat. Komunikat,w przeciwnym wypadku, jest nam potrzebny do pobrania ssid sieci, a także do utworzenia komunikatu-odpowiedzi.

Teraz sprawa najtrudniejsza - odczyt SSID sieci i flag zapytania. NetworkManager wysyła flagi zapytania informujące np. czy zezwolono na interakcję z użytkownikiem.

Pierwszym argumentem naszej funkcji DBus-a jest konfiguracja połączenia, drugim jest ścieżka do konfiguracji(nie jako pliku - w specjalnym interfejsie, w specjalnej ścieżce NetworkManager-a). Trzeci i czwarty nie wiem czym są - zwyczajnie nie były mi potrzebne, a ostatnim są flagi.

Na samym początku znajdują się deklaracje potrzebnych zmiennych:

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

DBusMessageIter to iteratory - pamiętają one na jakim argumencie się zatrzymaliśmy.

Pierwsze, co robimy, to ustawiamy pierwszy iterator na pierwszy argument. Ponieważ jest on tablicą, to będziemy przeglądać inne argumenty - stąd wywołanie dbus_message_iter_recurse(&container, &a); Następnie w pętli znowu patrzymy się na elementy naszego elementu tablicy. Jest to spowodowane tym, że ta tablica to słownik, a słownik w DBus-ie składa się z elementów typu DBUS_DICT_ENTRY. Ten rodzaj elementów składa się z dwóch podelementów, klucza i wartości. My wyszukujemy ciągu 802‑11-wireless, by następnie pobrać kolejny słowniki do iteratora o nazwie item. Szukamy teraz ssid, po czym ustawiamy iterator a na wartość w słowniku. Teraz sprawa się komplikuje, otóż ssid nie jest przechowywany jako ciąg znaków(nie wiem czemu), ale jako variant tablicy, przechowującej znaki. Z tego powodu wywołujemy recurse dwa razy!

[code=C] 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)); } [/code]

Każdy pobrany znak wstawiamy do naszego bufora. Następnie(if) sprawdzamy czy ssid został poprawnie pobrany - jeżeli nie, to opuszczamy naszą funkcję.

Skomplikowane, nie uważasz?

Teraz kwestia flag. Chodzi nam o piąty argument, dlatego wykonujemy dbus_message_iter_next cztery razy. Jeżeli flaga pierwsza lub druga jest ustawiona, to znaczy, że NetworkManager pozwolił na interakcję z użytkownikiem. Jeżeli nie pozwolił, to wychodzimy.

Na końcu funkcji tworzymy nasze okno i ustawiamy zmienną window w pętli dbus-a na nasze okno. Ważne jest, co się stanie, gdy użytkownik wprowadzi hasło. Zostanie wywołana metoda return_secret.

Funkcja return_secret wykonuje działanie odwrotne do poprzedniej funkcji, bo tworzy komunikat. Dlatego wywołuje dbus_message_iter_init_append. Sygnatura funkcji nie różni się niczym. Jedyna różnica jest w zachowaniu - funkcja zamiast czytać argumenty, będzie je zapisywać. Tutaj pomocna także będzie dbus_message_iter_append_basic - będzie służyć do zapisywania ciągów znaków, a także dbus_message_iter_open_container, która jest odpowiednikiem recurse, jednak służy do tworzenia elementów złożonych.

Sygnatura w DBus-ie, to ciąg znaków. Tworzymy taką sygnaturę funkcją 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);

Trzeci argument to cześć sygnatury. Będzie to tablica słowników, których pierwszym argumentem jest ciąg znaków, a drugim słownik składający się z dwóch ciągów znaków. Tak są zapisane ustawienia w NetworkManagerz-e i my musimy się tego trzymać wysyłając hasło!

Po wypełnieniu typu złożonego(słownik, tablica, itd.), należy go zamknąć. Ciągi znaków nie są typem złożonym! Do zamknięcia kontenera służy dbus_message_iter_close_container, która przyjmuje wskaźnik na iterator-rodzica naszego iteratora i wskaźnik na nasz iterator.

W wyniku naszej funkcji zwracamy:


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

Wspomnę, że typ variant służy do przechowywania zmiennych różnych typów.

Nie należy zapomnieć o najnowszej wersji libgreattao, którą ściągniecie z svn‑a. Po informacje, jak tego dokonać, zapraszam na SourceForge.

W przygotowaniu jest aplet do łączenia się z sieciami, również w libgreattao, ale najpierw muszę dorobić do libgreattao obsługę tray-a.

Wybrane dla Ciebie
Komentarze (12)