afsconfig-and-rcsid-all-around-20010705
[openafs.git] / src / kauth / kkids.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * ALL RIGHTS RESERVED
12  */
13
14 /* These two needed for rxgen output to work */
15 #include <afs/param.h>
16 #include <afsconfig.h>
17
18 RCSID("$Header$");
19
20 #include <stdio.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <errno.h>
24 #ifdef AFS_NT40_ENV
25 #include <winsock2.h>
26 #include <afs/fs_utils.h>
27 #else
28 #include <netinet/in.h>
29 #include <afs/venus.h>
30 #endif
31 #include <signal.h>
32 #include <afs/stds.h>
33 #include <rx/xdr.h>
34 #include <afs/prs_fs.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #define MAXNAME 100
39 #define MAXSIZE 2048
40
41 static int using_child = 0;
42 static FILE * childin, * childout;     /* file pointers on pipe to kpwvalid */
43
44 /* this removes symlinks from the tail end of path names.
45  * PRECONDITION: name must be either absolute ('/something') or
46  * explictly relative to current directory ('./something')
47  */
48 static int simplify_name(
49   char *orig_name, 
50   char *true_name)
51 {
52   int thru_symlink;
53   struct stat statbuff;
54
55   thru_symlink = 0;
56
57 #ifdef AFS_NT40_ENV
58   if (stat(orig_name, &statbuff) < 0) {
59     *true_name = '\0';
60     return 0;
61   }
62   else  {
63     strcpy(true_name, orig_name);
64     return 1;
65   }
66 #else /* !NT40 */
67   {
68     int link_chars_read;
69     char *last_component; 
70     if (lstat(orig_name, &statbuff) < 0) {
71       /* if lstat fails, it's possible that it's transient, but 
72          unlikely.  Let's hope it isn't, and continue... */
73       *true_name = '\0';
74       return 0;
75     }
76     
77     /*
78      * The lstat succeeded.  If the given file is a symlink, substitute
79      * the contents of the link for the file name.
80      */
81     if ((statbuff.st_mode & S_IFMT) == S_IFLNK) {
82       thru_symlink = 1;
83       link_chars_read = readlink(orig_name, true_name, 1024);
84       if (link_chars_read <= 0) {
85         *true_name = '\0';
86         return 0;
87       }
88       
89       true_name[link_chars_read++] = '\0';
90       
91       /*
92        * If the symlink is an absolute pathname, we're fine.  Otherwise, we
93        * have to create a full pathname using the original name and the
94        * relative symlink name.  Find the rightmost slash in the original
95        * name (we know there is one) and splice in the symlink contents.
96        */
97       if (true_name[0] != '/') {
98         last_component = (char *) rindex(orig_name, '/');
99         strcpy(++last_component, true_name);
100         strcpy(true_name, orig_name);
101       }
102     }
103     else
104       strcpy(true_name, orig_name);
105     
106     return 1 ; /* found it */
107   }
108 #endif /* !NT40 */
109 }
110
111
112   /* We find our own location by:
113    * 1. checking for an absolute or relative path name in argv[0]
114    *         this is moderately system-dependant: argv[0] is just a convention.
115    * 2. successively checking each component of PATH, and concatenating argv[0]
116    *    onto it, then stating the result.
117    * if it exists, it must be us, eh?
118    * NB there may be possible security implications involving
119    * symlinks; I think they are only relevant if the symlink points
120    * directly at kpasswd, not when it points at kpasswd's parent directory. 
121    */
122 static int find_me(
123   char *arg,
124   char *parent_dir)
125 {
126   char * bp; /* basename pointer */
127   char * dp; /* dirname pointer */
128   char *pathelt, orig_name[1024], truename[1022];
129
130 #define explicitname(a,b,c) \
131         (  ((a) == '/') ||                                \
132            (   ((a) == '.') &&                            \
133                (   ((b) == '/') ||                        \
134                    (   ((b) == '.') && ((c) == '/') )       \
135                )                                        \
136             )                                           \
137          )
138
139   if (strlen(arg) > 510)           /* just give up */ 
140     return 0;            
141   
142   *parent_dir = '\0';
143   truename[0] = '\0';
144
145   if (explicitname(arg[0], arg[1], arg[2])) {
146     strcpy (orig_name, arg);
147     simplify_name(orig_name, truename);
148   }
149   else {
150     bp = (char *) rindex(arg, '/');
151     if (bp) {
152       orig_name[0] = '.';
153       orig_name[1] = '/';
154       strcpy (orig_name+2, arg);
155       simplify_name(orig_name, truename);
156     }
157   }    
158   
159   if (!truename[0]) {  /* didn't find it */
160     char path[2046];
161
162     dp = getenv("PATH");
163     if (!dp) 
164       return 0;
165     strncpy (path, dp, 2045);
166      
167     for (pathelt = strtok(path, ":"); pathelt; pathelt = strtok(NULL, ":")) {
168       strncpy (orig_name, pathelt, 510); 
169
170       bp = orig_name + strlen(orig_name);
171       *bp = '/'; /* replace NUL with / */
172       strncpy (bp + 1, arg, 510);
173       
174       if (simplify_name(orig_name, truename))
175         break;
176     }
177   }
178   if (!truename[0])   /* didn't find it */
179     return 0;        /* give up */
180   
181   /* DID FIND IT */      
182   /*
183    * Find rightmost slash, if any.
184    */
185   bp = (char *) rindex(truename, '/');
186   if (bp) {
187     /*
188      * Found it.  Designate everything before it as the parent directory,
189      * everything after it as the final component.
190      */
191     strncpy(parent_dir, truename, bp - truename);
192     parent_dir[bp - truename] = 0;
193     bp++;   /*Skip the slash*/
194   }
195   else {
196     /*
197      * No slash appears in the given file name.  Set parent_dir to the current
198      * directory, and the last component as the given name.
199      */
200     strcpy(parent_dir, ".");
201     bp = truename;
202   }
203
204 return 1; /* found it */
205 }
206
207 #define SkipLine(str) { while (*str !='\n') str++; str++; }
208
209 /* this function returns TRUE (1) if the file is in AFS, otherwise false (0) */
210 static int InAFS(register char *apath)
211 {
212     struct ViceIoctl blob;
213     register afs_int32 code;
214     char space[MAXSIZE];
215
216     blob.in_size = 0;
217     blob.out_size = MAXSIZE;
218     blob.out = space;
219
220     code = pioctl(apath, VIOC_FILE_CELL_NAME, &blob, 1);
221     if (code) {
222         if ((errno == EINVAL) || (errno == ENOENT)) return 0;
223     }
224     return 1;
225 }
226
227 struct Acl {
228     int nplus;
229     int nminus;
230     struct AclEntry *pluslist;
231     struct AclEntry *minuslist;
232 };
233
234 struct AclEntry {
235     struct AclEntry *next;
236     char name[MAXNAME];
237     afs_int32 rights;
238 };
239
240 static struct Acl * ParseAcl (char *astr)
241 {
242     int nplus, nminus, i, trights;
243     char tname[MAXNAME];
244     struct AclEntry *first, *last, *tl;
245     struct Acl *ta;
246     sscanf(astr, "%d", &nplus);
247     SkipLine(astr);
248     sscanf(astr, "%d", &nminus);
249     SkipLine(astr);
250
251     ta = (struct Acl *) malloc (sizeof (struct Acl));
252     ta->nplus = nplus;
253
254     last = 0;
255     first = 0;
256     for(i=0;i<nplus;i++) {
257         sscanf(astr, "%100s %d", tname, &trights);
258         SkipLine(astr);
259         tl = (struct AclEntry *) malloc(sizeof (struct AclEntry));
260         if (!first) first = tl;
261         strcpy(tl->name, tname);
262         tl->rights = trights;
263         tl->next = 0;
264         if (last) last->next = tl;
265         last = tl;
266     }
267     ta->pluslist = first;
268
269     return ta;
270 }
271
272 static char *safestrtok(
273   char *str,
274   char *tok)
275 {
276     char * temp;
277
278     if (str) return (strtok(str, tok));
279
280     temp = strtok (NULL, tok);
281     if (temp) *(temp-1) = *tok;
282
283     return temp;
284
285 }
286
287
288 /* If it exists, we do some fussing about whether or not this
289  * is a reasonably secure path - not that it makes *much* difference, since
290  * there's not much point in being more secure than the kpasswd executable.
291  */
292 /* 1.  is this directory in AFS?
293  * 2.  Is every component of the pathname secure 
294  *     (ie, only system:administrators have w or a rights)? 
295  */
296 static int is_secure (char *dir)
297 {
298   char *temp;
299   struct ViceIoctl blob;
300   struct AclEntry *te;
301   char space[2046];
302   int secure = 1;
303   afs_int32 code;
304   struct Acl *ta;
305
306   if (!InAFS(dir))   /* final component *must* be in AFS */
307     return 0;
308
309 #ifndef INSECURE
310   for (temp = safestrtok(dir, "/"); temp; temp = safestrtok(NULL, "/")) {
311     /* strtok keeps sticking NUL in place of /, so we can look at 
312      * ever-longer chunks of the path.
313      */
314     if (!InAFS(dir))
315       continue;
316
317     blob.out_size = MAXSIZE;
318     blob.in_size = 0;
319     blob.out = space;
320     code = pioctl(dir, VIOCGETAL, &blob, 1);
321     if (code) {
322       continue;
323     }
324     ta = ParseAcl(space);
325     if (ta->nplus <= 0)
326       continue;
327
328     for(te = ta->pluslist;te;te=te->next) {
329       if (((te->rights & PRSFS_INSERT) && (te->rights & PRSFS_DELETE)) ||
330           (te->rights & (PRSFS_WRITE | PRSFS_ADMINISTER)))
331          if (strcmp (te->name, "system:administrators"))
332             return 0;   /* somebody who we can't trust has got power */
333     }
334   }
335 #endif /* INSECURE */
336
337   return 1;
338 }
339
340 /* Then, once we've found our own location, we look for a program named
341  * kpwvalid.  
342  */
343   
344 /* look for a password-checking program named kpwvalid.
345  * It has to be in a secure place (same place as this executable)
346  */
347 static int kpwvalid_is(char *dir)
348 {
349 struct stat statbuff;
350 int len;
351
352   len = strlen (dir);
353   strcpy (dir+len, "/kpwvalid");
354
355   if (stat(dir, &statbuff) < 0) {
356     /* if lstat fails, it's possible that it's transient, but 
357        unlikely.  Let's hope it isn't, and continue... */
358     *(dir + len) = '\0';
359     return 0;
360   }
361
362 *(dir + len) = '\0';
363 return 1;
364 }
365
366 #ifdef AFS_NT40_ENV
367 /* We don't allow the use of kpwvalid executable scripts to set policy
368  * for passwd changes. 
369  */
370 int init_child(char *myname)
371 {
372
373   using_child = 0;
374   return using_child;
375
376 }
377 #else /* !NT40 */
378 int init_child(char *myname)
379 {
380   int pipe1[2], pipe2[2];
381   pid_t pid;
382   char dirpath[1024];
383   char *argv[2];
384
385   if (!(find_me (myname, dirpath) && is_secure (dirpath) && 
386       kpwvalid_is(dirpath))) {
387      using_child = 0;
388      return 0;
389    }
390   
391   /* make a couple of pipes, one for the child's stdin, and the other
392    * for the child's stdout.  The parent writes to the former, and
393    * reads from the latter, the child reads from the former, and
394    * writes to the latter.
395    */
396   pipe(pipe1);
397   pipe(pipe2);
398   
399   /* fork a child */
400   pid = fork();
401   if (pid == -1) {
402     using_child = 0;
403     perror("kpasswd: can't fork because ");
404     return( using_child );
405   }
406   if (pid == 0) { /* in child process */
407     /* tie stdin and stdout to these pipes */
408     /* if dup2 doesn't exist everywhere, close and then dup, but make */
409     /* sure that you really get stdin or stdout from the dup. */
410     if (( -1 == dup2(pipe1[0],0)) || 
411         ( -1 == dup2(pipe2[1],1))) {
412         perror("kpasswd: can't exec kpwvalid because ");
413         exit(-1);
414       }
415
416      strcat (dirpath, "/kpwvalid");
417      argv[1]= NULL;
418      argv[0]= dirpath;
419      execv(dirpath, argv);
420   }
421   else {
422     using_child = pid;   /* save it for later */
423     childin = fdopen(pipe1[1], "w");
424     childout = fdopen(pipe2[0], "r");
425     return (using_child);
426   }
427 }
428 #endif /* not NT40 */
429
430 int password_bad (char *pw)
431 {
432 int rc;
433 rc = 0;
434
435 if (using_child) {
436   fprintf (childin, "%s\n",pw);
437   fflush  (childin);
438   fscanf  (childout, "%d", &rc);
439 }
440
441 return(rc);
442 }
443
444 /* this is originally only used to give the child the old password, so she
445  * can compare putative new passwords against it.
446  */
447 int give_to_child(char *pw)
448 {
449 int rc;
450 rc = 0;
451
452 if (using_child) {
453   fprintf (childin, "%s\n",pw);
454   fflush  (childin);
455 }
456
457 return(rc);
458 }
459
460 /* quickly and painlessly
461  */
462 int terminate_child(char *pw)
463 {
464   int rc;
465   rc = 0;
466
467 #ifndef AFS_NT40_ENV
468   if (using_child) {
469       rc = kill(using_child, SIGKILL);
470   }
471 #endif
472   return(rc);
473 }