blob: efc516600a81e23148770534f59ef8d628df9c6e [file] [log] [blame]
// An NSS module that extends local user account lookup to the files
// /etc/passwd.borg and /etc/passwd.borg.base
// passwd.borg.base is a subset of passwd.borg that is used as a fallback.
#include <errno.h>
#include <nss.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <syslog.h>
#include <bits/libc-lock.h>
__libc_lock_define_initialized(static, lock)
#define NSSBORG_LOCK __libc_lock_lock(lock)
#define NSSBORG_UNLOCK __libc_lock_unlock(lock);
#define EXEC_NAME_SIZE 4096
static FILE *f;
static FILE *fb;
static bool fb_eof;
static FILE *fbr;
static bool fbr_eof;
static char exec_name[EXEC_NAME_SIZE];
// This library will log into syslog any attempt to fetch a username from
// /etc/passwd.borg file, but only if /etc/passwd.borg.real file is present.
// This module makes the user lookup in following order:
// /etc/passwd.borg.real
// /etc/passwd.borg.base
// /etc/passwd.borg
// non_borg_user_lookup_warn is supposed to prevent log spew from a single
// process.
static int non_borg_user_lookup_warn = 0;
#define LOGNSS(priority, fmt, ...) \
do { \
openlog("nss_borg", LOG_PID, 0); \
syslog(LOG_USER | priority, fmt, ##__VA_ARGS__); \
closelog(); \
} while (0)
#define DEBUG(fmt, ...)
#define WARN(fmt, ...) LOGNSS(LOG_WARNING, fmt, ##__VA_ARGS__)
// _nss_borg_setpwent_locked()
// Internal setup routine
static enum nss_status _nss_borg_setpwent_locked(void) {
int readlink_ret;
DEBUG("Opening passwd.borg\n");
f = fopen("/etc/passwd.borg", "r");
DEBUG("Opening passwd.borg.base\n");
fb = fopen("/etc/passwd.borg.base", "r");
fb_eof = false;
DEBUG("Opening passwd.borg.real\n");
fbr = fopen("/etc/passwd.borg.real", "r");
fbr_eof = false;
DEBUG("Reading /proc/self/exe");
// readlink does not append null-byte, so we reserve last byte for it.
readlink_ret = readlink("/proc/self/exe", exec_name,
sizeof(exec_name) - 1);
if (readlink_ret == -1) {
DEBUG("Failed to readlink /proc/self/exe error: %d", errno);
strncpy(exec_name, "__unknown__", sizeof(exec_name));
} else {
exec_name[readlink_ret] = '\0';
}
if (f || fb || fbr) {
return NSS_STATUS_SUCCESS;
} else {
return NSS_STATUS_UNAVAIL;
}
}
// _nss_borg_endpwent_locked()
// Internal close routine
static enum nss_status _nss_borg_endpwent_locked(void) {
DEBUG("Closing passwd.borg\n");
if (f) {
fclose(f);
f = NULL;
}
DEBUG("Closing passwd.borg.base\n");
if (fb) {
fclose(fb);
fb = NULL;
}
DEBUG("Closing passwd.borg.real\n");
if (fbr) {
fclose(fbr);
fbr = NULL;
}
return NSS_STATUS_SUCCESS;
}
// Called internally. It's a wrapper around fgetpwent_r that prevents from
// multiple read calls once EOF was reached (b/72036184)
static int _nss_borg_fgetpwent_r_eof(FILE *fp, struct passwd *pwbuf,
char *buf, size_t buflen,
struct passwd **pwbufp, bool *eof) {
if (*eof) {
*pwbufp = NULL;
return ENOENT;
} else {
int ret = fgetpwent_r(fp, pwbuf, buf, buflen, pwbufp);
if (ret == ENOENT) {
*eof = true;
}
return ret;
}
}
// _nss_borg_getpwent_r_locked()
// Called internally to return the next entry from the passwd file
static enum nss_status _nss_borg_getpwent_r_locked(struct passwd *result,
char *buffer, size_t buflen,
int *errnop,
int *non_borg_user) {
enum nss_status ret;
// Save a copy of the buffer address, in case first call errors
// and sets it to 0.
struct passwd *sparecopy = result;
if (fbr != NULL && (_nss_borg_fgetpwent_r_eof(fbr, result, buffer, buflen,
&result, &fbr_eof) == 0)) {
DEBUG("Returning real borg user %d:%s\n", result->pw_uid, result->pw_name);
ret = NSS_STATUS_SUCCESS;
} else if (
// Yes, this is one of those cases where an assign makes sense.
fb != NULL && (result = sparecopy) &&
(_nss_borg_fgetpwent_r_eof(fb, result, buffer, buflen, &result,
&fb_eof) == 0)) {
DEBUG("Returning base borg user %d:%s\n", result->pw_uid, result->pw_name);
ret = NSS_STATUS_SUCCESS;
} else if (
// Yes, this is one of those cases where an assign makes sense.
f != NULL && (result = sparecopy) &&
(fgetpwent_r(f, result, buffer, buflen, &result) == 0)) {
DEBUG("Returning non borg user %d:%s\n", result->pw_uid, result->pw_name);
// Log only if passwd.borg.real file is present.
if (fbr) {
*non_borg_user = 1;
}
ret = NSS_STATUS_SUCCESS;
} else {
*errnop = errno;
switch (*errnop) {
case ERANGE:
ret = NSS_STATUS_TRYAGAIN;
break;
case ENOENT:
default:
ret = NSS_STATUS_NOTFOUND;
}
}
return ret;
}
// All methods below are public interface of this library.
// _nss_borg_setpwent()
// Called by NSS to open the passwd file
// Oddly, NSS passes a boolean saying whether to keep the database file open;
// ignore it
enum nss_status _nss_borg_setpwent(int stayopen) {
enum nss_status ret;
NSSBORG_LOCK;
ret = _nss_borg_setpwent_locked();
NSSBORG_UNLOCK;
return ret;
}
// _nss_borg_endpwent()
// Called by NSS to close the passwd file
enum nss_status _nss_borg_endpwent() {
enum nss_status ret;
NSSBORG_LOCK;
ret = _nss_borg_endpwent_locked();
NSSBORG_UNLOCK;
return ret;
}
// _nss_borg_getpwent_r()
// Called by NSS (I think) to look up next entry in passwd file
enum nss_status _nss_borg_getpwent_r(struct passwd *result, char *buffer,
size_t buflen, int *errnop) {
enum nss_status ret;
int non_borg_user = 0;
NSSBORG_LOCK;
ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop,
&non_borg_user);
if (non_borg_user && !non_borg_user_lookup_warn) {
non_borg_user_lookup_warn = 1;
WARN("Reached non borg section in getpwent call "
"in process: %s running as: %d\n", exec_name, getuid());
}
NSSBORG_UNLOCK;
return ret;
}
// _nss_borg_getpwuid_r()
// Called by NSS to find a user account by uid
enum nss_status _nss_borg_getpwuid_r(uid_t uid, struct passwd *result,
char *buffer, size_t buflen, int *errnop) {
enum nss_status ret;
int non_borg_user = 0;
NSSBORG_LOCK;
ret = _nss_borg_setpwent_locked();
DEBUG("Looking for uid %d\n", uid);
if (ret == NSS_STATUS_SUCCESS) {
while ((ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop,
&non_borg_user)) ==
NSS_STATUS_SUCCESS) {
if (result->pw_uid == uid) {
if (non_borg_user) {
WARN("Returning non borg user %d:%s in process: %s running as: %d\n",
result->pw_uid, result->pw_name, exec_name, getuid());
}
break;
}
}
}
_nss_borg_endpwent_locked();
NSSBORG_UNLOCK;
return ret;
}
// _nss_borg_getpwnam_r()
// Called by NSS to find a user account by name
enum nss_status _nss_borg_getpwnam_r(const char *name, struct passwd *result,
char *buffer, size_t buflen, int *errnop) {
enum nss_status ret;
int non_borg_user = 0;
NSSBORG_LOCK;
ret = _nss_borg_setpwent_locked();
DEBUG("Looking for user %s\n", name);
if (ret == NSS_STATUS_SUCCESS) {
while ((ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop,
&non_borg_user)) ==
NSS_STATUS_SUCCESS) {
if (!strcmp(result->pw_name, name)) {
if (non_borg_user) {
WARN("Returning non borg user %d:%s in process: %s running as: %d\n",
result->pw_uid, result->pw_name, exec_name, getuid());
}
break;
}
}
}
_nss_borg_endpwent_locked();
NSSBORG_UNLOCK;
return ret;
}