LOGO ISTY Institut des Sciences
et Techniques des Yvelines (ISTY)
English version
of this page
*

Rapport de Système


Table des matières



1 Introduction


1.1 Enonce du sujet

Le but de ce projet était de réaliser un système de diffusion de messages. C'est à dire un groupe de programmes capables de gérer les articles (serveur), capable de lire (lecteur) et poster (client). Nous avons réalisé notre projet en gardant à l'esprit (et au premier plan) la portabilité du système ainsi que sa sécurité.

1.2 Pourquoi faire le rapport en HTML ?

Aujourd'hui pratiquement tout le monde a ou peut avoir un browser Internet qui permet de lire les documents créés en HTML. Ainsi, on n'a pas besoin de se préoccuper du poste de travail, ni de l'imprimante utilisée par le destinataire.
Une présentation en format Internet peut être agrémentée de démonstrations directes interactives avec l'utilisation d'outils comme Javascript, ou Java.

1.3 Hypothèses de travail

Nous avons essayé d'avoir le moins de dépendances par rapport au système où est installé nos programmes. De même, n'importe qui peut installer/utiliser nos programmes. Il n'est absolument pas nécéssaire d'être root pour lancer le programme serveur (voir chap 2.1.1  pour plus d'explications). Par contre, il faudra que l'utilisateur choississe un répertoire (pour placer les futurs articles) ayant un accès général, tel que /users/public pour le serveur de l'ISTY.

2 Structures des données et des programmes utilisés


2.1 Les programmes

Nous avons organisé notre projet autour de 3 fichiers principaux:

Ces trois fichiers sont les exécutables du projet. Ceux-ci sont places, grace au programme d'installation integre dans le Makefile, dans un repertoire defini du systeme.
Chacun de ces trois programmes utilise un système commun de messagerie. Ce système de messagerie (assez rudimentaire) a pour fonction de rapporter au concepteurs des programmes diverses informations. Cette messagerie est entièrement configurable, et peut donc être redirigée vers l'écran (/dev/tty), la console (/dev/console), un fichier, ou même le démon SYSLOGD (voir chapitre 4.2  pour les améliorations):
Comme on peut le voir, nous avons même pensé à passer en paramètre un ordre de priorité pour l'information arrivante (NOTICE, WARNING, ERROR).

2.1.1 Le Serveur

Nous avons considéré le serveur comme étant un démon. donc, il nous a fallu employer les manières habituelles pour rendre notre programme indépendant de tout (equivalent de la commande 'nohup'). Pour cela, il nous a fallu entre autre detacher le programme du terminal:
Nous avons realisé ceci, comme on peut le voir ici, en créant un fils, et en tuant le père. De même, pour que le fils reste, il faut détourner certains signaux:
En fait, seuls le signal SIGTERM nous intéresse ici. Il est redirigé vers la fonction qui ignore les signaux (SIG_IGN). Les autres signaux permettent de gérer la fin du serveur. En effet, il ne suffit pas d'effectuer un brutal 'kill -9 -1' pour retirer le serveur. En faisant ceci, la file de messages allouée reste. On peut le voir en exécutant la commande 'ipcs'.
Donc, notre serveur intercepte les appels, et s'ils montrent que l'utilisateur souhaite terminer le programme, il retirera la file avant de quitter:
Remarque: Lorsque le serveur est lancé, il créé dans le repertoire des articles un fichier '.lock.serv'. Ce fichier sert au client pour savoir si le serveur est lancé.
La principale activité du serveur est d'attendre un message. Nous avons défini un petit protocole entre le client et le serveur, pour s'assurer que les messages arrivent bien:
Structure d'un envoi:
Pour réaliser ce petit système, nous n'avons utilisé qu'une seule file de messages, en utilisant ses propriétés (il est possible d'ajouter un type à chaque message envoyé). Le client a sa file de message configurée pour recevoir uniquement les messages de type ACK/NACK et inversement pour le serveur qui ne reçoit que les messages de type 'nom de repertoire'.
Bien sûr, si l'on avait voulu réaliser un système plus évolué, il nous aurait obligatoirement fallu utiliser au moin deux files de messages.
L'attribution des numéros d'identification aux noms des articles finaux se fait grâce à un fichier nommé 'data.fat' situé dans le répertoire des articles. A chaque fois qu'un article est ajouté, la valeur stockée dans ce fichier (fichier binaire) est incrémentée. Ceci évite de nombreux problèmes dûs à la numérotation des articles.
Donc, tout ce que fait le serveur, après avoir reçu le nom du fichier, est de renommer le fichier en un fichier "news.XXXX" où XXXX est le numéro de l'article.

2.1.2 Le Client

Le client est la partie la plus complexe puisque ce fichier nécéssite l'utilisation du SUID bit. D'où une grande nécéssité dans l'élaboration d'une bonne sécurité (pour la sécurité, voir 3.1). Ce programme a été réalisé de telle manière que rien ne puisse être perdu dans le traitement. Même les fichiers temporaires...
Il nous a fallu couper en deux notre futur fichier-article; en effet, les informations récupérées par notre programme (nom, e-mail, date, sujet) ne doivent pas être modifiables par l'utilisateur lors de l'exécution de l'éditeur de texte !
Le fichier de texte entré par l'utilisateur est mis temporairement dans le répertoire /tmp pour des raison de droits. En effet, pour la sécurité, nous avons changé le nom de l'utilisateur qui exécute le 'vi'. Donc il ne lui est pas possible d'écrire dans le répertoire des articles.
Ces deux fichiers sont fusionnés en un seul et mis dans le répertoire des articles.
Enfin, un message est envoyé au serveur pour signaler l'arrivée d'un message.

2.1.3 Le Lecteur

Comme énoncé dans les hypothèses de l'énoncé, le lecteur utilise un fichier '.forum' stocké dans le compte de l'utilisateur. Celui-ci contient les numéros des articles QUI ONT DEJA été lus.
Le format de ce fichier est très simple: Si il y a au moins deux numéros consécutifs, la suite de nombres est remplacée par les deux extrémités plus le signe '-'.
Par exemple, si on avait lus les articles 1 3 4 5 6 7 12, dans le fichier .forum, on aurait:
Nous avons réalisé un automate à trois états qui génère ce type de sortie:
Et voici le source correspondant:
for (i=0; i<=maxNum;i++){       
        switch(numeros[i]){
                case 0: switch(Etat){
                                case 0: break;
                                case 1: fprintf(fd," "); Etat = 0; break;
                                case 2: fprintf(fd,"%d ",i-1); Etat = 0; break;
                                }
                        break;
                case -1: switch(Etat){
                                case 0: fprintf(fd,"%d",i); Etat = 1; break;
                                case 1: fprintf(fd,"-"); Etat = 2; break;
                                case 2: break;
                                }
                        break;  
                }
        }
if (Etat == 2) fprintf(fd,"%d",i);
La sauvegarde des numéros des articles déjà lus dans le fichier .forum ne se fait pas dès qu'un article est lu. Il est fait UNIQUEMENT à la fin, lorsque l'utilisateur veut quitter. Ceci évite en effet de facheux problèmes, comme un coupure de courant intervenant au milieu de la lecture d'articles.
Dans notre cas, l'utilisateur retrouverait les articles qu'il a lu et non lu pendant cette précédente session.
Pour gérer ceci, il nous a suffit de transférer les numéros des articles déjà lus (fichier .forum) dans un tableau d'entiers. Lorsque, à une position t on a la valeur -1, c'est que l'article correspondant au numéro t est lu.
C'est ce tableau qui est traité juste avant la fin de l'exécution du programme.
L'interface gère tout ce qui a été demandé, et même un peu plus, puisque nous avons même rajouté un marqueur définissant le numéro courant.
L'utilisateur a aussi le choix de ne pas mettre à jour le .forum, s'il pense avoir oublié quelque chose...
Remarque: Si aucun article n'a été lu, et que l'utilisateur quitte le programme, le lecteur s'en rend compte, et ne traite donc pas la liste des articles déjà lus. On gagne donc du temps.
/users/public/News> lecteur
Lecteur -- 0.999

=============================================================
0            Luc Stepniewski      Message de bienvenue
1                       root      J verifie !
2            Bob Bob Testeur      Je pense que j'ai raison 
LECTEUR> q
Aucuns fichiers modifies, pas besoin d'updater le fichier .forum
/users/public/News> 

2.1.4 Le fichier Makefile

Le fichier Makefile a plusieurs exécutions possibles:
sans arguments: Permet de compiler tous les exécutables
'install': compile ET installe dans les bons répertoires
'clean': efface les fichier indésirables et inutiles
'dist': Réalise une archive des fichiers du projet pour une distribution ultérieure.
Bien sur il est possible de compiler les fichiers séparément en entrant leur nom:

Pour plus d'informations reportez vous au fichier 'INSTALL' de l'archive.

3 La securite dans le projet


3.1 Les différents problèmes de sécurité rencontrés et leur solution

Le principal problème est le fait d'utiliser un programme avec les droits d'une autre personne (exécutable client). En effet, ce client doit exécuter vi, donc, doit lancer un programme externe.
Sous vi , il est possible d'exécuter des commandes shell, voire même lancer un shell, grâce au commandes commençant par ':!' (':!ksh').
Ceci n'est pas acceptable, puisque, en se retrouvant avec un shell, la personne aurait les droits de celui qui a compilé le programme !
Donc, ce que nous faisont, c'est que le propriétaire du programme est changé juste pendant l'exécution de vi (dans le fils qui exécute vi). De cette manière, rien ne peut être tenté lors de l'utilisation de vi.
De même, nous avons porté une attention particulière sur la longueur des chaines récupérée lors de traitements interactifs.
En effet, toute chaine a une taille en mémoire qui est fixe. Donc si la personne entre un caractère de plus, cela risque d'écraser les variables qui se trouvent après (Trou de sécurité bien connus dans de nombreux anciens programmes):
char *Editeur, subject[256], reponse[1],nomfichier[80], entete[6*80], *buffer;
struct passwd *infos;
Par exemple, si nous laissions l'utilisateur entrer plus de 256 caractères dans la variable subject, il pourrait aisémment installer volontairement certaines valeurs précises dans les variables qui suivent !
Nous avons donc proscrit COMPLETEMENT l'utilisation de fonctions telles que 'gets'. A la place nous utilisons par exemple 'fgets' qui permet de passer en argument un taille maximale.
Une autre règle de sécurité est de ne faire que des références absolues vers des fichiers. Si nous avions juste mis 'vi' pour l'exécution, une personne aurait bien pu mettre un script shell de ce nom, et, pour peu que l'utilisateur ait le '.' en premier dans son PATH....

Enfin, pour le lancement du serveur, nous réinitialisons le umask par défaut, et nous nous plaçons dans le répertoire de base '/' au cas où nous serions sur un système de fichiers montés (NFS).

4 Ameliorations possibles


D'après nous il est toujours possible d'optimiser le traitement de la sécurité dans ce projet. Principalement dans l'algorithme et les files de messages.

4.1 Securite

Au lieu d'utiliser des files de messages, qui limitent l'utilisation des news à un seul réseau, il aurait été plus intéressant d'utiliser les socket, qui, elles, peuvent être utlisées d n'importe quel point d'Internet.
De même, il serait préférable (et recommandé) de tout envoyer à travers la file de message/socket. Ce qui aurait rendu le projet non seulement plus simple, mais aussi plus sécurisé.

4.2 Convivialite

Actuellement, l'informatique évolue en direction de l'interface graphique. Il aurait été très intéressant de pouvoir implémenter une interface à ce projet dans un langage tel que Tcl/Tk (interfaçable avec le langage C). Ou bien même utiliser la librairie Curses (De la même manière que le reader de mail 'elm'). Le problème avec Curses, c'est qu'il est très peu compatible et pose de nombreux problèmes, notamment sur Sun.

Une autre alternative serait de réaliser l'interface directement dans une page HTML disponible sur le Web, comme cela se fait de plus en plus. On aurait quelque chose de ce genre:
Nom: Prenom:

Message à envoyer au serveur de News:



Actuellement la tendance est à la centralisation des outils. AIX a ses outils d'administration (équivalent de SMIT) entièrement dans des fenêtres HTML. Avec l'arrivée de Java, on ne peut que s'attendre à une montée encore plus forte de cette tendance.

4.3 Compatibilité

Le projet, fonctionne parfaitement sur toutes les machines que nous avons testé, c'es à dire: HP, Sun, Linux. Ce travail nous a été facilité par l'utilisation du GNU Autoconf. Ce programme permet de tester l'architecture du système, les fonctions, les librairies, les en-têtes disponibles. Ces tests se font grâce à un script shell appelé 'configure'.
Ce script a lui-même été généré à partir du fichier configure.in que nous avons laissé dans l'archive, au cas ou vous souhaiteriez l'examiner. Le script 'configure' a pour tache finale de remplacer toutes pseudos-variables que nous avons définis dans le Makefile.in et donc de générer un Makefile correct, c'est à dire adapté au système actuel:
Ici, configure remplacera @CC@ par le meilleur compilateur C disponible (d'habitude gcc).
De même configure génère un fichier config.h à partir du fichier config.h.in. Celui-ci contient des #define, qui seront ou ne seront pas mis en commentaires:
/* DEFINES AUTO MODIFIE PAR LE PROGRAMME CONFIGURE */
#undef HAVE_UNISTD_H
#undef HAVE_DIRENT_H
#undef HAVE_SYS_NDIR_H
#undef HAVE_SYS_DIR_H 

#undef HAVE_SETSID
#undef HAVE_GETOPT

Ces variables, après passage de configure donnent (sur Linux):

/* DEFINES AUTO MODIFIE PAR LE PROGRAMME CONFIGURE */
#define HAVE_UNISTD_H 1
#define HAVE_DIRENT_H 1
/* #undef HAVE_SYS_NDIR_H */
#define HAVE_SYS_DIR_H 1 

#define HAVE_SETSID 1
#define HAVE_GETOPT 1

Il n'y donc plus qu'à tester ces constantes dans le programme et l'adapter.
Par exemple, si la focntion 'setsid()' n'existe pas sur le systeme
(HAVE_SETSID), nous avons défini une alternative dans le fichier serveur.c

#if HAVE_SETSID
if (setsid() == -1){
#else
if(setpgrp(getpid(),0) == -1) {
#endif
        SndLog(ERROR,"Probleme execution du SETSID\n");
        exit(1);
        }

5. Annexe


5.1 Listing des programmes

5.1.1 Le Posteur


/* Le POSTEUR */
#include 
#include "../config.h"

#include 
#include 
#include 

#ifdef HAVE_UNISTD_H
#include 
#include 
#endif

#include 
#include 
#include 

#define VERSION "0.888"
#define NOMPRG  "POSTEUR"

struct messbuf {
	long mtype;
	char texte[TAILLE];
	};
/*************************************************************************/
int main(int argc,char *argv[])
{
char *Editeur, subject[256], reponse[1],nomfichier[80], entete[6*80], *buffer;
struct passwd *infos;
time_t t;
char *tzstr = "TZ=PST8PDT", *ptr;
FILE *fsortie, *fentree;
char repfichtemp[512], strtmp[256];
Bool Continuer, Recommence;
int pID, nbLu;

key_t msgcle;
int drapeau,idtmsg,boucle;
struct messbuf tampon;

printf("\n========= %s %s =======\n\n\n",NOMPRG,VERSION);

SndLog("Lancement Client %s %s\n",NOMPRG,VERSION);

sprintf(strtmp,"%s/%s",REPNEWS,LOCKSERV);

if (access(strtmp,F_OK)){
	printf("Le serveur ne semble pas etre lance. Impossible de travailler!\n");
	exit(1);
	}

if ((Editeur=(char *)getenv("EDITOR")) == NULL){
	printf("Pas d'editeur defini avec la commande EDITOR !\n");
	exit(1);
	}
	
/* Il faudrait retirer toutes les autres variables d'environnement pour
   la securite......  */

printf("Sujet: ");
fgets(subject,256,stdin); 	/* PAS DE GETS ! SECURITE OBLIGE */

/* On choisi un nom de fichier maintenant */
sprintf(nomfichier,"/tmp/tmp%d",getpid());
umask(000);

Continuer = False;
do {
	if ((pID=fork())==0)
		{
		setuid(getuid()); setgid(getgid()); 
		execl(Editeur,Editeur,nomfichier,NULL);
		printf("Erreur execution de %s\n",Editeur);
		SndLog(ERROR,"client: Erreur exec EDITOR: %s par %d\n",Editeur,getuid());
		}
   wait(NULL);
   if (access(nomfichier,F_OK)) {
	printf("Fichier vide ! Rien a faire\n");
	exit(0);
	}
   do {
   	printf("e) Editer message s) Envoyer le message f) Oublier le message\n");
   	fflush(stdin);
   	reponse[0] = getchar();
   	switch(reponse[0]) {
   		case 's': Continuer = True; Recommence = False; break;
   		case 'e': Continuer = False; Recommence = False; break;
   		case 'f': unlink(nomfichier); printf("Message efface\n"); exit(1);
   		default: printf("Mauvais choix !\n"); Recommence = True; break;  
   		}
   	} while (Recommence);
   } while (!Continuer); 

/* On ouvre AVANT le fichier que l'on a tape, au cas ou il n'y */
/* serait plus, ou s'il est vide....On teste cela evite d'avoir a effacer  */
/* le pre-message qui a deja ete ecrit dans le rep du serveur... */

sprintf(repfichtemp,"%s/final.%d",REPNEWS,getpid());
chmod(repfichtemp,00400|00200);

if ((fsortie=fopen(repfichtemp,"w"))==NULL) {
        perror("ERREUR FICHIER #1");
	fclose(fsortie);
        exit(1);
        }

infos=getpwuid(getuid());
t = time(NULL);
fprintf(fsortie,"From: %s (%s)\nDate: %sSubject: %s\n",infos->pw_name,infos->pw_gecos,asctime(gmtime(&t)),subject);
buffer = (char *)malloc(sizeof(char)*4096);

if ((fentree=fopen(nomfichier,"r"))==NULL) {
        perror("ERREUR FICHIER #2");
        fclose(fentree);
	fclose(fsortie);
        exit(1);
        }

nbLu = fread(buffer,sizeof(char),4096,fentree);
while(!feof(fentree)) {
	fwrite(buffer,sizeof(char),nbLu,fsortie);
	nbLu = fread(buffer,sizeof(char),4096,fentree);
	}
fwrite(buffer,sizeof(char),nbLu,fsortie);

free(buffer);
fclose(fsortie);
fclose(fentree);
unlink(nomfichier);

/* On envoie le nom du fichier au serveur, avec TYPE=MSGSEND */
/* On installe une alarme, au cas ou le serveur ne repondrait pas */

/* recherche de la clef d'acces */
if ((msgcle = ftok("/etc",'Z')) == -1){
	perror("ftok: pas de clef trouvee");
	/* proposer de sauver mail dans un fichier */
	exit(1);
	}

/* Recherche de l'identifiant message */
if ((idtmsg = msgget(msgcle,0600)) == -1) {
	perror("msgget: identif.");
	/* proposer de sauver mail dans un fichier */
	exit(1);
	}
if (strlen(repfichtemp) >= 511){
	printf("Longueur repertoire trop grande !\n");
	exit(1);
	}
	
strcpy(tampon.texte,repfichtemp);
tampon.mtype = MSGSEND;

if (msgsnd(idtmsg,&tampon,TAILLE,0) == -1){
	printf("Erreur envoi du nom du fichier au serveur !\n");
	exit(1);
	}

printf("Message envoye\n");

boucle = True;
bzero(tampon.texte,511);


while (boucle) {
	/* On attend la confirmation par ACK maintenant */
	if (msgrcv(idtmsg,&tampon,TAILLE,0,0) == -1) {
		perror("msgrcv");
		exit(1);
		}
	if (!strncmp(tampon.texte,"ACK",3)) {
		boucle = False;
		printf("Accuse de reception bien recu !\n");
		} else {
		printf("Erreur dans la file: %s\n",tampon.texte);
		}
	}
printf("Bye\n");
}

5.1.2 Le Serveur


/* Le SERVEUR */
#include 
#include "../config.h"

#include 
#include 
#include 
#include 
#include 
#include 
#ifdef HAVE_UNISTD_H
#include 
#include 
#endif
#include 
#include 

/* Configuration compatibilite structure DIR* */
#if HAVE_DIRENT_H
# include 
# define NAMELEN(dirent) srtlen((dirent->d_name)
#else
# define dirent direct
# define NAMELEN(dirent) (dirent->d_namlen)
# if HAVE_SYS_NDIR_H
#  include 
# endif
# if HAVE_NDIR_H
#  include 
# endif
#endif


#define VERSION "0.999 beta"
#define NOMPRG  "SERVEUR"

struct messbuf {
	long mtype;
	char texte[TAILLE];
};

int idtmsg, nbjours;
/*************************************************************************/
void usage()
{
printf("Syntaxe: %s -e X [-f]\n",NOMPRG);
printf("\t-  X est la duree en jours d'un message avant expiration\n");
printf("\t- -f (option) permet de recuperer directement de stdin le msg\n");
exit(1);
}
/*************************************************************************/
int Quitte(int numsig)
{
char strtmp[256];

sprintf(strtmp,"%s/%s",REPNEWS,LOCKSERV);
SndLog(WARNING,"Arret du Serveur. Interception SIGNAL %d\n",numsig);
msgctl(idtmsg,IPC_RMID,0);
unlink(strtmp);
exit(1);
}
/*************************************************************************/
/* Traitement des expirations */
/*************************************************************************/
void TraiteExpire(int i)
{
DIR *dirp;
struct dirent *dp;
struct stat *buf;
int nb=0;

SndLog(NOTICE,"Lancement Traitement Expiration");

dirp = opendir(REPNEWS);
while ((dp = readdir(dirp)) != NULL) {
	if (strcmp("..",dp->d_name) && strcmp(".",dp->d_name)) {
		stat(dp->d_name,buf);
		if ((buf->st_mtime + (nbjours*3600*24)) > time(NULL)){
			unlink(dp->d_name);
			nb++;
			}
		}
	}
SndLog(NOTICE,"%d fichiers ont ete efface lors du traitement expiration\n",nb);
signal(SIGALRM,(void (*)())TraiteExpire);
}
/*************************************************************************/
/* Traitement du fichier recu. */
/*************************************************************************/
int TraiteFichier(char *nomtemp)
{
FILE *fichFAT;
char cheminFAT[256], nouveaunom[256];
unsigned long numero;

sprintf(cheminFAT,"%s/%s",REPNEWS,MSGCOUNT);

if ((fichFAT = fopen(cheminFAT,"r")) == NULL) numero = 0;
	else  fread(&numero,1,sizeof(unsigned long),fichFAT);
fclose(fichFAT); 

sprintf(nouveaunom,"%s/news.%d",REPNEWS,numero);
SndLog(NOTICE,"Reception Texte -> nom: %s\n",nouveaunom);

if (rename(nomtemp,nouveaunom)){
	SndLog(ERROR,"serveur.c: Renommage Impossible tmp->%s\n",nouveaunom);
	unlink(nomtemp);
	return(1);
	}
chmod(nouveaunom,0644);
numero++;
if ((fichFAT = fopen(cheminFAT,"w")) == NULL){
	SndLog(ERROR,"serveur.c: Fichier data.fat impossible a ecrire");
	exit(1);
	}
	
fwrite(&numero,1,sizeof(unsigned long),fichFAT);
fclose(fichFAT);
return(0);
}
/*************************************************************************/
int main(int argc,char *argv[])
{
char subject[256], reponse[1],nomfichier[80], entete[6*80], *buffer, strtmp[256];
struct passwd *infos;
time_t t;
char nomtemp[256];
Bool Continuer;
int pID, nbLu, pFils;

key_t msgcle;
int drapeau,boucle;
struct messbuf tampon;

int c, pidfils, fd;
extern char *optarg;
extern int optind, optopt;

printf("\n========= %s %s =======\n\n\n",NOMPRG,VERSION);
SndLog(NOTICE,"Lancement du Daemon SERVEUR\n");

/* La personne a-t-elle les droits en ecriture sur le repertoire des News ? */
if (access(REPNEWS,W_OK)){
	printf("Vous n'avez pas l'autorisation d'executer ce serveur !\n");
	SndLog(ERROR,"Quelqu'un tente d'executer le serveur sans autorisation\n");
	exit(1);
	}

sprintf(strtmp,"%s/%s",REPNEWS,LOCKSERV);

if (!access(strtmp,F_OK)) {
	SndLog(WARNING,"Le serveur est deja lance !\n");
	printf("Erreur, un serveur est deja lance ou bien vous avez mal quitte la derniere fois.\n");
	printf("Souhaitez-vous continuer ? (verifiez que le serveur est lance)(OoYy/Nn)\n");
	c = toupper(getchar());
	switch(c) {
		case 'N': exit(1);
		case 'Y':
		case 'O': break;
		default: exit(99); 
		}
	}

if (argc < 3) usage(); 

#if HAVE_GETOPT
while ((c = getopt(argc, argv, ":e:f")) != -1)
    switch (c) {
   	case 'e':
		nbjours = atoi(optarg); break;
	case 'f':
		printf("Entree standard activee\n");
		SndLog(NOTICE,"Entree standard activee"); break;
	case '?':
		printf("Erreur, commande non reconnue: %c !!\n",optopt);  exit(1);
	default: break;
	}
#else
nbjours = atoi(argv[2]);		/* Assez approximatif.... :-( */
#endif

if ((pidfils=fork()) < 0) SndLog(ERROR,"serveur.c: Pas pu lancer fils\n");
	else if (pidfils > 0) exit(0);

/* On est le FILS maintenant */
#if HAVE_SETSID
if (setsid() == -1){
#else
if(setpgrp(getpid(),0) == -1) {
#endif
	SndLog(ERROR,"Probleme execution du SETSID\n");
	exit(1);
	}
	
/*chdir("/");*/	/* Pour etre sur que l'on est pas sur un FS monte */
umask(0);	/* retire tous les modes heritages precedents */

if (creat(strtmp,0711) == -1){
	SndLog(ERROR,"Erreur creation du fichier .lock !\n");
	exit(1);
	}

	
signal(SIGTERM,SIG_IGN);
signal(SIGINT,(void (*)(int))Quitte);
signal(SIGHUP,(void (*)(int))Quitte);
signal(SIGQUIT,(void (*)(int))Quitte);
signal(SIGSEGV,(void (*)(int))Quitte);
signal(SIGBUS,(void (*)(int))Quitte);
signal(SIGALRM,(void (*)(int))TraiteExpire);
		
/* recherche de la clef d'acces */
if ((msgcle = ftok("/etc",'Z')) == -1){
	perror("ftok: pas de clef trouvee");
	SndLog(ERROR,"serveur.c: ftok: pas de clef trouvee");
	exit(1);
	}
	
/* Creation de l'identifiant message */
if ((idtmsg = msgget(msgcle,IPC_EXCL | IPC_CREAT | 0600)) == -1) {
	SndLog(ERROR,"serveur.c: msgget: identif.");
	perror("msgget: identif.");
	/* proposer de sauver mail dans un fichier */
	exit(1);
	}


alarm(nbjours*3600*24);

Continuer = False;
do {
    /* On attend ici un nom de fichier */
    if (msgrcv(idtmsg,&tampon,TAILLE,MSGSEND,MSG_NOERROR) == -1) {
    	SndLog(ERROR,"serveur.c: msgrcv\n");
    	unlink(strtmp);
	perror("msgrcv");
	exit(1);
	}
   SndLog(NOTICE,"Reception du fichier %s\n",tampon.texte);
   strcpy(nomtemp,tampon.texte);
   
   TraiteFichier(nomtemp);

   bzero(tampon.texte,512);
   /* Reponse indiquant que l'on a bien recu (ou mal) le mnessage */
   strcpy(tampon.texte,"ACK"); 
   tampon.mtype = 'Z';		/* Tout mais pas MSGSEND */

   if (msgsnd(idtmsg,&tampon,TAILLE,0) == -1){
   	SndLog(ERROR,"Erreur sortie fichier FAT contenant les numeros d'index...\n");
    	unlink(strtmp);
   	exit(1);
   	}
   SndLog(NOTICE,"ACK envoyé\n");
   } while (!Continuer);


/* Ne passe JAMAIS par ici ! */
msgctl(idtmsg,IPC_RMID,0);
}

5.3 Le Lecteur


/* Le Lecteur */
#include 
#include "../config.h"

#include 
#include 
#include 
#include 

#ifdef HAVE_UNISTD_H
#include 
#include 
#endif

/* Configuration compatibilite structure DIR* */
#if HAVE_DIRENT_H
# include 
# define NAMELEN(dirent) srtlen((dirent->d_name)
#else
# define dirent direct
# define NAMELEN(dirent) (dirent->d_namlen)
# if HAVE_SYS_NDIR_H
#  include 
# endif
# if HAVE_NDIR_H
#  include 
# endif
#endif

#define NOMPRG "Lecteur"
#define VERSION "0.999"

/* Ce fichier contient les ref vers les articles qui ONT ete lus */
/* On n'utilise pas le tilde car non supporte par tous les systemes */
#define NOMFORUM ".forum"

int *numeros;
int Courant = 0;
Bool modifie = False;
/*****************************************************************************/
unsigned long checkMax()
{
DIR *dirp;
struct dirent *dp;
unsigned long maximum;
char *ptr;
int i;

maximum = 0;
if ((dirp = opendir(REPNEWS))==NULL){
	perror("opendir");
	exit(1);
	}
	
while((dp = readdir(dirp)) != NULL){
	if (!strstr(dp->d_name,"news.")) continue;
	ptr = (char *)(dp->d_name+5);
	if((i=atoi(ptr)) > maximum)
		maximum = i;
	}
closedir(dirp);
return(maximum);
}
/*****************************************************************************/
/* Contruit une liste statique de nb a partir d'une chaine (celle du fichier */
/* .forum. Les numeros etant a -1, signifient qu'ils ont ete lus. */
void BuildListe(char *chaine, int maxNum)
{
char *Pos = chaine;
int numdernier, num, i, nb;
int multi, dansMulti = False;

/* On se replace au debut */
Pos = chaine;

while ((*Pos != '\0') && (*Pos != '\n')) {
	sscanf(Pos,"%d",&nb);
	if (nb >= maxNum)
		fprintf(stderr,"Attention:Vous utilisez un ancien .forum !\nNous vous conseillons de l'effacer...");
	numeros[nb] = -1;
	while ((*Pos != ' ') && (*Pos  != '-') && (*Pos != '\0')) Pos++;
	switch(*Pos) {
		case '\0': 
		case '\n':if (dansMulti) for(i=multi+1; i <= nb;i++) numeros[i] = -1;
			  break;
		case ' ': Pos++; 
			  if (dansMulti) {
			  	dansMulti = False;
			  	for(i=multi+1; i <= nb;i++) numeros[i] = -1;
			  	}
			  break;
		case '-': Pos++; dansMulti = True;
			  multi = nb;
			  break;
		default:  fprintf(stderr,"Caractere INCONNU: '%c' rencontre !\n");
			  free(numeros);
			  exit(1); break;
		}
	}
}
/*****************************************************************************/
int select(const struct dirent *dp)
{
if (strstr(dp->d_name,"news.")) return(1);
else return(0);
}
/*****************************************************************************/
void AfficheNews(void)
{
DIR *dirp;
/* struct dirent *dp; */
int nume;
char bidon[250], bidon2[150], from[50], sujet[150], *point;
FILE *fd;

struct dirent **namelist, **list;
int num_entrees, i, j, k;

	
if ((num_entrees = scandir(REPNEWS, &namelist,select, alphasort)) < 0) {
	fprintf(stderr, "Unexpected error\n");
        exit(1);
        }

if (num_entrees) {
	printf("=============================================================\n");
	for (i=0, list=namelist; id_name,'.')+1);
		if (numeros[nume] != -1) {
			sprintf(bidon,"%s/%s",REPNEWS,(*list)->d_name);
			if ((fd = fopen(bidon,"r"))==NULL){
				printf("Erreur ouverture!\n");
				exit(5);
				}
			fgets(bidon,150,fd);
			k = j = 0;
			while((bidon[k] != '(') && (bidon[k] != '\0')) k++;
				k++;
			while((bidon[k] != ')') && (bidon[k] != '\0')) { 
				from[j++] = bidon[k];
				k++;
				}
			from[j] = '\0';
			fgets(bidon,150,fd);
			fgets(bidon,150,fd);
			point = (char *)index(bidon,':');
			point++;
			fclose(fd);
			printf("%d\t%20s \t %s",nume,from,point);
			free(*list);
			}
		*list++;
		}
		free(namelist);
		}
}
/*****************************************************************************/
int DelArticle(char *question, int maxnum)
{
int i;

if (strlen(question) == 2) {
	i = Courant;
	} else {
	question = (char *) question+2;
	i = atoi(question);
	if (i > maxnum) {
	/*	printf("Pas de numero de ce genre ICI !(max:%d)\n",maxnum);*/
		return(-1);
		}
	}
numeros[i] = -1;
return(i);
}
/*****************************************************************************/
void SaveListe(char *maison,int maxNum)
{
char strtmp[200];
FILE *fd;
int i, Etat=0;

if (!modifie) {
	printf("Aucuns fichiers modifies, pas besoin d'updater le fichier .forum\n");
	exit(1);
	}
	
printf("Voulez-vous updater votre fichier .forum ?\n");
fgets(strtmp,10,stdin);

strtmp[0] = toupper(strtmp[0]);
if (strtmp[0] == 'N'){
	printf("Fichier %s NON update\n",NOMFORUM);
	exit(1);
	}
sprintf(strtmp,"%s/%s",maison,NOMFORUM);
if ((fd = fopen(strtmp,"w"))==NULL){
	fprintf(stderr,"Erreur ouverture fichier %s\n",strtmp);
	exit(1);
	}
/* On cherche maintenant TOUS les numeros ayant "-1" comme index. */
/* Tres utiles, les automates finis ! */
for (i=0; i<=maxNum;i++){	
	switch(numeros[i]){
		case 0: switch(Etat){
				case 0: break;
				case 1: fprintf(fd," "); Etat = 0; break;
				case 2: fprintf(fd,"%d ",i-1); Etat = 0; break;
				}
			break;
		case -1: switch(Etat){
				case 0: fprintf(fd,"%d",i); Etat = 1; break;
				case 1: fprintf(fd,"-"); Etat = 2; break;
				case 2: break;
				}
			break;	
		}
	}
if (Etat == 2) fprintf(fd,"%d",i);

printf("Fichier %s update !\n",NOMFORUM);
fclose(fd);
}
/*****************************************************************************/
void usage(void)
{
printf("\nCommandes Disponibles:\n");
printf("======================\n");
printf("(h)elp      \tAffiche cette aide\n");
printf("(l)ist      \tAffiche les articles disponibles (non lus)\n");
printf("(d) [nombre]\tEfface l'article/article courant\n");
printf("(D)elete    \tEfface tous les articles\n");
printf("(q)uitte    \tQuitte le programme en sauvant ou non les articles lus\n");
}
/*****************************************************************************/
int main(int argc, char *argv[])
{
char *maison, strtmp[256], *ligneNumeros=NULL, *Pager, question[300], *ptr;
FILE *fd;
int i, maxNum=checkMax();

printf("%s -- %s\n\n",NOMPRG,VERSION);
signal(SIGINT,SIG_IGN);

if ((maison = (char *)getenv("HOME"))==NULL){
	fprintf(stderr,"Variable d'environnement HOME absente !\n");
	exit(1);
	}

if ((Pager = (char *)getenv("PAGER"))==NULL) {
	fprintf(stderr,"Pas de variable d'environnement PAGER definie !\n");
	exit(1);
	}

/* VerifyLength(); Verification de la longueur */

sprintf(strtmp,"%s/%s",maison,NOMFORUM);

if ((numeros = (int *)calloc(maxNum+1,sizeof(int))) == NULL){
	fprintf(stderr,"Pas pu allouer\n");
	exit(1);
	}

if ((fd = fopen(strtmp,"r"))!= NULL){
	if ((ligneNumeros = (char *)malloc(5*1000))==NULL){
		fprintf(stderr,"Pas assez de memoire !\n");
		fclose(fd);
		exit(1);
		}
	
	fgets(ligneNumeros,5*1000,fd);
	BuildListe(ligneNumeros,maxNum);
	/* Plus besoin du fichier ! */
	fclose(fd);
	}
	
AfficheNews();
while (True) {
	printf("LECTEUR> ");
	fgets(question,30,stdin);
	switch(question[0]){
		case 'l': AfficheNews(); break;
		case 'q': SaveListe(maison,maxNum); exit(1);
		case 'd': i = DelArticle(question,maxNum);
			  if (i != -1) {
			  	printf("Article %d efface.\n",i);
			  	modifie = True;
			  	}
			  break;
		case 'h': usage(); break;
		case 'D': for(i=0; i<= maxNum;i++) numeros[i] = -1;
			  printf("Tous les articles effaces !\n");
			  modifie = True;
			  break;
		default:  sscanf(question,"%d\n",&i);
			  if ((i > maxNum) || isalpha(question[0])) {
			  	printf("Pas de ceci ICI !(max:%d)\n",maxNum);
			  	break;
			  	}
			  sprintf(question,"%s/news.%d",REPNEWS,i);
			  Courant = i;
			  if (fork() == 0){
			  	execl(Pager,Pager,question,NULL);
			  	perror("Erreur Execution PAGER");
			  	exit(1);
			  	}
			  wait(NULL);
			  modifie = True;
			  printf("%d\n",i);
			  numeros[i] = -1;
			  break;
		}
	
	}
printf("Fin\n");
}

5.4 Le Makefile


############################################################################
# Fichier MakeFile pour tous les modules.
#
############################################################################
SRCDIR	= @srcdir@
VPATH	= @srcdir@
INSTALL	= @INSTALL@

SERVEUR = serveur
CLIENT 	= client
LECTEUR = lecteur
CC	= @CC@
LIBS	= -L. @LIBS@
LDFLAGS = @LDFLAGS@
#DEBUG	= -g -Wall -DDEBUG
CPPLAGS	= -I. @CPPFLAGS@ $(DEBUG)
# Si vous utilisez Linux sur processeur Intel, utilisez la 
# ligne suivante pour optimisations.
# CFLAGS = -O4 -m486 -fomit-frame-pointer -malign-loops=2 -malign-jumps=2 -malign-functions=2
CFLAGS	= -O2 

SHELL	= /bin/sh

CLIOBJS	= $(CLIENT).o
SEROBJS	= $(SERVEUR).o
LECOBJS = $(LECTEUR).o

DISTDEP = Makefile
DISTFILES= src/$(CLIENT).c src/$(SERVEUR).c src/$(LECTEUR).c src/general.c \
	config.h.in src/Makefile.in configure configure.in install-sh INSTALL \
	README

REPINSTALL = /users/public/News

all: $(CLIENT) $(SERVEUR) $(LECTEUR)

$(LECTEUR): $(LECOBJS) general.o
	$(CC) -o $(LECTEUR) $(LECOBJS) $(CFLAGS) general.o
	strip $(LECTEUR) 

client: $(CLIOBJS) general.o
	$(CC) -o $(CLIENT) $(CLIOBJS) $(CFLAGS) general.o
	strip $(CLIENT) 

$(SERVEUR): $(SEROBJS) general.o
	$(CC) -o $(SERVEUR) $(SEROBJS) $(CFLAGS) general.o
	strip $(SERVEUR) 

.c.o:
	$(CC) $(CFLAGS) $(CPPLAGS) $(SYSTEM) -c $<


install: all
	@echo "Installation dans $(REPINSTALL)";\
	if [ ! -d $(REPINSTALL) ]; then mkdir $(REPINSTALL); chmod a+rx $(REPINSTALL); fi
	rm -f $(REPINSTALL)/$(CLIENT) $(REPINSTALL)/$(SERVEUR) $(REPINSTALL)/$(LECTEUR) 
	rm -rf $(REPINSTALL)/articles
	$(INSTALL) -m 711 $(SERVEUR) $(LECTEUR) $(REPINSTALL)
	$(INSTALL) -m 4711 $(CLIENT) $(REPINSTALL)
	if [ ! -d $(REPINSTALL)/articles ]; then \
	mkdir $(REPINSTALL)/articles; \
	fi
	chmod 755 /users/public/News/articles

clean:
	rm -f *.o
	rm -f $(CLIENT) $(SERVEUR) $(LECTEUR) ../config.log ../config.status 
	rm -f ../config.h ../config.cache Makefile

dist: $(DISTDEP)
	nomdist=`sed -e '/define NEWSVERSION/!d' \
	-e 's/[^0-9.]*\([0-9.a-z]*\).*/stepnews-\1/' -e q ../config.h`; \
	rm -rf ../$$nomdist; \
	mkdir ../$$nomdist; \
	mkdir ../$$nomdist/src; \
	mkdir ../$$nomdist/doc; \
	for file in $(DISTFILES); do \
	ln ../$$file ../$$nomdist/$$file \
	|| { echo copie ../$$file a la place; cp -p ../$$file ../$$nomdist/$$file; }; \
	done; \
	chmod -R a+rX ../$$nomdist; \
	cd ..; \
	tar -chvf $$nomdist.tar $$nomdist; \
	rm -fr $$nomdist

Pour le reste (fichiers secondaires), veuillez vous reporter à l'archive.