Czytanie shadow, passwd i group w C

Pucik

W tym artykule przedstawię funkcje które umożliwiają łatwe czytanie informacji z /etc/passwd, /etc/shadow i /etc/group. Jak wiadomo są to pliki odpowiedzialne za przechowywanie informacji o użytkownikach i grupach (hasło, katalog domowy itd). Pobieranie z nich informacji przy użyciu standardowych funkcji operujących na plikach i stringach może sprawiać problemy. O wiele łatwiej skorzystać z gotowych funkcji udostępnionych przez linux.

  1. /etc/shadow

Ostatnio w moim programie napotkałem się na problem z uwierzytelnianiem użytkowników. Na początku wykorzystałem bibliotekę libpam ale skompilowanie mojego programu sprawiało by problem na wielu maszynach. Uznałem że najlepszym wyjściem będzie stworzenie własnej procedury uwierzytelniającej. Przykładowy program który sprawdza czy użytkownik podał dobry login i hasło:

/* Sprawdza czy user podal dobry login i haslo
   gcc -o program program.c -lcrypt */
#include <stdio.h>
#include <unistd.h>
#include <shadow.h>

int main()
{
 struct spwd *shad;
 int i,j;
 int czy=0;
 char *h;
 char dane[1000], string[1000];
 char *slogin, *spasswd;
 char login[32], *passwd;
 
 printf("Podaj login: ");
 scanf("%s", login);
 printf("Podaj haslo: ");
 passwd=getpass("");
 if((shad=getspent())==NULL)
 {
  perror("/etc/shadow");
  exit(-1);
 }
 while(shad!=NULL)
 {
  slogin=shad->sp_namp;
  spasswd=shad->sp_pwdp;
  if (strcmp(login, slogin)==0)
  {
   for(j=0, i=3; spasswd[i]!='$'; i++, j++)
    string[j]=spasswd[i];
   string[j]=0;
   j=sprintf(dane, "$1$%s$", string);
   dane[j]=0;
   h=crypt(passwd, dane);
   if (strcmp(h, spasswd)==0)
   {
    printf("login ok\n");
    czy=1;
    break;
   }
 
  }
  shad=getspent();
 }
 if(czy==0) printf("zly login lub haslo\n");
 
 return 0;
}
/* Koniec programu */

Na początku opiszę funkcje znajdujące się w shadow.h.

#include <shadow.h>

struct spwd *getspent(); /* Zwraca wskaznik do struct spwd */

struct spwd {
 char *sp_namp; /* nazwa użytkownika */
 char *sp_pwdp; /* zaszyfrowane hasło */
 long sp_lstchg; /* ostatnio zmiana hasła */
 int  sp_min; /* ilość dni od kiedy zmiana hasła jest zezwolona */
 int  sp_max; /* ilość dni kiedy zmiana hasła wymagana*/
 int  sp_warn; /* ilość dni do wygaśnięcia konta */
 int  sp_inact; /* ilość dni nieaktywności konta*/
 int  sp_expire; /* data kiedy konto wygaśnie */
 int  sp_flag; /* nie wiem ... (reserved for future use) */
}

Funkcja getspent() zwraca NULL w razie błędu lub napotkania końca pliku shadow. Każde następne wywołanie getspent() zwraca dane z następnej linijki pliku. Więcej informacji w man 3 shadow.
Przy podawaniu hasła użyłem funkcji, która może wzbudzać wątpliwości:

#include <unistd.h>

char *getpass( const char * prompt );

Zwraca ona wskaźnik do danych wpisanych na konsoli na której został uruchomiony program (/dev/tty/) i nie pozwala drukować ich na ekranie (aby ktoś nie mógł podejrzeć hasła). Więcej w man 3 getpass.
No dobrze ale jak porównać hasło wpisane przez usera z zaszyfrowanym hasłem z /etc/shadow ?? Użyjemy do tego funkcji szyfrowania crypt():

#define _XOPEN_SOURCE
#include <unistd.h>

