Napisany w języku C program ma ułatwiać zarządzanie usługami. Sam demon odpowiada za uruchamianie odpowiednich modułów na żądanie użytkownika. Moduły natomiast, służą do tworzenia plików konfiguracyjnych na podstawie danych z bazy LMS'a oraz restartowania odpowiednich usług na serwerze. Spełniają także inne funkcje np. zbieranie statystyk, badanie aktywności hostów, naliczanie opłat, powiadamianie o zaległościach.
Oto lista rzeczy, które lmsd potrzebuje już na etapie kompilacji:
Przed kompilacją należy przy pomocy skryptu ./configure ustalić opcje przedstawione na poniższym listingu (w nawiasach podano wartości domyślne opcji):
--help pomoc --enable-debug0 logowanie zapytań SQL (wyłączone) --enable-debug1 logowanie zdarzeń (wyłączone) --with-pgsql gdy korzystasz z bazy PostgreSQL (wyłączone) --with-mysql gdy korzystasz z bazy MySQL (włączone) --prefix=PREFIX docelowy katalog instalacyjny demona i modułów (/usr/local) --lmsbindir=DIR docelowa lokalizacja binarki lmsd (PREFIX/lms/bin) --lmslibdir=DIR docelowa lokalizacja modułów lmsd (PREFIX/lms/lib) --libdir=DIR lokalizacja bibliotek bazy danych (/usr/lib) --incdir=DIR lokalizacja plików nagłówkowych bazy danych (/usr/include) --inifile=FILE plik konfiguracyjny - wyłącza konfigurację przez UI
Zatem wymagane jest określenie bazy z jakiej będziemy korzystać (-with-mysql lub -with-pgsql) oraz położenia bibliotek dostarczanych wraz z bazą (–incdir, –libdir). Możliwe jest zmuszenie demona do korzystania z plików konfiguracyjnych zamiast bazy danych. Nie jest możliwe używanie obu sposobów przechowywania konfiguracji, dlatego należy o tym zdecydować przed kompilacją.
# ./configure --with-pgsql --libdir=/usr/local/pgsql/lib --incdir=/usr/local/pgsql/include
Następnie kompilacja i instalacja (umieszczenie demona w katalogu określonym zmienną –prefix):
# make && make install
Skompilowane moduły (pliki z rozszerzeniem .so), znajdujące się w katalogu modules/nazwa_modułu zostają umieszczone w katalogu PREFIX/lms/lib, a główny program (lmsd) w katalogu PREFIX/lms/bin.
Całą konfigurację demona i modułów przeprowadza się przy pomocy LMS-UI w menu Konfiguracja → Demon. Konfigurację modułów omówiono w osobnych rozdziałach ich dotyczących. Podstawowe parametry pracy demona i dane do połączenia z bazą danych podaje się jako opcje linii komend, zgodnie z poniższym listingiem:
--dbhost -h host[:port] host na którym zainstalowana jest baza danych (domyślnie: 'localhost') --dbname -d nazwa_bazy nazwa bazy danych (domyślnie: 'lms') --dbuser -u użytkownik nazwa użytkownika bazy danych (domyślnie: 'lms') --dbpass -p hasło hasło do bazy danych (domyślnie: puste) --hostname -H nazwa_hosta host, na którym działa demon. Domyślnie przyjmowana jest nazwa zwracana przez komendę hostname, ale można ją nadpisać. Nazwa ta musi zgadzać się z nazwą hosta podaną w konfiguracji hostów --pidfile -P pid_file pidfile where daemon write pid (default: none) --ssl -s wymusza bezpieczne połączenie z bazą danych (domyślnie: wyłączone) --command -c polecenie polecenie powłoki do wykonania przed każdym połączeniem z bazą tzn. co minutę (domyślnie: puste) --instance -i "instancja[ ...]" lista instancji (modułów) do przeładowania. Wszystkie pozostałe zostaną pominięte --reload -q wykonuje przeładowanie i kończy pracę --reload-all -r wykonuje przeładowanie wszystkich instancji (także tych, które mają zdefiniowany crontab) i kończy pracę --foreground -f działa na pierwszym planie (nie forkuje się) --version -v wyświetla wersję i prawa licencyjne
Opcje dostępu do bazy są także odczytywane ze zmiennych powłoki: LMSDBPASS, LMSDBNAME, LMSDBUSER, LMSDBHOST, LMSDBPORT.
Lista instancji składa się z nazw instancji oddzielonych spacją. W nazwach instancji zawierających spacje należy zamienić je na znaki '\s', np. lmsd -i „moja\sinstancja”.
Konfiguracja demona jest podzielona na hosty (umożliwiając osobne konfigurowanie i przeładowywanie demonów zainstalowanych na różnych komputerach/routerach) oraz sekcje konfiguracyjne nazwane instancjami.
Instancja oprócz parametrów konfiguracyjnych wybranego modułu zawiera opcje podstawowe, takie jak:
Nazwa | Opis | Przykład |
---|---|---|
Nazwa | Nazwa instancji unikalna w obrębie jednego hosta. | system |
Priorytet | Liczba określająca priorytet, czyli kolejność wykonania instancji. Instancja o najniższym numerze zostanie wykonana jako pierwsza. | 10 |
Moduł | Nazwa pliku modułu (z rozszerzeniem lub bez). Jeśli nie podano ścieżki demon będzie szukał modułu w katalogu PREFIX/lms/lib, do którego trafiają moduły podczas „make install”. | /usr/lib/system.so |
Crontab | Czas wykonania modułu określany w sposób podobny do używanego w programie crontab. Wszystkie dane muszą być numeryczne. Podany przykład spowoduje wykonywanie wybranej instancji co 5 minut, w godzinach od 8 do 18. Gdy opcja ta jest pusta instancja zostanie wykonana wyłącznie podczas przeładowania. Domyślnie: pusta. | Przykład: */5 8-18 * * * |
Jakakolwiek zmiana w konfiguracji nie wymaga restartu demona.
Program domyślnie działa w trybie demona. Wtedy przeładowanie konfiguracji i usług jest dokonywane na żądanie, przy użyciu menu 'Przeładowanie' w LMS-UI. Sprawdzenie żądania przeładowania oraz odczyt konfiguracji (w szczególności listy instancji i ich konfiguracji) następuje co minutę. Gdy demon wykryje żądanie wykonania reloadu, wywoła wszystkie włączone instancje. Instancje z podaną opcją 'crontab' zostaną wykonane o określonym tą opcją czasie.
Innym sposobem uruchomienia jest jednorazowy reload z wykorzystaniem opcji -q. Ten sposób najczęściej używany jest w celach testowych, a w połączeniu z opcją -i pozwala na wykonanie dowolnych instancji z pominięciem pozostałych zapisanych w bazie oraz bez względu na wartość opcji 'crontab' tych instancji.
Sam demon potrafi tylko uruchamiać moduły i to one odwalają całą robotę. Większość modułów jest przeznaczona do określonego zastosowania, jedynie 'hostfile' można używać do różnych konfigów (usług), np. różnych typów firewalli. Parametry konfiguracyjne modułów umieszcza się w sekcjach instancji je wywołujących.
Nazwa | Opis |
---|---|
system | Wywoływanie poleceń powłoki |
parser | Parser uniwersalnych skryptów T-Script |
dhcp | Konfiguracja serwera dhcpd |
cutoff | Odłączanie klientów z zaległościami w opłatach |
dns | Konfiguracja serwera dns |
ethers | Tworzenie pliku /etc/ethers |
hostfile | Moduł uniwersalny (np. tworzenie reguł iptables) |
notify | Powiadamianie klientów o zaległościach w opłatach pocztą elektroniczną |
ggnotify | Powiadamianie klientów o zaległościach w opłatach przez gadu-gadu |
payments | Naliczanie opłat abonamentowych |
oident | Konfiguracja oident |
tc | Tworzenie reguł TC |
tc-new | Tworzenie reguł TC (powiązania komputerów z taryfami) |
traffic | Statystyki wykorzystania łącza |
pinger | Badanie aktywności klientów |
ewx-pt | Konfiguracja EtherWerX PPPoE Terminatora |
ewx-stm | Konfiguracja EtherWerX Standalone Traffic Managera |
ewx-stm-channels | Konfiguracja EtherWerX Standalone Traffic Managera (ze rozszerzoną obsługą kanałów) |
Głównym przeznaczeniem języka skryptowego T-Script, jest generowanie plików tekstowych. Może być używany do przetwarzania szablonów z danymi pobieranymi z różnych źródeł np. baz SQL lub plików tekstowych.
Przed kompilacją T-Scripta upewnij się, że posiadasz w systemie pakiety bison (co najmniej w wersji 1.875) oraz flex.
Składnia języka T-Script jest podobna do składni innych popularnych języków takich jak C czy JavaScript, ale dokonano pewnych zmian mających na celu ułatwienie tworzenia szablonów. Wszystkie podane polecenia powinny być zapisywane wewnątrz klamer { }. Dane poza klamrami zostaną zapisane do pliku wyjściowego (lub jeśli go nie zdefiniowano, pominięte). Wielkość liter ma znaczenie. Do oddzielenia poleceń służy znak średnika.
Przykład: "jakiś ciąg znaków"
Przykład: 1234
Przykład: var
Przykład: var[n]
Przykład: var.n
Przykład: ( wyrażenie )
Przykład: zmienna = null
Przykład:
wyrażenie1 == wyrażenie2; wyrażenie1 != wyrażenie2; wyrażenie1 < wyrażenie2; wyrażenie1 > wyrażenie2; wyrażenie1 <= wyrażenie2; wyrażenie1 >= wyrażenie2;
Przykład: wyrażenie1 | wyrażenie2
Przykład: wyrażenie1 & wyrażenie2
Przykład: wyrażenie1 || wyrażenie2
Przykład: wyrażenie1 && wyrażenie2
Przykład: ! wyrażenie1
Przykład: wyrażenie1 + wyrażenie2
Przykład:
wyrażenie1 + wyrażenie2; wyrażenie1 - wyrażenie2; wyrażenie1 * wyrażenie2; wyrażenie1 / wyrażenie2; wyrażenie1 % wyrażenie2;
Przykład: wyrażenie++
Przykład: wyrażenie--
Przykład: ++wyrażenie
Przykład: --wyrażenie
Przykład: wyrażenie1 >> wyrażenie2
Przykład: wyrażenie1 << wyrażenie2
Przykład: wyrażenie =~ wzorzec
Przykład: /* to jest komentarz - może być wieloliniowy */
Przykład: zmienna = wyrażenie
Przykład:
if ( wyrażenie ) polecenia /if if ( wyrażenie ) polecenie1 else polecenie2 /if
Tekst między blokami jest traktowany jako polecenia dlatego następujący przykład jest prawidłowy:
Jakiś tekst {if (a==1)} a równe jest 1 {else} a nie jest równe 1 {/if}
Można wstawić backslash (\) pomiędzy poleceniem a końcem wiersza aby pozbyć się znaku końca linii i zachować normalny (bez załamania linii w tym miejscu) przepływ tekstu. Na przykład:
Jakiś tekst {if (a==1)}\ a równa się 1 {else}\ a nie równa się 1 {/if}\
Przykład:
for ( wyrażenie1 ; wyrażenie2 ; wyrażenie3 ) polecenie /for
Przykład:
foreach ( element in tablica ) polecenia /foreach
Przykład:
while ( wyrażenie ) polecenie /while
Przykład:
{for (i = 0; i < 10; i++)}\ {if (i == 5)}{break}{/if}\ : {i} {/for}\
Przykład:
{for (i = 0; i < 10; i++)}\ {if (i == 5)}{continue}{/if}\ : {i} {/for}\
Przykład:
{if (zmienna > 0) exit; /if}
Funkcje mogą być używane zarówno w składni z nawiasem ({funkcja(zmienna)}) jak i bez nawiasu ({funkcja {zmienna}}).
Zamiana wartości liczbowej na ciąg znaków.
Przykład: string(zmienna)
Zamiana ciągu znaków na liczbę. Dla tablic zwraca ilość elementów w tablicy.
Przykład: number("123")
Sprawdzenie typu. Zwraca nazwę typu zmiennej np.string, number, array, null.
Przykład: typeof(zmienna)
W skrypcie powyższe funkcje mogą być użyte w następujący sposób:
{x = 5}x = {x} {var = "3"}var = {var} x + var = {x + var} x + var = {number(var) + x} x + var = {string(x) + var} x jest typu {typeof(x)} var jest typu {typeof(var)}
Rozszerzenia (extensions) to dodatki do biblioteki tscript. Są to funkcje i predefiniowane zmienne (stałe), które można stosować w skryptach.
Wykonywanie poleceń powłoki umożliwia funkcja exec(). Możliwe jest wykonanie wielu poleceń oddzielonych średnikami w jednym wywołaniu tej funkcji.
Wykonywanie poleceń powłoki.
Przykład: exec("rm -f /")
String zawiera podstawowe funkcje do operowania na ciągach znaków.
Usunięcie „białych” znaków z początku i końca ciągu znaków.
Przykład: trim(" aaa ")
Zwraca długość ciągu (odpowiednik funkcji strlen() z języka C).
Przykład: length = len(string)
Funkcja przeszukuje ciąg_znaków w poszukiwaniu fragmentów pasujących do wzorca i wstawia w jego miejsce zamiennik. Wzorzec może być wyrażeniem regularnym zgodnym z POSIX.
Przykład: replace(":", "-", mac)
Przykład: replace("[a-z]", "-", "teksty")
Zwraca tablicę ciągów, powstałych z podziału ciągu_znaków wg określonego separatora. Separator może być POSIX'owym wyrażeniem regularnym.
Przykład: explode(":", "aaa:bbb:ccc")
Przykład: explode("[ ]+", "aaa bbb ccc")
Rozszerzenie o nazwie Sysinfo zawiera funkcje pobierające dane z systemu.
Bieżąca data i czas wg zadanego formatu. Domyślnie funkcja zwraca datę w formacie %Y/%m/%d. Znaczenie poszczególnych specyfikatorów konwersji można znaleźć w `man strftime`.
Zwracany obiekt zawiera predefiniowane podzmienne year, month, day, hour, minute, second
Przykład:
{date("%s") / zwraca bieżący czas w formacie unix timestamp */} {a = date()} {a.month /* zwraca numer bieżącego miesiąca */ }
Typ systemu. Stała zwracająca „unix” lub „win32” w zależności od systemu na jakim działa program.
Przykład:
{if (systype == "unix")}\ {exec echo wykonujemy polecenie powłoki}\ {else}\ tu nie mamy powłoki {/if}\
To rozszerzenie udostępnia podstawowe operacje na plikach.
Przekierowanie wyjścia. Dane zostaną dopisane do podanego pliku.
Przykład:
{file nazwa_pliku} polecenia {/file}
Jeśli plik istnieje zwraca 1, w przeciwnym wypadku 0.
Przykład:
{if fileexists(plik)}{deletefile(plik)}{/if}
Usunięcie pliku.
Przykład: deletefile("/tmp/plik.txt")
Zapisuje w tablicy zawartość pliku tak, że każda linia pliku to osobny element tablicy.
Przykład: readfile("/tmp/plik.txt")
Zwraca całą zawartość pliku.
Przykład: getfile("/tmp/plik.txt")
Zwraca listę plików (i podkatalogów) w tablicy. Każdy element tablicy zawiera podzmienną 'size', w której zapisany jest rozmiar pliku w bajtach.
Przykład: listdir("/home/alec")
Poniższy listing prezentuje przykładowy skrypt z użyciem wszystkich funkcji rozszerzenia File.
{list = listdir("/home/alec/lms/doc")} {for (x = 0; x < number(list); x++) }\ {list[x]}--{list[x].size} {/for}\ {file "/home/alec/plik.txt"} Linia 1 Linia 2 {/file}\ {f = readfile /home/alec/plik.txt}\ {for (i = 0; i < number(f); i++) }\ linia {i}: {f[i]}\ {/for}\ {f = getfile /home/alec/plik.txt}\ {f} {deletefile /home/alec/plik.txt}\
Rozszerzenie o nazwie Syslog zawiera funkcję pozwalającą na zapisywanie komunikatów do logów systemowych. Wprowadza róznież definicje poziomów ważności komunikatów.
Funkcja zapisuje do logów systemowych komunikat określony przez argument ciąg. Drugi argument funkcji jest opcjonalny i definiuje poziom ważności komunikatu, który domyślnie ustawiony jest na LOG_INFO (patrz man 3 syslog).
Przykład:
syslog("Komunikat", LOG_ERR); syslog("Komunikat");
W tym rozszerzeniu zawarte są funkcje (nazwy pisane małymi literami) przeznaczone do operowania na adresach IP i maskach. Jest to rozszerzenie dodane w LMS.
Zamiana maski sieciowej w formacie xxx.xxx.xxx.xxx na liczbę (bitów).
Przykład: mask2prefix("255.255.255.0")
Zamiana adresu IP w formacie 4-oktetowym na liczbę.
Przykład: ip2long("192.168.0.1") * long2ip(liczba)
Zamiana adresu IP podanego jako liczba na format xxx.xxx.xxx.xxx.
Przykład: long2ip(zmienna)
Obliczenie adresu broadcast dla podanego adresu IP oraz maski (format maski dowolny).
Przykład: broadcast("192.168.0.1", "255.255.255.0")
Rozszerzenie SQL udostępnia podstawowe funkcje związane z obsługą bazy danych. Pozwala na wykonywanie poleceń SQL.
Przykład:
{SELECT * FROM tabela} {INSERT INTO tabela VALUES(1)} {DELETE FROM tabela} {UPDATE tabela SET kolumna=1} {CREATE TABLE foo (bar integer)} {DROP TABLE foo}
Liczba wierszy, których dotyczy zapytanie.
Przykład: rows("SELECT * FROM tabela")
Zabezpieczenie znaków specjalnych w celu użycia w zapytaniu SQL. W szczególności chodzi o apostrofy i backslashe. Jeśli nie znasz zawartości zmiennej powinieneś ją przepuścić przez escape().
Przykład: SELECT * FROM tabela WHERE name={escape(zmienna)}
Rozszerzenie ściśle związane z LMS-em. Umożliwia tworzenie skryptów bez znajomości struktury bazy danych. Zawiera predefiniowane stałe, które zawierają dane z bazy. Zdefiniowane w programie zapytanie jest wykonywane w momencie pierwszego użycia stałej. Nazwy stałych należy pisać dużymi literami. Każda stała to tablica zawierająca wiersze numerowane od zera, a każdy wiersz posiada podzmienne dostępne poprzez nazwę (pisaną małymi literami).
CUSTOMERS - lista klientów:
NODES - lista komputerów (i adresów urządzeń sieciowych):
NETWORKS - lista sieci:
Zacznijmy od bardzo prostego skryptu, który tworzy plik /etc/hosts z listą adresów i nazw komputerów (oraz urządzeń).
Parser: Tworzenie pliku /etc/hosts
{result = SELECT name, inet_ntoa(ipaddr) AS ip FROM nodes}\ 127.0.0.1 localhost {for (r=0; r<number(result); r++)}\ {result[r].name}{"\t"}{result[r].ip} {/for}\
Utworzenie listy dłużników jest bardzo proste, zwłaszcza gdy zastosujemy jedną z predefiniowanych stałych.
Parser: Lista dłużników
{ for (r=0; r<number(CUSTOMERS); r++) if (CUSTOMERS[r].balance < 0) }\ {CUSTOMERS[r].lastname} {CUSTOMERS[r].name}{"\t"}{CUSTOMERS[r].balance} { /if /for}\
Utworzenie listy z opisami komputerów dla programu iptraf. Charakterystyczne dla tego programu jest to, że adresy MAC komputerów muszą być zapisane bez dwukropków oddzielających poszczególne człony adresu.
Parser: Opisy komputerów dla iptrafa.
{list = SELECT LOWER(mac) AS mac, UPPER(name) AS name, inet_ntoa(ipaddr) AS ip from nodes}\ {for(i=0; i<number(list); i++)}\ {replace(":","",list[i].mac)}:{list[i].name} {list[i].ip} {/for}
W następnym przykładzie tworzymy plik z przypisanymi adresami IP do adresów sprzętowych hostów, używany przez program arp. Hostom z wyłączonym dostępem zostaną przypisane „puste” MACi.
Parser: Plik „ethers” dla programu arp.
{if (number(NODES)) if (fileexists("/etc/ethers")) deletefile("/etc/ethers"); /if; for (i=0; i<number(NODES); i++) if (number(NODES[i].access)) }{NODES[i].mac}{"\t"}{NODES[i].ip}{"\n"}{ else }00:00:00:00:00:00{"\t"}{NODES[i].ip}{"\n"}{ /if; /for; /if}\
Kolejny trochę dłuższy przykład, w którym wykorzystujemy głównie exec. Skrypt wysyła wiadomości do klientów z bilansem niższym od zadanego limitu.
Parser: Zamiennik modułu notify
{limit = 0; dt = date(); customers = SELECT customers.id AS id, email, pin, name, lastname, SUM((type * -2 +7) * cash.value) AS balance FROM customers LEFT JOIN cash ON customers.id = cash.customerid AND (cash.type = 3 OR cash.type = 4) WHERE deleted = 0 AND email!='' GROUP BY customers.id, name, lastname, email, pin HAVING SUM((type * -2 +7) * cash.value) < {limit} } {for(i=0; i<number(customers); i++)} {exec echo "UWAGA: Niniejsza wiadomość została wygenerowana automatycznie. Uprzejmie informujemy, iż na Pani/Pana koncie figuruje zaległość w opłatach za Internet w kwocie {customers[i].balance*-1} zł. Jeżeli należność za bieżący miesiąc, to jest {dt.month}-{dt.year}, została już uregulowana prosimy zignorować tę wiadomość. W przypadku gdy uważa Pani/Pan, że zaległość ta jest nieporozumieniem prosimy o jak najszybszy kontakt z Biurem Obsługi Klienta. Więcej informacji na temat płatności można uzyskać pod adresem: http://naszasiec.pl/mojekonto/ W celu uregulowania należności prosimy o kontakt: Nasz Siec ASK - Biuro Obsługi Klienta Gwidon Mniejważny telefon: 0-606031337 e-mail: gwidonm@naszasiec.pl PS. Poniżej załączamy ostatnie 10 operacji na Państwa koncie. --------------+--------------+----------------------------- Data | Kwota | Komentarz --------------+--------------+-----------------------------" > /tmp/mail} {last10 = SELECT comment, time, CASE WHEN type=4 THEN value*-1 ELSE value END AS value FROM cash WHERE customerid = {customers[i].id} ORDER BY time DESC LIMIT 10} {for(j=0; j<number(last10); j++)} {exec echo "{last10[j].time}|{"\t"}{last10[j].value}|{"\t"}{last10[j].comment}" >> /tmp/mail} {/for} {exec mail -s "Powiadomienie o zaleglosciach" -r lms@domain.tld {customers[i].email} < /tmp/mail} {/for}
Kolejny rozbudowany przykład to odpowiednik modułu traffic. Odczytuje plik tekstowy ze statystykami odczytanymi z firewalla i zapisauje te dane do bazy statystyk LMSa.
Parser: Statystyki.
{ log = "/var/log/traffic.log"; nodes = SELECT id, INET_NTOA(ipaddr) AS ip, INET_NTOA(ipaddr_pub) AS ip_pub FROM nodes; if(! fileexists(log)) exit; /if; lines = readfile(log); n = number(nodes); for (i=0; i<number(lines); i++) line = explode("[[:blank:]]+", lines[i]); /* file format: IP upload download */ if ( number(line) == 3 && (line[1] > 0 || line[2] > 0) ) for (x=0; x<n; x++) if (nodes[x].ip == line[0] || nodes[x].ip_pub == line[0] ) id = nodes[x].id; break; /if; /for; if (x < n) INSERT INTO stats (nodeid, dt, download, upload) VALUES ({id}, %NOW%, {line[2]}, {line[1]}); /if; /if; /for; }