X-Git-Url: https://git.distorted.org.uk/~mdw/become/blobdiff_plain/c4f2d992e4a0fc068281376d89ec38de56dc2f58..27d7bfc29d998f41d4d9230ffea96f7e405e563a:/src/check.c diff --git a/src/check.c b/src/check.c index 9ed0b6f..a9f8ec4 100644 --- a/src/check.c +++ b/src/check.c @@ -1,13 +1,13 @@ /* -*-c-*- * - * $Id: check.c,v 1.1 1997/07/21 13:47:53 mdw Exp $ + * $Id: check.c,v 1.4 1997/08/07 09:52:05 mdw Exp $ * * Check validity of requests * * (c) 1997 EBI */ -/*----- Licencing notice --------------------------------------------------* +/*----- Licensing notice --------------------------------------------------* * * This file is part of `become' * @@ -22,14 +22,21 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with `become'; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * along with `become'; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*----- Revision history --------------------------------------------------* * * $Log: check.c,v $ - * Revision 1.1 1997/07/21 13:47:53 mdw + * Revision 1.4 1997/08/07 09:52:05 mdw + * (Log entry for previous version is bogus.) Added support for multiple + * servers. + * + * Revision 1.2 1997/08/04 10:24:20 mdw + * Sources placed under CVS control. + * + * Revision 1.1 1997/07/21 13:47:53 mdw * Initial revision * */ @@ -66,117 +73,164 @@ #include "idea.h" #include "lexer.h" #include "name.h" +#include "netg.h" #include "rule.h" #include "parser.h" #include "tx.h" +#include "userdb.h" #include "utils.h" /*----- Main code ---------------------------------------------------------*/ -/* --- @check__client@ --- * +/* --- @check__send@ --- * + * + * Arguments: @unsigned char *crq@ = pointer to encrypted request + * @int fd@ = socket to send from + * @struct sockaddr_in *serv@ = pointer to table of servers + * @size_t n_serv@ = number of servers + * + * Returns: --- + * + * Use: Sends the request packet to the list of servers. If the + * message couldn't be sent to any of them, an error is + * reported. + */ + +static void check__send(unsigned char *crq, int fd, + struct sockaddr_in *serv, size_t n_serv) +{ + size_t i; + int ok = 0; + int err = 0; + + for (i = 0; i < n_serv; i++) { + if (sendto(fd, (char *)crq, crq_size, 0, + (struct sockaddr *)(serv + i), sizeof(serv[i])) < 0) { + T( trace(TRACE_CLIENT, "client: send to %s failed: %s", + inet_ntoa(serv[i].sin_addr), strerror(errno)); ) + err = errno; + } else + ok = 1; + } + + if (!ok) + die("couldn't send request to server: %s", strerror(err)); +} + +/* --- @check__ask@ --- * * * Arguments: @request *rq@ = pointer to request buffer - * @const char *serv@ = pointer to the server - * @int port@ = port number to use + * @struct sockaddr_in *serv@ = pointer to table of servers + * @size_t n_serv@ = number of servers * * Returns: Nonzero if OK, zero if forbidden * - * Use: Contacts a server to decide whether the request is OK. + * Use: Contacts a number of servers to decide whether the request + * is OK. */ -static int check__client(request *rq, const char *serv, int port) +static int check__ask(request *rq, struct sockaddr_in *serv, size_t n_serv) { int fd; - struct sockaddr_in ssin; unsigned char crq[crq_size]; - unsigned char k[IDEA_KEYSIZE]; unsigned char sk[IDEA_KEYSIZE]; time_t t; pid_t pid; - /* --- Create my socket --- */ + /* --- First, build the encrypted request packet --- */ - if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) - die("couldn't create socket: %s", strerror(errno)); + { + unsigned char k[IDEA_KEYSIZE]; + FILE *fp; - /* --- Bind myself to some address --- */ + /* --- Read in the encryption key --- */ - { - struct sockaddr_in sin; + if ((fp = fopen(file_KEY, "r")) == 0) { + die("couldn't open key file `%s': %s", file_KEY, + strerror(errno)); + } + tx_getBits(k, 128, fp); - sin.sin_family = AF_INET; - sin.sin_port = 0; - sin.sin_addr.s_addr = htonl(INADDR_ANY); + /* --- Now build a request packet --- */ - if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) - die("couldn't bind socket to address: %s", strerror(errno)); + t = time(0); + pid = getpid(); + crypt_packRequest(rq, crq, t, pid, k, sk); + burn(k); + T( trace(TRACE_CLIENT, "client: encrypted request packet"); ) } - /* --- Find the server's address --- */ + /* --- Create my socket --- */ { - struct hostent *he; + struct sockaddr_in sin; - /* --- Resolve the server's name --- */ + if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) + die("couldn't create socket: %s", strerror(errno)); - if ((he = gethostbyname(serv)) == 0) - die("couldn't find server host `%s'", serv); + /* --- Bind myself to some address --- */ - /* --- Build the address block --- */ + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = htonl(INADDR_ANY); - ssin.sin_family = AF_INET; - ssin.sin_port = htons(port); - memcpy(&ssin.sin_addr, he->h_addr, sizeof(struct in_addr)); + if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) + die("couldn't bind socket to address: %s", strerror(errno)); } - /* --- Read in the encryption key --- */ + /* --- Now wait for a reply --- */ { - FILE *fp; - - if ((fp = fopen(file_KEY, "r")) == 0) { - die("couldn't open key file `%s': %s", file_KEY, - strerror(errno)); - } - tx_getBits(k, 128, fp); - } - - /* --- Now build a request packet --- */ - - t = time(0); - pid = getpid(); - crypt_packRequest(rq, crq, t, pid, k, sk); + fd_set fds; + struct timeval start, now, tv; + int ind; + size_t i; - /* --- Send the packet to the server --- */ + /* --- State table for waiting for replies --- * + * + * For each number, send off the request to our servers, and wait for + * that many seconds to have elapsed since we started. If the number is + * %$-1$% then it's time to give up. + */ - if (sendto(fd, (char *)crq, sizeof(crq), 0, - (struct sockaddr *)&ssin, sizeof(ssin)) < 0) { - burn(k); - die("couldn't send request to server: %s", strerror(errno)); - } - burn(k); + static int tbl[] = { 0, 5, 10, 20, -1 }; - /* --- Now wait for a reply --- */ + /* --- Find out when we are --- */ - { - fd_set fds; - struct timeval when, now, tv; + gettimeofday(&start, 0); + ind = 0; - gettimeofday(&when, 0); - when.tv_sec += 10; + /* --- Now loop until everything's done --- */ for (;;) { - int i; + gettimeofday(&now, 0); + + /* --- If the current timer has expired, find one that hasn't --- * + * + * Also resend the request after I've found a timer which is still + * extant. If there aren't any, report an error. + */ + + if (now.tv_sec >= start.tv_sec + tbl[ind] && + now.tv_usec >= start.tv_usec) { + do { + ind++; + if (tbl[ind] < 0) + die("no reply from servers"); + } while (now.tv_sec >= start.tv_sec + tbl[ind] && + now.tv_usec >= start.tv_usec); + check__send(crq, fd, serv, n_serv); + T( trace(TRACE_CLIENT, "client: send request to servers"); ) + } - /* --- Sort out when to return --- */ + /* --- Now wait for a packet to arrive --- */ - gettimeofday(&now, 0); - if (now.tv_usec > when.tv_usec) { + if (now.tv_usec > start.tv_usec) { now.tv_usec -= 1000000; now.tv_sec += 1; } - tv.tv_sec = when.tv_sec - now.tv_sec; - tv.tv_usec = when.tv_usec - now.tv_usec; + tv.tv_sec = start.tv_sec + tbl[ind] - now.tv_sec; + tv.tv_usec = start.tv_usec - now.tv_usec; /* --- Sort out file descriptors to watch --- */ @@ -186,8 +240,8 @@ static int check__client(request *rq, const char *serv, int port) /* --- Wait for them --- */ i = select(FD_SETSIZE, &fds, 0, 0, &tv); - if (i == 0) - die("no answer from server"); + if (i == 0 || (i < 0 && errno == EINTR)) + continue; if (i < 0) die("error waiting for reply: %s", strerror(errno)); @@ -205,26 +259,245 @@ static int check__client(request *rq, const char *serv, int port) (struct sockaddr *)&sin, &slen) < 0) die("error reading server's reply: %s", strerror(errno)); - /* --- Verify the sender --- */ - - if (sin.sin_addr.s_addr != ssin.sin_addr.s_addr || - sin.sin_port != ssin.sin_port) + IF_TRACING(TRACE_CLIENT, { + struct hostent *h = gethostbyaddr((char *)&sin.sin_addr, + sizeof(sin.sin_addr), AF_INET); + trace(TRACE_CLIENT, "client: reply received from %s port %i", + h ? h->h_name : inet_ntoa(sin.sin_addr), + ntohs(sin.sin_port)); + }) + + /* --- Verify the sender --- * + * + * This is more to avoid confusion than for security: an active + * attacker is quite capable of forging the source address. We rely + * on the checksum in the reply packet for authentication. + */ + + for (i = 0; i < n_serv; i++) { + if (sin.sin_addr.s_addr == serv[i].sin_addr.s_addr && + sin.sin_port == serv[i].sin_port) + break; + } + if (i >= n_serv) { + T( trace(TRACE_CLIENT, "client: reply from unknown host"); ) continue; + } /* --- Unpack and verify the response --- */ answer = crypt_unpackReply(buff, sk, t, pid); - if (answer < 0) + if (answer < 0) { + T( trace(TRACE_CLIENT, + "client: invalid or corrupt reply packet"); ) continue; + } return (answer); } } } - die("internal error: can't get here in check_client"); + die("internal error: can't get here in check__ask"); return (0); } +/* --- @check__client@ --- * + * + * Arguments: @request *rq@ = pointer to a request block + * @FILE *fp@ = file containing server configuration + * + * Returns: Nonzero if OK, zero if forbidden + * + * Use: Asks one or several servers whether a request is acceptable. + */ + +int check__client(request *rq, FILE *fp) +{ + /* --- Format of the file --- * + * + * The `servers' file contains entries of the form + * + * %%\syntax{ [`:' ]}%% + * + * separates by whitespace. I build them all into an array of socket + * addresses and pass the whole lot to another function. + */ + + struct sockaddr_in *serv; /* Array of servers */ + size_t n_serv, max_serv; /* Number and maximum number */ + + /* --- Initialise the server array --- */ + + T( trace(TRACE_CLIENT, "client: reading server definitions"); ) + n_serv = 0; max_serv = 4; /* Four seems reasonable */ + serv = xmalloc(sizeof(*serv) * max_serv); + + /* --- Start reading the file --- */ + + { + char buff[256], *p, *l; /* A buffer and pointers for it */ + int port; /* Default port for servers */ + int state; /* Current parser state */ + struct in_addr t_host; /* Temp place for an address*/ + int t_port; /* Temp place for a port */ + int ch; /* The current character */ + + /* --- Parser states --- */ + + enum { + st_start, /* Waiting to begin */ + st_host, /* Reading a new hostname */ + st_colon, /* Expecting a colon, maybe */ + st_preport, /* Waiting before reading port */ + st_port, /* Reading a port number */ + st_commit, /* Commit a newly read server */ + st_done /* Finished reading the file */ + }; + + /* --- Find a default port --- */ + + { + struct servent *s = getservbyname(quis(), "udp"); + port = (s ? s->s_port : -1); + } + + /* --- Initialise for scanning the file --- */ + + state = st_host; + p = buff; + l = buff + sizeof(buff); + t_port = port; + ch = getc(fp); + + /* --- Go for it --- */ + + while (state != st_done) { + switch (state) { + + /* --- Initial whitespace before hostname --- */ + + case st_start: + if (ch == EOF) + state = st_done; + else if (isspace((unsigned char)ch)) + ch = getc(fp); + else + state = st_host; + break; + + /* --- Read a host name --- */ + + case st_host: + if (p == l) + die("string too long in `" file_SERVER "'"); + if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') { + *p++ = ch; + ch = getc(fp); + } else { + struct hostent *h; + + *p++ = 0; + if ((h = gethostbyname(buff)) == 0) + die("unknown host `%s' in `" file_SERVER "'", buff); + memcpy(&t_host, h->h_addr, sizeof(t_host)); + state = st_colon; + } + break; + + /* --- Waiting for a colon coming up --- */ + + case st_colon: + if (ch == EOF) + state = st_commit; + else if (isspace((unsigned char)ch)) + ch = getc(fp); + else if (ch == ':') { + state = st_preport; + ch = getc(fp); + } + else + state = st_commit; + break; + + /* --- Clearing whitespace before a port number --- */ + + case st_preport: + if (ch == EOF) + state = st_commit; + else if (isspace((unsigned char)ch)) + ch = getc(fp); + else { + state = st_port; + p = buff; + } + break; + + /* --- Read a port number --- */ + + case st_port: + if (p == l) + die("string too long in `" file_SERVER "'"); + if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') { + *p++ = ch; + ch = getc(fp); + } else { + struct servent *s; + + *p++ = 0; + s = getservbyname(buff, "udp"); + if (!s && isdigit((unsigned char)buff[0])) + t_port = htons(atoi(buff)); + else if (!s) + die("unknown service `%s' in `" file_SERVER "'", buff); + else + t_port = s->s_port; + state = st_commit; + } + break; + + /* --- A server has been read successfully --- */ + + case st_commit: + if (n_serv == max_serv) { + max_serv *= 2; + serv = xrealloc(serv, max_serv * sizeof(*serv)); + } + serv[n_serv].sin_family = AF_INET; + serv[n_serv].sin_addr = t_host; + serv[n_serv].sin_port = t_port; + n_serv++; + state = st_start; + p = buff; + t_port = port; + break; + + /* --- A safety net for a broken parser --- */ + + default: + die("internal error: can't get here in check__client"); + break; + } + } + } + + fclose(fp); + + /* --- Now start sending requests --- */ + + if (!n_serv) + die("no servers specified in `" file_SERVER "'"); + + IF_TRACING(TRACE_CLIENT, { + size_t i; + + for (i = 0; i < n_serv; i++) { + trace(TRACE_CLIENT, "client: server %s port %i", + inet_ntoa(serv[i].sin_addr), ntohs(serv[i].sin_port)); + } + }) + return (check__ask(rq, serv, n_serv)); +} + /* --- @check@ --- * * * Arguments: @request *rq@ = pointer to request buffer @@ -240,41 +513,20 @@ int check(request *rq) /* --- Check if we need to talk to a server --- */ - if ((fp = fopen(file_SERVER, "r")) != 0) { - char buff[64]; - int port; - int ch; - - if (fscanf(fp, " %63[^: \t] ", buff) < 1) - die("error in `%s'", file_SERVER); - ch = getc(fp); - if (ch == ':') { - char b[64]; - struct servent *se; - - fscanf(fp, "%s", b); - if ((se = getservbyname(b, 0)) != 0) - port = ntohs(se->s_port); - else if ((port = atoi(b)) == 0) - die("error in `%s'", file_SERVER); - } else { - struct servent *se; - - if ((se = getservbyname(quis(), "udp")) == 0) - die("no idea which port to use"); - port = ntohs(se->s_port); - } - fclose(fp); - return (check__client(rq, buff, port)); - } + if ((fp = fopen(file_SERVER, "r")) != 0) + return (check__client(rq, fp)); - /* --- Read the configuration in and go --- */ + /* --- Otherwise do this all the old-fashioned way --- */ if ((fp = fopen(file_RULES, "r")) == 0) { die("couldn't read configuration file `%s': %s", file_RULES, strerror(errno)); } + userdb_init(); + userdb_local(); + userdb_yp(); + netg_init(); name_init(); rule_init(); lexer_scan(fp);