char *crypt(const char *key, const char *salt);

Zwraca ona wskaźnik do zaszyfrowanego hasła. Przykładowe hasło z /etc/shadow wygląda tak:
$1$MixFeJnz$mBKCx/F453f2dplzChkI10
To co znajduje się pomiędzy 2 a 3 znakiem '$' to ciąg na podstawie którego szyfrowane jest hasło. Aby funkcja crypt() szyfrowała dane z MD5 musimy przekazać jej 2 argumenty którymi są hasło i "$1$<string>$".
Np. gdy hasło w shadow wygląda tak :
$1$Testowe$mBKCx/F453f2dplzChJkI10
aby zaszyfrować hasło w MD5 i sprawdzić czy się zgadza musimy wywołać
crypt("Haslo", "$1$Testowe$");
Funkcja zwróci zaszyfrowane hasło. Jeżeli zgadza się ono z tym z /etc/shadow, dane wpisane przez użytkownika są prawdziwe. Jeśli używasz w programie tej funkcji musisz go skompilować z parametrem -lcrypt.
Ze zrozumieniem reszty programu nie powinno być problemy. Pamiętaj, to tylko przykład, nie używaj go w swoim programie bo jest bardzo zawodny :P.
Więcej informacji na temat crypt() jest w man 3 crypt.
Jeżeli tworzysz jakiś większy program, w którym ważne jest bezpieczeństwo NIE używaj funkcji getpass() i crypt(). Mogą one spowodować złe działanie programu itd.

  1. /etc/passwd

Sprawa z /etc/passwd ma się podobnie jak z /etc/shadow. Mamy strukturę i funkcje która ją wypełnia.

#include <pwd.h>
#include <sys/types.h>

struct passwd *getpwent(void);

struct passwd {
 char    *pw_name;       /* nazwa usera */
 char    *pw_passwd;     /* haslo usera */
 uid_t   pw_uid;         /* id usera*/
 gid_t   pw_gid;         /* id grupy */
 char    *pw_gecos;      /* pelne imie */
 char    *pw_dir;        /* katalog domowy */
 char    *pw_shell;      /* domyslna powloka */
};

Funkcja getpwent() zwraca wskaźnik do struct passwd, a w razie błędu lub końca pliku - NULL. Następne wywołania tej funkcji zwracają dane z następnych linijek /etc/passwd. Przykładowy program:

/* Troche inny sposob wyswietlania /etc/passwd :) */
#include <stdio.h>
#include <pwd.h>
#include <sys/types.h>

int main()
{
 struct passwd *p;
 
 while((p=getpwent())!=NULL)
  printf("%s:%s:%d:%d:%s:%s:%s\n", p->pw_name, p->pw_passwd, p->pw_uid,p->pw_gid, p->pw_gecos, p->pw_dir, p->pw_shell);
}
 
/* KONIEC */

Tutaj nie ma czego opisywać, mam nadzieje ze wszystko jest oczywiste.
Operowanie na /etc/passwd jest przydatne w tworzeniu programów dodających nowych użytkowników itd. Więcej informacji w man 3 getpwent.
Do umieszczania struktury struct passwd w pliku służy funkcja:

#include <pwd.h>
#include <stdio.h>
#include <sys/types.h>

int putpwent(const struct passwd *p, FILE *stream);

Jej działanie tez nie jest zbyt trudną rzeczą :) Przykładowy program:

/* Umieszcza w /etc/passwd linie z danymi */
#include <stdio.h>
#include <pwd.h>
#include <sys/types.h>

int main()
{
 FILE* wp;
 wp=fopen("/etc/passwd", "a");
 struct passwd dane={"pucik","haslo", 0, 0, "pucik", "/root", "/bin/bash"};
 struct passwd *p;
 p=&dane;
 putpwent(p, wp);
 return 0;
}
/* KONIEC */

Program ten dopisuje do pliku linie z danymi. Więcej o funkcji w man 3 putpwent.

  1. /etc/group

