Page suivante - Page précédente - Table des matières

8. Ajouter le support shadow à un programme en C.

Ajouter le support shadow à un programme C est assez facile. Le seul problème est que le programme doit être lancé par root (ou SUID root) pour qu'il puisse accéder au fichier /etc/shadow.

Ceci présente un réel problème, il faut faire très attention lors de la création de programmes SUID. Par exemple, il ne faut pas qu'un programme SUID root puisse permettre un accès au shell.

La meilleure solution pour qu'un programme puisse accéder aux mots de passe encodés sans être SUID root, est de lancer ce programme SUID shadow à la place. C'est le cas par exemple du programme xlock.

Dans l'exemple donné précédemment, pppd-1.2.1d fonctionne déjà SUID root, donc ajouter le support shadow ne le rendra pas plus vulnérable.

8.1 Les fichiers d'en-tête

Les fichiers d'en-tête doivent être stockés dans le répertoire /usr/include/shadow. Le fichier /usr/include/shadow.h, doit être un lien symbolique vers /usr/include/shadow/shadow.h.

Pour ajouter le support shadow à un programme, vous devez inclure les fichiers de header:

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

La meilleure solution est d'utiliser des directives de compilation pour compiler conditionnellement le code shadow (Il y aura un exemple par la suite).

8.2 La bibliothèque libshadow.a

Quand vous avez installé l'ensemble shadow, le fichier libshadow.a a été créé et installé dans le répertoire /usr/lib.

Lorsque vous compilez un programme avec le support shadow, vous devez préciser à l'éditeur de liens d'inclure la bibliothèque libshadow.a dans le lien:

gcc programe.c -o program -lshadow

Ceci dit, et vous le verrez par la suite dans notre exemple, la plupart des programmes plus ou moins gros utilisent un fichier Makefile, qui en général, utilise une variable appelée LIBS=... que vous pourrez modifier.

8.3 La structure shadow

La bibliothèque libshadow.a utilise une structure appelée spwd pour récupérer les informations contenues dans le fichier /etc/shadow. Voici la définition de la structure spwd provenant de /usr/include/shadow/shadow.h:


struct spwd
{
 char *sp_namp;         /* nom de login */
 char *sp_pwdp;         /* mot de passe encode */
 sptime sp_lstchg;      /* date de la derniere modification */
 sptime sp_min;         /* nombre de jours minimum entre les modifs */
 sptime spmax;          /* nombre de jours maximum entre les modifs*/
 sptime sp_warn;        /* nombre de jours de warning avant l'expiration
 du mot de passe */
 sptime sp_inact;       /* nombre de jours d'utilisation du compte
 apres l'expiration. */
 sptime sp_expire;      /* nombre de jours a partir du 01/01/70 jusqu'a
 l'expiration du compte */
 unsigned long sp_flag; /* reserve pour une utilisation future */
};

L'ensemble shadow peut placer des données dans le champ sp_pwdp juste après le mot de passe encodé, le champ password pourrait contenir:

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

Cela signifie qu'en plus du mot de passe, le programme /sbin/extra sera appelé pour procéder à une authentification supplémentaire. Le programme appelé recevra comme argument, le nom d'utilisateur et un switch qui indiquera pourquoi il est appelé. Regardez le fichier /usr/include/shadow/pwauth.h et le code source de pwauth.c pour plus d'informations.

La fonction d'authentification pwauth est toujours utilisée avant la deuxième authentification..

8.4 Les fonctions Shadow.

Le fichier shadow.h contient aussi la déclaration des fonctions contenues dans la bibliothèque 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));

La fonction que nous allons étudier est getspnam, elle récupère une structure spwd à partir d'un nom donné.

8.5 Exemple

Voici un exemple d'ajout du support shadow à un programme qui en nécessite mais pour qui ce support n'existe pas par défaut.

Nous allons nous baser sur l'exemple du serveur pppd-1.2.1d ( Serveur Point-to-Point protocol) configuré avec l'option login: il va chercher les mots de passe pour son authentification PAP dans le fichier /etc/passwd au lieu des fichiers PAP ou CHAP. Vous n'avez pas besoin d'ajouter ce code à pppd-2.2.0, c'est déjà fait.

