R 17 00 DOC


Programowanie rozproszone — DCOM

Technologia COM, której zarys i przykład zastosowania przedstawiliśmy w poprzednim rozdziale, zdobywa sobie coraz większą popularność. Coraz więcej systemowych usług Windows implementowanych jest już nie w postaci tradycyjnych interfejsów programisty (API — Application Programming Interface), lecz właśnie w formie obiektów COM. Mimo to jest ona — w swej oryginalnej postaci — dotknięta przez jedną poważną przypadłość, jaką jest ograniczenie obszaru działania do granic pojedynczego komputera: nie jest mianowicie możliwe umieszczenie klienta na komputerze innym niż serwer — dzieje się tak mimo „przezroczystego” z założenia charakteru samej technologii, to jest niewrażliwości zarówno klienta, jak i serwera na fizyczną lokalizację swego partnera.

Receptą na opisaną dolegliwość okazało się rozszerzenie technologii COM o elementy sieciowe, umożliwiające aktywowanie przez klienta zdalnych serwerów rezydujących na innych komputerach. Owa rozszerzona postać COM, zwana rozproszonym COM i oznaczana akronimem DCOM (ang. Distributed COM) oprócz elementów komunikacji sieciowej pomiędzy komponentami wprowadza ponadto dodatkowe elementy, głównie o charakterze zabezpieczającym; Przyjrzymy się im dokładniej w niniejszym rozdziale.

Czym jest DCOM?

Specyfikacja DCOM to nic innego jak sieciowa wersja COM, określana niekiedy żargonowo jako „COM na drutach” (ang. wired COM). Obiekt COM osiągalny nie bezpośrednio, lecz poprzez sieć, jest obiektem DCOM. Technicznie rzecz biorąc, DCOM stanowi obiektowe rozszerzenie protokołu zdalnego wywoływania procedur (ang. RPC — Remote Procedure Calls) stworzone wraz z pojawieniem się Windows NT 4.0. Popularność tych ostatnich w zastosowaniach korporacyjnych sprawiła, iż mimo innych konkurencyjnych technologii DCOM wciąż zajmuje wiodącą pozycję; technologia ta przekroczyła już zresztą ramy samych Windows, znajdując zastosowanie w kilku wersjach UNIXa i jako taka może być obecnie używana jako międzyplatformowe (cross-platform) rozwiązanie projektowe dla systemów rozproszonych.

DCOM wykorzystuje oryginalną koncepcję COM tzw. przezroczystej lokalizacji (ang. location transparency) co oznacza, iż wzajemne położenie fizyczne klienta i serwera jest nieistotne — mogą one rezydować na tym samym komputerze, na dwóch różnych komputerach w sieci lokalnej, czy nawet — dzięki Internetowi — na różnych kontynentach. Z punktu widzenia klienta serwer DCOM zlokalizowany jest w miejscu określonym przez stosowny wpis w Rejestrze systemowym.

Ta prostota jest jednakże tylko jedną stroną medalu. Zdalny dostęp do serwera obwarowany jest wieloma regułami bezpieczeństwa, wynikających z tzw. sieciowego modelu domenowego (ang. Network Domain Model) — uzyskanie dostępu do obiektu COM serwera DCOM związane jest nierozerwalnie z logowaniem się klienta i weryfikacją jego tożsamości oraz uprawnień. Elementy składające się na system zabezpieczeń mogą być przy tym określane statycznie, za pomocą wpisów w Rejestrze — mamy wówczas do czynienia z bezpieczeństwem deklaratywnym — albo dynamicznie w kodzie aplikacji — co nazywane bywa bezpieczeństwem programowanym.

W niniejszym rozdziale zobaczymy, jak C++Builder realizuje ideę bezpieczeństwa programowanego, przedstawimy też oczywiście sposoby aktywowania zdalnego obiektu DCOM.

Rodzina systemów Windows a DCOM

