summaryrefslogtreecommitdiff
path: root/nss_mapuid.c
blob: cc6c5ec7befadae96f6b3a2ee80d314951c4c622 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
 * Copyright (C) 2017 Cumulus Networks, Inc.
 * All rights reserved.
 * Author: Dave Olson <olson@cumulusnetworks.com>
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program - see the file COPYING.
 */

/*
 * This plugin implements getpwuid_r for NSS to map a UID back to
 * a mapped username account, set up via nss_mapuser.
 *
 * A fixed account is used to get the base of the home directory,
 * and for the uid and gid.  All other fields are replaced, and
 * the password is always returned as 'x' (disabled).  The assumption
 * is that any authentication and authorization will be done via PAM
 * using some mechanism other than the local password file.
 *
 * Since this should match first whenever a mapped user's UID is being
 * looked up, this module should appear first in in nsswitch.conf for
 * the passwd database.
 *
 * It implements getpwuid_r for UIDs if and only if a mapped user is currently
 * logged in This means that if you do, e.g.:
 *     ls -ld ~SomeUserName
 * you will sometimes get a mapped username, and other times get the name of the
 * fixed account in the configuration file, depending on whether a mapped user
 * is logged in or not.
 *
 * See nss_mapuser.c for the matching getpwnam_r for UIDs.
 */

#include "map_common.h"
#include <sys/types.h>
#include <fcntl.h>
#include <dirent.h>
#include <ctype.h>

static const char *nssname = "nss_mapuid";	/* for syslogs */
static const char dbdir[] = "/run/mapuser/";

/*
 * If you aren't using glibc or a variant that supports this,
 * and you have a system that supports the BSD getprogname(),
 * you can replace this use with getprogname()
 */
extern const char *__progname;

/*
 * Read the requested session file (in the dbdir by intent), verify the
 * uid matches, and setup the passwd structure with the username found
 * in the file.
 */
static int chk_session_file(char *sfile, uid_t uid, struct pwbuf *pb)
{
	char rbuf[256], user[64];
	FILE *mapf;
	uid_t auid = 0;
	int ret = 1;

	mapf = fopen(sfile, "r");
	if (!mapf) {
		if (debug)
			syslog(LOG_DEBUG,
			       "%s:  session map file %s open fails: %m",
			       nssname, sfile);
		return ret;
	}
	user[0] = '\0';
	while (fgets(rbuf, sizeof rbuf, mapf)) {
		strtok(rbuf, " \t\n\r\f");	/* terminate buffer at first whitespace */
		if (!strncmp("user=", rbuf, 5)) {	/* should precede auid */
			snprintf(user, sizeof user, "%s", rbuf + 5);
			if (auid)	/* found out of order, but now have both */
				break;
		} else if (!strncmp("auid=", rbuf, 5)) {
			uid_t fuid = (uid_t) strtoul(rbuf + 5, NULL, 10);
			if (fuid && uid == fuid) {
				auid = fuid;
				if (user[0])
					break;	/*  normal ordering, else keep looking for user */
			}
		}
	}
	fclose(mapf);

	if (auid && user[0])	/*  otherwise not a match */
		ret = get_pw_mapuser(user, pb);	/* should always succeed */

	return ret;
}

/* find mapping for this sessionid */
static int find_mapped_name(struct pwbuf *pb, uid_t uid, uint32_t session)
{
	char sessfile[sizeof dbdir + 11];

	snprintf(sessfile, sizeof sessfile, "%s%u", dbdir, session);
	return chk_session_file(sessfile, uid, pb);
}

static int find_mappingfile(struct pwbuf *pb, uid_t uid)
{
	DIR *dir;
	struct dirent *ent;
	int ret = 1;

	dir = opendir(dbdir);
	if (!dir) {		/*  can happen if no mapped users logged in */
		if (debug > 1)
			syslog(LOG_DEBUG,
			       "%s: Unable to open mapping directory %s: %m",
			       nssname, dbdir);
		return 1;
	}

	/* Loop through all numeric files in dbdir, check for matching uid */
	while (ret && (ent = readdir(dir))) {
		char sessfile[sizeof dbdir + 11];
		if (!isdigit(ent->d_name[0]))	/* sanity check on session file */
			continue;
		snprintf(sessfile, sizeof sessfile, "%s%s", dbdir, ent->d_name);
		ret = chk_session_file(sessfile, uid, pb);
	}
	if (ret && debug)
		syslog(LOG_DEBUG, "%s: uid %u mapping not found in map files",
		       nssname, uid);
	closedir(dir);
	return ret;
}

static uint32_t get_sessionid(void)
{
	int fd = -1, cnt;
	uint32_t id = 0U;
	static char buf[12];

	fd = open("/proc/self/sessionid", O_RDONLY);
	if (fd != -1) {
		cnt = read(fd, buf, sizeof(buf));
		close(fd);
	}
	if (fd != -1 && cnt > 0) {
		id = strtoul(buf, NULL, 0);
	}
	return id;
}

/*
 * This is an NSS entry point.
 * We implement getpwuid(), for anything that wants to get the original
 * login name from the uid.
 * If it matches an entry in the map, we use that data to replace
 * the data from the local passwd file (not via NSS).
 * locally from the map.
 *
 * This can be made to work 2 different ways, and we need to choose
 * one, or make it configurable.
 *
 * 1) Given a valid session id, and a mapped user logged in,
 * we'll match only that user.   That is, we can only do the lookup
 * successfully for child processes of the mapped login, and
 * only while still logged in (map entry is valid).
 *
 * For now, if session are set, I try them, and if that lookup
 * fails, try the wildcard.
 *
 * Only works while the UID is in use for a mapped user, and only
 * for processes invoked from that session.  Other callers will
 * just get the files, ldap, etc. entry for the UID
 * Returns the first match if multiple mapped users.
 */
__attribute__ ((visibility("default")))
enum nss_status _nss_mapuid_getpwuid_r(uid_t uid, struct passwd *pw,
				       char *buffer, size_t buflen, int *errnop)
{
	struct pwbuf pb;
	enum nss_status status = NSS_STATUS_NOTFOUND;
	uint32_t session;

	/*
	 * the useradd family will not add/mod/del users correctly with
	 * the mapuid functionality, so return immediately if we are
	 * running as part of those processes.
	 */
	if (__progname && (!strcmp(__progname, "useradd") ||
			   !strcmp(__progname, "usermod") ||
			   !strcmp(__progname, "userdel")))
		return status;

	/*  this can happen for permission reasons, do don't complain except
	 *  at debug */
	if (nss_mapuser_config(errnop, nssname) == 1) {
		return status;	/* syslog already done */
	}

	if (min_uid != ~0U && uid < min_uid) {
		if (debug > 1)
			syslog(LOG_DEBUG,
			       "%s: uid %u < min_uid %u, don't lookup", nssname,
			       uid, min_uid);
		return status;
	}

	/* marshal the args for the lower level functions */
	pb.pw = pw;
	pb.buf = buffer;
	pb.buflen = buflen;
	pb.errnop = errnop;
	pb.name = NULL;

	/* session needs to be set to lookup this user.  May also be set
	 * for other users.
	 */
	session = get_sessionid();
	if (session && !find_mapped_name(&pb, uid, session))
		status = NSS_STATUS_SUCCESS;
	if (status != NSS_STATUS_SUCCESS) {
		/* lookup by some other user or unrelated process, try dir lookup */
		if (!find_mappingfile(&pb, uid))
			status = NSS_STATUS_SUCCESS;
	}
	return status;
}