chiark / gitweb /
Set close-on-exec flag for UDP socket.
[become] / src / check.c
1 /* -*-c-*-
2  *
3  * $Id: check.c,v 1.9 1998/06/19 13:48:16 mdw Exp $
4  *
5  * Check validity of requests
6  *
7  * (c) 1998 EBI
8  */
9
10 /*----- Licensing notice --------------------------------------------------*
11  *
12  * This file is part of `become'
13  *
14  * `Become' is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * `Become' is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with `become'; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------*
30  *
31  * $Log: check.c,v $
32  * Revision 1.9  1998/06/19 13:48:16  mdw
33  * Set close-on-exec flag for UDP socket.
34  *
35  * Revision 1.8  1998/06/18 15:10:44  mdw
36  * SECURITY HOLE: the file descriptor for the secret key was left open and
37  * inherited by the target process.  This is now fixed.  Also set
38  * close-on-exec flags on key file, close config file carefully, and close
39  * UDP socket after receiving reply from server.
40  *
41  * Revision 1.7  1998/04/23 13:22:08  mdw
42  * Support no-network configuration option, and new interface to
43  * configuration file parser.
44  *
45  * Revision 1.6  1998/01/12 16:45:47  mdw
46  * Fix copyright date.
47  *
48  * Revision 1.5  1997/09/26 09:14:58  mdw
49  * Merged blowfish branch into trunk.
50  *
51  * Revision 1.4.2.1  1997/09/26 09:08:01  mdw
52  * Use the Blowfish encryption algorithm instead of IDEA.  This is partly
53  * because I prefer Blowfish (without any particularly strong evidence) but
54  * mainly because IDEA is patented and Blowfish isn't.
55  *
56  * Revision 1.4  1997/08/07 09:52:05  mdw
57  * (Log entry for previous version is bogus.)  Added support for multiple
58  * servers.
59  *
60  * Revision 1.2  1997/08/04 10:24:20  mdw
61  * Sources placed under CVS control.
62  *
63  * Revision 1.1  1997/07/21  13:47:53  mdw
64  * Initial revision
65  *
66  */
67
68 /*----- Header files ------------------------------------------------------*/
69
70 /* --- ANSI headers --- */
71
72 #include <ctype.h>
73 #include <errno.h>
74 #include <stdio.h>
75 #include <stdlib.h>
76 #include <string.h>
77 #include <time.h>
78
79 /* --- Unix headers --- */
80
81 #include <sys/time.h>
82 #include <sys/types.h>
83 #include <sys/socket.h>
84
85 #include <netinet/in.h>
86
87 #include <arpa/inet.h>
88
89 #include <fcntl.h>
90 #include <netdb.h>
91 #include <unistd.h>
92
93 /* --- Local headers --- */
94
95 #include "become.h"
96 #include "blowfish.h"
97 #include "config.h"
98 #include "crypt.h"
99 #include "lexer.h"
100 #include "name.h"
101 #include "netg.h"
102 #include "rule.h"
103 #include "parser.h"
104 #include "tx.h"
105 #include "userdb.h"
106 #include "utils.h"
107
108 /*----- Client-end network support ----------------------------------------*/
109
110 #ifndef NONETWORK
111
112 /* --- @check__send@ --- *
113  *
114  * Arguments:   @unsigned char *crq@ = pointer to encrypted request
115  *              @int fd@ = socket to send from
116  *              @struct sockaddr_in *serv@ = pointer to table of servers
117  *              @size_t n_serv@ = number of servers
118  *
119  * Returns:     ---
120  *
121  * Use:         Sends the request packet to the list of servers.  If the
122  *              message couldn't be sent to any of them, an error is
123  *              reported.
124  */
125
126 static void check__send(unsigned char *crq, int fd,
127                         struct sockaddr_in *serv, size_t n_serv)
128 {
129   size_t i;
130   int ok = 0;
131   int err = 0;
132
133   for (i = 0; i < n_serv; i++) {
134     if (sendto(fd, (char *)crq, crq_size, 0,
135                (struct sockaddr *)(serv + i), sizeof(serv[i])) < 0) {
136       T( trace(TRACE_CLIENT, "client: send to %s failed: %s",
137                inet_ntoa(serv[i].sin_addr), strerror(errno)); )
138       err = errno;
139     } else
140       ok = 1;
141   }
142
143   if (!ok)
144     die("couldn't send request to server: %s", strerror(err));
145 }
146
147 /* --- @check__ask@ --- *
148  *
149  * Arguments:   @request *rq@ = pointer to request buffer
150  *              @struct sockaddr_in *serv@ = pointer to table of servers
151  *              @size_t n_serv@ = number of servers
152  *
153  * Returns:     Nonzero if OK, zero if forbidden
154  *
155  * Use:         Contacts a number of servers to decide whether the request
156  *              is OK.
157  */
158
159 static int check__ask(request *rq, struct sockaddr_in *serv, size_t n_serv)
160 {
161   int fd;
162   unsigned char crq[crq_size];
163   unsigned char sk[BLOWFISH_KEYSIZE];
164   time_t t;
165   pid_t pid;
166
167   /* --- First, build the encrypted request packet --- */
168
169   {
170     unsigned char k[BLOWFISH_KEYSIZE];
171     FILE *fp;
172
173     /* --- Read in the encryption key --- */
174
175     if ((fp = fopen(file_KEY, "r")) == 0) {
176       die("couldn't open key file `%s': %s", file_KEY,
177           strerror(errno));
178     }
179     if (fcntl(fileno(fp), F_SETFD, 1) < 0) {
180       die("couldn't set close-on-exec on key file `%s': %s", file_KEY,
181           strerror(errno));
182     }
183     tx_getBits(k, 128, fp);
184     fclose(fp);
185
186     /* --- Now build a request packet --- */
187
188     t = time(0);
189     pid = getpid();
190     crypt_packRequest(rq, crq, t, pid, k, sk);
191     burn(k);
192     T( trace(TRACE_CLIENT, "client: encrypted request packet"); )
193   }
194
195   /* --- Create my socket --- */
196
197   {
198     struct sockaddr_in sin;
199
200     if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
201       die("couldn't create socket: %s", strerror(errno));
202     if (fcntl(fd, F_SETFD, 1) < 0)
203       die("couldn't set close-on-exec flag for socket: %s", strerror(errno));
204
205     /* --- Bind myself to some address --- */
206
207     sin.sin_family = AF_INET;
208     sin.sin_port = 0;
209     sin.sin_addr.s_addr = htonl(INADDR_ANY);
210
211     if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
212       die("couldn't bind socket to address: %s", strerror(errno));
213   }
214
215   /* --- Now wait for a reply --- */
216
217   {
218     fd_set fds;
219     struct timeval start, now, tv;
220     int ind;
221     size_t i;
222
223     /* --- State table for waiting for replies --- *
224      *
225      * For each number, send off the request to our servers, and wait for
226      * that many seconds to have elapsed since we started.  If the number is
227      * %$-1$% then it's time to give up.
228      */
229
230     static int tbl[] = { 0, 5, 10, 20, -1 };
231
232     /* --- Find out when we are --- */
233
234     gettimeofday(&start, 0);
235     ind = 0;
236
237     /* --- Now loop until everything's done --- */
238
239     for (;;) {
240       gettimeofday(&now, 0);
241
242       /* --- If the current timer has expired, find one that hasn't --- *
243        *
244        * Also resend the request after I've found a timer which is still
245        * extant.  If there aren't any, report an error.
246        */
247
248       if (now.tv_sec >= start.tv_sec + tbl[ind] &&
249           now.tv_usec >= start.tv_usec) {
250         do {
251           ind++;
252           if (tbl[ind] < 0)
253             die("no reply from servers");
254         } while (now.tv_sec >= start.tv_sec + tbl[ind] &&
255                  now.tv_usec >= start.tv_usec);
256         check__send(crq, fd, serv, n_serv);
257         T( trace(TRACE_CLIENT, "client: send request to servers"); )
258       }
259
260       /* --- Now wait for a packet to arrive --- */
261
262       if (now.tv_usec > start.tv_usec) {
263         now.tv_usec -= 1000000;
264         now.tv_sec += 1;
265       }
266       tv.tv_sec = start.tv_sec + tbl[ind] - now.tv_sec;
267       tv.tv_usec = start.tv_usec - now.tv_usec;
268
269       /* --- Sort out file descriptors to watch --- */
270
271       FD_ZERO(&fds);
272       FD_SET(fd, &fds);
273
274       /* --- Wait for them --- */
275
276       i = select(FD_SETSIZE, &fds, 0, 0, &tv);
277       if (i == 0 || (i < 0 && errno == EINTR))
278         continue;
279       if (i < 0)
280         die("error waiting for reply: %s", strerror(errno));
281
282       /* --- A reply should be waiting now --- */
283
284       {
285         struct sockaddr_in sin;
286         int slen = sizeof(sin);
287         unsigned char buff[256];
288         int answer;
289
290         /* --- Read the reply data --- */
291
292         if (recvfrom(fd, (char *)buff, sizeof(buff), 0,
293                      (struct sockaddr *)&sin, &slen) < 0)
294           die("error reading server's reply: %s", strerror(errno));
295
296         IF_TRACING(TRACE_CLIENT, {
297           struct hostent *h = gethostbyaddr((char *)&sin.sin_addr,
298                                             sizeof(sin.sin_addr), AF_INET);
299           trace(TRACE_CLIENT, "client: reply received from %s port %i",
300                 h ? h->h_name : inet_ntoa(sin.sin_addr),
301                 ntohs(sin.sin_port));
302         })
303
304         /* --- Verify the sender --- *
305          *
306          * This is more to avoid confusion than for security: an active
307          * attacker is quite capable of forging the source address.  We rely
308          * on the checksum in the reply packet for authentication.
309          */
310
311         for (i = 0; i < n_serv; i++) {
312           if (sin.sin_addr.s_addr == serv[i].sin_addr.s_addr &&
313               sin.sin_port == serv[i].sin_port)
314             break;
315         }
316         if (i >= n_serv) {
317           T( trace(TRACE_CLIENT, "client: reply from unknown host"); )
318           continue;
319         }
320
321         /* --- Unpack and verify the response --- */
322
323         answer = crypt_unpackReply(buff, sk, t, pid);
324         if (answer < 0) {
325           T( trace(TRACE_CLIENT,
326                    "client: invalid or corrupt reply packet"); )
327           continue;
328         }
329         close(fd);
330         return (answer);
331       }
332     }
333   }
334
335   die("internal error: can't get here in check__ask");
336   return (0);
337 }
338
339 /* --- @check__client@ --- *
340  *
341  * Arguments:   @request *rq@ = pointer to a request block
342  *              @FILE *fp@ = file containing server configuration
343  *
344  * Returns:     Nonzero if OK, zero if forbidden
345  *
346  * Use:         Asks one or several servers whether a request is acceptable.
347  */
348
349 int check__client(request *rq, FILE *fp)
350 {
351   /* --- Format of the file --- *
352    *
353    * The `servers' file contains entries of the form
354    *
355    *    %%\syntax{<host> [`:' <port>]}%%
356    *
357    * separates by whitespace.  I build them all into an array of socket
358    * addresses and pass the whole lot to another function.
359    */
360
361   struct sockaddr_in *serv;             /* Array of servers */
362   size_t n_serv, max_serv;              /* Number and maximum number */
363
364   /* --- Initialise the server array --- */
365
366   T( trace(TRACE_CLIENT, "client: reading server definitions"); )
367   n_serv = 0; max_serv = 4;             /* Four seems reasonable */
368   serv = xmalloc(sizeof(*serv) * max_serv);
369
370   /* --- Start reading the file --- */
371
372   {
373     char buff[256], *p, *l;             /* A buffer and pointers for it */
374     int port;                           /* Default port for servers */
375     int state;                          /* Current parser state */
376     struct in_addr t_host;              /* Temp place for an address*/
377     int t_port;                         /* Temp place for a port */
378     int ch;                             /* The current character */
379
380     /* --- Parser states --- */
381
382     enum {
383       st_start,                         /* Waiting to begin */
384       st_host,                          /* Reading a new hostname */
385       st_colon,                         /* Expecting a colon, maybe */
386       st_preport,                       /* Waiting before reading port */
387       st_port,                          /* Reading a port number */
388       st_commit,                        /* Commit a newly read server */
389       st_done                           /* Finished reading the file */
390     };
391
392     /* --- Find a default port --- */
393
394     {
395       struct servent *s = getservbyname(quis(), "udp");
396       port = (s ? s->s_port : -1);
397     }
398
399     /* --- Initialise for scanning the file --- */
400
401     state = st_host;
402     p = buff;
403     l = buff + sizeof(buff);
404     t_port = port;
405     ch = getc(fp);
406
407     /* --- Go for it --- */
408
409     while (state != st_done) {
410       switch (state) {
411
412         /* --- Initial whitespace before hostname --- */
413
414         case st_start:
415           if (ch == EOF)
416             state = st_done;
417           else if (isspace((unsigned char)ch))
418             ch = getc(fp);
419           else
420             state = st_host;
421           break;
422
423         /* --- Read a host name --- */
424
425         case st_host:
426           if (p == l)
427             die("string too long in `" file_SERVER "'");
428           if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') {
429             *p++ = ch;
430             ch = getc(fp);
431           } else {
432             struct hostent *h;
433
434             *p++ = 0;
435             if ((h = gethostbyname(buff)) == 0)
436               die("unknown host `%s' in `" file_SERVER "'", buff);
437             memcpy(&t_host, h->h_addr, sizeof(t_host));
438             state = st_colon;
439           }
440           break;
441
442         /* --- Waiting for a colon coming up --- */
443
444         case st_colon:
445           if (ch == EOF)
446             state = st_commit;
447           else if (isspace((unsigned char)ch))
448             ch = getc(fp);
449           else if (ch == ':') {
450             state = st_preport;
451             ch = getc(fp);
452           }
453           else
454             state = st_commit;
455           break;
456
457         /* --- Clearing whitespace before a port number --- */
458
459         case st_preport:
460           if (ch == EOF)
461             state = st_commit;
462           else if (isspace((unsigned char)ch))
463             ch = getc(fp);
464           else {
465             state = st_port;
466             p = buff;
467           }
468           break;
469
470         /* --- Read a port number --- */
471
472         case st_port:
473           if (p == l)
474             die("string too long in `" file_SERVER "'");
475           if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') {
476             *p++ = ch;
477             ch = getc(fp);
478           } else {
479             struct servent *s;
480
481             *p++ = 0;
482             s = getservbyname(buff, "udp");
483             if (!s && isdigit((unsigned char)buff[0]))
484               t_port = htons(atoi(buff));
485             else if (!s)
486               die("unknown service `%s' in `" file_SERVER "'", buff);
487             else
488               t_port = s->s_port;
489             state = st_commit;
490           }
491           break;
492
493         /* --- A server has been read successfully --- */
494
495         case st_commit:
496           if (n_serv == max_serv) {
497             max_serv *= 2;
498             serv = xrealloc(serv, max_serv * sizeof(*serv));
499           }
500           serv[n_serv].sin_family = AF_INET;
501           serv[n_serv].sin_addr = t_host;
502           serv[n_serv].sin_port = t_port;
503           n_serv++;
504           state = st_start;
505           p = buff;
506           t_port = port;
507           break;
508
509         /* --- A safety net for a broken parser --- */
510
511         default:
512           die("internal error: can't get here in check__client");
513           break;          
514       }
515     }
516   }
517
518   fclose(fp);
519
520   /* --- Now start sending requests --- */
521
522   if (!n_serv)
523     die("no servers specified in `" file_SERVER "'");
524
525   IF_TRACING(TRACE_CLIENT, {
526     size_t i;
527
528     for (i = 0; i < n_serv; i++) {
529       trace(TRACE_CLIENT, "client: server %s port %i",
530             inet_ntoa(serv[i].sin_addr), ntohs(serv[i].sin_port));
531     }
532   })
533   return (check__ask(rq, serv, n_serv));
534 }
535
536 #endif
537
538 /*----- Main checking function --------------------------------------------*/
539
540 /* --- @check@ --- *
541  *
542  * Arguments:   @request *rq@ = pointer to request buffer
543  *
544  * Returns:     Nonzero if OK, zero if forbidden
545  *
546  * Use:         Checks to see if the request is acceptable.
547  */
548
549 int check(request *rq)
550 {
551   FILE *fp;
552
553   /* --- Check if we need to talk to a server --- */
554
555 #ifndef NONETWORK
556   if ((fp = fopen(file_SERVER, "r")) != 0)
557     return (check__client(rq, fp));
558 #endif
559
560   /* --- Otherwise do this all the old-fashioned way --- */
561
562   if ((fp = fopen(file_RULES, "r")) == 0) {
563     die("couldn't read configuration file `%s': %s",
564         file_RULES, strerror(errno));
565   }
566
567   userdb_init();
568   userdb_local();
569   userdb_yp();
570   netg_init();
571   name_init();
572   rule_init();
573   lexer_scan(fp);
574   parse();
575   fclose(fp);
576
577   return (rule_check(rq));
578 }
579
580 /*----- That's all, folks -------------------------------------------------*/