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