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