/* -*-c-*-
*
- * $Id: check.c,v 1.1 1997/07/21 13:47:53 mdw Exp $
+ * $Id: check.c,v 1.7 1998/04/23 13:22:08 mdw Exp $
*
* Check validity of requests
*
- * (c) 1997 EBI
+ * (c) 1998 EBI
*/
-/*----- Licencing notice --------------------------------------------------*
+/*----- Licensing notice --------------------------------------------------*
*
* This file is part of `become'
*
* 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.7 1998/04/23 13:22:08 mdw
+ * Support no-network configuration option, and new interface to
+ * configuration file parser.
+ *
+ * Revision 1.6 1998/01/12 16:45:47 mdw
+ * Fix copyright date.
+ *
+ * Revision 1.5 1997/09/26 09:14:58 mdw
+ * Merged blowfish branch into trunk.
+ *
+ * Revision 1.4.2.1 1997/09/26 09:08:01 mdw
+ * Use the Blowfish encryption algorithm instead of IDEA. This is partly
+ * because I prefer Blowfish (without any particularly strong evidence) but
+ * mainly because IDEA is patented and Blowfish isn't.
+ *
+ * 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
*
*/
/* --- Local headers --- */
#include "become.h"
+#include "blowfish.h"
#include "config.h"
#include "crypt.h"
-#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 ---------------------------------------------------------*/
+/*----- Client-end network support ----------------------------------------*/
-/* --- @check__client@ --- *
+#ifndef NONETWORK
+
+/* --- @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];
+ unsigned char sk[BLOWFISH_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[BLOWFISH_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);
- }
+ fd_set fds;
+ struct timeval start, now, tv;
+ int ind;
+ size_t i;
- /* --- Now build a request packet --- */
+ /* --- 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.
+ */
- t = time(0);
- pid = getpid();
- crypt_packRequest(rq, crq, t, pid, k, sk);
+ static int tbl[] = { 0, 5, 10, 20, -1 };
- /* --- Send the packet to the server --- */
+ /* --- Find out when we are --- */
- 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);
+ gettimeofday(&start, 0);
+ ind = 0;
- /* --- Now wait for a reply --- */
-
- {
- fd_set fds;
- struct timeval when, now, tv;
-
- 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 --- */
/* --- 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));
(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{<host> [`:' <port>]}%%
+ *
+ * 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));
+}
+
+#endif
+
+/*----- Main checking function --------------------------------------------*/
+
/* --- @check@ --- *
*
* Arguments: @request *rq@ = pointer to request buffer
/* --- 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));
- }
+#ifndef NONETWORK
+ if ((fp = fopen(file_SERVER, "r")) != 0)
+ return (check__client(rq, fp));
+#endif
- /* --- 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);
- yyparse();
+ parse();
return (rule_check(rq));
}