Technologia DCOM wbudowana jest organicznie w Windows 98, Windows NT 4.0 i Windows 2000; Windows 95 wymagają w tym celu zainstalowania dodatkowego składnika o nazwie DCOM95, który można ściągnąć z firmowej witryny Microsoftu (http://www.microsoft.com/com) i który instalowany jest także wraz z Internet Explorer'em w wersji 4 lub 5 (przy pełnej instalacji).

Jeżeli Twoja sieć wyposażona jest w kontroler domen (Domain Controller), powinieneś skonfigurować w swych komputerach zabezpieczenia Windows 95/98 jako zorientowane na użytkowników (user-level security); w przeciwnym razie nie będzie możliwe przeprowadzanie uwierzytelnień wywołań DCOM.

Z punktu widzenia bezpieczeństwa sieci Windows 95/98 bardzo dobrze nadają się do implementacji klientów DCOM — nie mają bowiem wbudowanych rodzimych mechanizmów zabezpieczających oraz nie mogą uruchamiać serwerów DCOM w sposób dynamiczny.

Konfigurowanie DCOM — program DCOMCnfg

Program DCOMCnfg.exe (DCOM Configuration Utility) jest narzędziem administratora umożliwiającym modyfikację ustawień DCOM zarówno w stosunku do pojedynczego serwera, jak i wszystkich zarejestrowanych serwerów DCOM znajdujących się na danym komputerze. Okno główne programu (rys. 17.1) składa się z czterech następujących kart: Aplikacje, Domyślne właściwości, Domyślne zabezpieczenia i Protokoły domyślne.

Niniejsza dyskusja na temat programu DCOMCnfg.exe odnosi się do wersji znajdującej się w Windows NT 4.0 z poprawką nr 4 (Service Pack 4) lub późniejszą oraz w Windows 2000. Windows 9x posługują się nieco zmodyfikowaną wersją tego narzędzia.

Tu proszę wkleić rysunek z pliku orig-17-01.bmp

Rysunek 17.1 Okno główne programu konfiguracyjnego DCOM (DCOMCnfg.exe)

Ustawienia ogólnosystemowe

Karta Aplikacje okna z rys. 17.1 zawiera wykaz wszystkich aplikacji, z którymi związane są zarejestrowane serwery DCOM istniejące na danym komputerze. Po wybraniu określonego serwera i kliknięciu w przycisk Właściwości uzyskujemy dostęp do jego ustawień.

Za pośrednictwem karty Domyślne właściwości (rys. 17.2) możliwe jest m.in. udostępnienie albo zablokowanie mechanizmów DCOM na danym komputerze, a także określenie domyślnych dla danego komputera ustawień uwierzytelnień i uosobienia.

Jedna z opcji tej karty określa dostępność internetowych usług COM (CIS — COM Internet Services) na danym komputerze. Usługi te wprowadzają do modelu DCOM nowe elementy, jak np. komunikację za pośrednictwem serwerów proxy i firewalli. Więcej informacji na temat usług CIS znajduje się na firmowej stronie Microsoftu pod adresem http://msdn.microsoft.com/library/backgrnd/html/cis/htm.

Tu proszę wkleić rysunek z pliku orig-17-02.bmp

Rysunek 17.2 Domyślne ustawienia DCOM na określonym komputerze

Uwierzytelnienie może być określone dla jednym z trzech następujących poziomów:

Można także całkowicie wyłączyć uwierzytelnianie, co jakkolwiek jest nieco ryzykowne, to jednak mogą na tym skorzystać niektórzy użytkownicy, szczególnie ci łączący się z serwerem za pośrednictwem Internetu.

Domyślny poziom uosobienia to poziom uprawnień, jakie przyznawane są aplikacji serwera przez aplikację klienta do wykonywania określonych działań w imieniu tej ostatniej. Poziom tych uprawnień może przyjmować następujące wartości, w kolejności od najniższego do najwyższego zabezpieczenia:

Na karcie Domyślne zabezpieczenia (rys. 17.3) możliwe jest określenie domyślnych uprawnień dostępu do aplikacji, uruchamiania aplikacji i konfiguracji OLE.

Tu proszę wkleić rysunek z pliku orig-17-03.bmp

Rysunek 17.3 Ustawienia domyślnych zabezpieczeń DCOM

Domyślne uprawnienia dostępu określają, kto ma prawo dostępu do serwerów DCOM zarejestrowanych w komputerze. Wartość Wszyscy (Everyone) oznacza dostęp dla każdego użytkownika.

Domyślne uprawnienia uruchamiania dotyczą praw do uruchamiania procesów serwera; wartość Wszyscy (Everyone) oznacza dowolnego użytkownika. Ta opcja nie może być ustawiana w sposób programowy.

Domyślne uprawnienia konfigurowania związane są z instalacją nowych serwerów OLE i zmianami w serwerach istniejących i określają rodzaj dostępu (Czytaj, Pełny dostęp, Specjalny dostęp) dla poszczególnych użytkowników.

Karta Protokoły domyślne (rys. 17.4) wykorzystywana jest do ustalenia dostępności i kolejności dostępu poszczególnych protokołów sieciowych używanych przez DCOM. Klikając w przycisk Właściwości można ustalić pewne aspekty funkcjonowania protokołu, na przykład dostęp za pośrednictwem firewalla. Generalnie ustawienia domyślnych protokołów nie powinny być zmieniane przez niezaawansowanych użytkowników.

Tu proszę wkleić rysunek z pliku orig-17-04.bmp

Rysunek 17.4 Karta domyślnych protokołów DCOM

Nie należy zapominać, iż ustawienia dokonywane na kartach Domyślne właściwości i Domyślne zabezpieczenia są ustawieniami ogólnosystemowymi — ich zmiana może spowodować komplikację w pracy niektórych serwerów funkcjonujących dotąd bez zarzutu. O wiele bezpieczniejsze są ustawienia dokonywane w odniesieniu do konkretnego serwera.

Ustawienia związane z konkretnym serwerem

Zmianę ustawień określonego serwera rozpoczynamy od wybrania go z listy w przedstawionej na rysunku 17.1 i kliknięciu w przycisk Właściwości. Wyświetlone w wyniku tego okno dialogowe właściwości serwera podzielone jest na pięć kart (rys. 17.5).

Tu proszę wkleić rysunek z pliku orig-17-05.bmp

Rysunek 17.5 Właściwości serwera DCOM

Na karcie Ogólne (rys. 17.5) widzimy zarejestrowaną nazwę serwera, jego typ, fizyczną lokalizację oraz poziom uwierzytelnień — ten ostatni może być zmieniany bez wpływu na inne serwery.

Karta Lokalizacja (rys. 17.6) umożliwia wybór komputera, na którym serwer będzie uruchamiany — mamy do dyspozycji komputer zawierający bazę danych, komputer, na którym uruchomiono program DCOMCnfg oraz komputer o wskazanej nazwie.

Tu proszę wkleić rysunek z pliku orig-17-06.bmp

Rysunek 17.6 Lokalizacja punktu uruchomienia serwera

Karta Zabezpieczenia (rys. 17.7) jest odpowiednikiem karty zabezpieczeń domyślnych w ustawieniach ogólnosystemowych i dla każdej kategorii uprawnień (dostęp, uruchamianie, konfiguracja) umożliwia dokonanie ustawień specyficznych dla serwera bądź skorzystanie z domyślnych ustawień systemu.

Tu proszę wkleić rysunek z pliku orig-17-07.bmp

Rysunek 17.7 Ustawienia zabezpieczeń serwera

Karta Identyfikacja (rys. 17.8) służy do określenia użytkownika, w którego kontekście zabezpieczenia uruchomiona zostanie aplikacja serwera. Mamy tu do wyboru trzy następujące opcje:

Dla każdego serwera, który nie został skonfigurowany za pomocą programu DCOMCnfg.exe DCOM używa domyślnych ustawień systemowych zapamiętanych w Rejestrze. Specyficzne ustawienia serwera mają pierwszeństwo przed ustawieniami ogólnosystemowymi, zaś aktualne ustawienia serwera — niezależnie od tego, czy dziedziczone z ustawień ogólnosystemowych, czy też specyficzne — mogą być (z nielicznymi wyjątkami) zmieniane w sposób programowy.

Należy zawsze pamiętać o ustawieniu uprawnień uruchamiania serwera — zaniedbanie tej czynności może sprawić, iż serwer pozostanie niedostępny dla zdalnych użytkowników.

Tu proszę wkleić rysunek z pliku orig-17-08.bmp

Rysunek 17.8 Identyfikacja użytkownika uruchamiającego aplikację serwera

Nie będziemy się tu zajmować kartą Punkty końcowe — zainteresowanych Czytelników odsyłamy do systemu pomocy programu DCOMCnfg.

Zastosowanie DCOM — przykład

Prezentację mechanizmów DCOM przeprowadzimy na podstawie dwóch projektów, zlokalizowanych w podkatalogach Server i Client katalogu głównego o nazwie EasyDCOM. Kompletny kod źródłowy obydwu projektów znajduje się na załączonym CD-ROMie.

Tworzenie aplikacji serwera

Rozpoczniemy od projektu serwera. W tym celu:

  1. Zainicjuj nową aplikację (File|New Application).

  2. Przejdź do folderu EasyDCOM\Server i zapisz projekt pod nazwą EasyDCOM.bpr, a jego moduł główny pod nazwą MainUnit.cpp.

  3. Za pomocą Inspektora Obiektów ustaw nazwę formularza na frmMain, a następnie umieść na nim stosowną etykietę i zmień jego rozmiary tak, by wyglądał podobnie do rysunku 17.9

Tu proszę wkleić rysunek z pliku orig-17-09.bmp

Rysunek 17.9 Formularz główny serwera

  1. Wyświetl okno New Items (File|New), przejdź na jego kartę ActiveX i kliknij dwukrotnie w pozycję Automation Object.

  2. Zostanie wyświetlone okno dialogowe New Automation Object. Wpisz HostInfo jako nazwę koklasy i „Easy DCOM Library 1.0” w pole Description. Kliknij przycisk OK.

  3. Za pomocą opcji View|Type Library uczyń okno edytora TLE oknem pierwszoplanowym. W jego lewym panelu podświetl pozycję reprezentującą interfejs IHostInfo.

  4. Rozwiń listę stowarzyszoną z przyciskiem New Property na pasku narzędziowym (drugi od lewej w prawej sekcji przycisków) i wybierz z niej opcję Read Only.

  5. Zmień domyślną nazwę nowej właściwości (Property1) na Info.

  6. Na prawym panelu przejdź do karty Parameters i zmień typ parametru na BSTR* (patrz rys. 17.10)

Tu proszę wkleić rysunek z pliku orig-17-10.bmp

Rysunek 17.10 Właściwość Info w oknie edytora TLE

  1. Kliknij w przycisk Refresh na pasku narzędziowym w celu odświeżenia zawartości plików źródłowych.

  2. Przejdź do pliku HostInfoImpl.cpp i dodaj następujący kod do metody get_Info():

STDMETHODIMP THostInfoImpl::get_Info(BSTR* Value)

{

try {

*Value = NULL;

char lpBuffer[MAX_COMPUTERNAME_LENGTH + 1];

unsigned long lngSize = sizeof(lpBuffer);

if (GetComputerName(lpBuffer, &lngSize) == 0)

return HRESULT_FROM_WIN32(GetLastError());

WideString strInfo = AnsiString().sprintf("Date and time at %s is:

%s", lpBuffer, DateTimeToStr(Now()));

*Value = strInfo.Detach();

}

catch(Exception &e) {

return Error(e.Message.c_str(), IID_IHostInfo);

}

return S_OK;

};

  1. Skompiluj i uruchom serwer (naciskając F9).

Zatrzymajmy się przez chwilę przy funkcji THostInfoImpl::get_Info(). Rozpoczyna się ona wywołaniem funkcji Win32 API GetComputerName() wpisującej nazwę komputera do zmiennej lpBuffer. Nazwa ta, po uzupełnieniu jej o bieżącą datę i czas wpisywana jest do pomocniczego łańcucha strInfo. Zawartość tego łańcucha zostaje następnie przepisana do parametru Value i jednocześnie od niego „odczepiona”, by po zakończeniu funkcji nie została zwolniona zajmowana przez nią pamięć — jak pamiętamy z rozdziału 16, parametry wyjściowe (out) alokowane są przez serwer i zwalniane przez klienta.

Tworzenie aplikacji klienta

Scenariusz tworzenia naszej przykładowej aplikacji-klienta DCOM jest następujący:

  1. Zainicjuj nową aplikację i zapisz jej pliki w katalogu EasyDCOM\Client; modułowi głównemu nadaj nazwę MainUnit.cpp, a plikowi projektu — EasyDCOMClient.bpr.

  2. Umieść na formularzu głównym wymienione poniżej komponenty i zmień ich nazwy, jak następuje:

Nazwa oryginalna

Nowa nazwa

Form1

frmMain

GroupBox1

gbxLocal

GroupBox2

gbxRemote

Edit1

txtLocal

Edit2

txtRemote

Button1

cmdInfo

Button2

cmdFinish

  1. Ustaw właściwości komponentów tak, by nadać formularzowi wygląd pokazany na rysunku 17.11.

Tu proszę wkleić rysunek z pliku orig-17-11.bmp

Rysunek 17.11 Formularz główny aplikacji-klienta

  1. Otwórz plik EasyDCOMClient.cpp i dodaj do niego następującą dyrektywę:

USEUNIT("..\Server\EasyDCOM_TLB.cpp");

  1. Otwórz plik MainUnit.h i dodaj do niego następującą dyrektywę:

#include "..\Server\EasyDCOM_TLB.h"

  1. Dodaj następującą deklarację w prywatnej sekcji formularza głównego TfrmMain:

TCOMIHostInfo m_objHost;

  1. Kliknij dwukrotnie w formularz główny i dodaj następujący kod do funkcji obsługującej jego zdarzenie OnCreate:

void __fastcall TfrmMain::FrmMain_Create(TObject *Sender)

{

char szBuffer[MAX_COMPUTERNAME_LENGTH + 1];

unsigned long lngSize = sizeof(szBuffer);

GetComputerName(szBuffer, &lngSize);

txtLocal->Text = AnsiString().sprintf("Date and time at %s is: %s",

szBuffer, DateTimeToStr(Now()));

}

  1. Kliknij dwukrotnie w przycisk cmdInfo i dodaj następujący kod do jego funkcji zdarzeniowej:

void __fastcall TfrmMain::CmdInfo_Click(TObject *Sender)

{

if (!m_objHost.IsBound())

{

AnsiString strHost;

if (InputQuery("Create Server", "Enter computer name:", strHost))

{

if (strHost.IsEmpty())

OleCheck(CoHostInfo::Create(m_objHost));

else

OleCheck(CoHostInfo::CreateRemote(WideString(strHost), m_objHost));

WideString strInfo;

OleCheck(m_objHost.get_Info(&strInfo));

txtRemote->Text = strInfo;

}

}

}

  1. Kliknij dwukrotnie w przycisk cmdFinish i dodaj do jego funkcji zdarzeniowej pojedynczą instrukcję

Close();

  1. Za pomocą Inspektora Obiektów utwórz funkcję obsługi zdarzenia OnClose formularza głównego i wpisz do jej wnętrza następującą instrukcję:

m_objHost.Unbind();

  1. Skompiluj i uruchom aplikację klienta.

Przeanalizujmy pokrótce przedstawiony kod.

Funkcja obsługująca zdarzenie OnCreate formularza pobiera i wyświetla nazwę lokalnego komputera oraz bieżącą datę i czas.

Po kliknięciu w przycisk cmdInfo funkcja obsługująca to zdarzenie wywołuje metodę IsBound() „eleganckiego” interfejsu TCOMIHostInfo w celu stwierdzenia, czy obiekt serwera został już utworzony — jeżeli nie, to funkcja zdarzeniowa kończy swą pracę.

Funkcja InputQuery() pobiera nazwę NETBIOS, nazwę DNS i adres IP zdalnego komputera i zapisuje te dane w zmiennej strHost.

Jeżeli łańcuch strHost jest pusty, następuje lokalne utworzenie serwera za pomocą metody Create(), w przeciwnym razie metoda CreateRemote() próbuje utworzyć obiekt serwera na wskazanej maszynie zdalnej. Poprawność wywołań obydwu metod kontrolowana jest przez funkcję OleCheck().

Wywoływana jest metoda get_Info() z adresem łańcucha WideString jako jedynym parametrem. Łańcuch ten jest następnie wypełniany informacjami uzyskanymi ze zdalnego komputera, po czym informacje te wyświetlane są w ramach kontrolki txtRemote.

Przed zamknięciem formularza wywoływana jest metoda Unbind() „eleganckiego” interfejsu TCOMIHostInfo dokonująca zwolnienia obiektu serwera.

Pora teraz na zasadniczy test, czyli przeniesienie aplikacji serwera (EasyDCOM.exe) na inny komputer, jednokrotne jej uruchomienie w celu autorejestracji, a następnie próbę uzyskania dostępu do niej przez aplikację klienta.

Jeżeli serwer znajduje się na komputerze z systemem Windows 9x, nie nastąpi jego automatyczne uruchomienie — trzeba go uruchomić w sposób jawny.

Konfigurowanie uprawnień do dostępu i uruchamiania serwera

Załóżmy na potrzeby naszego przykładu, iż komputer klienta i zdalny komputer serwera rezydują w domenie sieciowej Microsoft Network i dokonajmy ustawienia specyficznych dla serwera uprawnień uruchamiania i dostępu

W tym celu należy uruchomić program DCOMCnfg i z karty Aplikacje wybrać pozycję reprezentującą nasz serwer — „Easy DCOM Type Library 1.0”. Po dwukrotnym kliknięciu w tę pozycję wyświetlone zostanie okno dialogowe właściwości serwera; należy wówczas przejść na jego kartę Zabezpieczenia, zaznaczyć opcję „Użyj niestandardowych uprawnień uruchamiania” i kliknąć w przycisk „Edytuj” związany z tą opcją. Wyświetlone zostanie okno rejestracji uprawnień (rys. 17.12); za pomocą przycisku „Dodaj” należy dodać do tego okna konto „Wszyscy”. W ten sposób zezwalamy wszystkim użytkownikom na uruchamianie serwera.

Tu proszę wkleić rysunek z pliku orig-17-12.bmp, po uprzednim usunięciu niepotrzebnych elementów na drugim planie (tak, aby zostały tylko dwa interesujące nas okna, podobnie jak w oryginale na stronie 730).

Rysunek 17.12 Zezwolenie wszystkim użytkownikom na uruchamianie serwera

Dostęp do usług uruchomionego serwera określony jest obecnie przez ustawienia ogólnosystemowe. Możemy zmienić ten stan rzeczy, zaznaczając opcję „Użyj niestandardowych uprawnień dostępu” i po kliknięciu w związany z nią przycisk Edycja specyfikując użytkowników, którym zezwala się na dostęp (rys. 17.13).

Tu proszę wkleić rysunek z pliku orig-17-13.bmp, po uprzednim usunięciu niepotrzebnych elementów na drugim planie (tak, aby zostały tylko dwa interesujące nas okna, podobnie jak w oryginale na stronie 731).

Rysunek 17.13 Specyfikacja użytkowników posiadających dostęp do usług serwera

Konfigurowanie identyfikacji

Jak już wcześniej pisaliśmy, za pomocą karty Identyfikacja właściwości serwera (patrz rysunek 17.8) możemy określić, w kontekście zabezpieczeń którego użytkownika uruchamiana będzie aplikacja serwera. Najbardziej „konkretną” z trzech możliwych opcji jest oczywiście określenie konkretnego użytkownika — należy podać jego nazwę i hasło (dwukrotnie), a następnie kliknąć OK.

Uruchomienie przykładu

Określiliśmy już zarówno uprawnienia uruchamiania serwera i dostępu do niego, jak również użytkownika, w kontekście którego uruchamianie to będzie następować. Pora więc wypróbować funkcjonowanie serwera.

Uruchom aplikację klienta na komputerze lokalnym, kliknij w przycisk Go Get i w wyświetlone okno dialogowe wprowadź nazwę zdalnego komputera lub jego adres IP. Jeżeli wszystko przebiegnie pomyślnie, powinieneś zobaczyć nazwę zdalnego komputera i wskazanie jego zegara, jak pokazuje to rysunek 17.14.

Tu proszę wkleić rysunek z pliku orig-17-14.bmp

Rysunek 17.14 Dialog aplikacji-klienta z serwerem na zdalnym komputerze

Bezpieczeństwo programowane

Ustawienia związane z poziomem zabezpieczeń, dokonywane przez program DCOMCnfg, odczytywane są (z Rejestru) przez aplikację COM i przekazywane do funkcji API o nazwie CoInitializeSecurity(). Funkcja ta wywoływana jest dokładnie raz dla każdego procesu wykorzystującego obiekt COM, dzięki czemu ustalone zostają dla tego procesu elementy składające się na jego bezpieczeństwo — poziom uwierzytelnień, lista uprawnionych użytkowników, postać pakietów danych związanych z zabezpieczeniami itp. W dalszej części rozdziału pokażemy, jak samodzielnie posługiwać się wspomnianą funkcją.

Parametry funkcji CoInitializeSecurity

Funkcja CoInitializeSecurity() jest jedną z najważniejszych funkcji COM związanych z jego bezpieczeństwem. Posługuje się ona następującymi parametrami wywołania:

IN PSECURITY_DESCRIPTOR pSecDesc // wskaźnik na deskryptor

// zabezpieczenia

IN LONG cAuthSvc // liczba pozycji w asAuthSvc

IN SOLE_AUTHENTICATION_SERVICE *asAuthSvc // tablica rejestrowanych nazw

IN void *pReserved1 // zarezerwowane dla przyszłego

// użytku

IN DWORD dwAuthnLevel // domyślny poziom

// uwierzytelnienia

IN DWORD dwImpLevel // domyslny poziom uosobienia

IN SOLE_AUTHENTICATION_LIST *pAuthList, // informacja o

// uwierzytelnieniach dla każdej

// usługi uwierzytelniającej

IN DWORD dwCapabilities // dodatkowe możliwości klienta

// i (lub) serwera

IN void *pReserved3 // zarezerwowane dla przyszłego

// użytku

Niektóre z wymienionych parametrów odnoszą się zarówno do klienta, jak i do serwera, niektóre tylko do jednego z nich.

Pierwszy z parametrów — pSecDesc — istotny jest tylko wówczas, gdy proces jest procesem serwera. Jego zawartością jest wówczas wskaźnik na: GUID aplikacji (AppID GUID), strukturę SECURITY_DESCRIPTOR lub interfejs IAccessControl. Jeżeli parametr ten ma wartość NULL, COM przydzieli uprawnienia wszystkim użytkownikom — taki sam efekt daje określenie w programie DCOMCnfg konta WSZYSCY (Everyone). Jeżeli parametr ten jest niepustym wskaźnikiem, piąty parametr (dwAuthnLevel) nie może mieć wartości RPC_C_AUTH_LEVEL_NONE.

Drugi i trzeci parametr — cAuthSvc i asAuthSvc — również odnoszą się do serwera: pierwszy z nich jest licznikiem, drugi tablicą następujących struktur, reprezentujących pakiety uwierzytelniające:

typedef struct tagSOLE_AUTHENTICATION_SERVICE

{

DWORD dwAuthnSvc;

DWORD dwAuthzSvc;

OLECHAR __RPC_FAR *pPrincipalName;

HRESULT hr;

} SOLE_AUTHENTICATION_SERVICE;

Przypisując parametrom wartości cAuthSvc=-1 i asAuthSvc=NULL powodujemy wykorzystanie usług ogólnosystemowych. W Windows NT 4.0 jedyną dostępną usługą uwierzytelniającą jest NTLM (NT LAN Manager), w Windows 2000 dostępny jest ponadto Kerberos.

Piąty parametr — dwAuthnLevel — określa poziom uwierzytelnienia i dotyczy zarówno klienta, jak i serwera; odpowiada to ustawieniom specyficznym dla serwera w programie DCOMCnfg (patrz rys. 17.5). Wartość tego parametru nie może być zmieniana poniżej wartości określonej przez program DCOMCnfg, w przeciwnym razie wywołanie zakończy się niepowodzeniem.

Szósty parametr — dwImplLevel — odnosi się tylko do klienta przy jego odwołaniach do serwera. Nie ma on odpowiednika w ustawieniach programu DCOMCnfg (związanego tylko z ustawieniami serwera), może jednak przyjmować identyczne wartości jak analogiczny parametr serwera (rys. 17.2):

RPC_C_IMP_LEVEL_DEFAULT

RPC_C_IMP_LEVEL_ANONYMOUS

RPC_C_IMP_LEVEL_IDENTIFY

RPC_C_IMP_LEVEL_IMPERSONATE

RPC_C_IMP_LEVEL_DELEGATE

Ósmy parametr — dwCapabilities — dotyczy klienta i serwera i określa ich dodatkowe możliwości. Może on stanowić sumę logiczną następujących flag:

Wykorzystanie CoInitializeSecurity()

Zmodyfikujemy teraz naszą przykładową aplikację tak, by ustawienia zabezpieczeń dokonywane były przez funkcję wymienioną w tytule. Uczyni to naszą aplikację zdolną do komunikowania się przez Internet — w tym celu należy dopuścić dostęp anonimowych użytkowników oraz wyłączyć uwierzytelnianie i uosobienie; da się to zrobić w prosty sposób właśnie dzięki funkcji CoInitializeSecurity().

Jak już wcześniej napisaliśmy, funkcja CoInitializeSecurity() musi być wywołana dla danego procesu tylko raz, przez pierwszym odwołaniem się do obiektu COM; jej wywołanie musi nastąpić zarówno po stronie klienta, jak i po stronie serwera, po wywołaniu CoInitialize().

W tym celu:

  1. Otwórz projekt EasyDCOM.bpr i wyświetl jego główny plik źródłowy (Project|View Source).

  2. Uzupełnij następująco funkcję WinMain() (wyblakły druk oznacza kod już istniejący):

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

{

CoInitialize(NULL);

CoInitializeSecurity(

NULL,

-1,

NULL,

NULL,

RPC_C_AUTHN_LEVEL_NONE,

RPC_C_IMP_LEVEL_ANONYMOUS,

NULL,

EOAC_NONE,

NULL

);

try

{

Application->Initialize();

Application->Title = "Easy DCOM Server";

Application->CreateForm(__classid(TfrmMain), &frmMain);

Application->Run();

}

catch (Exception &exception) {

Application->ShowException(&exception);

}

CoUninitialize();

return 0;

}

  1. Otwórz projekt EasyDCOMClient.bpr, wyświetl jego główny plik źródłowy (EasyDCOMClient.cpp) i zmodyfikuj funkcję WinMain() identycznie, jak w pkt. 2.

  2. Dodaj do modułu EasyDCOMClient.cpp dyrektywę dołączającą plik nagłówkowy objbase.h:

#include <vcl.h>

#include <objbase.h>

#pragma hdrstop

Wywołanie funkcji CoInitialize() spowoduje, że odwołanie do serwera zostanie zrealizowane w ramach wspólnego apartamentu modelu wątkowego STA (patrz rozdział 16). Funkcja CoInitializeSecurity() ustala następnie odpowiednie parametry zabezpieczeń.

I tak ustawienie pierwszego parametru na NULL oznacza, iż dostęp do serwera nie jest kontrolowany. Dwa następne parametry (licznik = -1, wskaźnik tablicy=NULL) informują o nieobecności dodatkowych pakietów uwierzytelniających, co spowoduje przyjęcie w tym względzie ustawień ogólnosystemowych. Ponieważ nie jesteśmy zainteresowani uwierzytelnianiem odwołań do serwera, parametr dwAuthnLevel ma wartość RPC_C_AUTHN_LEVEL_NONE. Nie znając użytkownika łączącego się z serwerem przez Internet, nie jesteśmy także zainteresowani jego uosabianiem na serwerze i działaniem w jego imieniu, tak więc parametr dwImpLevel ma wartość RPC_C_IMP_LEVEL_ANONYMOUS. Wreszcie — charakter naszego serwera nie wymaga używania żadnej z flag związanych z ostatnim parametrem, nadaliśmy więc temu parametrowi wartość zerową (EOAC_NONE).

Serwery DCOM używające punktów połączeniowych również powinny zlikwidować swe zabezpieczenia w opisany sposób. Gdybyśmy chcieli uruchomić przykładowy serwer z rozdziału 16. jako zdalny serwer DCOM, skończyłoby się to komunikatem o odmowie dostępu (Access Denied). Stałoby się tak dlatego, iż klient próbowałby weryfikować uprawnienia serwera próbującego uzyskać dostęp do jego ujścia zdarzeń.

Można by obejść ten problem w inny sposób, utożsamiając kontro serwera z kontem klienta — innymi słowy użytkownik-klient na komputerze-kliencie musiałby mieć taką samą nazwę i hasło jak użytkownik określony na karcie Identyfikacja (patrz rysunek 17.8). Problem w tym, iż wymaga to wcześniejszej znajomości użytkownika chcącego uruchomić aplikację kliencką.

CoInitializeSecurity() a serwer wewnątrzprocesowy

Cóż jednak począć, gdy chcielibyśmy zaimplementować klienta DCOM w postaci kontrolki ActiveX przeznaczonej do wykorzystania w ramach jakiejś aplikacji-kontenera, jak np. Internet Explorer czy IIS i Active Server Pages?

Serwer wewnątrzprocesowy, będący biblioteką DLL, ładowany jest do przestrzeni adresowej aplikacji-kontenera; funkcja CoInitializeSecurity() wywoływana jest przez COM przed tym załadowaniem (jawnie lub automatycznie, wszystko jedno). Nawet jeżeli zostanie ona wywołana w kodzie klienta, i tak będzie już za późno — wszelkie ustawienia zabezpieczeń będą już wówczas sprecyzowane i nie będzie można ich zmienić.

Jednym z możliwych rozwiązań tego problemu jest stworzenie dodatkowego zewnątrzprocesowego (EXE) serwera pośredniczącego, odwołującego się do zdalnego serwera DCOM w imieniu klienta i traktowanego przez tego ostatniego jako swoisty reprezentant serwera zdalnego. Nie zmienia to jednak faktu, iż z poziomu biblioteki DLL nie jest możliwe kontrolowanie ustawień zabezpieczających przez wywołanie funkcji CoInitializeSecurity(); należy o tym pamiętać, decydując się na tworzenie klienta zdalnego serwera DCOM w postaci biblioteki DLL.

Implementowanie programowej kontroli dostępu

Możliwe jest selektywne udzielanie zezwoleń na dostęp do serwera użytkownikom lub ich grupom. Pierwszy parametr wywołania funkcji CoInitializeSecurity() jest wówczas poprawnym wskaźnikiem, zaś parametr dwAuthnLevel musi mieć wartość nie mniejszą niż RPC_C_AUTH_LEVEL_CONNECT, w przeciwnym razie wywołanie funkcji zakończy się błędem.

Jednym z możliwych obiektów wskazywanych przez pierwszy parametr jest struktura tzw. deskryptora zabezpieczeń:

typedef struct _SECURITY_DESCRIPTOR {

BYTE Revision;

BYTE Sbz1;

SECURITY_DESCRIPTOR_CONTROL Control;

PSID Owner;

PSID Group;

PACL Sacl;

PACL Dacl;

} SECURITY_DESCRIPTOR;

Bezpośrednie operowanie deskryptorami zabezpieczeń za pomocą funkcji Win32 API nie jest czynnością zbyt wygodną; C++Builder 5 wraz ze swą biblioteką ATL oferuje więc w tym celu specjalną klasę CSecurityDescriptor. Może być ona używana wszędzie tam, gdzie wymagana jest struktura SECURITY_DESCRIPTOR, dzięki zdefiniowanemu operatorowi konwersji:

class CSecurityDescriptor

{

public:

operator PSECURITY_DESCRIPTOR()

{

return m_pSD;

}

public:

PSECURITY_DESCRIPTOR m_pSD;

Programowa modyfikacja list kontroli dostępu (ACL — Access Control Lists) za pomocą funkcji Win32 Security API jest czynnością złożoną, wymagającą ostrożnego kodowania i testowania. Począwszy od Windows NT 4.0 z drugą poprawką (Service Pack 2) COM udostępnia jako alternatywę interfejs IAccessControl, implementowany przez obiekt systemowy CLSID_DCOMAccessControl.

Wbudowane konto SYSTEM musi być bezwarunkowo włączone do list kontroli dostępu, ponieważ Menedżer Sterowania Usługami (System Service Control Manager) wymaga tego do zarządzania serwerami COM.

Dla celów ilustracji programowego ustawiania kontroli dostępu zmodyfikujemy nasz serwer tak, by dostęp do niego mieli jedynie klienci używający konta Guest; konto to jest nieaktywne po pierwszej instalacji systemu.

Otwórz więc projekt serwera i do pliku EasyDCOM.cpp dodaj dyrektywę włączającą plik atlcom.h:

#include <vcl.h>

#pragma hdrstop

#include <atl\atlmod.h>

#include <atl\atlcom.h>

#include "HostInfoImpl.h"

Przed linią zwierającą wywołanie funkcji CoInitialize() (w funkcji WinMain()) dodaj następujący blok kodu:

CSecurityDescriptor sd;

sd.InitializeFromProcessToken();

sd.Allow("Guest", COM_RIGHTS_EXECUTE);

sd.Allow("NT_AUTHORITY\\SYSTEM", COM_RIGHTS_EXECUTE);

Nowo utworzony obiekt sd klasy CSecurityDescriptor inicjowany jest tutaj za pomocą metody InitializeFromProcessToken() dokonującej ustawienia jego wewnętrznych danych w kontekście bieżącego procesu. Dwa wywołania metody Allow() ustanawiają zezwolenie na uruchamianie serwera w stosunku do użytkownika Guest oraz systemowego konta NT_AUTHORITY\SYSTEM.

Zmień następnie parametry wywołania funkcji CoInitializeSecurity() w następujący sposób:

CoInitializeSecurity(

sd, // tu zmiana

-1,

NULL,

NULL,

RPC_C_AUTHN_LEVEL_CONNECT, // tu zmiana

RPC_C_IMP_LEVEL_IDENTIFY, // tu zmiana

NULL,

EOAC_NONE,

NULL

)

Przejdź teraz do projektu klienta, otwórz plik EasyDCOMClient.cpp i usuń linie funkcji WinMain() zawierające wywołania funkcji CoInitialize(), CoInitializeSecurity() i CoUninitialize().

Abyś mógł wypróbować działanie zmodyfikowanych projektów, musisz mieć uprawnienia posługiwania się kontem Guest i używać takiego samego hasła na komputerze klienta i serwera. Możesz też zalogować się jako Guest na maszynie serwera i uruchomić na niej aplikację kliencką. Próba uruchomienia aplikacji klienckiej z konta innego niż Guest spowoduje odrzucenie przez serwer próby dostępu.

Nazwę Guest można zastąpić nazwą dowolnego konta lokalnego lub domenowego; przekazując tę nazwę do metody Allow() klasy CSecurityDescriptor musisz nadać jej format <Domena>\\<UG> albo <Komputer>\\UG>, gdzie <Domena> i <Komputer> oznaczają nazwę (odpowiednio) domeny i komputera, zaś <UG> jest nazwą użytkownika albo grupy.

Windows 2000 oferują nowy program użytkowy o nazwie RunAs pozwalający uruchomić aplikację w kontekście uprawnień wskazanego konta. Stwarza to ogromne ułatwienie dla testowania serwerów COM pod względem uruchamiania ich przez różnych klientów.

Bezpieczeństwo interfejsu

Do tej pory zajmowaliśmy się zabezpieczeniami procesu jako całości, wykorzystując w tym celu funkcje CoInitializeSecurity(). Niekiedy jednak zabezpieczenia powinny mieć charakter bardziej selektywny, na przykład wymagając szyfrowania danych w stosunku do wywołania tylko jednej, wskazanej metody, nie zaś całego procesu.

COM umożliwia nam taką selektywną kontrolę zabezpieczeń, udostępniając w tym celu dwa interfejsy. Po stronie klienta warstwa programowa, reprezentowana przez menedżera proxy (Proxy Manager) implementuje mianowicie metody interfejsu IClientSecurity. Analogiczna warstwa po stronie serwera — Stub Manager — implementuje interfejs IServerSecurity.

Interfejs IClientSecurity

Na interfejs IClientSecurity składają się trzy metody: QueryBlanket(), SetBlanket() i CopyProxy().

Metoda QueryBlanket() uzyskuje informację uwierzytelniającą przekazaną w wywołaniu funkcji CoInitializeSecurity() przy inicjowaniu procesu i używaną przez proxy. SetBlanket() konfiguruje taką informację na użytek proxy, zaś metoda CopyProxy() tworzy prywatną kopię proxy w celu późniejszego jej użycia przez metodę SetBlanket().

Parametry wywołania metod QueryBlanket() i SetBlanket() odpowiadają poszczególnym parametrom wywołania funkcji CoInitializeSecurity(), z jednym wszakże istotnym wyjątkiem, którym jest siódmy parametr — pAuthInfo. Stanowi on wskaźnik na strukturę zależną od używanych pakietów — gdy używane są pakiety NTLM, strukturą tą jest COAUTHIDENTITY:

typedef struct _COAUTHIDENTITY

{

USHORT *User;

ULONG UserLength;

USHORT *Domain;

ULONG DomainLength;

USHORT *Password;

ULONG PasswordLength;

ULONG Flags;

} COAUTHIDENTITY;

Jak łatwo się domyślić, poszczególne pola struktury odzwierciedlają określone konto — nazwę użytkownika lub domeny oraz hasło. Jeżeli parametr pAuthInfo jest pustym wskaźnikiem, wywołania metod uwierzytelniane są na zasadach określonych dla całego procesu klienta.

Aby wywołać jakąś metodę interfejsu IClientSecurity, należy wpierw zażądać jego egzemplarza wywołują metodę QueryInterface() menedżera proxy, wywołać żądaną metodę, a następnie zwolnić ów egzemplarz za pomocą metody Release(). COM udostępnia otoczki metod interfejsu IClientSecurity automatycznie realizujące tę sekwencję — noszą one nazwy (odpowiednio) CoQueryproxyBlanket(), CoSetProxyBlanket() i CoCopyProxy().

Interfejs IServerSecurity

Odpowiednikiem interfejsu IClientSecurity po stronie serwera jest IServerSecurity odpowiedzialny za uosobienie klienta. Wskaźnik do jego egzemplarza dostępny jest w treści wywoływanych metod za pośrednictwem funkcji CoGetCallContext().

Interfejs IServerSecurity posiada cztery metody: QueryBlanket(), ImpersonateClient(), RevertToSelf() oraz IsImpersonating(); dla pierwszych trzech COM udostępnia otoczki o nazwach (odpowiednio) CoQueryClientBlanket(), CoImpersonateClient() i CoRevertToSelf().

Metoda QueryBlanket() jest odpowiednikiem swej partnerki po stronie klienta: udostępnia ona bieżące ustawienia zabezpieczeń. Za jej pomocą można na przykład dowiedzieć się, czy klient stosuje szyfrowanie bądź też uzyskać dane tegoż klienta.

Zadaniem metody ImpersonateClient() jest przyjęcie przez serwer uosobienia określonego klienta. Poziom tego uosobienia decyduje o tym, czy serwer ma prawo dostępu do obiektów systemowych, czy też tylko wykonać może sprawdzenie uprawnień dostępu.

Wywołanie metody RevertToSelf() anuluje uosobienie będące konsekwencją wywołania metody ImpersonateClient(). COM automatycznie wywołuje metodę RevertToSelf() po każdym powrocie z wywoływanej metody serwera.

Do sprawdzenia, czy serwer znajduje się aktualnie w stanie uosabiającym jakiegoś klienta, służy metoda IsImpersonating().

Wykorzystanie blankietu

Działanie interfejsów IServerSecurity i IClientSecurity zilustrujemy za pomocą kolejnej pary projektów. Działająca aplikacja kliencka dokonywać będzie zmian w uprawnieniach użytkownika, a następnie zwracać się będzie do serwera z poleceniem rozpoznania ich bieżącego stanu; serwer będzie także miał możliwość zapisywania ustawień klienta w zewnętrznym pliku.

Przed rozpoczęciem eksperymentu należy założyć w systemie dwa lokalne konta: CommonUser i ExtraUser będące członkami grupy User, na obydwu komputerach — serwera i klienta. Logowanie na maszynie klienta odbywać się będzie z użyciem konta ExtraUser. Za pomocą programu DCOMCnfg należy ponadto skonfigurować serwer tak, by był dostępny dla dowolnego użytkownika (Wszyscy lub EveryOne) i ustawić identyfikację serwera (patrz rysunek 17.8) na użytkownika Administrator.

Kompletny kod obydwu projektów znajduje się na załączonym CD-ROMie; tutaj prześledzimy pokrótce proces ich budowy.

Rozpocznijmy od projektu serwera:

  1. Zainicjuj nową aplikację i nadaj projektowi nazwę Blanket.bpr. Z okna New Items wybierz pozycję Automation Object i nadaj jego koklasie nazwę ObjBlanket. Posługując się następnie edytorem TLE dodaj do serwera dwie następujące metody:

[id(1)] HRESULT _stdcall BlanketInfo([out, retval] BSTR * Value);

[id(2)] HRESULT _stdcall CreateFile([in] BSTR value);

  1. Otwórz plik ObjBlanketImpl.cpp i wpisz następującą treść metody BlanketInfo():

STDMETHODIMP TObjBlanketImpl::BlanketInfo(BSTR* Value)

{

try {

*Value = NULL;

LPWSTR pPrivs;

OleCheck(CoQueryClientBlanket(NULL, NULL, NULL, NULL, NULL,

(LPVOID*)&pPrivs, NULL));

WideString strInfo = pPrivs;

*Value = strInfo.Detach();

}

catch(Exception &e) {

return Error(e.Message.c_str(), IID_IObjBlanket);

}

return S_OK;

};

Powyższa funkcja wywołuje funkcję CoQueryClientBlanket() w celu uzyskania nazwy użytkownika i zwrócenia jej do wywołującegoi. Wszystkie inne elementy informacji o użytkowniku nie są w tej chwili interesujące, dlatego reprezentujące je parametry są pustymi wskaźnikami.

  1. W tym samym pliku wpisz kod implementujący metodę CreateFile():

STDMETHODIMP TObjBlanketImpl::CreateFile(BSTR Value)

{

try {

OleCheck(CoImpersonateClient());

HANDLE hFile = ::CreateFile(AnsiString(Value).c_str(),

GENERIC_WRITE,

FILE_SHARE_WRITE,

NULL,

CREATE_ALWAYS,

FILE_ATTRIBUTE_NORMAL,

NULL);

if (INVALID_HANDLE_VALUE == hFile)

return HRESULT_FROM_WIN32(GetLastError());

CloseHandle(hFile);

OleCheck(CoRevertToSelf());

}

catch(Exception &e) {

return Error(e.Message.c_str(), IID_IObjBlanket);

}

return S_OK;

};

Wywołując funkcję CoImpersonateClient(), uosabiamy na serwerze bieżącego klienta — jak pamiętamy, zgodnie z oryginalnymi ustawieniami serwer pracuje w kontekście konta Administrator; dane uosobionego klienta zapisywane są do lokalnego pliku, po czym następuje powrót serwera do oryginalnego kontekstu, czyli do konta Administrator.

Po skompilowaniu projektu skopiuj jego plik wynikowy Blanket.exe na zdalny komputer i uruchom jednokrotnie w celu zarejestrowania.

Scenariusz budowy aplikacji-klienta przedstawia się następująco:

  1. Zainicjuj nową aplikację i zapisz jej pliki pod nazwami BlanketClient.bpr i MainUnit.cpp.

  2. Zaprojektuj formularz główny w sposób pokazany na rysunku 17.15. nadając jego poszczególnym komponentom następujące nazwy:

Nazwa oryginalna

Nowa nazwa

Form1

frmMain

RadioButton1

rdoLoged

RadioButton2

rdoIdnty

GroupBox1

gbxIdn

GroupBox2

gbxInfo

Label1

lblUser

Label2

lblDomain

Label3

lblPwrd

Edit1

txtUser

Edit2

txtDomain

Edit3

txtPwrd

Edit4

txtInfo

Button1

cmdCallServer

Button2

cmdSetIdentity

Button3

cmdGetInfo

Button4

cmdCreateFile

Button5

cmdFinish

Tu proszę wkleić rysunek z pliku orig-17-15.bmp

Rysunek 17.15 Formularz główny aplikacji klienckiej

  1. Otwórz plik MainUnit.h i dodaj do niego dyrektywę

#include "..\server\Blanket_TLB.h"

  1. Dodaj do części prywatnej deklaracji formularza TfrmMain dwa następujące pola:

TCOMIObjBlanket m_objBlanket;

SEC_WINNT_AUTH_IDENTITY m_AuthInfo;

  1. Dodaj do części prywatnej deklaracji formularza TfrmMain deklarację dwóch następujących metod:

void GetAuthInfo();

void SetAuthInfo();

Kompletny kod implementacyjny głównego formularza, zawarty w pliku MainUnit.cpp prezentujemy na wydruku 17.1:

Wydruk 17.1 Implementacja modułu głównego formularza aplikacji-klienta

#include <vcl.h>

#pragma hdrstop

#include "MainUnit.h"

#pragma package(smart_init)

#pragma resource "*.dfm"

TfrmMain *frmMain;

__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner)

{

m_AuthInfo.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;

m_AuthInfo.User = NULL;

m_AuthInfo.Domain = NULL;

m_AuthInfo.Password = NULL;

}

void __fastcall TfrmMain::FrmMain_Close(TObject *Sender, TCloseAction &Action)

{

m_objBlanket.Unbind();

delete[] m_AuthInfo.User;

delete[] m_AuthInfo.Domain;

delete[] m_AuthInfo.Password;

}

void __fastcall TfrmMain::CmdCallServer_Click(TObject *Sender)

{

if (!m_objBlanket.IsBound()) {

AnsiString strHost;

if (InputQuery("Create Server", "Enter computer name:", strHost)) {

if (strHost.IsEmpty())

OleCheck(CoObjBlanket::Create(m_objBlanket));

else

OleCheck(CoObjBlanket::CreateRemote(WideString(strHost),

m_objBlanket));

ShowMessage("Server created");

}

}

}

void __fastcall TfrmMain::CmdCreateFile_Click(TObject *Sender)

{

if (m_objBlanket.IsBound()) {

AnsiString strFile;

if (InputQuery("Create File", "Enter path and file name:", strFile)) {

OleCheck(m_objBlanket.CreateFile(WideString(strFile)));

ShowMessage("File created");

}

}

}

void __fastcall TfrmMain::CmdFinish_Click(TObject *Sender)

{

Close();

}

void __fastcall TfrmMain::CmdGetInfo_Click(TObject *Sender)

{

if (m_objBlanket.IsBound())

GetAuthInfo();

}

void __fastcall TfrmMain::CmdIdentity_Click(TObject *Sender)

{

gbxIdn->Enabled = rdoIdnty->Checked ? true : false;

txtDomain->Color = rdoIdnty->Checked ? clWindow : clBtnFace;

txtPwrd ->Color = rdoIdnty->Checked ? clWindow : clBtnFace;

txtUser ->Color = rdoIdnty->Checked ? clWindow : clBtnFace;

}

void __fastcall TfrmMain::CmdSetIdentity_Click(TObject *Sender)

{

if (m_objBlanket.IsBound()) {

if (rdoIdnty->Checked)

SetAuthInfo();

OleCheck(CoSetProxyBlanket(m_objBlanket,

RPC_C_AUTHN_WINNT,

RPC_C_AUTHZ_NONE,

NULL,

RPC_C_AUTHN_LEVEL_CONNECT,

RPC_C_IMP_LEVEL_IMPERSONATE,

rdoIdnty->Checked ? &m_AuthInfo :NULL,

EOAC_NONE));

}

}

void TfrmMain::GetAuthInfo()

{

WideString strInfo;

OleCheck(m_objBlanket.BlanketInfo(&strInfo));

txtInfo->Text = strInfo;

}

void TfrmMain::SetAuthInfo()

{

delete[] m_AuthInfo.User;

delete[] m_AuthInfo.Domain;

delete[] m_AuthInfo.Password;

m_AuthInfo.UserLength = txtUser ->Text.Length();

m_AuthInfo.DomainLength = txtDomain->Text.Length();

m_AuthInfo.PasswordLength = txtPwrd ->Text.Length();

m_AuthInfo.User = (PUCHAR)new TCHAR[txtUser ->Text.Length()+1];

m_AuthInfo.Domain = (PUCHAR)new TCHAR[txtDomain->Text.Length()+1];

m_AuthInfo.Password = (PUCHAR)new TCHAR[txtPwrd ->Text.Length()+1];

lstrcpy((LPTSTR)m_AuthInfo.User , (LPCTSTR)txtUser ->Text.c_str());

lstrcpy((LPTSTR)m_AuthInfo.Domain , (LPCTSTR)txtDomain->Text.c_str());

lstrcpy((LPTSTR)m_AuthInfo.Password, (LPCTSTR)txtPwrd ->Text.c_str());

}

Jak widzimy na prezentowanym wydruku, niektóre pola struktury m_AuthInfo inicjowane są w konstruktorze formularza. Zgodnie z plikiem pomocy C++Buildera 5 „OLE Programmer's Reference”, COM utrzymuje wskaźnik do tej struktury aż do momentu swej deinicjalizacji (wartość tego wskaźnika może być zmieniana).

Metoda SetAuthInfo() dokonuje wpisania nowych wartości we wspomnianą strukturę m_AuthInfo, zwalniając jednocześnie pamięć zajmowaną przez informację dotychczasową i zapobiegając w ten sposób wyciekom pamięci.

Przycisk „Set Identify” umożliwia zmianę identyfikacji klienta. Jego funkcja zdarzeniowa wywołuje funkcję CoSetProxyBlanket() informującą Proxy Manager'a, iż należy zmienić kontekst użytkownika zgodnie z przekazaną strukturą. Wartość wskaźnika reprezentującego wspomnianą strukturę zależna jest od stanu opcji w górnej części formularza i jest albo wskazaniem na strukturę m_AuthInfo, albo wskazaniem pustym.

Funkcja zdarzeniowa przycisku „Get Info” wywołuje metodę BlanketInfo() serwera w celu uzyskania identyfikacji aktualnego klienta i wpisania jej w pole txtInfo.

Kliknięcie w przycisk „Create File” spowoduje utworzenie pliku na zdalnym komputerze. Funkcja zdarzeniowa wyświetla najpierw dialog prowadzący do pobrania nazwy pliku, a następnie wywołuje metodę CreateFile() serwera.

Zakończyliśmy konstruowanie naszych projektów, sprawdźmy więc, jak ze sobą współpracują:

  1. Uruchom program BlanketClient.exe i kliknij w przycisk „Call Server”. W wyświetlonym oknie dialogowych wpisz nazwę zdalnego komputera — spowoduje to uruchomienie serwera.

  2. Kliknij w przycisk „Get Info” ; w polu „Client Name” pojawi się nazwa aktualnie zalogowanego użytkownika (rys. 17.16)

Tu proszę wkleić rysunek z pliku orig-17-16.bmp

Rysunek 17.16 Klient odwołujący się do serwera w kontekście aktualnie zalogowanego użytkownika

  1. Kliknij w przycisk „Create File”, wprowadź dowolną nazwę w wyświetlone okno dialogowe i zatwierdź ją. Następnie obejrzyj właściwości utworzonego pliku, i zauważ, kto jest jego właścicielem (rys. 17.17).

Tu proszę wkleić rysunek z pliku orig-17-17.bmp po uprzednim oczyszczeniu go ze zbędnego tła na drugim planie, by widoczne były tylko dwa okna jak na rysunku 17.17 w oryginale na stronie 747.

Rysunek 17.17 Właścicielem utworzonego pliku jest użytkownik zalogowany

  1. Wybierz opcję „Use this identity”, wpisz w pole „Name” nazwę CommonUser i kliknij w przycisk „Set Identity”. W polu „Client Name” pojawi się nazwa uosobionego użytkownika (rys. 17.18).

Tu proszę wkleić rysunek z pliku orig-17-18.bmp

Rysunek 17.16 Klient odwołujący się do serwera z uosobionym użytkownikiem

Ponownie utwórz plik (o innej nazwie niż w pkt. 3) i sprawdź teraz, czyją jest on własnością (rys. 17.19)

Tu proszę wkleić rysunek z pliku orig-17-19.bmp po uprzednim oczyszczeniu go ze zbędnego tła na drugim planie, by widoczne były tylko dwa okna jak na rysunku 17.19 w oryginale na stronie 748.

Rysunek 17.17 Właścicielem utworzonego pliku jest użytkownik uosobiony na serwerze

Podsumowanie

Mechanizmy DCOM są tematem na tyle obszernym, iż ich wyczerpujące przedstawienie wymagałoby nie tyle osobnej książki, ile raczej potężnego księgozbioru. Z konieczności więc skupiliśmy się w niniejszym rozdziale na wybranych zagadnieniach, z uwzględnieniem ich praktycznego charakteru.

Rozpoczęliśmy od elementów, które czynnik rozproszenia wprowadza do oryginalnej technologii COM — głównie pod kątem bezpieczeństwa aplikacji. Zaprezentowaliśmy przykład wykorzystania programu DCOMCnfg, stanowiącego narzędzie konfiguracyjne dla mechanizmów DCOM w środowisku Windows.

W uzupełnieniu do deklaratywnych elementów zabezpieczeń, konfigurowanych przez program DCOMCnfg, przedstawiliśmy także przykładowe zastosowanie zabezpieczenia programowanego, opartego na wykorzystaniu funkcji CoInitializeSecurity() oraz interfejsach IClientSecurity i IServerSecurity.

Czytelnikom szczególnie zainteresowanych tematyką aplikacji rozproszonych polecamy następny rozdział poświęcony technologiom MIDAS i InternetExpress. Technologie te dostępne są w wersji Enterprise C++Buildera 5 i stanowią wspaniałą alternatywę dla DCOM.

Uwaga HELIONie!

Ostatni akapit uzależniony jest od faktu, iż następny rozdział w stosunku do niniejszego jest poświecony tematyce MIDAS i InternetExpress — na co powinien być wrażliwy Redaktor prowadzący niniejszej książki.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

2 E:\HELION\CPP Builder 5\r17\r-17-00.doc



Wyszukiwarka

Podobne podstrony:
13 ZACHOWANIA ZDROWOTNE gr wtorek 17;00
aaa Interna 01 godz 17 00
18.05.2012 godz 17.00, 6 rok, gieldy, gieldy 6 rok, interna
Interna 01 godz 17 00(1)
13 ZACHOWANIA ZDROWOTNE gr wtorek 17;00
17 LATKA DOC
ćw 17 wstęp doc
R 16 00 DOC
ćw 26,23,17 teoria doc
17 00 to nie była przypadkowa godzina
17 (43) DOC
R 15 00 DOC
17 SygnałzegarowyH doc
~$arakterystyka przedsiębiorstwa (17 stron) (2) doc
¦ćw 17 obliczenia doc
17 01 00 00 Fährbetriebsverordnung m L doc
17 01 00 00 Fährbetriebsverordnung o L doc
(1995) WIEDZA KTÓRA PROWADZI DO ŻYCIA WIECZNEGO (DOC), rozdział 17, Rozdział 1
kk, ART 9 KK, Wyrok z dnia 02 sierpnia 2000 roku, II AKa 140/00, OSA 2001/3/17

więcej podobnych podstron