Port to Debian Buster and OpenAFS 1.8
[hcoop/debian/libnss-afs.git] / nss_afs.c
1
2 /*****************************************************************************
3 * libnss-afs (nss_afs.c)
4 *
5 * Copyright 2008, licensed under GNU Library General Public License (LGPL)
6 * see COPYING file for details
7 *
8 * by Adam Megacz <megacz@hcoop.net>
9 * derived from Frank Burkhardt's libnss_ptdb,
10 * which was derived from Todd M. Lewis' libnss_pts
11 *****************************************************************************/
12
13 /*
14 * If you are reading this code for the first time, read the rest of
15 * this comment block, then start at the bottom of the file and work
16 * your way upwards.
17 *
18 * All functions which return an int use zero to signal success --
19 * except cpstr(), which returns zero on *failure*. This should be
20 * fixed.
21 *
22 * A note about memory allocation:
23 *
24 * NSS plugins generally ought to work without attempting to call
25 * malloc() (which may fail). Therefore, glibc allocates a buffer
26 * before calling NSS library functions, and passes that buffer to
27 * the NSS library; library functions store their results in the
28 * buffer and return pointers into that buffer.
29 *
30 * The convention used throughout this library is to pass around a
31 * char** which points to a pointer to the first unused byte in the
32 * provided buffer, and a size_t* which points to an int indicating
33 * how many bytes are left between the char** and the end of the
34 * available region.
35 */
36
37 #include <ctype.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <getopt.h>
42 #include <grp.h>
43 #include <limits.h>
44 #include <netinet/in.h>
45 #include <nss.h>
46 #include <pthread.h>
47 #include <pwd.h>
48 #include <rx/rx.h>
49 #include <rx/xdr.h>
50 #include <signal.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <sys/select.h>
55 #include <sys/socket.h>
56 #include <sys/stat.h>
57 #include <sys/time.h>
58 #include <sys/types.h>
59 #include <unistd.h>
60 #include <afs/afsutil.h>
61 #include <afs/cellconfig.h>
62 #include <afs/com_err.h>
63 #include <afs/param.h>
64 #include <afs/ptclient.h>
65 #include <afs/pterror.h>
66 #include <afs/ptuser.h>
67 #include <afs/stds.h>
68
69 #define HOMEDIR_AUTO 0
70 #define HOMEDIR_ADMINLINK 1
71 #define HOMEDIR_PREFIX 2
72 #define SHELL_BASH 0
73 #define SHELL_ADMINLINK 1
74 #define SHELL_USERLINK 2
75
76 #define AFS_MAGIC_ANONYMOUS_USERID 32766
77 #define MIN_PAG_GID 0x41000000L
78 #define MAX_PAG_GID 0x41FFFFFFL
79 #define MIN_OLDPAG_GID 0x3f00
80 #define MAX_OLDPAG_GID 0xff00
81
82 #define MAXCELLNAMELEN 256
83 #define MAXUSERNAMELEN 256
84
85 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
86
87 extern struct ubik_client *pruclient;
88
89 int afs_initialized = 0;
90 char cellname[MAXCELLNAMELEN];
91 char homedir_prefix[PATH_MAX];
92 char cell_root[PATH_MAX];
93 int homedir_prefix_len=0;
94 char homedirs_method=0;
95 char shells_method=0;
96
97 int init_afs ();
98
99 /**
100 * The cpstr() function copies a null-terminated string from str*
101 * (the first argument) into buf and updates both buf and buflen. If
102 * the string would overflow the buffer, no action is taken. The
103 * number of bytes copied is returned (zero indicates failure).
104 */
105 int cpstr( char *str, char **buf, size_t *buflen) {
106 int len = strlen(str);
107 if ( len >= *buflen-1 ) return 0;
108 strcpy(*buf,str);
109 *buflen -= len + 1;
110 *buf += len + 1;
111 return len;
112 }
113
114 /**
115 * Look up the name corresponding to uid, store in buffer.
116 */
117 enum nss_status ptsid2name(int uid, char **buffer, size_t *buflen) {
118 int ret, i;
119 idlist lid;
120 namelist lnames;
121
122 if (init_afs()) return NSS_STATUS_UNAVAIL;
123
124 if (uid==AFS_MAGIC_ANONYMOUS_USERID) {
125 if (!cpstr("anonymous", buffer, buflen)) return NSS_STATUS_UNAVAIL;
126 return NSS_STATUS_SUCCESS;
127 }
128
129 if (pthread_mutex_lock(&mutex)) return NSS_STATUS_UNAVAIL;
130
131 lid.idlist_val = (afs_int32*)&uid;
132 lid.idlist_len = 1;
133 lnames.namelist_val = 0;
134 lnames.namelist_len = 0;
135
136 if (ubik_PR_IDToName(pruclient,0,&lid,&lnames) != PRSUCCESS) {
137 perror("ubik_Call() in ptsid2name() failed\n");
138 pthread_mutex_unlock(&mutex);
139 return NSS_STATUS_UNAVAIL;
140 }
141
142 ret = NSS_STATUS_NOTFOUND;
143 for (i=0;i<lnames.namelist_len;i++) {
144 int delta = strlen(lnames.namelist_val[i]);
145 if ( (delta < *buflen) && islower(*(lnames.namelist_val[i])) ) {
146 cpstr(lnames.namelist_val[i], buffer, buflen);
147 ret = NSS_STATUS_SUCCESS;
148 }
149 }
150 free(lnames.namelist_val);
151 /* free(lid.idlist_val); */
152 lid.idlist_val = 0;
153 lid.idlist_len = 0;
154
155 pthread_mutex_unlock(&mutex);
156 return ret;
157 }
158
159 /**
160 * Look up the uid corresponding to name in ptserver.
161 */
162 enum nss_status ptsname2id(char *name, uid_t* uid) {
163 int res;
164 idlist lid;
165 namelist lnames;
166 char uname[MAXUSERNAMELEN];
167
168 if (init_afs()) return NSS_STATUS_UNAVAIL;
169
170 if (!strcmp(name,"anonymous")) {
171 *uid = AFS_MAGIC_ANONYMOUS_USERID;
172 return NSS_STATUS_SUCCESS;
173 }
174
175 if (pthread_mutex_lock(&mutex)) return NSS_STATUS_UNAVAIL;
176
177 lid.idlist_val = 0;
178 lid.idlist_len = 0;
179 lnames.namelist_val = (prname*)uname;
180 // apparently ubik expects to be able to modify this?
181 strncpy(uname, name, MAXUSERNAMELEN);
182 lnames.namelist_len = 1;
183
184 if (ubik_PR_NameToID(pruclient,0,&lnames,&lid) != PRSUCCESS) {
185 perror("ubik_Call() in ptsname2id() failed\n");
186 pthread_mutex_unlock(&mutex);
187 return NSS_STATUS_UNAVAIL;
188 }
189 pthread_mutex_unlock(&mutex);
190
191 res = (uid_t)lid.idlist_val[0];
192 if (res == AFS_MAGIC_ANONYMOUS_USERID) return NSS_STATUS_NOTFOUND;
193 *uid = res;
194 return NSS_STATUS_SUCCESS;
195 }
196
197 /**
198 * Initialize the library; returns zero on success
199 */
200 int init_afs() {
201 FILE *thiscell;
202 int len;
203 struct stat statbuf;
204
205 char buf[6];
206 int fd;
207 int pos;
208
209 if (afs_initialized) {
210 /* wait until /afs/@cell/ appears as a proxy for "the network is up" */
211 if (stat(cell_root, &statbuf)) return -1;
212 return 0;
213 }
214
215 // check to make sure that we are running inside nscd
216 pos = 0;
217 fd = open("/proc/self/cmdline", O_RDONLY);
218 if (fd==-1) return -1;
219 while(1) {
220 int numread;
221 numread = read(fd, buf+pos, 1);
222 if (buf[ (pos+5)%6 ] == 'd' &&
223 buf[ (pos+4)%6 ] == 'c' &&
224 buf[ (pos+3)%6 ] == 's' &&
225 buf[ (pos+2)%6 ] == 'n' &&
226 (buf[(pos+1)%6 ] == '/' || pos==4) &&
227 (buf[(pos+0)%6 ] == 0 || numread==-1)
228 )
229 break;
230 pos = (pos+1)%6;
231 if (numread==0) { close(fd); return -1; }
232 }
233 close(fd);
234
235 if (pthread_mutex_lock(&mutex)) return -1;
236 do {
237 homedirs_method=HOMEDIR_PREFIX;
238 shells_method=SHELL_USERLINK;
239
240 len = snprintf(cellname, MAXCELLNAMELEN,
241 "%s/ThisCell", AFSDIR_CLIENT_ETC_DIRPATH);
242 if (len < 0 || len >= MAXCELLNAMELEN) return -1;
243
244 thiscell=fopen(cellname,"r");
245 if (thiscell == NULL) break;
246 len=fread(cellname,1,MAXCELLNAMELEN,thiscell);
247 if (!feof(thiscell)) {
248 // Cellname too long
249 fclose(thiscell);
250 strcpy(homedir_prefix,"/tmp/\0");
251 homedir_prefix_len=5;
252 break;
253 }
254 fclose(thiscell);
255
256 if (cellname[len-1] == '\n') len--;
257 cellname[len]='\0';
258
259 /* wait until /afs/@cell/ appears as a proxy for "the network is up" */
260 sprintf(cell_root,"/afs/%s/",cellname);
261 if (stat(cell_root, &statbuf)) break;
262
263 sprintf(homedir_prefix,"/afs/%s/user/",cellname);
264 homedir_prefix_len=strlen(homedir_prefix);
265
266 /* time out requests after 5 seconds to avoid hanging things */
267 rx_SetRxDeadTime(5);
268
269 if (pr_Initialize(0L,AFSDIR_CLIENT_ETC_DIRPATH, 0)) {
270 perror("pr_Initialize() failed\n");
271 break;
272 }
273
274 afs_initialized = 1;
275 pthread_mutex_unlock(&mutex);
276 return 0;
277
278 } while(0);
279 pthread_mutex_unlock(&mutex);
280 return -1;
281 }
282
283
284 /**
285 * Retrieves the homedir for a given user; returns 0 on success.
286 */
287 int get_homedir(char *name, char **buffer, size_t *buflen) {
288 char buf[256];
289 int temp;
290 char *b;
291 b=*buffer;
292 switch (homedirs_method) {
293 case HOMEDIR_PREFIX:
294 homedir_prefix[homedir_prefix_len+0]=name[0];
295 homedir_prefix[homedir_prefix_len+1]='/';
296 homedir_prefix[homedir_prefix_len+2]=name[0];
297 homedir_prefix[homedir_prefix_len+3]=name[1];
298 homedir_prefix[homedir_prefix_len+4]='/';
299 homedir_prefix[homedir_prefix_len+5]=0;
300 strncpy(&homedir_prefix[homedir_prefix_len+5],name,40);
301 if (! cpstr(homedir_prefix,buffer,buflen) ) return -1;
302 break;
303 case HOMEDIR_AUTO:
304 homedir_prefix[homedir_prefix_len]=0;
305 strncpy(&homedir_prefix[homedir_prefix_len],name,40);
306 if (! cpstr(homedir_prefix,buffer,buflen) ) return -1;
307 break;
308 case HOMEDIR_ADMINLINK:
309 if ( snprintf(buf,256,"/afs/%s/admin/homedirs/%s",cellname,name) > 0 ) {
310 temp=readlink(buf,*buffer,*buflen);
311 if ( temp > -1) {
312 b[temp]=0;
313 *buflen = *buflen - temp - 1;
314 return -1;
315 }
316 }
317 if (! cpstr("/tmp",buffer,buflen) ) return -1;
318 break;
319 }
320 return 0;
321 }
322
323 /**
324 * Retrieves the shell for a given user; returns 0 on success.
325 */
326 int get_shell(char *name, char **buffer, size_t *buflen) {
327 char buf[256];
328 int temp;
329 char *b;
330 char* bufx = buf;
331 size_t bufxlen = 256;
332 b=*buffer;
333
334 switch (shells_method) {
335 case SHELL_BASH:
336 break;
337
338 case SHELL_ADMINLINK:
339 if (snprintf(buf,256,"/afs/%s/admin/shells/%s",cellname,name)<=0) break;
340 temp = readlink(buf,*buffer,*buflen);
341 if (temp < 0) break;
342 b[temp]=0;
343 *buflen = *buflen - temp - 1;
344 return 0;
345
346 case SHELL_USERLINK:
347 if (get_homedir(name, &bufx, &bufxlen)) break;
348 if (strncpy(buf+strlen(buf),"/.loginshell",bufxlen)<=0) break;
349 temp = readlink(buf,*buffer,*buflen);
350 if (temp < 0) break;
351 b[temp]=0;
352 *buflen = *buflen - temp - 1;
353 return 0;
354 }
355 if (! cpstr("/bin/bash",buffer,buflen) )
356 return -1;
357 return 0;
358 }
359
360
361 /*
362 * This function is exported; glibc will invoke it in order to find
363 * the name and list of members of a group specified by a numerical
364 * groupid.
365 */
366 enum nss_status _nss_afs_getgrgid_r (gid_t gid,
367 struct group *result,
368 char *buffer,
369 size_t buflen,
370 int *errnop) {
371 int length;
372 int showgid = 0;
373 if (gid >= MIN_PAG_GID && gid <= MAX_PAG_GID) {
374 showgid = gid-MIN_PAG_GID;
375 } else if (gid >= MIN_OLDPAG_GID && gid <= MAX_OLDPAG_GID) {
376 showgid = gid-MIN_OLDPAG_GID;
377 } else {
378 *errnop=ENOENT;
379 return NSS_STATUS_NOTFOUND;
380 }
381 do {
382 result->gr_gid=gid;
383
384 result->gr_name=buffer;
385 length=snprintf(buffer,buflen,"AfsPag-%x",showgid);
386
387 if (length < 0) break;
388 length += 1;
389 buflen -= length;
390 buffer += length;
391
392 result->gr_passwd=buffer;
393
394 if (!cpstr("z",&buffer,&buflen)) break;
395
396 if (buflen < sizeof(char*)) break;
397 result->gr_mem=&buffer;
398 result->gr_mem[0] = NULL;
399
400 *errnop=errno;
401 return NSS_STATUS_SUCCESS;
402
403 } while(0);
404 *errnop=ENOENT;
405 return NSS_STATUS_UNAVAIL;
406 }
407
408 /**
409 * A helper function to fill in the fields of "struct passwd"; used by
410 * both _nss_afs_getpwuid_r() and _nss_afs_getpwnam_r().
411 */
412 enum nss_status fill_result_buf(uid_t uid,
413 char* name,
414 struct passwd *result_buf,
415 char *buffer,
416 size_t buflen,
417 int *errnop) {
418 result_buf->pw_name = name;
419 do {
420 /* set the password to "z"; we can't use "x" because of pam_unix.so */
421 result_buf->pw_passwd = buffer;
422 if ( ! cpstr("z",&buffer, &buflen) ) break;
423
424 /* the uid and gid are both the uid passed in */
425 result_buf->pw_uid = uid;
426 result_buf->pw_gid = 65534;
427
428 /* make the gecos the same as the PTS name */
429 result_buf->pw_gecos = buffer;
430 if ( ! cpstr(result_buf->pw_name, &buffer, &buflen ) ) break;
431
432 // Set the homedirectory
433 result_buf->pw_dir = buffer;
434 if ( get_homedir(result_buf->pw_name,&buffer,&buflen) ) break;
435
436 // Set the login shell
437 result_buf->pw_shell = buffer;
438 if ( get_shell(result_buf->pw_name,&buffer,&buflen) ) break;
439
440 #ifdef LIMIT_USERNAME_CHARS
441 if ( strlen(result_buf->pw_name) > LIMIT_USERNAME_CHARS ) {
442 result_buf->pw_name[LIMIT_USERNAME_CHARS] = '\0';
443 buflen = buflen + ( buffer - &result_buf->pw_name[LIMIT_USERNAME_CHARS+1] );
444 buffer = &result_buf->pw_name[LIMIT_USERNAME_CHARS+1];
445 }
446 #endif
447
448 *errnop = errno;
449 return NSS_STATUS_SUCCESS;
450 } while(0);
451
452 *errnop = ERANGE;
453 return NSS_STATUS_UNAVAIL;
454 }
455
456
457 /**
458 * This function is exported; glibc will invoke it in order to gather
459 * the user information (userid, homedir, shell) associated with a
460 * numerical userid.
461 */
462 enum nss_status _nss_afs_getpwuid_r (uid_t uid,
463 struct passwd *result_buf,
464 char *buffer,
465 size_t buflen,
466 int *errnop) {
467 int temp;
468 char* name;
469
470 if (init_afs()) return NSS_STATUS_UNAVAIL;
471
472 name = buffer;
473 temp = ptsid2name( uid, &buffer, &buflen);
474 if (temp != NSS_STATUS_SUCCESS) {
475 *errnop = ENOENT;
476 return temp;
477 }
478
479 return fill_result_buf(uid, name, result_buf, buffer, buflen, errnop);
480 }
481
482 /**
483 * This function is exported; glibc will invoke it in order to gather
484 * the user information (userid, homedir, shell) associated with a
485 * username.
486 */
487 enum nss_status _nss_afs_getpwnam_r (char *name,
488 struct passwd *result_buf,
489 char *buffer,
490 size_t buflen,
491 int *errnop) {
492 uid_t uid;
493 int temp;
494
495 if (init_afs()) return NSS_STATUS_UNAVAIL;
496
497 temp = ptsname2id(name,&uid);
498 if (temp != NSS_STATUS_SUCCESS) {
499 *errnop = ENOENT;
500 return temp;
501 }
502
503 return fill_result_buf(uid, name, result_buf, buffer, buflen, errnop);
504 }
505