Plik ten zawiera informacje o grupach istniejących w systemie. Funkcje pobierające z niego dane są równie proste jak w przypadku passwd i shadow.

#include <grp.h>
#include <sys/types.h>

struct group *getgrent(void);

struct group {
 char    *gr_name;        /* nazwa grupy */
 char    *gr_passwd;      /* haslo grupy */
 gid_t   gr_gid;          /* id grupy */
 char    **gr_mem;        /* czlonkowie grupy */
};

Już chyba nie musze tłumaczyć co i jak, getgreant() zwraca wskaźnik do struct group tak jak w poprzednich przypadkach. Więcej informacji w man 3 getgrent. Przykładowy program:

/* Szuka danej grupy i drukuje info*/
#include <stdio.h>
#include <grp.h>
#include <sys/types.h>


int main()
{
 struct group *g;
 char grupa[100]; 
 printf("podaj nazwe grupy: ");
 scanf("%s", grupa);
 if((g=getgrnam(grupa))!=NULL)
      printf("%s:%s:%d:%sn", g->gr_name, g->gr_passwd, g->gr_gid, g->gr_mem[0]);
 else
  printf("nie ma takiej grupy\n");
 return 0;
}
/* Koniec */

Program szuka grupy o danej nazwie i drukuje informacje o niej (tylko 1 członka).
Jak widzisz grp.h nie kończy się na 1 funkcji. Znajduje się w nim wiele więcej funkcji ułatwiających poruszanie się po /etc/group.

To już chyba koniec tego artykułu. W linuxie znajduje się mnóstwo funkcji ułatwiających czytanie podstawowych plików takich jak shadow, passwd, goroup czy fstab. Zachęcam do czytania stron man bo to moim zdaniem najlepsze źródło informacji.
Jeśli masz jakieś pytania, uwagi itd. nie bój się pisać, mój mail [email protected]. Zapraszam na www.CC-Team.org, tam znajduje się wiele ciekawych artykułów na temat programowania w linuxie !!

7 komentarzy

Dokladnie jak przedmowca :) powiem że jeżeli pisze się coś co operuje na systemie zabezpieczeń bo po to jest wlasnie shadow (aby zapewnic bezpieczeństwo haseł to nasz '14sto' latek powyżyej pisze kod programu który bedzie robił coś odwrotnego "z zabezpieczenia zrobił dziure" pozwalajaca wylozyc caly kod na kilka sposobow i zdobyć UID 0 (bo tylko z takim program ma najwiekszy sens istnienia) z jakim wykonujemy program.

NIE UŻYWAĆ !
A autor niech najpierw zasiegnie lektury o bezpiecznym programowaniu i przepełnieniach stosu itp.

Ludzie! NAUCZCIE SIE NAJPIERW PODSTAW BEZPIECZNEGO PROGRAMOWANIA a dopiero pozniej zamieszczajcie artykuly dotyczace takich spraw jak /etc/passwd i /etc/shadow. Bo pozniej dzieci to czytaja i copy-paste'uja.

TAK NIE WOLNO!!!!!!!!!!! :

scanf("%s", login).

Jesli ktos bedzie uzywal zamieszczonego tutaj kodu to niech pod zadnym pozorem nie daje prawa wykonywania go innym uzytkownikom (obowiazkowy chmod 700) Przyjalem, ze skoro programy tu przedtawione czytaja /etc/shadow to musza chodzic z uprawnieniami roota.

Niepowiem ciekawe.... ;)

lofix powiedz w którym kodzie wyświetlały się błedy ?? moze zapomniales o -lcrypt ? ;)

To jest kod sprawdzany na linuxie nie wiem jak pod *BSD i innych unixo-podobnych systemach

z ciekawosci skopiowalem kod i chcialem skmpilowac - bledy.....

napoczątku zapomnialem o cpp /cpp, wiec go wywylilem i wrzucilem jeszcze raz ...

po co wsadzasz ten sam artykuł dwukrotnie?