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