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