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