remember to release mutex if we fail due to empty ThisCell
[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   init_afs();
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   init_afs();
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   if (afs_initialized) {
203     /* wait until /afs/@cell/ appears as a proxy for "the network is up" */
204     if (stat(cell_root, &statbuf)) return -1;
205     return 0;
206   }
207   
208   if (pthread_mutex_lock(&mutex)) return -1;
209   do {
210     homedirs_method=HOMEDIR_PREFIX;
211     shells_method=SHELL_USERLINK;
212
213     len = snprintf(cellname, MAXCELLNAMELEN,
214                    "%s/ThisCell", AFSDIR_CLIENT_ETC_DIRPATH);
215     if (len < 0 || len >= MAXCELLNAMELEN) break;
216
217     thiscell=fopen(cellname,"r");
218     if (thiscell == NULL) break;
219     len=fread(cellname,1,MAXCELLNAMELEN,thiscell);
220     if (!feof(thiscell)) {
221       // Cellname too long
222       fclose(thiscell);
223       strcpy(homedir_prefix,"/tmp/\0");
224       homedir_prefix_len=5;
225       break;
226     }
227     fclose(thiscell);
228
229     if (cellname[len-1] == '\n') len--;
230     cellname[len]='\0';
231
232     /* wait until /afs/@cell/ appears as a proxy for "the network is up" */
233     sprintf(cell_root,"/afs/%s/",cellname);
234     if (stat(cell_root, &statbuf)) break;
235
236     sprintf(homedir_prefix,"/afs/%s/user/",cellname);
237     homedir_prefix_len=strlen(homedir_prefix);
238
239     /* time out requests after 5 seconds to avoid hanging things */
240     rx_SetRxDeadTime(5);    
241
242     if (pr_Initialize(0L,AFSDIR_CLIENT_ETC_DIRPATH, 0)) {
243       perror("pr_Initialize() failed\n");
244       break;
245     }
246     
247     afs_initialized = 1;
248     pthread_mutex_unlock(&mutex);
249     return 0;
250
251   } while(0);
252   pthread_mutex_unlock(&mutex);
253   return -1;
254 }
255
256
257 /**
258  * Retrieves the homedir for a given user; returns 0 on success.
259  */
260 int get_homedir(char *name, char **buffer, size_t *buflen) {
261   char buf[256];
262   int temp;
263   char *b;
264   b=*buffer;
265   switch (homedirs_method) {
266     case HOMEDIR_PREFIX:
267       homedir_prefix[homedir_prefix_len+0]=name[0];
268       homedir_prefix[homedir_prefix_len+1]='/';
269       homedir_prefix[homedir_prefix_len+2]=name[0];
270       homedir_prefix[homedir_prefix_len+3]=name[1];
271       homedir_prefix[homedir_prefix_len+4]='/';
272       homedir_prefix[homedir_prefix_len+5]=0;
273       strncpy(&homedir_prefix[homedir_prefix_len+5],name,40);
274       if (! cpstr(homedir_prefix,buffer,buflen) ) return -1;
275       break;
276     case HOMEDIR_AUTO:
277       homedir_prefix[homedir_prefix_len]=0;
278       strncpy(&homedir_prefix[homedir_prefix_len],name,40);
279       if (! cpstr(homedir_prefix,buffer,buflen) ) return -1;
280       break;
281     case HOMEDIR_ADMINLINK:
282       if ( snprintf(buf,256,"/afs/%s/admin/homedirs/%s",cellname,name) > 0 ) {
283         temp=readlink(buf,*buffer,*buflen);
284         if ( temp > -1) {
285           b[temp]=0;
286           *buflen = *buflen - temp - 1;
287           return -1;
288         }
289       }
290       if (! cpstr("/tmp",buffer,buflen) ) return -1;
291       break;
292   }
293   return 0;
294 }
295
296 /**
297  * Retrieves the shell for a given user; returns 0 on success.
298  */
299 int get_shell(char *name, char **buffer, size_t *buflen) {
300   char buf[256];
301   int temp;
302   char *b;
303   char* bufx = buf;
304   int bufxlen = 256;
305   b=*buffer;
306
307   switch (shells_method) {
308     case SHELL_BASH:
309       break;
310
311     case SHELL_ADMINLINK:
312       if (snprintf(buf,256,"/afs/%s/admin/shells/%s",cellname,name)<=0) break;
313       temp = readlink(buf,*buffer,*buflen);
314       if (temp < 0) break;
315       b[temp]=0;
316       *buflen = *buflen - temp - 1;
317       return 0;
318
319     case SHELL_USERLINK:
320       if (get_homedir(name, &bufx, &bufxlen)) break;
321       if (strncpy(buf+strlen(buf),"/.loginshell",bufxlen)<=0) break;
322       temp = readlink(buf,*buffer,*buflen);
323       if (temp < 0) break;
324       b[temp]=0;
325       *buflen = *buflen - temp - 1;
326       return 0;
327   }
328   if (! cpstr("/bin/bash",buffer,buflen) )
329     return -1;
330   return 0;
331 }
332
333
334 /*
335  * This function is exported; glibc will invoke it in order to find
336  * the name and list of members of a group specified by a numerical
337  * groupid.
338  */
339 enum nss_status _nss_afs_getgrgid_r (gid_t gid,
340                                      struct group *result,
341                                      char *buffer,
342                                      size_t buflen,
343                                      int *errnop) {
344   int length;
345   int showgid = 0;
346   if (gid >= MIN_PAG_GID && gid <= MAX_PAG_GID) {
347     showgid = gid-MIN_PAG_GID;
348   } else if (gid >= MIN_OLDPAG_GID && gid <= MAX_OLDPAG_GID) {
349     showgid = gid-MIN_OLDPAG_GID;
350   } else {
351     *errnop=ENOENT;
352     return NSS_STATUS_NOTFOUND;
353   }
354   do {
355     result->gr_gid=gid;
356
357     result->gr_name=buffer;
358     length=snprintf(buffer,buflen,"AfsPag-%x",showgid);
359     
360     if (length < 0) break;
361     length += 1;
362     buflen -= length;
363     buffer += length;
364
365     result->gr_passwd=buffer;
366
367     if (!cpstr("x",&buffer,&buflen)) break;
368
369     if (buflen < sizeof(char*)) break;
370     result->gr_mem=buffer;
371     result->gr_mem[0] = NULL;
372
373     *errnop=errno;
374     return NSS_STATUS_SUCCESS;
375
376   } while(0);
377   *errnop=ENOENT;
378   return NSS_STATUS_UNAVAIL;
379 }
380
381 /**
382  * A helper function to fill in the fields of "struct passwd"; used by
383  * both _nss_afs_getpwuid_r() and _nss_afs_getpwnam_r().
384  */
385 enum nss_status fill_result_buf(uid_t uid,
386                                 char* name,
387                                 struct passwd *result_buf,
388                                 char *buffer,
389                                 size_t buflen,
390                                 int *errnop) {
391   result_buf->pw_name = name;
392   do {
393     /* set the password to "x" */
394     result_buf->pw_passwd = buffer;
395     if ( ! cpstr("x",&buffer, &buflen) ) break;
396
397     /* the uid and gid are both the uid passed in */
398     result_buf->pw_uid = uid;
399     result_buf->pw_gid = 65534;
400
401     /* make the gecos the same as the PTS name */
402     result_buf->pw_gecos = buffer;
403     if ( ! cpstr(result_buf->pw_name, &buffer, &buflen ) ) break;
404
405     // Set the homedirectory
406     result_buf->pw_dir = buffer;
407     if ( get_homedir(result_buf->pw_name,&buffer,&buflen) ) break;
408
409     // Set the login shell
410     result_buf->pw_shell = buffer;
411     if ( get_shell(result_buf->pw_name,&buffer,&buflen) ) break;
412
413 #ifdef LIMIT_USERNAME_CHARS
414     if ( strlen(result_buf->pw_name) > LIMIT_USERNAME_CHARS ) {
415       result_buf->pw_name[LIMIT_USERNAME_CHARS] = '\0';
416       buflen = buflen + ( buffer - &result_buf->pw_name[LIMIT_USERNAME_CHARS+1] );
417       buffer = &result_buf->pw_name[LIMIT_USERNAME_CHARS+1];
418     }
419 #endif
420
421     *errnop = errno;
422     return NSS_STATUS_SUCCESS;
423   } while(0);
424
425   *errnop = ERANGE;
426   return NSS_STATUS_UNAVAIL;
427 }
428
429
430 /**
431  * This function is exported; glibc will invoke it in order to gather
432  * the user information (userid, homedir, shell) associated with a
433  * numerical userid.
434  */
435 enum nss_status _nss_afs_getpwuid_r (uid_t uid,
436                                      struct passwd *result_buf,
437                                      char *buffer,
438                                      size_t buflen,
439                                      int *errnop) {
440   int temp;
441   char* name;
442
443   if (init_afs()) return NSS_STATUS_UNAVAIL;
444
445   name = buffer;
446   temp = ptsid2name( uid, &buffer, &buflen);
447   if (temp != NSS_STATUS_SUCCESS) {
448     *errnop = ENOENT;
449     return temp;
450   }
451
452   return fill_result_buf(uid, name, result_buf, buffer, buflen, errnop);
453 }
454
455 /**
456  * This function is exported; glibc will invoke it in order to gather
457  * the user information (userid, homedir, shell) associated with a
458  * username.
459  */
460 enum nss_status _nss_afs_getpwnam_r (char *name,
461                                      struct passwd *result_buf,
462                                      char *buffer,
463                                      size_t buflen,
464                                      int *errnop) {
465   uid_t uid;
466   int temp;
467
468   if (init_afs()) return NSS_STATUS_UNAVAIL;
469
470   temp = ptsname2id(name,&uid);
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