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