Następna strona Poprzednia strona Spis treści

8. Obsługa przesłanianych haseł w programach w C.

Dodawanie obsługi haseł przesłanianych do programów jest całkiem proste. Jedynym problemem jest to, że program musi zostać uruchomiony z prawami "root-a", aby mieć dostęp do pliku /etc/shadow.

Tutaj pojawia się jeden wielki problem: podczas pisania takich programów należy się trzymać jak najściślej zasad bezpieczeństwa. Na przykład jeśli program posiada wyjście do powłoki, to nie może się to zdarzyć na prawach "root-a".

Jeśli program musi mieć dostęp do /etc/shadow i nie potrzebuje więcej praw "root-a", to lepiej uruchomić go z prawami grupy "shadow". Program xlock jest przykładem takiego programu.

W przykładzie podanym niżej, pppd-1.2.1d jest już uruchomiony z prawami "root-a", tak więc dodanie obsługi haseł przesłanianych nie powinno zagrozić bezpieczeństwu.

8.1 Pliki nagłówkowe.

Pliki te powinny znajdować się w /usr/include/shadow. Powinien tam być także plik /usr/include/shadow.h, ale będzie to symboliczne dołaczenie do /usr/include/shadow/shadow.h.

Aby dodać obsługę haseł przesłanianych do programu, musisz dołączyć pliki nagłówkowe:

#include <shadow/shadow.h>
#include <shadow/pwauth.h>

Dobrym pomysłem byłoby użycie dyrektyw kompilatora, aby skompilować warunkowo kod z obsługą haseł przesłanianych. (tak jak w przykładzie poniżej.)

8.2 Biblioteka libshadow.a

Biblioteka ta została zainstalowana w /usr/lib jak instalowałeś pakiet Shadow Suite.

Kiedy kompilujesz obsługę przesłaniancyh haseł w programie trzeba poinformować "linker-a", aby dołączył bibliotekę libshadow.a.

Robi się to tak:

gcc program.c -o program -lshadow

Chociaż jak zobaczymy w poniższym przykładzie, większość dużych programów używa plików Makefile i ma zwykle zmienną LIBS=..., którą się modyfikuje.

8.3 Struktura Shadow.

Biblioteka libshadow.a używa struktury spwd dla informacji, które otrzyma z pliku /etc/shadow. Oto definicja tej struktury wzięta z /usr/include/shadow/shadow.h:


struct spwd
{
  char *sp_namp;                /* identyfikator */
  char *sp_pwdp;                /* zakodowane hasło */
  sptime sp_lstchg;             /* data ostatniej zmiany */
  sptime sp_min;                /* minimalna ilość dni między zmianami */
  sptime sp_max;                /* maksymalna ilość dni między zmianami */
  sptime sp_warn;               /* ilość dni przed wygaśnięciem hasła
                                   kiedy będzie wysyłane ostrzeżenie */
  sptime sp_inact;              /* ilość dni, po jakiej wygasa hasło
                                   dopóki konto nie będzie zablokowane */
  sptime sp_expire;             /* ilość dni od 01.01.1970 dopóki
                                   konto nie wygaśnie */
  unsigned long sp_flag;        /* zarezerwowane na przyszłość */
};

Do pola sp_pwdp można dodatkowo wstawić nawzę programu:

identyfikator:Npge08pfz4wuk;@/sbin/extra:9479:0:10000::::

Oznacza to, że oprócz podania hasła będzie wykonany program /sbin/extra, który wykona dalszą autentykację. Wywołany program otrzyma identyfikator i przełącznik, który określi dlaczego został wywołany. Więcej informacji znajdziesz w /usr/include/shadow/pwauth.h i pwauth.c.

Oznacza to, że powinniśmy użyć funkcji pwauth do przeprowadzenia poprawnej autentykacji, ponieważ zajmie się ona także drugą autentykacją. Jest to użyte w przykładzie poniżej.

Autor pakietu Shadow Suite twierdzi, że odkąd większość programów tego nie stosuje, może to zostać usunięte lub zmienione w przyszłych wersjach pakietu.

8.4 Funkcje pakietu Shadow Suite.

Plik shadow.h zawiera deklaracje funkcji zawartych w bibliotece libshadow.a:


extern void setspent __P ((void));
extern void endspent __P ((void));
extern struct spwd *sgetspent __P ((__const char *__string));
extern struct spwd *fgetspent __P ((FILE *__fp));
extern struct spwd *getspent __P ((void));
extern struct spwd *getspnam __P ((__const char *__name));
extern int putspent __P ((__const struct spwd *__sp, FILE *__fp));

Funkcja, którą użyjemy w przykładzie to getspnam, która odczyta strukturę spwd dla podanego identyfikatora.

8.5 Przykład.

Jest to przykład na dodanie obsługi haseł przesłanianych do programu, który tego potrzebuje, ale nie ma domyślnie włączonego.

Przykład ten używa serwera Point-to-Point Protocol (pppd-1.2.1d), który ma tryb do autentykacji PAP używając identyfikatora i hasła z pliku /etc/passwd zamiast z plików PAP czy CHAP. Nie musisz dodawać tego kodu do pppd-2.2.0, ponieważ on już tam jest.

Ta właściwość pppd przypuszczalnie nie jest często używana, ale jeśli zainstalowałeś Shadow Suite, to serwer ten nie będzie działał ponieważ hasła nie są już zapisywane w /etc/passwd.

Kod do autentykacji użytkowników przez pppd-1.2.1d umieszczony jest w pliku /usr/src/pppd-1.2.1d/pppd/auth.c.

Poniższy kod musi zostać dodany na początku pliku gdzie znajdują się wszystkie inne dyrektywy #include. Otoczyliśmy je dyrektywami warunkowymi (czyli skompilują się tylko jeśli kompilujemy z właczoną obsługą haseł przesłanianych).