Bien que cette possibilité de pppd ne soit pas très utilisée, elle ne fonctionnera plus dès lors que vous aurez installé l'ensemble shadow: les mots de passe ne sont plus stockés dans /etc/passwd.

La partie du code source d'authentification des utilisateurs avec pppd-1.2.1d se trouve dans le fichier /usr/src/pppd-1.2.1d/pppd/auth.c.

Le code qui suit doit être ajouté au début du fichier, là où sont toutes les autres directives #include.


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

Maintenant, il faut modifier le code actuel. Nous sommes toujours avec le fichier auth.c.

La fonction auth.c avant les modifications:


/*
 * login - Controle le nom d'utilisateur et le mot de passe par rapport
 * a ceux stockes sur le systeme.
 * Accepte la connection si l'utilisateur est OK.
 *
 * retourne:
 *      UPAP_AUTHNAK: Connection refusee.
 *      UPAP_AUTHACK: Connection Acceptee.
 * Dans un cas comme dans l'autre, msg pointe sur le message approprie.
 */
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 Si il n'y a pas de mots de passe, accepte la connection.
 */
 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);
 /*
 * Ecris une entree wtmp pour cet utilisateur.
 */
 tty = strrchr(devname, '/');
 if (tty == NULL)
 tty = devname;
 else
 tty++;
 logwtmp(tty, user, "");    /* Ajoute une entree wtmp de connection */
 logged_in = TRUE;
 return (UPAP_AUTHACK);
}

Le mot de passe de l'utilisateur est placé dans pw->pw_passwd, donc, nous devons ajouter la fonction getspnam qui placera le mot de passe dans spwd->sp_pwdp.

Nous rajouterons la fonction pwauth pour l'authentification actuelle. Une seconde authentification sera effectuée si le fichier shadow est configuré pour.

Voici la fonction auth.c apres les modifications pour le support de shadow:


/*
 * login - Controle le nom d'utilisateur et le mot de passe par rapport
 * a ceux stockes sur le systeme.
 * Accepte la connection si l'utilisateur est OK.
 *
 * Cette fonction a ete modifiee pour etre compatible avec les mots de
 * passe Shadow Linux si USE_SHADOW a ete defini
 *
 * retourne:
 *      UPAP_AUTHNAK: Connection refusee.
 *      UPAP_AUTHACK: Connection Acceptee.
 * Dans un cas comme dans l'autre, msg pointe sur le message approprie.
 */
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
 if ((spwd = getspnam(user)) == NULL) {
 pw->pw_passwd = "";
 } else {
 pw->pw_passwd = spwd->sp_pwdp;
 }
#endif
 /*
 *  XXX Si il n'y a pas de mots de passe, accepte la connection.
 */
 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);
 /*
 * Ecris une entree wtmp pour cet utilisateur.
 */
 tty = strrchr(devname, '/');
 if (tty == NULL)
 tty = devname;
 else
 tty++;
 logwtmp(tty, user, "");     /* Ajoute une entree wtmp de connection  */
 logged_in = TRUE;
 return (UPAP_AUTHACK);
}

En examinant précisément le code, vous verrez que d'autres modifications ont été effectuées. La version originale autorisait l'accès (en retournant UPAP_AUTHACK) quand il n'y avait pas de mots de passe dans le fichier passwd. Il ne fallait pas laisser ceci car utilisé avec l'option login, pppd utilise le nom d'utilisateur dans /etc/passwd et le mot de passe dans /etc/shadow pour son authentification PAP.

Donc si nous avions gardé la version originale, n'importe qui aurait pu établir une connexion ppp avec un mot de passe vide.

Nous avons arrangé ça en retournant UPAP_AUTHNAK à la place de UPAP_AUTHACK dans le cap ou le champ mot de passe est vide.

A savoir que pppd-2.2.0 possède le même problème.

Nous devons modifier le Makefile pour que deux choses soient prises en compte: USE_SHADOW doit être défini, et libshadow.a doit être ajouté au processus d'édition de liens.

Editez le Makefile, et ajoutez:

LIBS = -shadow

Alors, trouvez la ligne:

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

et replacez-la par:

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

Maintenant, lancez make et installez.


Page suivante - Page précédente - Table des matières