Imported Upstream version 2.4.5
[debian/amanda] / common-src / security.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: security.c,v 1.17.2.6.4.1.2.5.2.1 2004/04/29 20:47:22 martinea Exp $
28  *
29  * wrapper file for kerberos security
30  */
31
32 #include "amanda.h"
33 #include "clock.h"
34
35 /*
36  * Change the following from #undef to #define to cause detailed logging
37  * of the security steps, e.g. into /tmp/amanda/amandad*debug.
38  */
39 #undef SHOW_SECURITY_DETAIL
40
41 /*
42  * If we don't have the new-style wait access functions, use our own,
43  * compatible with old-style BSD systems at least.  Note that we don't
44  * care about the case w_stopval == WSTOPPED since we don't ask to see
45  * stopped processes, so should never get them from wait.
46  */
47 #ifndef WEXITSTATUS
48 #   define WEXITSTATUS(r)       (((union wait *) &(r))->w_retcode)
49 #   define WTERMSIG(r)          (((union wait *) &(r))->w_termsig)
50
51 #   undef  WIFSIGNALED
52 #   define WIFSIGNALED(r)       (((union wait *) &(r))->w_termsig != 0)
53 #endif
54
55 #if defined(TEST)                                               /* { */
56 #define SHOW_SECURITY_DETAIL
57 #undef dbprintf
58 #define dbprintf(p)     printf p
59 #endif                                                          /* } */
60
61 #if defined(SHOW_SECURITY_DETAIL)                               /* { */
62 void show_stat_info(a, b)
63     char *a, *b;
64 {
65     char *name = vstralloc(a, b, NULL);
66     struct stat sbuf;
67     struct passwd *pwptr;
68     char *owner;
69     struct group *grptr;
70     char *group;
71
72     if (stat(name, &sbuf) != 0) {
73         dbprintf(("%s: cannot stat %s: %s\n",
74                   debug_prefix_time(NULL), name, strerror(errno)));
75         amfree(name);
76         return;
77     }
78     if ((pwptr = getpwuid(sbuf.st_uid)) == NULL) {
79         owner = alloc(NUM_STR_SIZE);
80         ap_snprintf(owner, NUM_STR_SIZE, "%ld", (long)sbuf.st_uid);
81     } else {
82         owner = stralloc(pwptr->pw_name);
83     }
84     if ((grptr = getgrgid(sbuf.st_gid)) == NULL) {
85         group = alloc(NUM_STR_SIZE);
86         ap_snprintf(owner, NUM_STR_SIZE, "%ld", (long)sbuf.st_gid);
87     } else {
88         group = stralloc(grptr->gr_name);
89     }
90     dbprintf(("%s: processing file: %s\n", debug_prefix_time(NULL), name));
91     dbprintf(("%s: owner=%s group=%s mode=%03o\n",
92               debug_prefix(NULL), owner, group, (int) (sbuf.st_mode & 0777)));
93     amfree(name);
94     amfree(owner);
95     amfree(group);
96 }
97 #endif                                                          /* } */
98
99 #ifdef KRB4_SECURITY                                            /* { */
100 #include "krb4-security.c"
101 #endif                                                          /* } */
102
103 int bsd_security_ok P((struct sockaddr_in *addr,
104                        char *str, uint32_t cksum, char **errstr));
105
106 char *get_bsd_security()
107 {
108     struct passwd *pwptr;
109
110     if((pwptr = getpwuid(getuid())) == NULL)
111         error("can't get login name for my uid %ld", (long)getuid());
112     return stralloc2("SECURITY USER ", pwptr->pw_name);
113 }
114
115 int security_ok(addr, str, cksum, errstr)
116 struct sockaddr_in *addr;
117 char *str;
118 uint32_t cksum;
119 char **errstr;
120 {
121 #ifdef KRB4_SECURITY                                            /* { */
122     if(krb4_auth)
123         return krb4_security_ok(addr, str, cksum, errstr);
124     else
125 #endif                                                          /* } */
126         return bsd_security_ok(addr, str, cksum, errstr);
127 }
128
129 #ifdef BSD_SECURITY                                             /* { */
130
131 int bsd_security_ok(addr, str, cksum, errstr)
132      struct sockaddr_in *addr;
133      char *str;
134      uint32_t cksum;
135      char **errstr;
136 {
137     char *remotehost = NULL, *remoteuser = NULL, *localuser = NULL;
138     char *bad_bsd = NULL;
139     struct hostent *hp;
140     struct passwd *pwptr;
141     int myuid, i, j;
142     char *s, *fp;
143     int ch;
144     char number[NUM_STR_SIZE];
145 #ifdef USE_AMANDAHOSTS                                          /* { */
146     FILE *fPerm;
147     char *pbuf = NULL;
148     char *ptmp;
149     int pbuf_len;
150     int amandahostsauth = 0;
151 #else                                                           /* } { */
152     FILE *fError;
153     int saved_stderr;
154     int fd[2];
155     amwait_t wait_exitcode;
156     int exitcode;
157     pid_t pid, ruserok_pid;
158 #endif                                                          /* } */
159
160     *errstr = NULL;
161
162     /* what host is making the request? */
163
164     hp = gethostbyaddr((char *)&addr->sin_addr, (int)sizeof(addr->sin_addr),
165                        AF_INET);
166     if(hp == NULL) {
167         /* XXX include remote address in message */
168         *errstr = vstralloc("[",
169                             "addr ", inet_ntoa(addr->sin_addr), ": ",
170                             "hostname lookup failed",
171                             "]", NULL);
172         return 0;
173     }
174     remotehost = stralloc(hp->h_name);
175
176     /* Now let's get the hostent for that hostname */
177     hp = gethostbyname( remotehost );
178     if(hp == NULL) {
179         /* XXX include remote hostname in message */
180         *errstr = vstralloc("[",
181                             "host ", remotehost, ": ",
182                             "hostname lookup failed",
183                             "]", NULL);
184         amfree(remotehost);
185         return 0;
186     }
187
188     /* Verify that the hostnames match -- they should theoretically */
189     if( strncasecmp( remotehost, hp->h_name, strlen(remotehost)+1 ) != 0 ) {
190         *errstr = vstralloc("[",
191                             "hostnames do not match: ",
192                             remotehost, " ", hp->h_name,
193                             "]", NULL);
194         amfree(remotehost);
195         return 0;
196     }
197
198     /* Now let's verify that the ip which gave us this hostname
199      * is really an ip for this hostname; or is someone trying to
200      * break in? (THIS IS THE CRUCIAL STEP)
201      */
202     for (i = 0; hp->h_addr_list[i]; i++) {
203         if (memcmp(hp->h_addr_list[i],
204                    (char *) &addr->sin_addr, sizeof(addr->sin_addr)) == 0)
205             break;                     /* name is good, keep it */
206     }
207
208     /* If we did not find it, your DNS is messed up or someone is trying
209      * to pull a fast one on you. :(
210      */
211
212    /*   Check even the aliases list. Work around for Solaris if dns goes over NIS */
213
214     if( !hp->h_addr_list[i] ) {
215         for (j = 0; hp->h_aliases[j] !=0 ; j++) {
216              if ( strcmp(hp->h_aliases[j],inet_ntoa(addr->sin_addr)) == 0)
217                  break;                          /* name is good, keep it */
218         }
219         if( !hp->h_aliases[j] ) {
220             *errstr = vstralloc("[",
221                                 "ip address ", inet_ntoa(addr->sin_addr),
222                                 " is not in the ip list for ", remotehost,
223                                 "]",
224                                 NULL);
225             amfree(remotehost);
226             return 0;
227         }
228     }
229
230     /* next, make sure the remote port is a "reserved" one */
231
232     if(ntohs(addr->sin_port) >= IPPORT_RESERVED) {
233         ap_snprintf(number, sizeof(number), "%d", ntohs(addr->sin_port));
234         *errstr = vstralloc("[",
235                             "host ", remotehost, ": ",
236                             "port ", number, " not secure",
237                             "]", NULL);
238         amfree(remotehost);
239         return 0;
240     }
241
242     /* extract the remote user name from the message */
243
244     s = str;
245     ch = *s++;
246
247     bad_bsd = vstralloc("[",
248                         "host ", remotehost, ": ",
249                         "bad bsd security line",
250                         "]", NULL);
251
252 #define sc "USER "
253     if(strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
254         *errstr = bad_bsd;
255         bad_bsd = NULL;
256         amfree(remotehost);
257         return 0;
258     }
259     s += sizeof(sc)-1;
260     ch = s[-1];
261 #undef sc
262
263     skip_whitespace(s, ch);
264     if(ch == '\0') {
265         *errstr = bad_bsd;
266         bad_bsd = NULL;
267         amfree(remotehost);
268         return 0;
269     }
270     fp = s - 1;
271     skip_non_whitespace(s, ch);
272     s[-1] = '\0';
273     remoteuser = stralloc(fp);
274     s[-1] = ch;
275     amfree(bad_bsd);
276
277     /* lookup our local user name */
278
279     myuid = getuid();
280     if((pwptr = getpwuid(myuid)) == NULL) {
281         error("error [getpwuid(%d) fails]", myuid);
282     }
283
284     localuser = stralloc(pwptr->pw_name);
285
286     dbprintf(("%s: bsd security: remote host %s user %s local user %s\n",
287               debug_prefix_time(NULL), remotehost, remoteuser, localuser));
288
289 #ifndef USE_AMANDAHOSTS                                         /* { */
290     /*
291      * Note that some versions of ruserok (eg SunOS 3.2) look in
292      * "./.rhosts" rather than "~localuser/.rhosts", so we have to
293      * change directories ourselves.  Sigh.
294      *
295      * And, believe it or not, some ruserok()'s try an initgroup just
296      * for the hell of it.  Since we probably aren't root at this point
297      * it'll fail, and initgroup "helpfully" will blatt "Setgroups: Not owner"
298      * into our stderr output even though the initgroup failure is not a
299      * problem and is expected.  Thanks a lot.  Not.
300      */
301     if (pipe(fd) != 0) {
302         error("error [pipe() fails]");
303     }
304     if ((ruserok_pid = fork ()) < 0) {
305         error("error [fork() fails]");
306     } else if (ruserok_pid == 0) {
307         close(fd[0]);
308         fError = fdopen(fd[1], "w");
309         /* pamper braindead ruserok's */
310         if(chdir(pwptr->pw_dir) != 0) {
311             fprintf(fError, "[chdir(%s) failed: %s]",
312                     pwptr->pw_dir, strerror(errno));
313             fclose(fError);
314             exit(1);
315         }
316
317 #if defined(SHOW_SECURITY_DETAIL)                               /* { */
318         {
319         char *dir = stralloc(pwptr->pw_dir);
320
321         dbprintf(("%s: calling ruserok(%s, %d, %s, %s)\n",
322                   debug_prefix_time(NULL),
323                   remotehost, myuid == 0, remoteuser, localuser));
324         if (myuid == 0) {
325             dbprintf(("%s: because you are running as root, ",
326                       debug_prefix(NULL)));
327             dbprintf(("/etc/hosts.equiv will not be used\n"));
328         } else {
329             show_stat_info("/etc/hosts.equiv", NULL);
330         }
331         show_stat_info(dir, "/.rhosts");
332         amfree(dir);
333         }
334 #endif                                                          /* } */
335
336         saved_stderr = dup(2);
337         close(2);
338         (void)open("/dev/null", 2);
339
340         if(ruserok(remotehost, myuid == 0, remoteuser, localuser) == -1) {
341             dup2(saved_stderr,2);
342             close(saved_stderr);
343             *errstr = vstralloc("[",
344                                 "access as ", localuser, " not allowed",
345                                 " from ", remoteuser, "@", remotehost,
346                                 "] ruserok failed",
347                                 NULL);
348             fputs(*errstr, fError);
349             fputc('\n', fError);
350             fclose(fError);
351             dbprintf(("%s: check failed: %s\n",
352                       debug_prefix_time(NULL), *errstr));
353             amfree(*errstr);
354             exit(1);
355         }
356
357         dup2(saved_stderr,2);
358         close(saved_stderr);
359         dbprintf(("%s: bsd security check to %s from %s@%s passed\n",
360                   debug_prefix_time(NULL),
361                   localuser, remoteuser, remotehost));
362         amfree(remotehost);
363         amfree(remoteuser);
364         amfree(remoteuser);
365         exit(0);
366     }
367     close(fd[1]);
368     fError = fdopen(fd[0], "r");
369
370     while((pid = wait(&wait_exitcode)) == (pid_t)-1 && errno == EINTR) {}
371     if (pid == (pid_t)-1) {
372         *errstr = vstralloc("[",
373                             "access as ", localuser, " not allowed",
374                             " from ", remoteuser, "@", remotehost,
375                             "] wait failed: ",
376                             strerror(errno),
377                             NULL);
378         exitcode = 0;
379     } else if (pid != ruserok_pid) {
380         ap_snprintf(number, sizeof(number), "%ld", (long)pid);
381         *errstr = vstralloc("[",
382                             "access as ", localuser, " not allowed",
383                             " from ", remoteuser, "@", remotehost,
384                             "] wait got pid ",
385                             number,
386                             NULL);
387         exitcode = 0;
388     } else if (WIFSIGNALED(wait_exitcode)) {
389         ap_snprintf(number, sizeof(number), "%d", WTERMSIG(wait_exitcode));
390         *errstr = vstralloc("[",
391                             "access as ", localuser, " not allowed",
392                             " from ", remoteuser, "@", remotehost,
393                             "] got signal ", number,
394                             NULL);
395         exitcode = 0;
396     } else {
397         exitcode = WEXITSTATUS(wait_exitcode);
398     }
399     if(exitcode) {
400         if((*errstr = agets(fError)) == NULL) {
401             *errstr = vstralloc("[",
402                                 "access as ", localuser, " not allowed",
403                                 " from ", remoteuser, "@", remotehost,
404                                 "] could not get result",
405                                 NULL);
406         }
407     }
408     fclose(fError);
409     amfree(remotehost);
410     amfree(localuser);
411     amfree(remoteuser);
412     return *errstr == NULL;
413 #else                                                           /* } { */
414 #if defined(SHOW_SECURITY_DETAIL)                               /* { */
415     show_stat_info(pwptr->pw_dir, "/.amandahosts");
416 #endif                                                          /* } */
417
418     ptmp = stralloc2(pwptr->pw_dir, "/.amandahosts");
419     if((fPerm = fopen(ptmp, "r")) == NULL) {
420         /*
421          * Put an explanation in the amandad.debug log that will help a
422          * system administrator fix the problem, but don't send a clue
423          * back to the other end to tell them what to fix in order to
424          * be able to hack our system.
425          */
426         dbprintf(("%s: fopen of %s failed: %s\n",
427                   debug_prefix_time(NULL), ptmp, strerror(errno)));
428         *errstr = vstralloc("[",
429                             "access as ", localuser, " not allowed",
430                             " from ", remoteuser, "@", remotehost,
431                             "] open of ",
432                             ptmp,
433                             " failed", NULL);
434         amfree(ptmp);
435         amfree(remotehost);
436         amfree(localuser);
437         amfree(remoteuser);
438         return 0;
439     }
440     amfree(ptmp);
441
442     for(; (pbuf = agets(fPerm)) != NULL; free(pbuf)) {
443 #if defined(SHOW_SECURITY_DETAIL)                               /* { */
444         dbprintf(("%s: processing line: <%s>\n", debug_prefix(NULL), pbuf));
445 #endif                                                          /* } */
446         pbuf_len = strlen(pbuf);
447         s = pbuf;
448         ch = *s++;
449
450         /* Find end of remote host */
451         skip_non_whitespace(s, ch);
452         if(s - 1 == pbuf) {
453             memset(pbuf, '\0', pbuf_len);       /* leave no trace */
454             continue;                           /* no remotehost field */
455         }
456         s[-1] = '\0';                           /* terminate remotehost field */
457
458         /* Find start of remote user */
459         skip_whitespace(s, ch);
460         if(ch == '\0') {
461             ptmp = localuser;                   /* no remoteuser field */
462         } else {
463             ptmp = s-1;                         /* start of remoteuser field */
464
465             /* Find end of remote user */
466             skip_non_whitespace(s, ch);
467             s[-1] = '\0';                       /* terminate remoteuser field */
468         }
469 #if defined(SHOW_SECURITY_DETAIL)                               /* { */
470         dbprintf(("%s: comparing %s with\n", debug_prefix(NULL), pbuf));
471         dbprintf(("%s:           %s (%s)\n",
472                   debug_prefix(NULL), remotehost,
473                   (strcasecmp(pbuf, remotehost) == 0) ? "match" : "no match"));
474         dbprintf(("%s:       and %s with\n", debug_prefix(NULL), ptmp));
475         dbprintf(("%s:           %s (%s)\n",
476                   debug_prefix(NULL), remoteuser,
477                   (strcasecmp(ptmp, remoteuser) == 0) ? "match" : "no match"));
478 #endif                                                          /* } */
479         if(strcasecmp(pbuf, remotehost) == 0
480            && strcasecmp(ptmp, remoteuser) == 0) {
481             amandahostsauth = 1;
482             break;
483         }
484         memset(pbuf, '\0', pbuf_len);           /* leave no trace */
485     }
486     afclose(fPerm);
487     amfree(pbuf);
488
489     if(amandahostsauth) {
490         dbprintf(("%s: amandahosts security check passed\n",
491                   debug_prefix_time(NULL)));
492         amfree(remotehost);
493         amfree(localuser);
494         amfree(remoteuser);
495         return 1;
496     }
497
498     *errstr = vstralloc("[",
499                         "access as ", localuser, " not allowed",
500                         " from ", remoteuser, "@", remotehost,
501                         "] amandahostsauth failed", NULL);
502     dbprintf(("%s: check failed: %s\n", debug_prefix_time(NULL), *errstr));
503
504     amfree(remotehost);
505     amfree(localuser);
506     amfree(remoteuser);
507     return 0;
508
509 #endif                                                          /* } */
510 }
511
512 #else   /* ! BSD_SECURITY */                                    /* } { */
513
514 int bsd_security_ok(addr, str, cksum, errstr)
515 struct sockaddr_in *addr;
516 char *str;
517 uint32_t cksum;
518 char **errstr;
519 {
520 #if defined(SHOW_SECURITY_DETAIL)                               /* { */
521     dbprintf(("You configured Amanda using --without-bsd-security, so it\n"));
522     dbprintf(("will let anyone on the Internet connect and do dumps of\n"));
523     dbprintf(("your system unless you have some other kind of protection,\n"));
524     dbprintf(("such as a firewall or TCP wrappers.\n"));
525 #endif                                                          /* } */
526     return 1;
527 }
528
529 #endif /* ! BSD_SECURITY */                                     /* } */
530
531 #if defined(TEST)                                               /* { */
532
533 int
534 main (argc, argv)
535 {
536     char *remoteuser;
537     char *remotehost;
538     struct hostent *hp;
539     struct sockaddr_in fake;
540     char *str;
541     char *errstr;
542     int r;
543     struct passwd       *pwent;
544
545     /*
546      * The following is stolen from amandad to emulate what it would
547      * do on startup.
548      */
549     if(client_uid == (uid_t) -1 && (pwent = getpwnam(CLIENT_LOGIN)) != NULL) {
550         client_uid = pwent->pw_uid;
551         client_gid = pwent->pw_gid;
552         endpwent();
553     }
554 #ifdef FORCE_USERID
555
556     /* we'd rather not run as root */
557     if(geteuid() == 0) {
558 #ifdef KRB4_SECURITY
559         if(client_uid == (uid_t) -1) {
560             error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
561         }
562
563         /*
564          * if we're using kerberos security, we'll need to be root in
565          * order to get at the machine's srvtab entry, so we hang on to
566          * some root privledges for now.  We give them up entirely later.
567          */
568         setegid(client_gid);
569         seteuid(client_uid);
570 #else
571         initgroups(CLIENT_LOGIN, client_gid);
572         setgid(client_gid);
573         setuid(client_uid);
574 #endif  /* KRB4_SECURITY */
575     }
576 #endif  /* FORCE_USERID */
577
578     fputs("Remote user: ", stdout);
579     fflush(stdout);
580     if ((remoteuser = agets(stdin)) == NULL) {
581         return 0;
582     }
583     str = stralloc2("USER ", remoteuser);
584
585     fputs("Remote host: ", stdout);
586     fflush(stdout);
587     if ((remotehost = agets(stdin)) == NULL) {
588         return 0;
589     }
590
591     set_pname("security");
592     startclock();
593
594     if ((hp = gethostbyname(remotehost)) == NULL) {
595         dbprintf(("%s: cannot look up remote host %s\n",
596                   debug_prefix_time(NULL), remotehost));
597         return 1;
598     }
599     memcpy((char *)&fake.sin_addr, (char *)hp->h_addr, sizeof(hp->h_addr));
600     fake.sin_port = htons(IPPORT_RESERVED - 1);
601
602     if ((r = bsd_security_ok(&fake, str, 0, &errstr)) == 0) {
603         dbprintf(("%s: security check of %s@%s failed\n",
604                   debug_prefix_time(NULL), remoteuser, remotehost));
605         dbprintf(("%s: %s\n", debug_prefix(NULL), errstr));
606     } else {
607         dbprintf(("%s: security check of %s@%s passed\n",
608                   debug_prefix_time(NULL), remoteuser, remotehost));
609     }
610     return r;
611 }
612
613 #endif                                                          /* } */