#ifdef HAS_SHADOW
#include <shadow.h>
#include <shadow/pwauth.h>
#endif

Następnym krokiem jest modyfikacja właściwiego kodu. Ciągle modyfikujemy plik auth.c.

Funkcja auth.c przed modyfikacją:


/*
 * login - Check the user name and password against the system
 * password database, and login the user if OK.
 *
 * returns:
 *      UPAP_AUTHNAK: Login failed.
 *      UPAP_AUTHACK: Login succeeded.
 * In either case, msg points to an appropriate message.
 */
static int
login(user, passwd, msg, msglen)
    char *user;
    char *passwd;
    char **msg;
    int *msglen;
{
    struct passwd *pw;
    char *epasswd;
    char *tty;

    if ((pw = getpwnam(user)) == NULL) {
        return (UPAP_AUTHNAK);
    }
     /*
     * XXX If no passwd, let them login without one.
     */
    if (pw->pw_passwd == '\0') {
        return (UPAP_AUTHACK);
    }

    epasswd = crypt(passwd, pw->pw_passwd);
    if (strcmp(epasswd, pw->pw_passwd)) {
        return (UPAP_AUTHNAK);
    }

    syslog(LOG_INFO, "user %s logged in", user);

    /*
     * Write a wtmp entry for this user.
     */
    tty = strrchr(devname, '/');
    if (tty == NULL)
        tty = devname;
    else
        tty++;
    logwtmp(tty, user, "");             /* Add wtmp login entry */
    logged_in = TRUE;

    return (UPAP_AUTHACK);
}

Hasło użytkownika jest umieszczane w pw->pw_passwd, tak że wszystko co musimy zrobić, to dodać funkcję getspnam. Umieści to hasło w spwd->sp_pwdp.

Dodamy funkcję pwauth, aby przeprowadzić właściwą autentykację. Przeprowadzi to także drugą autentykację jeśli plik shadow jest odpowiednio ustawiony.

Funkcja auth.c po modyfikacji do obsługi haseł przesłanianych:


/*
 * login - Check the user name and password against the system
 * password database, and login the user if OK.
 *
 * This function has been modified to support the Linux Shadow Password
 * Suite if USE_SHADOW is defined.
 *
 * returns:
 *      UPAP_AUTHNAK: Login failed.
 *      UPAP_AUTHACK: Login succeeded.
 * In either case, msg points to an appropriate message.
 */
static int
login(user, passwd, msg, msglen)
    char *user;
    char *passwd;
    char **msg;
    int *msglen;
{
    struct passwd *pw;
    char *epasswd;
    char *tty;

#ifdef USE_SHADOW
    struct spwd *spwd;
    struct spwd *getspnam();
#endif

    if ((pw = getpwnam(user)) == NULL) {
        return (UPAP_AUTHNAK);
    }

#ifdef USE_SHADOW
        spwd = getspnam(user);
        if (spwd)
                pw->pw_passwd = spwd->sp-pwdp;
#endif
 
     /*
     * XXX If no passwd, let NOT them login without one.
     */
    if (pw->pw_passwd == '\0') {
        return (UPAP_AUTHNAK);
    }
#ifdef HAS_SHADOW
    if ((pw->pw_passwd && pw->pw_passwd[0] == '@'
         && pw_auth (pw->pw_passwd+1, pw->pw_name, PW_LOGIN, NULL))
        || !valid (passwd, pw)) {
        return (UPAP_AUTHNAK);
    }
#else
    epasswd = crypt(passwd, pw->pw_passwd);
    if (strcmp(epasswd, pw->pw_passwd)) {
        return (UPAP_AUTHNAK);
    }
#endif

    syslog(LOG_INFO, "user %s logged in", user);

    /*
     * Write a wtmp entry for this user.
     */
    tty = strrchr(devname, '/');
    if (tty == NULL)
        tty = devname;
    else
        tty++;
    logwtmp(tty, user, "");             /* Add wtmp login entry */
    logged_in = TRUE;

    return (UPAP_AUTHACK);
}

Po dokładnym prześledzaniu tego kodu okaże się, że zrobiliśmy jeszcze jedną zmianę. Oryginalna wersja pozwalała na dostęp (zwracała UPAP_AUTHACK) jeśli nie było hasła w pliku /etc/passwd. Nie jest to dobrze, ponieważ popularnym użyciem tej właściwości programu login jest używanie jednego konta, na dostęp do programu PPP, a potem sprawdzenie identyfikatora i hasła dostarczonych przez PAP z tymi w plikach /etc/passwd i /etc/shadow.

Tak więc jeśli ustwilibyśmy oryginalną wersję, aby uruchamiała powłokę dla użytkownika np. ppp, to każdy mógłby uzyskać połączenie ppp przez podanie identyfikatora ppp i pustego hasła.

Poprawiliśmy to przez zwrócenie UPAP_AUTHNAK zamiast UPAP_AUTHACK jeśli pole z hasłem jest puste.

Interesujące jest to, że pppd-2.2.0 ma ten sam problem.

Następnie musimy zmodyfikować plik Makefile tak, żeby pojawiły się dwie rzeczy:
USE_SHADOW musi być zdefiniowane i libshadow.a musi być dodana w procesie "linkowania".

Wyedytuj plik Makefile i dodaj:

LIBS = -lshadow

Potem znajdź linię:

COMPILE_FLAGS = -I.. -D_linux_=1 -DGIDSET_TYPE=gid_t

i zmień ją na:

COMPILE_FLAGS = -I.. -D_linux_=1 -DGIDSET_TYPE=gid_t -DUSE_SHADOW

Teraz kompilacja i instalacja.


Następna strona Poprzednia strona Spis treści