Strona używa cookies (ciasteczek). Dowiedz się więcej o celu ich używania i zmianach ustawień. Korzystając ze strony wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.    X

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: bus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error); 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.DBusConnection *bus_connection;iDBusError error;

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 dbus_bus_get(DBUS_BUS_SYSTEM, &error); trzeba zainicjować zmienną przechowującą błąd:dbus_error_init(&error);

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: DBusMessage *msg; DBusError bus_error; msg = dbus_message_new_method_call("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/AgentManager", "org.freedesktop.NetworkManager.AgentManager", "Register" ); 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ź. 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); } 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. dbus_error_free(&bus_error);

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): 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);

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. timer = tao_add_timer(100, (void (*)(void *))nm_dbus_loop, (void*)bus_connection); 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: 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); } } 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: DBusMessageIter container, item, a, character, b; int buffer_position; char buffer[1024]; char ok; char *key; struct nm_settings *prompt; unsigned int flags;

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!

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)); } 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. 

linux programowanie

Komentarze

0 nowych
nintyfan   11 #1 09.02.2015 05:21

Nie wiem, kto edytował mój wpis(i nie przejrzałem wszystkich zmian), ale zmiana w tytule jest błędna.

O ile usunięcie wykrzyknika jest ok, to wstawienie przecinka jest mylne. Chodziło mi o aplikację, która podaje hasła.

Nie gniewać się - po prostu stwierdzam fakty.

  #2 09.02.2015 07:29

Nie chce mi się uważnie przeczytać całego tekstu. Możesz w skrócie napisać jakie jest praktyczne zastosowanie tego rozwiązania?

  #3 09.02.2015 07:35

Dobra już widzę, że napisałeś to dla zabawy. Taka ciekawostka. Jedni w wolnym czasie idą do kina albo na rowery, a tobie odpowiada wymyślanie koła od nowa.

mordesku   8 #4 09.02.2015 08:36

super wpis, muszę poszukać informacji jak gadać po DBus w javie.

marrrysin   6 #5 09.02.2015 09:19

@nintyfan: Ten, kto to edytował najwyraźniej ma spore problemy z logicznym myśleniem :D

nintyfan   11 #6 09.02.2015 11:19

@@nintyfan (niezalogowany): Obecnie jest utrzymywanych wiele portfeli, co nie jest dobre. Poza tym, to moje rozwiązanie jest najlepszym sposobem na nawiązanie połączenia w konsoli. Uruchamiasz program, a jeśli łączyłeś się kiedyś z daną siecią bezprzewodową, to program poprosi o hasło - i wiola - masz połączenie w terminalu.

(bez żadnych zabaw z nmcli)

Autor edytował komentarz.
nintyfan   11 #7 09.02.2015 12:33

@mordesku: Rozumiem ironię - zbyt ciężko jest pisać na tak niskim poziomie. Sam na początku szukałem jakiś bibliotek do gadania z NM, lecz były one zintegrowane z GLib-em lub z QT.

mordesku   8 #8 09.02.2015 12:38

@nintyfan: to żadna irionia, po prostu wygodniej mi pisać w javie :D

prap   3 #9 09.02.2015 14:50

Trochę za wiele tego "nie wiem co to", "nie wiem czemu".

"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."

Czytał kolega w ogóle dokumentację tej funkcji? http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html#ga8d6431f17a...

timeout_milliseconds timeout in milliseconds, -1 (or DBUS_TIMEOUT_USE_DEFAULT) for default or DBUS_TIMEOUT_INFINITE for no timeout

Ustawiając -1 ustawił kolega wartość domyślną, a nie żadną najwyższą możliwą. Poza tym argument tej funkcji to int, a nie uint.

Reszty nie chciało mi się na razie sprawdzać. Może w wolnej chwili.

nintyfan   11 #10 09.02.2015 15:43

@prap: Czytałem dokumentację funkcji, ale przeoczyłem znaczenie parametrów., Dzięki za naprowadzenie na właściwą drogę.

Teraz widzę, że:
#define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff)
DBUS_TIMEOUT_INFINITE to maksymalna wartość dodatnia.

Autor edytował komentarz.
kostek135   8 #11 10.02.2015 18:55

@mordesku: Zawsze możesz napisać funkcję natywną przy pomocy JNI albo JNA i wykorzystać to co już napisał nintyfan.

nintyfan   11 #12 11.02.2015 10:16

Dla tych, co chcą automatycznego łączenia się z siecią, to podaję skrypt:

starttimers
waitforwindows 0 1 1 /Objects/Attributes
=window passwordwnd /Objects/Attributes[1]
setinput $passwordwnd /attributes/password "nasze_hasło"
run $passwordwnd /attributes/password
exit 0

Należy go gdzieś zapisać i wywoływać z poziomu bash-a:
while true
do
ścieżka_do_tao_network_manager_password --tao-shell-execute-file ścieżka_do_naszego_skryptu_applilkacji
done

Co ten skrypt robi? Podaje nasze hasło, gdy networkmanager o nie zapyta. Ponieważ shell libgreattao jeszcze nie obsługuje pęteli, to posiłkujemy się bash-em.