#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; static char *strip_end(char *str) { int len = strlen(str); for (str += len - 1 ; len > 0 && (isspace(*str) || *str == 26) ; len--, str--) *str = '\0'; return str+1; } static char *str_skip (const char *s) { while (isspace(*s)) s++; return (char*)s; } int pam_check_pair( const char *user, const char *pass); #if 0 static struct passwd *livre_findbygecos(const char *gecos) { struct passwd *ret = NULL; setpwent(); while ((p=getpwent())!=NULL){ if (strcasecmp(gecos,p->pw_gecos)==0){ ret = p; break; } } return ret; } #endif static const char *livre_ctrl = NULL; static unsigned livre_len = 0; static unsigned dossier_len = 0; static const char *dossier_ctrl = NULL; static char *group_excl=NULL; /* Lit le fichier /etc/livraison.conf. Ce fichier est optionnel et il n'y a aucun message s'il est absent */ static void livre_readconf() { if (livre_ctrl == NULL){ livre_ctrl = "%u-%A-%m-%j_%h-%M-%s__%f"; livre_len = 100; dossier_len = 100; dossier_ctrl = "%s__%X"; FILE *fin = fopen ("/etc/livraison.conf","r"); if (fin != NULL){ char buf[1000]; int noline = 0; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ noline++; strip_end (buf); if (buf[0] == '\0' || buf[0] == '#') continue; if (strncmp(buf,"format:",7)==0){ livre_ctrl = strdup(str_skip(buf+7)); }else if (strncmp(buf,"maxlen:",7)==0){ livre_len = atoi(str_skip(buf+7)); }else if (strncmp(buf,"dformat:",8)==0){ dossier_ctrl = strdup(str_skip(buf+8)); }else if (strncmp(buf,"dmaxlen:",8)==0){ dossier_len = atoi(str_skip(buf+8)); }else if (strncmp(buf,"gexclu:",7)==0){ group_excl = strdup(str_skip(buf+7)); }else{ syslog (LOG_ERR,"Directive invalide /etc/livraison.conf, ligne %d: %s" ,noline,buf); } } fclose (fin); } } } /* Comme strcpy, mais retourne le pointer à la fin du buffer */ static char *livre_stpcpy (char *dest, const char *src) { strcpy (dest,src); return dest + strlen(src); } /* Remplace les tokens d'une chaine de controle */ static void livre_format ( const char *sctl, unsigned maxlen, char *sdest, const char *user, const char *name, const char *ecole, const char *prof, const char *docum) // Nom du document { time_t ti = time(NULL); struct tm *tt = localtime(&ti); for (int X=0; X<1000; X++){ bool x_was_used = false; char *dest = sdest; const char *ctl = sctl; while (*ctl != '\0'){ char carac = *ctl++; if (carac == '%'){ char spc = *ctl++; switch (spc){ case 'f': if (strlen(docum)>maxlen){ sprintf (dest,"%*.*s",maxlen,maxlen,docum); dest += maxlen; }else{ dest = livre_stpcpy (dest,docum); } break; case 'a': sprintf (dest,"%02d",tt->tm_year%100); dest += 2; break; case 'A': sprintf (dest,"%04d",tt->tm_year+1900); dest += 4; break; case 'm': sprintf (dest,"%02d",tt->tm_mon+1); dest += 2; break; case 'j': sprintf (dest,"%02d",tt->tm_mday); dest += 2; break; case 'h': sprintf (dest,"%02d",tt->tm_hour); dest += 2; break; case 'M': sprintf (dest,"%02d",tt->tm_min); dest += 2; break; case 's': sprintf (dest,"%02d",tt->tm_sec); dest += 2; break; case 'n': dest = livre_stpcpy (dest,name); break; case 'u': dest = livre_stpcpy (dest,user); break; case 'p': dest = livre_stpcpy (dest,prof); break; case 'X': dest += sprintf (dest,"%04d",X); x_was_used = true; break; default: *dest++ = '%'; *dest++ = spc; } }else{ *dest++ = carac; } } *dest = '\0'; struct stat st; if (!x_was_used || stat(sdest,&st)==-1) break; } } static int livre ( const char *user, const char *name, const char *ecole, const char *prof, const char *_subdir, const char *docum, const char *original) // Nom original { int ret = -1; while (isspace(*prof)) prof++; char subdir[strlen(_subdir)+1]; strcpy (subdir,_subdir); strip_end (subdir); // En premier on cherche le propriétaire du répertoire prof char path[PATH_MAX]; if (subdir[0] != '\0' && strcmp(subdir,"/")!=0){ snprintf (path,sizeof(path)-1,"/ecoles/%s/tp/%s/%s" ,ecole,prof,subdir); }else{ snprintf (path,sizeof(path)-1,"/ecoles/%s/tp/%s" ,ecole,prof); } struct stat st; if (stat(path,&st)!=-1 && S_ISDIR(st.st_mode)){ livre_readconf(); char pathf[PATH_MAX]; char ctl[PATH_MAX]; snprintf (ctl,sizeof(ctl)-1,"%s/%s",path,livre_ctrl); livre_format (ctl,livre_len,pathf,user,name,ecole,prof,original); FILE *fin = fopen (docum,"r"); if (fin != NULL){ FILE *fout = fopen (pathf,"w"); if (fout != NULL){ char buf[10000]; int n; while ((n=fread(buf,1,10000,fin))>0){ fwrite (buf,1,n,fout); } ret = fclose (fout); if (ret == 0){ chown (pathf,st.st_uid,st.st_gid); chmod (pathf,0640); } } fclose (fin); }else{ printf ("Pas de répertoire de livraison %s\n",subdir); } }else{ printf ("Pas de répertoire de livraison pour le professeur %s
\n" ":%s:
\n" ":%s:
\n" ":%s:
\n" ,prof,path,docum,original); } return ret; } /* Vérifie si un répertoire est valide (ne contient pas de ..) */ static bool valid_subpath (const char *subdir) { bool ret = true; if (strstr(subdir,"..")!=NULL){ syslog (LOG_ERR,"Requete %s rejeté",subdir); ret = false; } return ret; } const int MAXIMUM_ENTRIES=10000; static int liste_recur ( const char *path, const char *subdir, bool recur, bool showdir, char *tb[], int nb, int level) { int ret = -1; char spath[PATH_MAX]; if (!valid_subpath (subdir)) return -1; if (subdir[0] != '\0'){ snprintf (spath,sizeof(spath)-1,"%s/%s",path,subdir); }else{ snprintf (spath,sizeof(spath)-1,"%s",path); } DIR *d = opendir (spath); if (d != NULL){ struct dirent *ent; ret = nb; while (nb < MAXIMUM_ENTRIES && (ent=readdir(d))!=NULL){ const char *name = ent->d_name; if (name[0] != '.' && (strcmp(name,"Desktop")!=0 || level >0) && (strcmp(name,"Netscape")!=0 || level >0)){ char sspath[PATH_MAX]; snprintf (sspath,sizeof(sspath)-1,"%s/%s/%s",path,subdir,name); struct stat st; if (lstat(sspath,&st)!=-1){ char ssub[PATH_MAX]; if (subdir[0] != '\0'){ snprintf (ssub,sizeof(ssub)-1,"%s/%s",subdir,name); }else{ snprintf (ssub,sizeof(ssub)-1,"%s",name); } bool is_dir = S_ISDIR(st.st_mode) != 0; if (is_dir){ if (showdir){ tb[nb++] = strdup(ssub); } if (recur){ nb = liste_recur(path,ssub,recur,showdir,tb,nb,level+1); } }else if (!showdir && S_ISREG(st.st_mode)){ tb[nb++] = strdup(ssub); } } } } closedir(d); ret = nb; } return ret; } static int cmp_func (const void *p1, const void *p2) { char *s1 = *(char**)p1; char *s2 = *(char**)p2; return strcasecmp(s1,s2); } static int liste ( const char *path, const char *subdir, bool recur, bool showdir) { if (!valid_subpath (subdir)) return -1; char *tb[MAXIMUM_ENTRIES]; int ret = liste_recur (path,subdir,recur,showdir,tb,0,0); if (ret > 0){ qsort (tb,ret,sizeof(char*),cmp_func); for (int i=0; ipw_dir; struct stat st; if (stat(home,&st)==-1){ // Pas de home, vérifions le répertoire parent char tmp[strlen(home)+1]; strcpy (tmp,home); char *pt = strrchr(tmp,'/'); if (pt != NULL){ *pt = '\0'; if (stat(tmp,&st)==-1 || !S_ISDIR(st.st_mode)){ syslog (LOG_ERR,"Usager %s (%s), home manquant (mauvais serveur ?): %s" ,p->pw_name,p->pw_gecos,home); etranger = true; }else{ setuid (geteuid()); string s = "/root/scripts/homepreexec.sh"; s += " "; s += p->pw_dir; s += " "; s += p->pw_name; s += " "; s += g->gr_name; s += " "; s += password; syslog (LOG_NOTICE,"Création du répertoire de l'usager %s",p->pw_name); system (s.c_str()); if (stat(home,&st)==-1){ syslog (LOG_ERR,"Echec création du du répertoire de l'usager %s",p->pw_name); ret = false; } } }else{ syslog (LOG_ERR,"Le home n'est pas dans un sous-repertoire: %s",home); ret = false; } } return ret; } #endif /* Valide l'usager et le mot de passe Met a jour ctx avec l'information sur l'usager S'assure aussi que le home de l'usager est créé. Si le home manquant et que le répertoire parent du home existe, alors un script est appelé pour créer le répertoire. Si par contre, le home existe pas, pas plus que le répertoire parent alors cet usager sera rejeté. */ static int livre_getpwnam ( const char *user, const char *pass, CTX &ctx) { int ret = -1; ctx.pw_name = ""; ctx.gr_name = ""; ctx.pw_dir = ""; ctx.pw_gecos = ""; ctx.pw_uid = -1; ctx.etranger = true; ctx.ecole[0] = '\0'; if (pam_check_pair (user,pass)){ livre_readconf(); bool pasexclu = true; if (group_excl != NULL){ // Ok, c'est un usager valide. Est-il dans le groupe qui l'exclu struct group *g = getgrnam(group_excl); if (g == NULL){ syslog (LOG_ERR,"Groupe %s n'existe pas",group_excl); }else{ char **gr_mem = g->gr_mem; while (*gr_mem != NULL){ if (strcmp(user,*gr_mem)==0){ pasexclu = false; break; } gr_mem++; } } } if (pasexclu){ struct passwd *p = getpwnam (user); if (p != NULL){ ctx.pw_name = p->pw_name; ctx.pw_dir = p->pw_dir; ctx.pw_gecos = p->pw_gecos; ctx.pw_uid = p->pw_uid; gid_t gid = p->pw_gid; struct group *g = getgrgid (gid); if (g != NULL){ ctx.gr_name = g->gr_name; if (strcmp(user,"root")==0 || strcmp(user,"maitre")==0){ strcpy (ctx.ecole,"0"); ret = 0; }else{ const char *group = g->gr_name; while (isalpha(*group)) group++; if (isdigit(*group)){ snprintf (ctx.ecole,sizeof(ctx.ecole)-1,"%s",group); // Ok, on verifie le home #ifdef CHECKHOME if (livre_checkhome(p,g,pass,ctx.etranger)){ ret = 0; }else{ syslog (LOG_ERR,"Usager %s, ne peut créer le home (%m)" ,user); } #else ctx.etranger = false; ret = 0; #endif }else{ syslog (LOG_ERR,"Usager %s, membre du group %s. N'a pas accès à cette application" ,user,group); } } }else{ syslog (LOG_ERR,"Usager %s, ne peut identifier le groupe %d" ,user,gid); } } } } return ret; } static bool is_admin_print (CTX &ctx) { bool ret = false; FILE *fin = fopen ("/etc/print/admin.conf","r"); if (fin != NULL){ char buf[100]; while (ret == false && fgets(buf,sizeof(buf)-1,fin)!=NULL){ if (buf[0] == '#') continue; int len = strlen(buf); while (len > 0 && buf[len-1] <= ' '){ buf[len-1] = '\0'; len--; } if (len > 0){ set s,seen; s.insert(buf); while (ret == false && s.size() > 0){ string tmp = *s.begin(); seen.insert (tmp); s.erase (tmp); const char *pt = tmp.c_str(); if (pt[0] == '@'){ if (strcmp(pt+1,ctx.gr_name.c_str())==0){ ret = true; break; }else{ // Trouve les membres de ce groupe. Ces // membres peuvent etre des usagers ou des // groupes. On les ajoutes dans le set struct group *g = getgrnam(pt+1); if (g == NULL){ syslog (LOG_ERR,"is_admin_print: Pas d'information sur le groupe %s",pt+1); }else{ for (int i=0; g->gr_mem[i] != NULL; i++){ const char *n = g->gr_mem[i]; if (seen.find(n)==seen.end()) s.insert(n); } } } }else if (strcmp(pt,ctx.pw_name.c_str())==0){ ret = true; break; } } } } fclose (fin); } return ret; } /* Retourne vrai si le fichier ne contient pas de caractère spéciaux pouvant nuire à la sécurité. */ static bool livre_checkfname (const char *original) { bool ret = true; if (strchr(original,'/')!=NULL || strchr(original,'(')!=NULL || strchr(original,')')!=NULL || strchr(original,'"')!=NULL || strchr(original,'\'')!=NULL || strchr(original,'`')!=NULL || strchr(original,'*')!=NULL || strchr(original,'?')!=NULL){ ret = false; } return ret; } static void nostranger(const CTX &ctx, const char *fonction) { if (ctx.etranger){ syslog (LOG_ERR,"Accès refusé à un étranger, fonction %s, usager %s, groupe %s" ,fonction,ctx.pw_name.c_str(),ctx.gr_name.c_str()); exit (-1); } } int main (int argc, char *argv[]) { int ret = -1; // Ce programme agit en fonction du nombre de paramètre // 2 paramètres: Valide le mot de passe // 3 paramètres: Valide le mot de passe et extrait les sous-répertoires // de livraison pour un prof. Cela veut dire que // /ecoles/num-ecole/tp peut avoir des accès très restreint // Par exemple, chaque répertoire de professeur pourrait avoir // des permissions 700. Les étudiant peuvent livrer, mais // ne peuvent pas lire les documents, ni meme savoir quels // existe. // 6 paramètres: Valide le mot de passe et fait une livraison openlog ("livre_check_pass",LOG_PID,LOG_DAEMON); if (argc >= 3){ const char *user = argv[1]; CTX ctx; if (livre_getpwnam (user,argv[2],ctx)!=-1){ if (argc == 3){ printf ("%s\n%s\n",ctx.ecole,ctx.gr_name.c_str()); ret = 0; }else if (argc == 4){ if (strcmp(argv[3],"--userquota")==0){ char tmp[1000]; snprintf (tmp,sizeof(tmp)-1,"/usr/sbin/dquotaget --user %d",ctx.pw_uid); setuid (geteuid()); FILE *fin = popen (tmp,"r"); if (fin != NULL){ while (fgets(tmp,sizeof(tmp)-1,fin)!=NULL){ fputs (tmp,stdout); } ret = pclose (fin); } }else if (strcmp(argv[3],"--profliste")==0){ nostranger(ctx,"profliste"); // On présente la liste des profs // soit la liste de sous-répertoire dans /ecoles/XX/tp char path[PATH_MAX]; snprintf (path,sizeof(path)-1 ,"/ecoles/%s/tp/",ctx.ecole); liste (path,"",false,true); ret = 0; }else if (strcmp(argv[3],"--adminprint")==0){ // Confirme si cet usager est un administrateur d'imprimante if (is_admin_print (ctx)){ printf ("ok\n"); ret = 0; } }else if (strcmp(argv[3],"--userdirs")==0){ nostranger(ctx,"userdirs"); // Affiche la liste des répertoires et sous-répertoires // dans le HOME de l'usager liste (ctx.pw_dir.c_str(),"",true,true); }else if (strcmp(argv[3],"--proftp")==0){ nostranger(ctx,"proftp"); // Affiche la liste des sous-répertoire dans le // répertoire tp d'un prof. char path[PATH_MAX]; snprintf (path,sizeof(path)-1 ,"/ecoles/%s/tp/%s",ctx.ecole,ctx.pw_gecos.c_str()); liste (path,"",true,true); ret = 0; }else if (strcmp(argv[3],"--localuser")==0){ if (!ctx.etranger){ printf ("%s\n%s\n",ctx.ecole,ctx.gr_name.c_str()); ret = 0; } } }else if (argc == 5){ if (strcmp(argv[3],"--profdirs")==0){ nostranger(ctx,"profdirs"); // Affiche la liste des sous-répertoire dans le // répertoire tp d'un prof. char path[PATH_MAX]; snprintf (path,sizeof(path)-1 ,"/ecoles/%s/tp/%s",ctx.ecole,argv[4]); liste (path,"",true,true); ret = 0; }else if (strcmp(argv[3],"--removejob")==0){ // Efface une tâche d'impression if (is_admin_print(ctx)){ char path[PATH_MAX]; snprintf (path,sizeof(path)-1 ,"/var/print/%s",argv[4]); unlink (path); ret = 0; } }else if (strcmp(argv[3],"--userfiles")==0){ nostranger(ctx,"userfiles"); // Affiche les fichiers d'un répertoire d'un usager ret = liste (ctx.pw_dir.c_str(),argv[4],false,false); }else if (strcmp(argv[3],"--userfile")==0){ nostranger(ctx,"userfile"); // Récupère un fichier d'un usager if (valid_subpath (argv[4])){ char spath[PATH_MAX]; snprintf (spath,sizeof(spath)-1,"%s/%s" ,ctx.pw_dir.c_str() ,argv[4]); struct stat st; if (stat(spath,&st)!=-1 && S_ISREG(st.st_mode)){ printf ("%ld\n",st.st_size); FILE *fin = fopen (spath,"r"); if (fin != NULL){ char buf[1024*8]; int n; while ((n=fread(buf,1,sizeof(buf),fin))>0){ fwrite (buf,1,n,stdout); } fclose (fin); ret = 0; } } } } }else if (argc == 6){ if (strcmp(argv[3],"--userupload")==0){ nostranger(ctx,"userupload"); // Recoit un document et le met dans le home de l'usager // dans le sous-répertoire recu. const char *original = argv[5]; if (!livre_checkfname (original)){ ret = -1; }else{ char spath[PATH_MAX],pathf[PATH_MAX]; snprintf (spath,sizeof(spath)-1,"%s/recu" ,ctx.pw_dir.c_str()); mkdir (spath,0770); chown (spath,ctx.pw_uid,(gid_t)-1); // Encode un nom avec la date livre_readconf(); char ctl[PATH_MAX]; snprintf (ctl,sizeof(ctl)-1,"%s/%s",spath,dossier_ctrl); livre_format (ctl,dossier_len,pathf ,ctx.pw_name.c_str() ,ctx.pw_gecos.c_str() ,ctx.ecole,"prof",original); FILE *fin = fopen (argv[4],"r"); if (fin != NULL){ FILE *fout = fopen (pathf,"w"); if (fout != NULL){ char buf[8*1024]; int n; while ((n=fread(buf,1,8*1024,fin))>0){ fwrite (buf,1,n,fout); } ret = fclose (fout); if (ret == 0){ chown (pathf,ctx.pw_uid,(gid_t)-1); }else{ syslog(LOG_ERR,"--userupload: Ne peut sauver %s (%m)",pathf); } }else{ syslog(LOG_ERR,"--userupload: Ne peut écrire %s (%m)",pathf); } fclose (fin); }else{ syslog(LOG_ERR,"--userupload: Ne peut lire %s (%m)",argv[4]); } } } }else if (argc == 7){ nostranger(ctx,"livre"); const char *original = argv[6]; if (!livre_checkfname(original)){ ret = -1; }else{ ret = livre (ctx.pw_name.c_str(),ctx.pw_gecos.c_str() ,ctx.ecole ,argv[3],argv[4],argv[5] ,original); } } } } return ret; }