/** Fichier db.c ** ** Routines de gestion des mots de passe et des fichiers ** de configuration pour calife. ** ** Copyright (c) 1991-1995 by Ollivier ROBERT ** A distribuer selon les regles de la GNU GPL General Public Licence ** Voir le fichier COPYING fourni. ** **/ #include "config.h" /* GNU configure */ /* fichier de configuration */ #include "conf.h" /** On cherche et ouvre la database locale. ** ** Dans tous les cas, le fichier log est ouvert (il est local). Si syslogd(8) ** est actif on ouvre le "fichier" pour syslog(3). Le fichier de log devient ** maintenant dependant de NO_SYSLOG: s'il n'y a pas de syslog d'utilisable ** alors on ouvre le fichier de log. ** ** Parametres : neant ** ** Retourne : int s'il y a un probleme renvoie 1 sinon 0. **/ int open_databases (void) { #ifdef DEBUG fprintf (stderr, "Opening databases...\n"); fflush (stderr); #endif /* * become root again */ GET_ROOT; /* FIXME: don't use access(2) */ if (access (AUTH_CONFIG_FILE, 0)) { syslog (LOG_AUTH | LOG_ERR, "No database in `%s', launching su...\n", AUTH_CONFIG_FILE); fprintf (stderr, "No database in `%s'...\n", AUTH_CONFIG_FILE); return 1; } else { /* * open log file */ /* open protected database */ fp = fopen (AUTH_CONFIG_FILE, "r"); if (fp == NULL) die (6, "calife: internal error, fp == NULL at %d\n", __LINE__); fchmod (fileno (fp), 0400); } /* * stay non root for a time */ RELEASE_ROOT; return 0; } /** Verifie si l'utilisateur de nom name a le droit d'utiliser ** calife. ** ** Line format is : ** ** login[:][shell][:user1,user2,user3,...] ** @group[:][shell][:user1,user2,...] ** ** Parametres : name char * nom de l'utilisateur ** user_to_be char * futur nom d'utilisateur ** ** Retourne : int 1 si l'utilisateur est autorise 0 sinon. **/ int verify_auth_info (char * name, char * user_to_be) { int allowed = 0, do_tok = 1; char ** user_list = NULL; size_t l_line = 0; int nb_users = 0; char * line, * ptr; char * line_name, * line_shell; char * group_name = NULL, * group = NULL, **p; struct group *gr_group; /* * let's search if user allowed or not */ #ifdef HAVE_WORKING_SYSCONF l_line = (size_t) sysconf (_SC_LINE_MAX); /* returns long usually */ #else l_line = MAX_STRING; #endif /* HAVE_WORKING_SYSCONF */ line = (char *) xalloc (l_line); while (!feof (fp)) { int lastc; if (fgets (line, l_line, fp) == NULL) break; /* * remove trailing '\n' if present */ lastc = strlen (line) - 1; if (line [lastc] == '\n') line [lastc] = '\0'; /* * empty line or comment or null login -- not allowed but ignored */ if (!line || (*line) == '#' || (*line) == '\0') continue; MESSAGE_1 ("Line read = |%s|\n", line); line_name = line; /* * Look for a @ as first character (for groups) */ if (*line == '@') { group = strdup(line); group_name = strtok(group, ":"); group_name++; /* skip '@' */ /* * Look into /etc/groups (or its equivalent with get) */ gr_group = (struct group *) getgrnam(group_name); if (gr_group == NULL) { die(1, "No such group %s", group_name); allowed = 0; goto end; } for ( p = &gr_group->gr_mem[0]; *p ; p++ ) { MESSAGE_3 ("matching %s to %s in %s\n", name, *p, group_name); if (strcmp(name, *p) == 0) { MESSAGE_2 ("User %s Allowed through group %s\n", name, group_name); _group = strdup (group_name); strcpy (_group, group_name); allowed = 2; break; } } } /* * Look for first ':' */ ptr = strchr (line, ':'); if (ptr == NULL) { /* * No shell and no user list */ custom_shell = 0; /* * Bypass if already allowed through group */ if (group_name != NULL && allowed == 1) goto escape; if (strcmp (line_name, name)) /* not us */ continue; goto escape; } /* * I got a ':', put a '\0' instead. */ *ptr++ = '\0'; /* * Do we have anything behind ? */ if (*ptr == '\0') custom_shell = 0; MESSAGE_2 ("Current line_name = |%s| / name = |%s|\n", line_name,name); /* * Bypass if already allowed through group */ if (group_name != NULL && allowed != 0) goto end; if (strcmp (line_name, name)) /* not us */ continue; line_shell = ptr; /* * Look for another ':', maybe just after the first one */ ptr = strchr (line_shell, ':'); if (ptr == line_shell) custom_shell = 0; /* * If we got another one, put a '\0' instead */ if (ptr) *ptr++ = '\0'; /* * Analyze the shell candidate */ if ((*(line_shell) == '*')) /* LOCKED ACCOUNT */ { allowed = 0; goto end; } /* * look for pathname */ if ((*(line_shell) == '/')) { /* * Is this a real shell ? */ if (access (line_shell, R_OK | X_OK)) { MESSAGE_1 (" access of |%s| failed, no custom_shell\n", line_shell); custom_shell = 0; } else { custom_shell = 1; shell = (char *) xalloc (strlen (line_shell) + 1); strncpy (shell, line_shell, strlen (line_shell)); shell [strlen (line_shell)] = '\0'; MESSAGE_1 (" current line_shell = |%s|\n", line_shell); } } else { custom_shell = 0; do_tok = 0; } MESSAGE_1 (" custom_shell = |%d|\n", custom_shell); /* * Analyze the rest of the line */ if (ptr) { MESSAGE_1 ("ptr = %s\n", ptr); /* * we've got a user list */ if (*ptr) { char * p, ** q; MESSAGE_1 (" current user_list = |%s|\n", ptr); /* * the list should be user1,user2,...,user_n */ p = ptr; /* count users # in the list */ while (p && *p) if (*p++ == ',') nb_users++; if (*--p != ',') /* last char is not a ',' */ nb_users++; MESSAGE_1 (" users # = |%d|\n", nb_users); /* * two passes to avoid fixed size allocations */ user_list = (char **) calloc (nb_users + 1, sizeof (char *)); if (user_list == NULL) die (255, "Out of memory at line %d in file %s\n", __LINE__, __FILE__); /* * put pointers in user_list */ p = ptr; q = user_list; /* * 1st user is immediate */ (*q++) = p; while (p && *p) { if (*p == ',') /* found a ',' */ { *p = '\0'; if (*++p == ',') continue; (*q++) = p++; /* next char is user name */ } else p++; /* only regular letters */ } } } /* * user_list is complete, now compare */ escape: if (user_list) { int i; for ( i = 0; i < nb_users; i++ ) { MESSAGE_2(" comparing |%s| to |%s|\n", user_to_be, user_list [i]); if (!strcmp (user_list [i], user_to_be)) { allowed = 1; nb_users = 0; goto end; /* beuh but too many loops */ } } } else { allowed = 1; nb_users = 0; break; } } end: free (line); if (group) free(group); if (user_list) free (user_list); fclose (fp); if (geteuid() == 0) allowed = 1; MESSAGE_1 ("Exit from verify_auth_info with allowed=%d\n", allowed); return allowed; } /** Demande et verifie le mot de passe pour l'utilisateur name ** Si name == root on ne fait rien. ** ** Parametres : name char * nom de l'utilisateur ** user_to_be char * qui va-t-on devenir ** this_time char * quand ? ** tty char * sur quel tty ** ** Retourne : neant ** ** Algo: ** si WITH_PAM est définit ** on essaie auth_pam() ** si ça plante, on continue avec un warning ** sinon retour ok ** fin ** vérification mdp ** **/ void verify_password (char * name, char * user_to_be, char * this_time, char * tty) { int i = 0, rval; size_t l_size = 0; #if defined (HAVE_SHADOW_H) && defined (HAVE_GETSPNAM) && !defined(UNUSED_SHADOW) struct spwd * scalife; #endif struct passwd * calife; char got_pass = 0; char * pt_pass, * pt_enc, * user_pass = NULL, * enc_pass = NULL, salt [10]; /* * returns long usually */ #ifdef HAVE_WORKING_SYSCONF l_size = (size_t) sysconf (_SC_LINE_MAX); #else l_size = MAX_STRING; #endif /* HAVE_WORKING_SYSCONF */ user_pass = (char *) xalloc (l_size); enc_pass = (char *) xalloc (l_size); /* * become root again */ GET_ROOT; calife = getpwnam (name); /* null or locked password */ if (calife == NULL) die (1, "Bad pw data at line %d", __LINE__); RELEASE_ROOT; #ifdef WITH_PAM if (getuid () != 0) { MESSAGE ("Trying PAM\n"); for ( i = 0; i < MAX_ATTEMPTS; i++ ) { pt_pass = (char *) getpass ("Password:"); /* * XXX don't assume getpass(3) will check the buffer * length. Linux glibc apparently lacks such checking and * will happily segfault if the previous entered password * was too big. * cf. */ if (pt_pass == NULL) die(1, "Corrupted or too long password"); memset (user_pass, '\0', l_size); /* * Be a bit more careful there */ strncpy (user_pass, pt_pass, l_size); user_pass[l_size - 1] = '\0'; MESSAGE ("Testing auth with PAM.\n"); rval = auth_pam (&calife, user_pass); MESSAGE_1 ("PAM auth_pam returned %d\n", rval); if (rval) { syslog (LOG_AUTH | LOG_ERR, "PAM failed with code %d for %s", rval, name); /* * Check return value: * - 0: auth succeeded * - >0: auth failed. */ /* Fallback to previous methods? */ } else { syslog (LOG_AUTH | LOG_INFO, "PAM auth succeeded for %s", name); got_pass = 1; break; } } /* end for */ } else got_pass = 1; #endif MESSAGE_1 ("Testing w/o PAM with got_pass = %d\n", got_pass); if (!got_pass) { #if defined (HAVE_SHADOW_H) && defined (HAVE_GETSPNAM) && !defined(UNUSED_SHADOW) /* * become root again */ GET_ROOT; scalife = getspnam (name); /* null or locked password */ /* * stay non root for a time */ RELEASE_ROOT; if (scalife) { calife->pw_passwd = (char *) xalloc (strlen (scalife->sp_pwdp) + 1); strcpy (calife->pw_passwd, scalife->sp_pwdp); } #endif /* HAVE_SHADOW_H */ MESSAGE ("Not using PAM.\n"); if ((*(calife->pw_passwd)) == '\0' || (*(calife->pw_passwd)) == '*') { syslog (LOG_AUTH | LOG_ERR, "NULL CALIFE %s to %s on %s", name, user_to_be, tty); closelog (); die (10, "Sorry.\n"); } if (getuid () != 0) { char * pt_pass, * pt_enc, * user_pass, * enc_pass, salt [10]; user_pass = (char *) xalloc (l_size); enc_pass = (char *) xalloc (l_size); /* * cope with MD5 based crypt(3) */ if (!strncmp (calife->pw_passwd, "$1$", 3)) /* MD5 */ { char * pp = (char *) xalloc (strlen (calife->pw_passwd) + 1); char * md5_salt; char * md5_pass; strcpy (pp, calife->pw_passwd + 3); md5_salt = strtok (pp, "$"); md5_pass = strtok (NULL, "$"); if (md5_pass == NULL || md5_salt == NULL || (strlen (md5_salt) > 8)) /* garbled password */ { syslog (LOG_AUTH | LOG_ERR, "GARBLED PASSWORD %s to unknown %s on %s", name, user_to_be, tty); die (8, "Bad password string.\n"); } MESSAGE_1 ("MD5 password found, salt=%s\n", md5_salt); strcpy (salt, md5_salt); free (pp); } else { strncpy (salt, calife->pw_passwd, 2); salt [2] = '\0'; } for ( i = 0; i < MAX_ATTEMPTS; i ++ ) { pt_pass = (char *) getpass ("Password:"); /* * XXX don't assume getpass(3) will check the buffer * length. Linux glibc apparently lacks such checking and * will happily segfault if the previous entered password * was too big. * cf. */ if (pt_pass == NULL) die(1, "Corrupted or too long password"); memset (user_pass, '\0', l_size); /* * Be a bit more careful there */ strncpy (user_pass, pt_pass, l_size); user_pass[l_size - 1] = '\0'; pt_enc = (char *) crypt (user_pass, calife->pw_passwd); /* * Wipe out the cleartext password */ memset (user_pass, '\0', l_size); /* * Move from the static buffer intoa safe location of our own */ memset (enc_pass, '\0', l_size); strcpy (enc_pass, pt_enc); /* * assumes standard crypt(3) */ if (!strcmp (enc_pass, calife->pw_passwd)) { got_pass = 1; break; } } /* for */ } /* end if for getuid() */ } /* end got_pass == 1 ? */ end: MESSAGE_1 ("Auth process returned %d\n", got_pass); if (got_pass != 1) { syslog (LOG_AUTH | LOG_ERR, "BAD CALIFE %s to %s on %s", name, user_to_be, tty); closelog (); fprintf (stderr, "Sorry.\n"); exit (9); } free (user_pass); free (enc_pass); }