2008 02 Polowanie na wirusy – GUI dla programu ClamAV [Programowanie]


Programowanie
GUI
Polowanie na wirusy
 GUI dla programu
ClamAV
Marek Sawerwain
Od początku swojego istnienia Linux był i jest przykładem system operacyjnego, w którym problem
wirusów jest problemem marginalnym. Należy, naturalnie, odnotować, iż istnieje kilkanaście wirusów,
a niektóre zródła podają, że istnieje nawet sto wirusów. Jednakże w porównaniu do systemu Microsoft
Windows jest to wielkość znikoma. Co więcej, aby w praktyce spotkać program, który jest zainfekowany,
naprawdę trzeba mieć wiele szczęścia albo ogromnego pecha.
imo braku zagrożenia istnieje wiele Schemat blokowy programu
programów antywirusowych, których Nasz program nie jest dużym projektem. Mimo to pierw-
zadaniem jest wykrywanie wirusów np. szy krok jaki warto wykonać to przygotowanie diagramu
Mw poczcie albo w plikach systemu Win- najważniejszych zdarzeń, które pojawiają się w programie.
dows, szczególnie jeśli nasz system pełni rolę serwe- Wykonanie takiego diagramu albo schematu ułatwi dal-
ra plików dla użytkowników systemu Windows. A pro- sze tworzenie programu. Rysunek 1 przedstawia tego typu
blem wirusów w przypadku użytkowników popularnych schemat. Odzwierciedla on również podstawową własność
 okienek jest szczególnie istotny. naszego programu, czyli rekurencyjne przeszukiwanie kata-
