/* -*-c-*-
*
- * $Id: check.c,v 1.7 1998/04/23 13:22:08 mdw Exp $
+ * $Id: check.c,v 1.12 2003/11/29 23:39:16 mdw Exp $
*
* Check validity of requests
*
/*----- Revision history --------------------------------------------------*
*
* $Log: check.c,v $
+ * Revision 1.12 2003/11/29 23:39:16 mdw
+ * Debianization.
+ *
+ * Revision 1.11 2003/10/12 00:14:55 mdw
+ * Major overhaul. Now uses DSA signatures rather than the bogus symmetric
+ * encrypt-and-hope thing. Integrated with mLib and Catacomb.
+ *
+ * Revision 1.10 1999/05/04 16:17:12 mdw
+ * Change to header file name for parser. See log for `parse.h' for
+ * details.
+ *
+ * Revision 1.9 1998/06/19 13:48:16 mdw
+ * Set close-on-exec flag for UDP socket.
+ *
+ * Revision 1.8 1998/06/18 15:10:44 mdw
+ * SECURITY HOLE: the file descriptor for the secret key was left open and
+ * inherited by the target process. This is now fixed. Also set
+ * close-on-exec flags on key file, close config file carefully, and close
+ * UDP socket after receiving reply from server.
+ *
* Revision 1.7 1998/04/23 13:22:08 mdw
* Support no-network configuration option, and new interface to
* configuration file parser.
#include <arpa/inet.h>
+#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
+/* --- mLib headers --- */
+
+#include <mLib/alloc.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sym.h>
+#include <mLib/trace.h>
+
+/* --- Catacomb headers --- */
+
+#include <catacomb/buf.h>
+#include <catacomb/dsa.h>
+#include <catacomb/key.h>
+#include <catacomb/mp.h>
+#include <catacomb/noise.h>
+#include <catacomb/rand.h>
+#include <catacomb/sha.h>
+
/* --- Local headers --- */
#include "become.h"
-#include "blowfish.h"
#include "config.h"
-#include "crypt.h"
#include "lexer.h"
#include "name.h"
#include "netg.h"
#include "rule.h"
-#include "parser.h"
-#include "tx.h"
+#include "parse.h"
#include "userdb.h"
-#include "utils.h"
/*----- Client-end network support ----------------------------------------*/
/* --- @check__send@ --- *
*
- * Arguments: @unsigned char *crq@ = pointer to encrypted request
+ * Arguments: @char *buf@ = pointer to encrypted request
+ * @size_t sz@ = size of request
* @int fd@ = socket to send from
* @struct sockaddr_in *serv@ = pointer to table of servers
* @size_t n_serv@ = number of servers
* reported.
*/
-static void check__send(unsigned char *crq, int fd,
+static void check__send(char *buf, size_t sz, int fd,
struct sockaddr_in *serv, size_t n_serv)
{
size_t i;
int err = 0;
for (i = 0; i < n_serv; i++) {
- if (sendto(fd, (char *)crq, crq_size, 0,
+ if (sendto(fd, buf, sz, 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)); )
}
if (!ok)
- die("couldn't send request to server: %s", strerror(err));
+ die(1, "couldn't send request to server: %s", strerror(err));
}
/* --- @check__ask@ --- *
static int check__ask(request *rq, struct sockaddr_in *serv, size_t n_serv)
{
+ static int tbl[] = { 0, 5, 10, 20, -1 };
+
+ char buff[2048], rbuff[2048];
+ size_t rqlen;
+ octet hmsg[SHA_HASHSZ];
+ octet h[SHA_HASHSZ];
+ key_packstruct kps[DSA_PUBFETCHSZ];
+ key_packdef *kp;
+ dsa_pub kpub;
+ buf b;
+ sha_ctx hc;
int fd;
- unsigned char crq[crq_size];
- unsigned char sk[BLOWFISH_KEYSIZE];
- time_t t;
- pid_t pid;
+ struct sockaddr_in sin;
+ socklen_t slen;
+ ssize_t sz;
+ int ans;
+ fd_set fds;
+ struct timeval start, now, tv;
+ mp *m, *r, *s;
+ key_file f;
+ key *k;
+ key_iter ki;
+ int ind;
+ size_t i;
- /* --- First, build the encrypted request packet --- */
+ /* --- Open the public keyring --- */
- {
- unsigned char k[BLOWFISH_KEYSIZE];
- FILE *fp;
+ if ((key_open(&f, file_PUBKEY, KOPEN_READ, key_moan, 0)) != 0)
+ die(1, "couldn't open public keyring");
+ kp = key_fetchinit(dsa_pubfetch, kps, &kpub);
- /* --- Read in the encryption key --- */
+ /* --- Build the request packet --- */
- 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);
- burn(k);
- T( trace(TRACE_CLIENT, "client: encrypted request packet"); )
- }
+ rand_noisesrc(RAND_GLOBAL, &noise_source);
+ rand_seed(RAND_GLOBAL, 160);
+ buf_init(&b, buff, sizeof(buff));
+ rand_get(RAND_GLOBAL, buf_get(&b, SHA_HASHSZ), SHA_HASHSZ);
+ buf_putu32(&b, rq->from);
+ buf_putu32(&b, rq->to);
+ buf_putu16(&b, strlen(rq->cmd));
+ buf_put(&b, rq->cmd, strlen(rq->cmd));
+ rqlen = BLEN(&b);
+ sha_init(&hc);
+ sha_hash(&hc, buff, rqlen);
+ sha_done(&hc, hmsg);
/* --- Create my socket --- */
- {
- struct sockaddr_in sin;
+ if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+ die(1, "couldn't create socket: %s", strerror(errno));
+ if (fcntl(fd, F_SETFD, 1) < 0)
+ die(1, "couldn't set close-on-exec flag for socket: %s", strerror(errno));
- if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
- die("couldn't create socket: %s", strerror(errno));
+ /* --- Bind myself to some address --- */
- /* --- Bind myself to some address --- */
+ sin.sin_family = AF_INET;
+ sin.sin_port = 0;
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
- sin.sin_family = AF_INET;
- sin.sin_port = 0;
- sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
+ die(1, "couldn't bind socket to address: %s", strerror(errno));
- if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
- die("couldn't bind socket to address: %s", strerror(errno));
- }
+ /* --- Find out when we are --- */
- /* --- Now wait for a reply --- */
+ gettimeofday(&start, 0);
+ ind = 0;
- {
- fd_set fds;
- struct timeval start, now, tv;
- int ind;
- size_t i;
+ /* --- Now loop until everything's done --- */
+
+ for (;;) {
+ gettimeofday(&now, 0);
- /* --- State table for waiting for replies --- *
+ /* --- If the current timer has expired, find one that hasn't --- *
*
- * 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.
+ * Also resend the request after I've found a timer which is still
+ * extant. If there aren't any, report an error.
*/
- static int tbl[] = { 0, 5, 10, 20, -1 };
+ if (now.tv_sec >= start.tv_sec + tbl[ind] &&
+ now.tv_usec >= start.tv_usec) {
+ do {
+ ind++;
+ if (tbl[ind] < 0)
+ die(1, "no reply from servers");
+ } while (now.tv_sec >= start.tv_sec + tbl[ind] &&
+ now.tv_usec >= start.tv_usec);
+ check__send(buff, rqlen, fd, serv, n_serv);
+ T( trace(TRACE_CLIENT, "client: send request to servers"); )
+ }
- /* --- Find out when we are --- */
+ /* --- Now wait for a packet to arrive --- */
- gettimeofday(&start, 0);
- ind = 0;
+ if (now.tv_usec > start.tv_usec) {
+ now.tv_usec -= 1000000;
+ now.tv_sec += 1;
+ }
+ tv.tv_sec = start.tv_sec + tbl[ind] - now.tv_sec;
+ tv.tv_usec = start.tv_usec - now.tv_usec;
- /* --- Now loop until everything's done --- */
+ /* --- Sort out file descriptors to watch --- */
- for (;;) {
- gettimeofday(&now, 0);
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
- /* --- 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.
- */
+ /* --- Wait for them --- */
- 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"); )
- }
+ i = select(FD_SETSIZE, &fds, 0, 0, &tv);
+ if (i == 0 || (i < 0 && errno == EINTR))
+ continue;
+ if (i < 0)
+ die(1, "error waiting for reply: %s", strerror(errno));
- /* --- Now wait for a packet to arrive --- */
+ /* --- Read the reply data --- */
- if (now.tv_usec > start.tv_usec) {
- now.tv_usec -= 1000000;
- now.tv_sec += 1;
- }
- 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 --- */
-
- FD_ZERO(&fds);
- FD_SET(fd, &fds);
-
- /* --- Wait for them --- */
-
- i = select(FD_SETSIZE, &fds, 0, 0, &tv);
- if (i == 0 || (i < 0 && errno == EINTR))
- continue;
- if (i < 0)
- die("error waiting for reply: %s", strerror(errno));
-
- /* --- A reply should be waiting now --- */
-
- {
- struct sockaddr_in sin;
- int slen = sizeof(sin);
- unsigned char buff[256];
- int answer;
-
- /* --- Read the reply data --- */
-
- if (recvfrom(fd, (char *)buff, sizeof(buff), 0,
- (struct sockaddr *)&sin, &slen) < 0)
- die("error reading server's reply: %s", strerror(errno));
-
- 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) {
- T( trace(TRACE_CLIENT,
- "client: invalid or corrupt reply packet"); )
- continue;
- }
- return (answer);
+ slen = sizeof(sin);
+ if ((sz = recvfrom(fd, (char *)rbuff, sizeof(rbuff), 0,
+ (struct sockaddr *)&sin, &slen)) < 0)
+ die(1, "error reading server's reply: %s", strerror(errno));
+
+ 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 signature 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 --- */
+
+ buf_init(&b, rbuff, sz);
+ if (buf_ensure(&b, sizeof(hmsg))) goto bad;
+ if (memcmp(BCUR(&b), hmsg, sizeof(hmsg)) != 0) goto bad;
+ BSTEP(&b, sizeof(hmsg));
+ if ((ans = buf_getbyte(&b)) < 0) goto bad;
+
+ sha_init(&hc);
+ sha_hash(&hc, BBASE(&b), BLEN(&b));
+ sha_done(&hc, h);
+ if ((r = buf_getmp(&b)) == 0 || (s = buf_getmp(&b)) == 0) goto bad;
+ m = mp_loadb(MP_NEW, h, sizeof(h));
+
+ key_mkiter(&ki, &f);
+ while ((k = key_next(&ki)) != 0) {
+ if (key_expired(k)) continue;
+ if (strcmp(k->type, "become-dsa") != 0) continue;
+ if (key_fetch(kp, k)) continue;
+ i = dsa_vrfy(&kpub.dp, kpub.y, m, r, s);
+ dsa_pubfree(&kpub);
+ if (i) {
+ key_fetchdone(kp);
+ key_close(&f);
+ close(fd);
+ mp_drop(m);
+ mp_drop(r);
+ mp_drop(s);
+ return (ans);
}
}
+
+ bad:
+ T( trace(TRACE_CLIENT,
+ "client: invalid or corrupt reply packet"); )
}
- die("internal error: can't get here in check__ask");
+ die(1, "internal error: can't get here in check__ask");
return (0);
}
{
struct servent *s = getservbyname(quis(), "udp");
- port = (s ? s->s_port : -1);
+ port = (s ? s->s_port : htons(SERVER_PORT));
}
/* --- Initialise for scanning the file --- */
case st_host:
if (p == l)
- die("string too long in `" file_SERVER "'");
+ die(1, "string too long in `" file_SERVER "'");
if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') {
*p++ = ch;
ch = getc(fp);
*p++ = 0;
if ((h = gethostbyname(buff)) == 0)
- die("unknown host `%s' in `" file_SERVER "'", buff);
+ die(1, "unknown host `%s' in `" file_SERVER "'", buff);
memcpy(&t_host, h->h_addr, sizeof(t_host));
state = st_colon;
}
case st_port:
if (p == l)
- die("string too long in `" file_SERVER "'");
+ die(1, "string too long in `" file_SERVER "'");
if (ch != EOF && !isspace((unsigned char)ch) && ch != ':') {
*p++ = ch;
ch = getc(fp);
if (!s && isdigit((unsigned char)buff[0]))
t_port = htons(atoi(buff));
else if (!s)
- die("unknown service `%s' in `" file_SERVER "'", buff);
+ die(1, "unknown service `%s' in `" file_SERVER "'", buff);
else
t_port = s->s_port;
state = st_commit;
case st_commit:
if (n_serv == max_serv) {
max_serv *= 2;
- serv = xrealloc(serv, max_serv * sizeof(*serv));
+ serv = xrealloc(serv, n_serv * sizeof(*serv),
+ max_serv * sizeof(*serv));
}
serv[n_serv].sin_family = AF_INET;
serv[n_serv].sin_addr = t_host;
/* --- A safety net for a broken parser --- */
default:
- die("internal error: can't get here in check__client");
+ die(1, "internal error: can't get here in check__client");
break;
}
}
/* --- Now start sending requests --- */
if (!n_serv)
- die("no servers specified in `" file_SERVER "'");
+ die(1, "no servers specified in `" file_SERVER "'");
IF_TRACING(TRACE_CLIENT, {
size_t i;
/* --- Otherwise do this all the old-fashioned way --- */
if ((fp = fopen(file_RULES, "r")) == 0) {
- die("couldn't read configuration file `%s': %s",
+ die(1, "couldn't read configuration file `%s': %s",
file_RULES, strerror(errno));
}
rule_init();
lexer_scan(fp);
parse();
+ fclose(fp);
return (rule_check(rq));
}