/* * Copyright 2004 Jana Saout * A simple NSS modules for passwd entries based on home directories * (a lot of code borrowed from the GNU C Library nss_files implementation) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Every subdirectory in a certain directory defines a UNIX user with the * same name as that directory and with the uid/gid of that directory. * (useful as an emergency fallback in case of an LDAP server failure) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define EMIT(res, format...) \ do { \ res = buffer; \ if (internal_emit(&buffer, &buflen, format)) { \ *errnop = ERANGE; \ return NSS_STATUS_TRYAGAIN; \ } \ } while(0) static char *homes_path = "/home/mail"; static char *gecos_format = "%s mail domain"; static char *user_shell = "/bin/false"; static DIR *dir = NULL; static off_t position; static enum { NO_USE, GET_ENT, GET_BY } last_use; static int keep_stream = 0; static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; static int internal_emit(char **buf, size_t *avail, const char *format, ...) { va_list args; int size; if (*avail < 1) return 1; va_start(args, format); size = vsnprintf(*buf, *avail - 1, format, args); va_end(args); if (size < 0 || size >= *avail) return 1; size++; *buf += size; **buf = 0; *avail -= size; return 0; } static enum nss_status internal_setent(int stayopen) { enum nss_status status = NSS_STATUS_UNAVAIL; if (!dir) { int result, flags; dir = opendir(homes_path); if (!dir) { if (errno == EAGAIN) status = NSS_STATUS_TRYAGAIN; goto out; } result = flags = fcntl(dirfd(dir), F_GETFD, 0); if (result >= 0) { flags |= FD_CLOEXEC; result = fcntl(dirfd(dir), F_SETFD, flags); } if (result < 0) { closedir(dir); dir = NULL; goto out; } } else rewinddir(dir); status = NSS_STATUS_SUCCESS; out: if (dir) keep_stream |= stayopen; return status; } enum nss_status _nss_homes_setpwent(int stayopen) { enum nss_status status; __libc_lock_lock(lock); status = internal_setent(stayopen); if (status == NSS_STATUS_SUCCESS && (position = telldir(dir)) < 0) { closedir(dir); dir = NULL; status = NSS_STATUS_UNAVAIL; } last_use = GET_ENT; __libc_lock_unlock(lock); return status; } enum nss_status _nss_homes_endpwent(int stayopen) { __libc_lock_lock(lock); if (dir) { closedir(dir); dir = NULL; } keep_stream = 0; __libc_lock_unlock(lock); return NSS_STATUS_SUCCESS; } static enum nss_status internal_GET_ENT(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errnop) { static char buf[PATH_MAX + 1]; struct stat st; int size; if (strchr(name, '/')) return NSS_STATUS_NOTFOUND; size = snprintf(buf, PATH_MAX, "%s/%s", homes_path, name); buf[PATH_MAX] = 0; if (size < 0 || size > PATH_MAX) { *errnop = ERANGE; return NSS_STATUS_TRYAGAIN; } if (lstat(buf, &st) < 0) { if (errno == ENOENT) return NSS_STATUS_RETURN; return NSS_STATUS_UNAVAIL; } if (!S_ISDIR(st.st_mode)) return NSS_STATUS_RETURN; EMIT(result->pw_name, "%s", name); EMIT(result->pw_passwd, "x"); result->pw_uid = st.st_uid; result->pw_gid = st.st_gid; EMIT(result->pw_gecos, gecos_format, name); EMIT(result->pw_dir, "%s", buf); EMIT(result->pw_shell, "%s", user_shell); return NSS_STATUS_SUCCESS; } enum nss_status _nss_homes_getpwent_r(struct passwd *result, char *buffer, size_t buflen, int *errnop) { enum nss_status status = NSS_STATUS_SUCCESS; struct dirent *ent; __libc_lock_lock(lock); if (!dir) status = internal_setent(0); if (status != NSS_STATUS_SUCCESS) goto out; for(;;) { status = NSS_STATUS_UNAVAIL; if (last_use != GET_ENT) { seekdir(dir, position); last_use = GET_ENT; } ent = readdir(dir); if (!ent) break; if (ent->d_name[0] == '.' && (!ent->d_name[1] || (ent->d_name[1] == '.' && !ent->d_name[2]))) continue; status = internal_GET_ENT(ent->d_name, result, buffer, buflen, errnop); if (status != NSS_STATUS_RETURN) break; } if (status == NSS_STATUS_SUCCESS) position = telldir(dir); else last_use = NO_USE; if (!ent) { status = NSS_STATUS_NOTFOUND; *errnop = ENOENT; goto out; } out: __libc_lock_unlock(lock); return status; } enum nss_status _nss_homes_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, int *errnop) { enum nss_status status; __libc_lock_lock(lock); if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) { status = NSS_STATUS_NOTFOUND; goto out; } status = internal_GET_ENT(name, result, buffer, buflen, errnop); if (status == NSS_STATUS_RETURN) { status = NSS_STATUS_NOTFOUND; goto out; } out: __libc_lock_unlock(lock); return status; } enum nss_status _nss_homes_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, size_t buflen, int *errnop) { enum nss_status status; struct dirent *ent; __libc_lock_lock(lock); status = internal_setent(keep_stream); if (status != NSS_STATUS_SUCCESS) goto out; for(;;) { status = NSS_STATUS_UNAVAIL; last_use = GET_BY; ent = readdir(dir); if (!ent) break; if (ent->d_name[0] == '.' && (!ent->d_name[1] || (ent->d_name[1] == '.' && !ent->d_name[2]))) continue; status = internal_GET_ENT(ent->d_name, result, buffer, buflen, errnop); if (status == NSS_STATUS_SUCCESS) { if (result->pw_uid == uid) break; continue; } else if (status != NSS_STATUS_RETURN) break; } if (!ent) { status = NSS_STATUS_NOTFOUND; *errnop = ENOENT; goto out; } out: __libc_lock_unlock(lock); return status; }