Jednym z dostępnych programów antywirusowych logu w poszukiwaniu wirusów.
jest ClamAV. Jest to aplikacja OpenSource, czyli z peł- Pierwsze czynności są związane z inicjalizacją bi-
nym kodem zródłowym na licencji GNU GPL. Stoso- bliotek GTK+ oraz ClamAV, co zostało odzwierciedlo-
wanie tego programu w praktyce sprowadza się do wy- ne w diagramie. Następnie wchodzimy w główną pę-
woływania poszczególnych komend z linii poleceń. tlę zdarzeń GTK+, co powoduje iż nasza aplikacja bę-
Za skanowanie katalogu odpowiedzialne jest polece- dzie reagować, mówiąc bardzo kolokwialnie, na przesu-
nie clamscan. Nie jest to wygodny sposób, szczególnie wający się kursor myszki i kliknięcia przycisków mysz-
dla początkujących użytkowników. Warto byłoby dys- ki. Naturalnie po wybraniu przycisku uruchamiającego
ponować jakimś programem działającym w środowi- proces skanowania, zgodnie z diagramem, będziemy re-
sku graficznym np.: w GNOME. Istnieją dwa programy kurencyjne przeszukiwać katalogi. Podczas tego proce-
tego typu korzystające z aplikacji ClamAV o nazwach su nadal będzie aktywne główna pętla zdarzeń, co ozna-
ClamTK oraz AVScan. Jednak my spróbujemy opraco- cza, że będziemy mogli przerwać proces skanowania al-
wać własny tego typu program, co  jak się za chwilę bo tylko go wstrzymać, aby po chwili ponownie konty-
okaże - nie jest zadaniem trudnym. nuować skanowanie.
64 luty 2008
linux@software.com.pl
Programowanie
GUI
Podczas skanowania interesować będą nas mieszczać samodzielnie. Zakładając tak- W podobny sposób uzyskamy odniesienia do
przede wszystkim informacje o wykrytych wi- że, że okno aplikacji nie będzie zmieniać pozostałych widgetów, jakie są nam potrzeb-
rusach, stąd też na naszym diagramie pojawiło swojego rozmiaru, to korzystanie z kontrol- ne. Kolejna czynność to wykonanie połączeń
się odpowiednie zapytanie. Wyjście z procesu ki GtkFixed jest wygodniejsze. Poszczegól- między sygnałami a obsługą tych sygnałów,
skanowania może nastąpić w dwóch przypad- ne widgety powinny także posiadać znaczą- generowanych np. w sytuacji, gdy użytkownik
kach. W pierwszym przypadku proces skano- ce nazwy, szczególnie te, które służą bezpo- przyciskiem Start rozpocznie skanowanie. Po-
wania zostanie zakończony, ponieważ wszyst- średnio do komunikacji z użytkownikiem. nieważ nasza aplikacja została w dość specjal-
kie pliki i katalogi zostaną sprawdzone. Dru- Uzyskamy w ten sposób bardziej czytelny ny sposób skompilowana (więcej informacji w
ga sytuacja to przerwanie procesu skanowania kod zródłowy. ramce pt.  Kompilacja projektu ), to nie musi-
przez użytkownika. my samodzielnie podłączać sygnałów, wystar-
Schemat może zostać uzupełniony o Podstawowe czy skorzystać z funkcji:
dodatkowe zdarzenia, np. czasowe wstrzy- czynności w funkcji main
manie procesu skanowania, jednakże przy W dalszych punktach zostaną przedstawio- glade_xml_signal_autoconnect(xml);
pierwszym tworzeniu schematu warto spró- ne inne szczegóły naszego programu, jed-
bować przedstawić tylko najważniejsze zda- nak mimo wszystko trzeba zacząć od po- Do wykonania zostały jeszcze dwie czynności.
rzenia. czątku, czyli od zawartości funkcji ma- Pierwsza czynność odnosi się do przycisków
in. Pierwsza czynność, jaką należy bez- Stop oraz Pauza  staną się one nieaktywne, po
Interfejs użytkownika warunkowo wykonać, to inicjalizacja bi- wykonaniu dwóch poniższych instrukcji:
Rysunek 2. pokazuje proces projektowa- blioteki GTK+ za pomocą funkcji gtk_in-
nia interfejsu naszego programu. Ponieważ it. Potem, możemy wczytać plik z opisem gtk_widget_set_sensitive(PauseSearchB
program tworzymy dla środowiska GTK+, interfejsu: TN, FALSE);
to zastosujemy program GLADE w naj- gtk_widget_set_
nowszej wersji 3.x.x. Jednak nie będziemy xml = glade_xml_new( "mainwin.glade", sensitive(StopSearchBTN, FALSE);
potrzebować żadnych specjalnych widge- NULL, NULL );
tów, więc i starsza wersja programu GLA- Druga czynność odnosi się do dwóch kontro-
DE 2.x.x może zostać z powodzeniem uży- Działania jakie podejmujemy w dalszej części lek GtkTextView, w których umieszczamy in-
ta. Ma ona jedną przewagę nad nowszą, bo- programu wymagają uzyskania referencji np. formacje pojawiające się podczas skanowania.
wiem można za jej pomocą wygenerować do kontrolki GtkTextView, w której będziemy Należy utworzyć bufory, do których będziemy
kod zródłowy programu, który utworzy za- umieszczać komunikaty o wykrytych wiru- wprowadzać informacje. Dla kontrolki o na-
projektowane okno. Jednakże my nie korzy- sach. Uzyskanie odniesienia jest bardzo proste zwie LogTextView utworzenie bufora przed-
stamy z tej funkcjonalności, gdyż interfejs i realizujemy je w następujący sposób: stawia się następująco:
będziemy wczytywać dynamicznie za po-
mocą biblioteki libglade. LogTextViewInfectedFiles = glade_xml_ buffer = gtk_text_buffer_new( NULL );
Sam interfejs, jak potwierdza to Rysu- get_widget ( xml, "LogTextViewInfecte gtk_text_view_set_buffer( GTK_TEXT_
nek 2, to typowe okno. Znajdują się na nim dFiles" ); VIEW ( LogTextView ), buffer );
cztery przyciski, za pomocą których użyt-
kownik będzie mógł uruchomić proces ska-
nowania lub wstrzymać ten proces na chwi-
lę, albo go zatrzymać. Mamy także pole
edycyjne, gdzie można wpisać ścieżkę do-
stępu do katalogu, który wraz z podkatalo-
gami zostanie przeskanowany.
Mamy także widget o nazwie GtkNo-
tebook. Ponieważ chcemy, aby informacje
o wirusach były umieszczane w oddziel-
nej kontrolce, to widget GtkNotebook bę-
dzie posiadał dwie zakładki. W pierwszej
umieszczane będą informacje o kolejnych
przeskanowanych plikach, natomiast w dru-
giej zakładce znajdować się będą informa-
cje o plikach, w których wykryto obecność
wirusów.
W oknie znajdują się także etykiety oraz
pewne elementy ozdobne oraz pasek statu-
su. Ważną rolę pełni kontrolka, której nie
widać bezpośrednio  kontrolka GtkFixed.
Jest to tzw. kontrolka rozmieszczenia, po-
nieważ projektujemy okno aplikacji, które-
go poszczególne elementy będziemy roz- Rysunek 1. Diagram podstawowych zdarzeń występujących w aplikacji ClamAV-GUI
www.lpmagazine.org 65
Programowanie
GUI
Ostatnim elementem związanym z interfej-
sem jest wyświetlenie wszystkich kontrolek
za pomocą funkcji gtk_widget_show_al-
l(mainwin);. Jednakże, nie są to wszyst-
kie wstępne czynności jakie trzeba wyko-
nać. Należy jeszcze uruchomić system Cla-
mAV, który będzie służył do wykrywania wi-
rusów w plikach.
W pierwszej kolejności deklarujemy trzy
podstawowe zmienne:
struct cl_engine *clav_engine = NULL;
struct cl_limits limits;
unsigned int sigs = 0;
W naszym programie są to zmienne global-
ne, każda funkcja ma do nich bezpośredni do-
stęp. Uruchomienie systemu ClamAV wymaga
w pierwszej kolejności załadowania bazy z sy-
gnaturami wirusów:
ret = cl_load(cl_retdbdir(), &clav_
Rysunek 2. Projektowanie interfejsu w programie GLADE-3 engine, &sigs, CL_DB_STDOPT);
Funkcja cl_retdbdir wskazuje na domyśl-
Listing 2. Funkcja testująca, czy plik został zainfekowany wirusem
ny katalog z bazą wirusów. Ważna jest rów-
void scanfile_for_virus( const char *file_name, struct cl_engine *engine ) { nież zmienna clav_engine, bowiem to ona
char tmp[512]; const char *virus_name; będzie reprezentować system antywirusowy.
int fd, ret_code; unsigned long int size = 0; Przydatna, w celach informacyjnych jest tak-
static long lines = 0; GtkTextIter start_iter, end_iter ; że zmienna sigs, gdyż w niej znajdzie się in-
if( engine == NULL) { formacja o tym, ile sygnatur wirusów zawie-
sprintf( &tmp[0], "Błąd wewnętrzny, system ClamAV nie został poprawnie ra załadowana baza.
zainicjalizowany!\n"); Poprawne załadowanie bazy oznacza, że
gtk_text_buffer_insert_at_cursor(buffer, &tmp[0], strlen(&tmp[0])); możemy ostatecznie uruchomić system za
return ;} pomocą funkcji cl_build: cl_build(c-
ret_code = CL_CLEAN; lav_engine);.
ret_code = cl_scanfile( file_name, &virus_name, &size, engine, &limits, Przed wejściem do głównej pętli GTK+,
CL_SCAN_STDOPT); dzięki wywołaniu gtk_main, istotna jest
if ( ret_code == CL_VIRUS ) { jeszcze jedna czynność, a mianowicie wy-
sprintf( &tmp[0], "plik o nazwie %s jest zainfekowany wirusem %s\n", pełnienie struktury limits np. w następują-
file_name, virus_name); cy sposób.
gtk_text_buffer_insert_at_cursor(buffer_infected_files, &tmp[0],
strlen(&tmp[0]));} limits.maxfiles = 1000;
if ( ret_code == CL_CLEAN ) { limits.maxfilesize = 10 * 1048576;
sprintf( &tmp[0], "plik o nazwie %s wydaje sie bezpieczny\n", file_ limits.maxreclevel = 5;
name); limits.maxmailrec = 64;
gtk_text_buffer_insert_at_cursor(buffer, &tmp[0], strlen(&tmp[0])); limits.maxratio = 200;
lines++;
if(lines > log_size) { Wartości w tej zmiennej odnoszą się do pli-
lines = 0; ków z archiwami oraz do plików z pocztą.
gtk_text_buffer_get_start_iter ( buffer, &start_iter ); Określamy, ile plików może maksymalnie
gtk_text_buffer_get_end_iter ( buffer, &end_iter ); zawierać archiwum, wielkość skanowanych
gtk_text_buffer_delete ( buffer, &start_iter, &end_iter );}} plików, oraz co ważne określamy maksymal-
ifile++; ny poziom rekursji dla archiwów oraz plików
sprintf(&tmp[0], "%d / %d (%d KB)", ifile, files, space); z pocztą. Oznacza to, że przypadku występo-
gtk_label_set_label(GTK_LABEL(FilesCountLBL), &tmp[0]); wania wielu podkatalogów, niestety system
} ClamAV nie przeskanuje pewnych elemen-
tów, ale z drugiej strony zyskamy na czasie.
Strukturę limits będziemy wykorzystywać
66 luty 2008
Programowanie
GUI
podczas skanowania, więc można ją tworzyć Proces skanowania katalogów ment depth, jest potrzebny, aby śledzić po-
również w innym miejscu programu albo po- Skanowaniem zajmuje się funkcja o nazwie ziom rekurencji.
traktować jej elementy jako pola do konfigu- virus_dir_search. Jej zadanie to rekuren- Po deklaracji potrzebnych zmiennych
racji z poziomu interfejsu użytkownika, co cyjne przeszukiwanie katalogów w poszuki- dotyczących struktury katalogowej spraw-
byłoby bardzo dobrym rozwiązaniem. waniu wirusów. Przy czym za wykrycie wi- dzamy wartość parametru depth. Parametr
rusa odpowiedzialna jest inna funkcja o na- ten, jak powiedziano przed chwilą, oznacza
Zanim zaczniemy skanować zwie scanfile_for_virus, ale o tej funkcji głębokość rekurencji, jeśli jego wartość jest
Choć program jaki piszemy jest raczej nie- więcej szczegółów zostało podanych w dal- większa niż wartość zmiennej max_depth,
wielki (około 12kb kodu zródłowego), to szej części artykułu. to nastąpi powrót z funkcji. Oznacza to, iż
niestety nie możemy opisać wszystkich Funkcja virus_dir_search posiada nasza funkcja nie będzie skanować plików
szczegółów, dlatego pokrótce przedstawimy trzy parametry, pierwszy o nazwie dir_na- leżących poniżej pewnej głębokości. Spraw-
czynności jakie są wykonywane przed roz- me  to nazwa katalogu do sprawdzenia czy dzenie drugiej zmiennej, tym razem global-
poczęciem procesu skanowania. zawiera pliki z wirusami. Drugi argument, nej is_stop, jest potrzebne aby sprawdzić,
Jeśli użytkownik za pomocą klawisza Start engine, reprezentuje bibliotekę ClamAV, czy użytkownik klawiszem Stop nie prze-
rozpocznie proces skanowania, to uruchamia która odpowiada za wykrycie, czy podany rwał procesu skanowania. Jeśli tak się stało,
funkcję on_StartSearchBTN_clicked. Funk- plik zawiera wirusa. Ostatni, trzeci argu- to ponownie opuszczamy funkcję.
cja ta inicjuje proces skanowania, jednakże nim
zostanie przeprowadzone skanowanie, pierwszą
Listing 1. Funkcja przeszukująca rekurencyjnie wskazany katalog
czynnością jest uzyskanie nazwy katalogu, któ-
ry będzie skanowany: void virus_dir_search( const char *dir_name, struct cl_engine *engine,
unsigned int depth ) {
strcpy( &beg_dirname[0], gtk_entry_ DIR *dd;
get_text(GTK_ENTRY(StartDirectoryEDT struct dirent *dent ;
))); struct stat statbuf ;
char *fname ;
Przed właściwym skanowaniem uruchamia- if ( depth > max_depth ) return;
na jest funkcja o nazwie disk_usage. Zada- if (is_stop == _YES_) return ;
niem tej funkcji jest wyznaczenie liczby pli- depth++;
ków, jakie zostaną przeskanowane. Umożli- if((dd = opendir(dir_name)) != NULL) {
wi to użytkownikowi śledzenie postępów w while((dent = readdir(dd))) {
skanowaniu. if(dent->d_ino) {
Do innych zadań funkcji on_StartSe- if( strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..") )
archBTN_clicked należy zablokowanie dzia- {
łania pewnych klawiszy. Po uruchomieniu fname = malloc(strlen(dir_name) + strlen(dent->d_name) +
skanowania przyciskiem Start wszystkie sta- 2);
ją nieaktywne, a wykonujemy to np. dla przy- sprintf(fname, "%s/%s", dir_name, dent->d_name);
cisku Start w następujący sposób: if(lstat(fname, &statbuf) != -1) {
if(S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_
gtk_widget_set_sensitive(StartSearchB mode)) {
TN, FALSE); virus_dir_search(fname, engine, depth);
}
Po zebraniu potrzebnych informacji za po- else {
mocą wywołania disk_usage, aktywne bę- if(S_ISREG(statbuf.st_mode))
dą przyciski Pauzy oraz Stop. Informację o scanfile_for_virus( fname, engine );
tym, iż rozpoczęto skanowanie umieszczamy }
w dwóch miejscach, w kontrolce GtkViewText }
o nazwie LogTextViewInfectedFiles: free(fname);
}
sprintf( &tmp[0], ".: Skanowanie }
rozpoczęte :.\n"); while(gtk_events_pending())
gtk_text_buffer_insert_at_ gtk_main_iteration();
cursor(buffer_infected_files, &tmp[0], while(gtk_events_pending() || is_pause == _YES_)
strlen(&tmp[0])); gtk_main_iteration();
if (is_stop == _YES_) break;
oraz na pasku statusu: }
}
gtk_statusbar_push(GTK_ closedir(dd);
STATUSBAR(StatusBar), 0, "szukanie }
wirusów ...");
www.lpmagazine.org 67
Programowanie
GUI
Jeśli obydwa warunki nie były prawdzi- przerwać skanowanie. Jest to spowodowane Natomiast argument trzeci jest wyko-
we, to następuje zwiększenie wartości para- faktem, iż w katalogu może znajdować się rzystywany do wpisania wielkości przeska-
metru depth. Następnie za pomocą opendir wiele plików, i w takim przypadku może mi- nowanego pliku. Argument czwarty to kilka
próbujemy uzyskać dostęp do wskazanego nąć wiele czasu, aż nastąpi kolejne wywoła- razy już wspominany wskaznik na struktu-
katalogu  jeśli to się nie uda, to opuszcza- nie rekurencyjne, czyli sprawdzenie warto- rę systemu ClamAV. W kolejnym argumen-
my spory fragment kodu i w rzeczywistości ści zmiennej is_stop: cie podajemy ograniczenia zawiązane z ar-
nie wykonamy żadnych istotnych czynno- chiwami. W ostatnim argumencie możemy
ści. W przeciwnym przypadku przy pomo- if (is_stop == _YES_) break; określić jakie typy plików możemy skano-
cy pętli while i funkcji readdir odczytuje- wać. Podanie wartości CL_SCAN_STDOPT
my poszczególne wpisy z katalogu, o na- Test na obecność wirusa określa typowy zestaw plików.
zwie zapisanej w zmiennej dir_name. Na- Poprzednia omawiana funkcja przeglądając Wynik działania tej funkcji zostanie za-
leży jednak zwrócić baczną uwagę na to, strukturę katalogową znajduje pliki, które pisany do zmiennej ret_code. Jeśli wartość
czy badana nazwa nie jest przypadkiem po- należy sprawdzić, czy zostały zainfekowa- tej zmiennej będzie równa CL_CLEAN, to sys-
jedynczą kropką oznaczającą katalog aktu- ne przez wirusa. Tym zadaniem zajmuje się tem ClamAV nie wykrył wirusa. Jednak w
alny, bądz czy nie są to dwie kropki, ozna- funkcja scanfile_for_virus. Listing 2 za- przypadku otrzymania wartości CL_VIRUS
czające katalog nadrzędny. Te dwa przypad- wiera pełny kod zródłowy tej funkcji. Przyj- mamy pewność, iż wskazany plik zawiera
ki trzeba pominąć. muje ona dwa argumenty. W pierwszym wirusa, a jego nazwa zostanie umieszczona
Jeśli nazwa jest właściwa, to nale- zgodnie z nazwą podajemy nazwę pliku, w zmiennej virus_name.
ży sprawdzić dwie kolejne możliwości. Po a w drugim wskaznik reprezentujący sys- W tym przypadku do jednej z kontrolek
pierwsze, nazwa może reprezentować plik. tem ClamAV. GtkTextView, poprzez bufor buffer_infec-
Jeśli tak jest, to plik ten zostanie przeska- Pierwsza czynność jest bardzo istot- ted_files, wpisujemy odpowiedni komuni-
nowany. Nazwa może być również katalo- na, należy sprawdzić czy argument engine kat. Zwróćmy też uwagę, że w przypadku
giem, w tym przypadku następuje ponow- jest równy NULL. Jeśli tak jest, to niestety plików niezainfekowanych co pewien czas
ne wywołanie funkcji virus_dir_search, system ClamAV, nie może zostać zastoso- kasujemy zawartość okna z komunikata-
co należy rozumieć jako rekurencyjne wy- wany do sprawdzenia, czy w pliku znajdu- mi, aby nie obciążać nadto kontrolki Gtk-
wołanie. W omawianej funkcji mamy jesz- je się wirus. Dlatego za pomocą słowa klu- TextView.
cze taką oto pętlę: czowego return opuszczamy funkcję scan- Wielkość ta, a dokładniej ilość linii jest
file_for_virus. Gdy opisana sytuacja nie zapisana w zmiennej log_size. Ostatnim
while(gtk_events_pending() || is_pause występuje, możemy sprawdzić czy plik jest elementem jest uaktualnienie etykiety o na-
== _YES_) bezpieczny. Do zmiennej ret_code wpi- zwie FilesCountLBL. W niej wyświetlamy,
gtk_main_iteration(); sujemy wartość CL_CLEAN. Potem jeste- ile plików zostało już przeskanowanych jak
śmy gotowi to wywołania funkcji o nazwie również całkowitą ilość plików.
Oznacza ona, iż jeśli pojawiły się jakieś cl_scanfile, która sprawdzi, czy plik jest
zdarzenia GTK+, to zostaną one obsłużo- bezpieczny: Podsumowanie
ne. Jest to potrzebne, aby nasza aplikacja re- Wiele istotnych elementów można dodać do
agowała na uaktualnienia etykiet, np. licz- ret_code = cl_scanfile( file_name, naszego programu. Przede wszystkim wię-
bę przeskanowanych plików. Powyższa pę- &virus_name, &size, engine, &limits, cej opcji związanych z konfiguracją. np.
tla while będzie też czekać, jeśli użytkow- CL_SCAN_STDOPT); maksymalna głębokość rekursji podczas
nik wybrał przycisk Pauzy  skanowanie skanowania.
w tym przypadku zostanie wstrzymane do Jej poszczególne argumenty są następujące: Warto także dodać możliwość śledze-
czasu, aż zmienna is_pause zmieni swo- pierwszy to nazwa pliku do sprawdzenia, dru- nia tego parametru, np. w sposób jak śledzi-
ją wartość.A Należy także ponownie spraw- gi argument będzie zawierał nazwę wirusa, je- my liczbę przeskanowanych plików, czyli za
dzić, czy użytkownik przyciskiem Stop chce śli jakiś zostanie wykryty. pomocą prostej etykiety. Jak już wspomnia-
no w artykule, struktura limits również jest
świetnym kandydatem do konfiguracji przez
użytkownika.
Możemy również tworzyć log, czyli za-
W Sieci
pisywać wszystkie informacje o przeskano-
" Program antywirusowy ClamAV  http://www.clamav.org
wanych plikach do pliku tekstowego.
" Strona domowa projektu GTK+  http://www.gtk.org
Możliwości dodatkowych unowocze-
" Strona programu GLADE-3  http://www.glade.gnome.org
śnień jest wiele, możemy zaimplemento-
wać możliwość kasowania plików zarażo-
nych przez wirusy, lub wprowadzić opcję
przenoszenia ich do specjalnego archi-
O autorze
wum.
Dlatego, jak zawsze na koniec, zachę-
Autor zajmuje się tworzeniem oprogramowania dla WIN32 i Linuksa. Zainteresowania: teo-
cam do wprowadzania własnych, nawet naj-
ria języków programowania oraz dobra literatura.
mniejszych innowacji do przedstawionego
Kontakt z autorem: autorzy@linux.com.pl.
programu.
68 luty 2008


Wyszukiwarka

Podobne podstrony:
Sandemo Margit Saga O Ludziach Lodu 02 Polowanie Na Czarownice
2008 02 Multimedia dla początkujących użytkowników [Poczatkujacy]
estymacja?nych z?d na poziomie pow dla lat95 02
2008 02 Extreme Programming i CMMI – kreatywność czy dyscyplina (Cz 3) [Inzynieria Oprogramowania]
02 Ramka na stronę świąteczna
Żak Bucholc J & Agnosiewicz M Polowanie na czarownicE
Psychologia kryzysu testy 2008 02
niemieckie polowanie na zebry
INWENTARYZATOR informacje dla programistow v03 070329
SIMR ALG1 EGZ 2008 02 07a rozw

więcej podobnych podstron