home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.ee.lbl.gov
/
2014.05.ftp.ee.lbl.gov.tar
/
ftp.ee.lbl.gov
/
acld-1.11.tar.gz
/
acld-1.11.tar
/
acld-1.11
/
child.c
< prev
next >
Wrap
C/C++ Source or Header
|
2012-02-07
|
27KB
|
1,187 lines
/*
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef lint
static const char rcsid[] =
"@(#) $Id: child.c 806 2012-02-08 03:40:06Z leres $ (LBL)";
#endif
#include <sys/types.h>
#include <sys/time.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <unistd.h>
#include "acld.h"
#include "cf.h"
#include "child.h"
/* Forwards */
static void childaclcleanup(struct state *sp, struct req *rp);
static int childresponse(char **, struct state *);
static int failed(const char *, const char *);
/* Fix up things if updating the router failed */
static void
childaclcleanup(struct state *sp, struct req *rp)
{
struct acllist *ap;
switch (rp->type) {
case REQ_DROP:
case REQ_BLOCKHOSTHOST:
/* Need to delete ACL entry since we failed */
ap = aclfindlistbyaddr(sp->cf, &rp->acl.addr1);
if (ap == NULL) {
lg(LOG_ERR, "childinput: NULL %s", rp->cmd);
exit(EX_SOFTWARE);
}
if (!acldeleteacl(ap, &rp->acl))
lg(LOG_ERR, "childinput: acldeleteacl failed");
break;
case REQ_PERMITUDPDSTHOSTPORT:
case REQ_PERMITTCPDSTHOSTPORT:
case REQ_DROPTCPDSTHOSTPORT:
/* Need to delete ACL entry since we failed */
ap = aclfindlistdefault(sp->cf, &rp->acl.addr1);
if (ap == NULL) {
lg(LOG_ERR, "childinput: NULL %s", rp->cmd);
exit(EX_SOFTWARE);
}
if (!acldeleteacl(ap, &rp->acl))
lg(LOG_ERR, "childinput: acldeleteacl failed");
break;
case REQ_RESTORE:
case REQ_RESTOREHOSTHOST:
/* Need to restore ACL entry since we failed */
ap = aclfindlistbyaddr(sp->cf, &rp->acl.addr1);
if (ap == NULL) {
lg(LOG_ERR, "childinput: NULL %s", rp->cmd);
exit(EX_SOFTWARE);
}
acladdacl(ap, &rp->acl);
break;
case REQ_UNPERMITUDPDSTHOSTPORT:
case REQ_UNPERMITTCPDSTHOSTPORT:
case REQ_RESTORETCPDSTHOSTPORT:
/* Need to restore ACL entry since we failed */
ap = aclfindlistdefault(sp->cf, &rp->acl.addr1);
if (ap == NULL) {
lg(LOG_ERR, "childinput: NULL %s", rp->cmd);
exit(EX_SOFTWARE);
}
acladdacl(ap, &rp->acl);
break;
case REQ_DROPUDPPORT:
case REQ_DROPTCPPORT:
/* Need to delete/restore ACL entry since we failed */
ap = aclfindlistbyname(sp->cf, rp->aclname);
if (ap == NULL) {
lg(LOG_ERR, "childinput: NULL %s", rp->cmd);
exit(EX_SOFTWARE);
}
if (!acldeleteacl(ap, &rp->acl))
lg(LOG_ERR, "childinput: acldeleteacl failed");
break;
case REQ_RESTOREUDPPORT:
case REQ_RESTORETCPPORT:
/* Need to delete/restore ACL entry since we failed */
ap = aclfindlistbyname(sp->cf, rp->aclname);
if (ap == NULL) {
lg(LOG_ERR, "childinput: NULL %s", rp->cmd);
exit(EX_SOFTWARE);
}
acladdacl(ap, &rp->acl);
break;
case REQ_NULLZERO:
/* Need to delete nullzero route since we failed */
if (!routedelete(sp, &rp->nullzero))
lg(LOG_ERR, "childinput: routedelete failed");
break;
case REQ_NONULLZERO:
/* Need to restore nullzero route since we failed */
routeadd(sp, &rp->nullzero);
break;
default:
break;
}
}
/* Process input from the child */
int
childinput(struct state *sp, struct client *cl)
{
int an, flags, i, haveline;
char *cp;
struct acllist *ap, *ap2;
struct req *rp;
struct iobuf *ip;
char **av;
static char *p1, *p2;
static char prompt[] = "expect>";
if (cl != NULL)
rp = cl->req;
else
rp = sp->req;
ip = &sp->rbuf;
switch (sp->state) {
case ASTATE_READRESPONSE:
if (childresponse(&p1, sp)) {
if (sp->state == ASTATE_READRESPONSE) {
sp->state = ASTATE_LOGGEDIN;
sp->cmd = NULL;
} else {
sp->state = ASTATE_CONNECTED;
sp->cmd = NULL;
}
if (p1 != NULL) {
trimws(p1, strlen(p1));
lg(LOG_DEBUG, "childinput response: \"%s\"",
pretty(p1));
free(p1);
p1 = NULL;
}
}
return (0);
case ASTATE_READERROR:
if (childresponse(&p2, sp)) {
if (sp->state == ASTATE_READRESPONSE) {
sp->state = ASTATE_LOGGEDIN;
sp->cmd = NULL;
} else {
sp->state = ASTATE_CONNECTED;
sp->cmd = NULL;
/* Wait a bit before trying again */
timerset(&sp->t_login, sp->cf->c_login_secs);
}
if (p2 != NULL) {
trimws(p2, strlen(p2));
lg(LOG_DEBUG, "childinput error: \"%s\"",
pretty(p2));
free(p2);
p2 = NULL;
}
}
return (0);
case ASTATE_READACL:
while ((haveline = iohaveline(ip)) &&
(cp = iogetstr(ip)) != NULL) {
/* Find the last ACL list that hasn't been updated */
ap2 = sp->cf->c_acllist;
ap = NULL;
for (i = 0; i < sp->cf->c_acllistlen; ++i) {
if (!ap2->sentlistacl)
break;
ap = ap2++;
}
if (ap == NULL) {
lg(LOG_ERR, "childinput: can't happen");
exit(EX_SOFTWARE);
}
/* Remove trailing newline for possible error message */
trimws(cp, strlen(cp));
if (aclstraddacl(sp->cf, ap, cp)) {
sp->state = ASTATE_LOGGEDIN;
sp->cmd = NULL;
break;
}
}
/* We're not done if there wasn't a newline in the buffer */
return (!haveline);
case ASTATE_READROUTE:
while ((haveline = iohaveline(ip)) &&
(cp = iogetstr(ip)) != NULL) {
/* Remove trailing newline for possible error message */
trimws(cp, strlen(cp));
if (routestradd(sp, cp)) {
sp->state = ASTATE_LOGGEDIN;
sp->cmd = NULL;
break;
}
}
/* We're not done if there wasn't a newline in the buffer */
return (!haveline);
default:
if (rp != NULL && rp->state == RSTATE_READRESPONSE) {
while ((cp = iogetstr(ip)) != NULL) {
/* Strip trailing "\r\n" */
trimws(cp, strlen(cp));
if (strcmp(cp, ".") != 0) {
lg(LOG_DEBUG,
"childinput response: \"%s\"",
pretty(cp));
if (cl != NULL)
ioappendline(&rp->payload,
cp, strlen(cp));
continue;
}
if (cl != NULL)
clientsend(cl, rp);
else {
rp->state = RSTATE_DONE;
getts(&rp->cts);
}
/* Log for NETS */
if (cl != NULL)
nets_log(sp, rp);
/* Fix up things if the router command failed */
if ((rp->flags & RFLAG_FAILED) != 0)
childaclcleanup(sp, rp);
break;
}
return (0);
}
break;
}
/* Get input */
cp = iogetstr(ip);
if (strncmp(cp, prompt, sizeof(prompt) - 1) == 0) {
if (sp->state == ASTATE_NOTCONNECTED) {
sp->state = ASTATE_CONNECTED;
sp->cmd = NULL;
}
if (debug)
fprintf(lf, "%s childinput: (ready)\n", tsstr());
cp += sizeof(prompt) - 1;
if (*cp == '\0')
return (0);
}
/* Break into arguments */
av = NULL;
an = makeargv(cp, &av);
if (an <= 0) {
freeargv(av);
return (0);
}
if (debug) {
fprintf(lf, "%s childinput:", tsstr());
for (i = 0; i < an; ++i)
fprintf(lf, " %s", av[i]);
putc('\n', lf);
}
switch (sp->state) {
case ASTATE_SENTLISTACL:
case ASTATE_SENTLISTROUTE:
case ASTATE_SENTLOGIN:
case ASTATE_SENTATTR:
flags = 0;
if (failed(sp->cmd, av[0]))
flags |= RFLAG_FAILED;
else if (strcmp(sp->cmd, av[0]) != 0) {
lg(LOG_ERR,
"childinput: unexpected response %s waiting for %s",
av[0], sp->cmd);
freeargv(av);
return (0);
}
if (an > 1 && strcmp(av[1], "-") == 0)
flags |= RFLAG_CONTINUE;
switch (sp->state) {
case ASTATE_SENTLOGIN:
case ASTATE_SENTATTR:
if ((flags & RFLAG_FAILED) == 0) {
if ((flags & RFLAG_CONTINUE) == 0)
sp->state = ASTATE_LOGGEDIN;
else
sp->state = ASTATE_READRESPONSE;
} else {
if ((flags & RFLAG_CONTINUE) == 0) {
sp->state = ASTATE_CONNECTED;
/* Wait a bit before trying again */
timerset(&sp->t_login,
sp->cf->c_login_secs);
} else
sp->state = ASTATE_READERROR;
}
break;
case ASTATE_SENTLISTACL:
if ((flags & RFLAG_FAILED) == 0) {
if ((flags & RFLAG_CONTINUE) == 0)
sp->state = ASTATE_LOGGEDIN;
else
sp->state = ASTATE_READACL;
} else {
if ((flags & RFLAG_CONTINUE) == 0) {
sp->state = ASTATE_LOGGEDIN;
childkill(sp);
} else
sp->state = ASTATE_READERROR;
}
break;
case ASTATE_SENTLISTROUTE:
if ((flags & RFLAG_FAILED) == 0) {
if ((flags & RFLAG_CONTINUE) == 0)
sp->state = ASTATE_LOGGEDIN;
else
sp->state = ASTATE_READROUTE;
} else {
if ((flags & RFLAG_CONTINUE) == 0) {
sp->state = ASTATE_LOGGEDIN;
childkill(sp);
} else
sp->state = ASTATE_READERROR;
}
break;
default:
break;
}
freeargv(av);
return (0);
default:
break;
}
if (rp == NULL) {
lg(LOG_ERR, "childinput: no server/client request to process");
freeargv(av);
return (0);
}
if (rp->state != RSTATE_CHILD) {
lg(LOG_ERR, "childinput: not waiting for child to talk!");
freeargv(av);
return (0);
}
rp->flags = 0;
if (failed(rp->cmd, av[0]))
rp->flags = RFLAG_FAILED;
else if (strcmp(rp->cmd, av[0]) != 0) {
lg(LOG_ERR, "childinput: unexpected response %s waiting for %s",
av[0], rp->cmd);
freeargv(av);
return (0);
}
if (an > 1 && strcmp(av[1], "-") == 0) {
rp->flags |= RFLAG_CONTINUE;
rp->state = RSTATE_READRESPONSE;
freeargv(av);
/* Need to collect response before we're done */
return (0);
} else {
rp->state = RSTATE_DONE;
getts(&rp->cts);
/* Log for NETS */
if (cl != NULL)
nets_log(sp, rp);
}
/* Done with cracked arguments */
freeargv(av);
if (cl != NULL)
clientsend(cl, rp);
/* Fix up things if the router command failed */
if ((rp->flags & RFLAG_FAILED) != 0)
childaclcleanup(sp, rp);
return (0);
}
/* XXX unfortunately, this duplicates a lot of serverdroprestore() */
void
childdroprestore(struct state *sp, struct client *cl, struct req *rp)
{
const char *p;
struct acllist *ap;
struct acl *al;
struct addr *wp, *tp;
struct cf *cf;
char buf[132];
/* Find ACL list */
cf = sp->cf;
switch (rp->acl.type) {
case ATYPE_PERMITHOST:
case ATYPE_BLOCKHOST:
case ATYPE_PERMITNET:
case ATYPE_BLOCKNET:
case ATYPE_BLOCKHOSTHOST:
case ATYPE_BLOCKTCPDSTHOSTPORT:
/* By address */
ap = aclfindlistbyaddr(cf, &rp->acl.addr1);
if (ap == NULL) {
clientsenderr(cl, rp, "can't find acllist for %s"
" (is the default ACL missing?)",
addr2str(&rp->acl.addr1));
return;
}
rp->acllist = ap;
break;
case ATYPE_BLOCKUDPPORT:
case ATYPE_BLOCKTCPPORT:
/* By ACL name */
ap = aclfindlistbyname(cf, rp->aclname);
if (ap == NULL) {
clientsenderr(cl, rp, "can't find ACL %s", rp->aclname);
return;
}
rp->acllist = ap;
break;
case ATYPE_PERMITUDPDSTHOSTPORT:
case ATYPE_PERMITTCPDSTHOSTPORT:
/* Use default ACL list for permit guys */
ap = aclfindlistdefault(cf, &rp->acl.addr1);
if (ap == NULL) {
clientsenderr(cl, rp,
"can't find acllist for %s (is the default ACL"
" for its address type missing?)",
addr2str(&rp->acl.addr1));
return;
}
rp->acllist = ap;
break;
default:
clientsenderr(cl, rp, "ACL type %d not implemented",
rp->acl.type);
return;
}
/* Check if ACL should or should not already exist */
al = aclfindacl(ap, &rp->acl, 0);
switch (rp->type) {
case REQ_DROP:
stats_setrate(&ap->stats);
tp = &rp->acl.addr1;
wp = whitelist(cf, tp);
if (wp != NULL) {
snprintf(buf, sizeof(buf), "%s", addr2str(tp));
clientsenderr(cl, rp, "%s is on the whitelist (%s)",
buf, addr2str(wp));
return;
}
if (al != NULL) {
rp->flags |= RFLAG_IGNORE;
clientsendfmt(cl, rp,
"Note: %s is already blocked in ACL %s",
addr2str(tp), ap->name);
/* Log for NETS */
nets_log(sp, rp);
return;
}
break;
case REQ_BLOCKHOSTHOST:
stats_setrate(&ap->stats);
tp = &rp->acl.addr1;
wp = whitelist(cf, tp);
if (wp == NULL) {
/* Check second address too */
tp = &rp->acl.addr2;
wp = whitelist(cf, tp);
}
if (wp != NULL) {
snprintf(buf, sizeof(buf), "%s", addr2str(tp));
clientsenderr(cl, rp, "%s is on the whitelist (%s)",
buf, addr2str(wp));
return;
}
if (al != NULL) {
rp->flags |= RFLAG_IGNORE;
snprintf(buf, sizeof(buf), "%s", addr2str(tp));
clientsendfmt(cl, rp,
"Note: %s/%s is already blocked in ACL %s",
buf, addr2str(&rp->acl.addr2), ap->name);
/* Log for NETS */
nets_log(sp, rp);
return;
}
break;
case REQ_DROPUDPPORT:
case REQ_DROPTCPPORT:
stats_setrate(&ap->stats);
if (al != NULL) {
rp->flags |= RFLAG_IGNORE;
clientsendfmt(cl, rp,
"Note: %s/%s is already blocked in ACL %s",
tcporudpreqstr(rp->type), rp->acl.port1, ap->name);
/* Log for NETS */
nets_log(sp, rp);
return;
}
break;
case REQ_PERMITUDPDSTHOSTPORT:
case REQ_PERMITTCPDSTHOSTPORT:
if (al != NULL) {
rp->flags |= RFLAG_IGNORE;
clientsendfmt(cl, rp,
"Note: %s %s/%s is already permitted in ACL %s",
addr2str(&rp->acl.addr1), tcporudpreqstr(rp->type),
rp->acl.port1, ap->name);
/* Log for NETS */
nets_log(sp, rp);
return;
}
break;
case REQ_DROPTCPDSTHOSTPORT:
if (al != NULL) {
rp->flags |= RFLAG_IGNORE;
clientsendfmt(cl, rp,
"Note: %s %s/%s is already blocked in ACL %s",
addr2str(&rp->acl.addr1), tcporudpreqstr(rp->type),
rp->acl.port1, ap->name);
/* Log for NETS */
nets_log(sp, rp);
return;
}
break;
case REQ_RESTORE:
case REQ_RESTOREHOSTHOST:
if (al == NULL) {
clientsenderr(cl, rp, "%s not found in ACL %s",
addr2str(&rp->acl.addr1), ap->name);
/* Log for NETS */
rp->flags |= RFLAG_IGNORE;
nets_log(sp, rp);
return;
}
break;
case REQ_RESTOREUDPPORT:
case REQ_RESTORETCPPORT:
if (al == NULL) {
clientsenderr(cl, rp, "%s/%s not found in ACL %s",
tcporudpreqstr(rp->type), rp->acl.port1, ap->name);
/* Log for NETS */
rp->flags |= RFLAG_IGNORE;
nets_log(sp, rp);
return;
}
break;
case REQ_UNPERMITUDPDSTHOSTPORT:
case REQ_UNPERMITTCPDSTHOSTPORT:
case REQ_RESTORETCPDSTHOSTPORT:
if (al == NULL) {
clientsenderr(cl, rp, "%s:%s/%s not found in ACL %s",
addr2str(&rp->acl.addr1), tcporudpreqstr(rp->type),
rp->acl.port1, ap->name);
/* Log for NETS */
rp->flags |= RFLAG_IGNORE;
nets_log(sp, rp);
return;
}
break;
default:
lg(LOG_ERR, "childdroprestore: bad type %d (1)", rp->type);
exit(EX_SOFTWARE);
}
/* If removing an ACL, check that its seq number is in the range */
switch (rp->type) {
case REQ_RESTORE:
case REQ_RESTOREHOSTHOST:
/* Host sequence range */
if (al->seq < cf->c_lowseq || al->seq > cf->c_highseq) {
clientsenderr(cl, rp,
"seq %d in ACL %s not in host seq range (%d-%d)",
al->seq, ap->name, cf->c_lowseq, cf->c_highseq);
/* Log for NETS */
rp->flags |= RFLAG_IGNORE;
nets_log(sp, rp);
return;
}
break;
case REQ_RESTOREUDPPORT:
case REQ_RESTORETCPPORT:
/* Port sequence range */
if (al->seq < cf->c_lowportseq || al->seq > cf->c_highportseq) {
clientsenderr(cl, rp,
"seq %d in ACL %s not in port seq range (%d-%d)",
al->seq, ap->name,
cf->c_lowportseq, cf->c_highportseq);
/* Log for NETS */
rp->flags |= RFLAG_IGNORE;
nets_log(sp, rp);
return;
}
break;
case REQ_UNPERMITUDPDSTHOSTPORT:
case REQ_UNPERMITTCPDSTHOSTPORT:
case REQ_RESTORETCPDSTHOSTPORT:
/* Permit host/port sequence range */
if (al->seq < cf->c_lowpermithostportseq ||
al->seq > cf->c_highpermithostportseq) {
clientsenderr(cl, rp,
"seq %d in ACL %s not in port seq range (%d-%d)",
al->seq, ap->name,
cf->c_lowpermithostportseq,
cf->c_highpermithostportseq);
/* Log for NETS */
rp->flags |= RFLAG_IGNORE;
nets_log(sp, rp);
return;
}
break;
default:
break;
}
/* Add or remove the ACL */
switch (rp->type) {
case REQ_DROP:
case REQ_BLOCKHOSTHOST:
/* Are we moving up or down? */
if (cf->c_incrseq) {
if (cf->c_highseq >= 0 &&
ap->lastseq >= cf->c_highseq) {
(void)snprintf(buf, sizeof(buf),
"Last sequence number in ACL %s"
" already in use (%d)",
ap->name, cf->c_highseq);
clientsenderr(cl, rp, "%s", buf);
syslog(LOG_INFO, "%s", buf);
return;
}
} else {
if (cf->c_lowseq >= 0 &&
ap->lastseq <= cf->c_lowseq) {
(void)snprintf(buf, sizeof(buf),
"First sequence number in ACL %s"
" already in use (%d)",
ap->name, cf->c_lowseq);
clientsenderr(cl, rp, "%s", buf);
syslog(LOG_INFO, "%s", buf);
return;
}
}
/* Check cap and limits */
p = checklimits(cf, ap);
if (p != NULL) {
clientsenderr(cl, rp, "%s", p);
syslog(LOG_INFO, "%s", p);
return;
}
if (cf->c_incrseq)
rp->acl.seq = ++ap->lastseq;
else
rp->acl.seq = --ap->lastseq;
/* Add here to avoid the race (remove later if it fails) */
acladdacl(ap, &rp->acl);
break;
case REQ_DROPUDPPORT:
case REQ_DROPTCPPORT:
if (cf->c_highportseq >= 0 &&
ap->lastportseq >= cf->c_highportseq) {
clientsenderr(cl, rp,
"Last port sequence number in ACL %s"
" already in use (%d)",
ap->name, cf->c_highportseq);
return;
}
/* Check cap and limits */
p = checklimits(cf, ap);
if (p != NULL) {
clientsenderr(cl, rp, "%s", p);
syslog(LOG_INFO, "%s", p);
return;
}
rp->acl.seq = ++ap->lastportseq;
/* Add here to avoid the race (remove later if it fails) */
acladdacl(ap, &rp->acl);
break;
case REQ_PERMITUDPDSTHOSTPORT:
case REQ_PERMITTCPDSTHOSTPORT:
case REQ_DROPTCPDSTHOSTPORT:
if (cf->c_highpermithostportseq >= 0 &&
ap->lastpermithostportseq >=
cf->c_highpermithostportseq) {
clientsenderr(cl, rp,
"Last permit host/port sequence number in ACL %s"
" already in use (%d)",
ap->name, cf->c_highpermithostportseq);
return;
}
/* Check cap and limits */
p = checklimits(cf, ap);
if (p != NULL) {
clientsenderr(cl, rp, "%s", p);
syslog(LOG_INFO, "%s", p);
return;
}
rp->acl.seq = ++ap->lastpermithostportseq;
/* Add here to avoid the race (remove later if it fails) */
acladdacl(ap, &rp->acl);
break;
case REQ_RESTORE:
case REQ_RESTOREHOSTHOST:
case REQ_RESTOREUDPPORT:
case REQ_RESTORETCPPORT:
case REQ_UNPERMITUDPDSTHOSTPORT:
case REQ_UNPERMITTCPDSTHOSTPORT:
case REQ_RESTORETCPDSTHOSTPORT:
rp->acl.seq = al->seq;
/* Remove here to avoid the race (restore later if it fails) */
if (!acldeleteacl(ap, &rp->acl)) {
lg(LOG_ERR, "childdroprestore: acldeleteacl failed");
exit(EX_SOFTWARE);
}
break;
default:
lg(LOG_ERR, "childdroprestore: bad type %d (2)", rp->type);
exit(EX_SOFTWARE);
}
/* Send the request to the child */
switch (rp->acl.type) {
case ATYPE_BLOCKHOST:
childsend(sp, "%s %s %s %d",
rp->cmd, addr2str(&rp->acl.addr1), ap->name, rp->acl.seq);
break;
case ATYPE_BLOCKNET:
childsend(sp, "%s %s %s %d",
rp->cmd, addr2str(&rp->acl.addr1), ap->name, rp->acl.seq);
break;
case ATYPE_BLOCKHOSTHOST:
(void)snprintf(buf, sizeof(buf), "%s",
addr2str(&rp->acl.addr2));
childsend(sp, "%s %s %s %s %d",
rp->cmd, addr2str(&rp->acl.addr1), buf,
ap->name, rp->acl.seq);
break;
case ATYPE_BLOCKUDPPORT:
case ATYPE_BLOCKTCPPORT:
childsend(sp, "%s %s %s %d",
rp->cmd, rp->acl.port1, ap->name, rp->acl.seq);
break;
case ATYPE_PERMITUDPDSTHOSTPORT:
case ATYPE_PERMITTCPDSTHOSTPORT:
case ATYPE_BLOCKTCPDSTHOSTPORT:
childsend(sp, "%s %s %s %s %d",
rp->cmd, addr2str(&rp->acl.addr1), rp->acl.port1,
ap->name, rp->acl.seq);
break;
default:
lg(LOG_ERR, "childdroprestore: bad atype %d", rp->acl.type);
exit(EX_SOFTWARE);
}
/* Update request state */
rp->state = RSTATE_CHILD;
/* Schedule a sync */
if (cf->c_sync_secs > 0 && timercheck(&sp->t_sync) < 0)
timerset(&sp->t_sync, cf->c_sync_secs);
}
void
childkill(struct state *sp)
{
if (sp->wfd >= 0 && sp->state == ASTATE_LOGGEDIN)
childsend(sp, "logout");
sp->state = ASTATE_NOTCONNECTED;
sp->cmd = NULL;
errno = 0;
if (sp->pid > 0 && kill(sp->pid, SIGTERM) < 0 && errno != ESRCH)
lg(LOG_ERR, "childkill: kill %d: %s",
(int)sp->pid, strerror(errno));
sp->pid = -1;
if (sp->rfd >= 0)
close(sp->rfd);
sp->rfd = -1;
if (sp->wfd >= 0)
close(sp->wfd);
sp->wfd = -1;
iofree(&sp->rbuf);
timerset(&sp->t_ayt, 0);
timerset(&sp->t_login, 0);
timerset(&sp->t_sync, 0);
}
void
childlistacl(struct state *sp)
{
int i;
struct acllist *ap;
struct cf *cf;
cf = sp->cf;
for (i = 0, ap = cf->c_acllist; i < cf->c_acllistlen; ++i, ++ap)
if (!ap->sentlistacl) {
sp->state = ASTATE_SENTLISTACL;
sp->cmd = "listacl";
childsend(sp, "%s %s {%s}",
sp->cmd, ap->name, ap->intlist->i_name);
++ap->sentlistacl;
return;
}
/* If we got here, we've listed all the ACLs; sort 'em */
++sp->listedallacls;
aclsortacls(cf);
}
/* XXX Should we only do this if nullzero routes if enabled? */
void
childlistroute(struct state *sp)
{
routelistsfree(sp);
sp->state = ASTATE_SENTLISTROUTE;
sp->cmd = "listroute";
childsend(sp, "%s", sp->cmd);
++sp->listedroutes;
}
void
childlogin(struct state *sp)
{
int odebug;
struct cf *cf;
cf = sp->cf;
/* Free ACL lists so we'll re-aquire them from the router */
acllistsfree(sp);
/* Zero out attr status */
attrclear(sp);
/* Free routes so we'll re-aquire them from the router */
routelistsfree(sp);
sp->state = ASTATE_SENTLOGIN;
sp->cmd = "login";
odebug = debug;
if (odebug) {
/* Avoid exposing router passwords in debugging output */
debug = 0;
fprintf(lf, "%s childsend: \"%s %s %s %s %s %s %s %s\"\n",
tsstr(),
sp->cmd,
cf->c_router,
cf->c_cuser,
"?",
"?",
cf->c_euser,
"?",
"?");
}
childsend(sp, "%s %s %s %s %s %s %s %s",
sp->cmd,
cf->c_router,
cf->c_cuser,
cf->c_cpass1,
cf->c_cpass2,
cf->c_euser,
cf->c_epass1,
cf->c_epass2);
debug = odebug;
timerreset(&sp->t_ayt);
timerreset(&sp->t_sync);
}
void
childnullzero(struct state *sp, struct client *cl, struct req *rp)
{
struct cf *cf;
struct route *rtp;
struct addr *wp;
const char *p;
char buf[64];
if (rp->nullzero.type != ROUTE_NULLZERO) {
lg(LOG_ERR, "childnullzero: bad routetype %d",
rp->nullzero.type);
exit(EX_SOFTWARE);
}
cf = sp->cf;
/*
* Everything is ok (subject to whitelist check) if no
* configured nullzero nets
*/
if (cf->c_nullzeronets != NULL &&
!goodnullzero(cf, &rp->nullzero.dst)) {
clientsenderr(cl, rp, "%s not part of a configured nullzeronet",
addr2str(&rp->nullzero.dst));
return;
}
rtp = routefind(sp, &rp->nullzero);
switch (rp->type) {
case REQ_NULLZERO:
stats_setrate(&sp->nullzerostats);
wp = whitelist(cf, &rp->nullzero.dst);
if (wp != NULL) {
snprintf(buf, sizeof(buf), "%s",
addr2str(&rp->nullzero.dst));
clientsenderr(cl, rp, "%s is on the whitelist (%s)",
buf, addr2str(wp));
return;
}
if (rtp != NULL) {
rp->flags |= RFLAG_IGNORE;
clientsendfmt(cl, rp,
"Note: %s is already nullzero routed",
addr2str(&rp->nullzero.dst));
/* Log for NETS */
nets_log(sp, rp);
return;
}
p = checkmaskwidth(cf, &rp->nullzero.dst);
if (p != NULL) {
clientsenderr(cl, rp, "%s", p);
return;
}
if (cf->c_nullzeromax != 0 &&
sp->nullzerolen >= cf->c_nullzeromax) {
rp->flags |= RFLAG_IGNORE;
clientsenderr(cl, rp,
"Too many nullzero routes (%d in use, %d allowed)",
sp->nullzerolen, cf->c_nullzeromax);
/* Log for NETS */
nets_log(sp, rp);
return;
}
/* Add here to avoid the race (remove later if it fails) */
routeadd(sp, &rp->nullzero);
/* Queue up the command */
childsend(sp, "%s %s", rp->cmd, addr2str(&rp->nullzero.dst));
break;
case REQ_NONULLZERO:
if (rtp == NULL) {
rp->flags |= RFLAG_IGNORE;
clientsenderr(cl, rp, "%s not found",
addr2str(&rp->nullzero.dst));
return;
}
p = checkmaskwidth(cf, &rp->nullzero.dst);
if (p != NULL) {
clientsenderr(cl, rp, "%s", p);
return;
}
/* Remove here to avoid the race (restore later if it fails) */
if (!routedelete(sp, &rp->nullzero)) {
lg(LOG_ERR, "childnullzero: deleteroute failed");
exit(EX_SOFTWARE);
}
/* Queue up the command */
childsend(sp, "%s %s", rp->cmd, addr2str(&rp->nullzero.dst));
break;
default:
lg(LOG_ERR, "childnullzero: bad type %d (1)", rp->type);
exit(EX_SOFTWARE);
}
/* Update request state */
rp->state = RSTATE_CHILD;
/* Schedule a sync */
if (cf->c_sync_secs > 0 && timercheck(&sp->t_sync) < 0)
timerset(&sp->t_sync, cf->c_sync_secs);
}
static int
childresponse(char **pp, struct state *sp)
{
char *cp;
struct iobuf *ip;
/* Look for end of message (and strip leading dots) */
ip = &sp->rbuf;
while ((cp = iogetstr(ip)) != NULL) {
if (*cp == '.') {
++cp;
if (*cp == '\0' || *cp == '\n')
return (1);
}
strappend(pp, cp);
}
return(0);
}
void
childsend(struct state *sp, const char *fmt, ...)
{
char *cp;
size_t n;
ssize_t cc;
char buf[1024];
va_list ap;
va_start(ap, fmt);
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
va_end(ap);
n = strlen(buf);
cp = buf + n - 1;
if (debug)
fprintf(lf, "%s childsend: \"%s\"\n", tsstr(), buf);
if (cp >= buf) {
*++cp = '\n';
*++cp = '\0';
++n;
}
cc = write(sp->wfd, buf, n);
if (cc < 0) {
/* XXX close off child here? */
lg(LOG_ERR, "childsend: write: %s", strerror(errno));
return;
}
if (cc != n) {
lg(LOG_ERR, "childsend: short write (%d != %d)",
(int)cc, (int)n);
return;
}
}
void
childsendattr(struct state *sp)
{
int i, didany;
struct cf *cf;
struct attrlist *atp;
cf = sp->cf;
didany = 0;
for (i = 0, atp = cf->c_attrlist; i < cf->c_attrlistlen; ++i, ++atp)
if (!atp->a_sentattr) {
sp->state = ASTATE_SENTATTR;
sp->cmd = "attr";
childsend(sp, "%s", attrfmt(atp));
++atp->a_sentattr;
++didany;
break;
}
if (!didany)
++sp->sentattrs;
}
static int
failed(const char *what, const char *str)
{
size_t len;
len = strlen(what);
if (strncmp(what, str, len) == 0 && strcmp(str + len, "-failed") == 0)
return (1);
return (0);
}