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