/*
* Structure to hold request procedure information
*/
#define NOAUTH 0
#define AUTH 1
#define NO_REQUEST (-1)
/*
* Because we now have v6 addresses in the messages, we need to compensate
* for the larger size. Therefore, we introduce the alternate size to
* keep us friendly with older implementations. A little ugly.
*/
static int client_v6_capable = 0; /* the client can handle longer messages */
struct req_proc {
short request_code; /* defined request code */
short needs_auth; /* true when authentication needed */
short sizeofitem; /* size of request data item (older size)*/
short v6_sizeofitem; /* size of request data item (new size)*/
void (*handler) (sockaddr_u *, endpt *,
struct req_pkt *); /* routine to handle request */
};
/*
* Authentication keyid used to authenticate requests. Zero means we
* don't allow writing anything.
*/
keyid_t info_auth_keyid;
/*
* Statistic counters to keep track of requests and responses.
*/
u_long numrequests; /* number of requests we've received */
u_long numresppkts; /* number of resp packets sent with data */
/*
* lazy way to count errors, indexed by the error code
*/
u_long errorcounter[MAX_INFO_ERR + 1];
/*
* A hack. To keep the authentication module clear of ntp-ism's, we
* include a time reset variable for its stats here.
*/
u_long auth_timereset;
/*
* Response packet used by these routines. Also some state information
* so that we can handle packet formatting within a common set of
* subroutines. Note we try to enter data in place whenever possible,
* but the need to set the more bit correctly means we occasionally
* use the extra buffer and copy.
*/
static struct resp_pkt rpkt;
static int reqver;
static int seqno;
static int nitems;
static int itemsize;
static int databytes;
static char exbuf[RESP_DATA_SIZE];
static int usingexbuf;
static sockaddr_u *toaddr;
static endpt *frominter;
/*
* prepare_pkt - prepare response packet for transmission, return pointer
* to storage for data item.
*/
static void *
prepare_pkt(
sockaddr_u *srcadr,
endpt *inter,
struct req_pkt *pkt,
size_t structsize
)
{
DPRINTF(4, ("request: preparing pkt\n"));
/*
* Fill in the implementation, request and itemsize fields
* since these won't change.
*/
rpkt.implementation = pkt->implementation;
rpkt.request = pkt->request;
rpkt.mbz_itemsize = MBZ_ITEMSIZE(structsize);
/*
* Compute the static data needed to carry on.
*/
toaddr = srcadr;
frominter = inter;
seqno = 0;
nitems = 0;
itemsize = structsize;
databytes = 0;
usingexbuf = 0;
/*
* return the beginning of the packet buffer.
*/
return &rpkt.u;
}
/*
* more_pkt - return a data pointer for a new item.
*/
static void *
more_pkt(void)
{
/*
* If we were using the extra buffer, send the packet.
*/
if (usingexbuf) {
DPRINTF(3, ("request: sending pkt\n"));
rpkt.rm_vn_mode = RM_VN_MODE(RESP_BIT, MORE_BIT, reqver);
rpkt.auth_seq = AUTH_SEQ(0, seqno);
rpkt.err_nitems = htons((u_short)nitems);
sendpkt(toaddr, frominter, -1, (struct pkt *)&rpkt,
RESP_HEADER_SIZE + databytes);
numresppkts++;
/*
* Copy data out of exbuf into the packet.
*/
memcpy(&rpkt.u.data[0], exbuf, (unsigned)itemsize);
seqno++;
databytes = 0;
nitems = 0;
usingexbuf = 0;
}
databytes += itemsize;
nitems++;
if (databytes + itemsize <= RESP_DATA_SIZE) {
DPRINTF(4, ("request: giving him more data\n"));
/*
* More room in packet. Give him the
* next address.
*/
return &rpkt.u.data[databytes];
} else {
/*
* No room in packet. Give him the extra
* buffer unless this was the last in the sequence.
*/
DPRINTF(4, ("request: into extra buffer\n"));
if (seqno == MAXSEQ)
return NULL;
else {
usingexbuf = 1;
return exbuf;
}
}
}
/*
* flush_pkt - we're done, return remaining information.
*/
static void
flush_pkt(void)
{
DPRINTF(3, ("request: flushing packet, %d items\n", nitems));
/*
* Must send the last packet. If nothing in here and nothing
* has been sent, send an error saying no data to be found.
*/
if (seqno == 0 && nitems == 0)
req_ack(toaddr, frominter, (struct req_pkt *)&rpkt,
INFO_ERR_NODATA);
else {
rpkt.rm_vn_mode = RM_VN_MODE(RESP_BIT, 0, reqver);
rpkt.auth_seq = AUTH_SEQ(0, seqno);
rpkt.err_nitems = htons((u_short)nitems);
sendpkt(toaddr, frominter, -1, (struct pkt *)&rpkt,
RESP_HEADER_SIZE+databytes);
numresppkts++;
}
}
/*
* Given a buffer, return the packet mode
*/
int
get_packet_mode(struct recvbuf *rbufp)
{
struct req_pkt *inpkt = (struct req_pkt *)&rbufp->recv_pkt;
return (INFO_MODE(inpkt->rm_vn_mode));
}
/*
* Do some sanity checks on the packet. Return a format
* error if it fails.
*/
ec = 0;
if ( (++ec, ISRESPONSE(inpkt->rm_vn_mode))
|| (++ec, ISMORE(inpkt->rm_vn_mode))
|| (++ec, INFO_VERSION(inpkt->rm_vn_mode) > NTP_VERSION)
|| (++ec, INFO_VERSION(inpkt->rm_vn_mode) < NTP_OLDVERSION)
|| (++ec, INFO_SEQ(inpkt->auth_seq) != 0)
|| (++ec, INFO_ERR(inpkt->err_nitems) != 0)
|| (++ec, INFO_MBZ(inpkt->mbz_itemsize) != 0)
|| (++ec, rbufp->recv_length < (int)REQ_LEN_HDR)
) {
NLOG(NLOG_SYSEVENT)
if (current_time >= quiet_until) {
msyslog(LOG_ERR,
"process_private: drop test %d"
" failed, pkt from %s",
ec, stoa(srcadr));
quiet_until = current_time + 60;
}
return;
}
reqver = INFO_VERSION(inpkt->rm_vn_mode);
/*
* Get the appropriate procedure list to search.
*/
if (inpkt->implementation == IMPL_UNIV)
proc = univ_codes;
else if ((inpkt->implementation == IMPL_XNTPD) ||
(inpkt->implementation == IMPL_XNTPD_OLD))
proc = ntp_codes;
else {
req_ack(srcadr, inter, inpkt, INFO_ERR_IMPL);
return;
}
/*
* Search the list for the request codes. If it isn't one
* we know, return an error.
*/
while (proc->request_code != NO_REQUEST) {
if (proc->request_code == (short) inpkt->request)
break;
proc++;
}
if (proc->request_code == NO_REQUEST) {
req_ack(srcadr, inter, inpkt, INFO_ERR_REQ);
return;
}
DPRINTF(4, ("found request in tables\n"));
/*
* If we need data, check to see if we have some. If we
* don't, check to see that there is none (picky, picky).
*/
/* This part is a bit tricky, we want to be sure that the size
* returned is either the old or the new size. We also can find
* out if the client can accept both types of messages this way.
*
* Handle the exception of REQ_CONFIG. It can have two data sizes.
*/
temp_size = INFO_ITEMSIZE(inpkt->mbz_itemsize);
if ((temp_size != proc->sizeofitem &&
temp_size != proc->v6_sizeofitem) &&
!(inpkt->implementation == IMPL_XNTPD &&
inpkt->request == REQ_CONFIG &&
temp_size == sizeof(struct old_conf_peer))) {
DPRINTF(3, ("process_private: wrong item size, received %d, should be %d or %d\n",
temp_size, proc->sizeofitem, proc->v6_sizeofitem));
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
if ((proc->sizeofitem != 0) &&
((size_t)(temp_size * INFO_NITEMS(inpkt->err_nitems)) >
(recv_len - REQ_LEN_HDR))) {
DPRINTF(3, ("process_private: not enough data\n"));
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
/*
* If we need to authenticate, do so. Note that an
* authenticatable packet must include a mac field, must
* have used key info_auth_keyid and must have included
* a time stamp in the appropriate field. The time stamp
* must be within INFO_TS_MAXSKEW of the receive
* time stamp.
*/
if (proc->needs_auth && sys_authenticate) {
/*
* For 16-octet digests, regardless of itemsize and
* nitems, authenticated requests are a fixed size
* with the timestamp, key ID, and digest located
* at the end of the packet. Because the key ID
* determining the digest size precedes the digest,
* for larger digests the fixed size request scheme
* is abandoned and the timestamp, key ID, and digest
* are located relative to the start of the packet,
* with the digest size determined by the packet size.
*/
noslop_len = REQ_LEN_HDR
+ INFO_ITEMSIZE(inpkt->mbz_itemsize) *
INFO_NITEMS(inpkt->err_nitems)
+ sizeof(inpkt->tstamp);
/* 32-bit alignment */
noslop_len = (noslop_len + 3) & ~3;
if (recv_len > (noslop_len + MAX_MAC_LEN))
mac_len = 20;
else
mac_len = recv_len - noslop_len;
/*
* If this guy is restricted from doing this, don't let
* him. If the wrong key was used, or packet doesn't
* have mac, return.
*/
/* XXX: Use authistrustedip(), or equivalent. */
if (!INFO_IS_AUTH(inpkt->auth_seq) || !info_auth_keyid
|| ntohl(tailinpkt->keyid) != info_auth_keyid) {
DPRINTF(5, ("failed auth %d info_auth_keyid %u pkt keyid %u maclen %lu\n",
INFO_IS_AUTH(inpkt->auth_seq),
info_auth_keyid,
ntohl(tailinpkt->keyid), (u_long)mac_len));
#ifdef DEBUG
msyslog(LOG_DEBUG,
"process_private: failed auth %d info_auth_keyid %u pkt keyid %u maclen %lu\n",
INFO_IS_AUTH(inpkt->auth_seq),
info_auth_keyid,
ntohl(tailinpkt->keyid), (u_long)mac_len);
#endif
req_ack(srcadr, inter, inpkt, INFO_ERR_AUTH);
return;
}
if (recv_len > REQ_LEN_NOMAC + MAX_MAC_LEN) {
DPRINTF(5, ("bad pkt length %zu\n", recv_len));
msyslog(LOG_ERR,
"process_private: bad pkt length %zu",
recv_len);
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
if (!mod_okay || !authhavekey(info_auth_keyid)) {
DPRINTF(5, ("failed auth mod_okay %d\n",
mod_okay));
#ifdef DEBUG
msyslog(LOG_DEBUG,
"process_private: failed auth mod_okay %d\n",
mod_okay);
#endif
if (!mod_okay) {
sys_restricted++;
}
req_ack(srcadr, inter, inpkt, INFO_ERR_AUTH);
return;
}
/*
* calculate absolute time difference between xmit time stamp
* and receive time stamp. If too large, too bad.
*/
NTOHL_FP(&tailinpkt->tstamp, &ftmp);
L_SUB(&ftmp, &rbufp->recv_time);
LFPTOD(&ftmp, dtemp);
if (fabs(dtemp) > INFO_TS_MAXSKEW) {
/*
* He's a loser. Tell him.
*/
DPRINTF(5, ("xmit/rcv timestamp delta %g > INFO_TS_MAXSKEW %g\n",
dtemp, INFO_TS_MAXSKEW));
req_ack(srcadr, inter, inpkt, INFO_ERR_AUTH);
return;
}
/*
* So far so good. See if decryption works out okay.
*/
if (!authdecrypt(info_auth_keyid, (u_int32 *)inpkt,
recv_len - mac_len, mac_len)) {
DPRINTF(5, ("authdecrypt failed\n"));
req_ack(srcadr, inter, inpkt, INFO_ERR_AUTH);
return;
}
}
DPRINTF(3, ("process_private: all okay, into handler\n"));
/*
* Packet is okay. Call the handler to send him data.
*/
(proc->handler)(srcadr, inter, inpkt);
}
/*
* list_peers - send a list of the peers
*/
static void
list_peers(
sockaddr_u *srcadr,
endpt *inter,
struct req_pkt *inpkt
)
{
struct info_peer_list * ip;
const struct peer * pp;
ip = (struct info_peer_list *)prepare_pkt(srcadr, inter, inpkt,
v6sizeof(struct info_peer_list));
for (pp = peer_list; pp != NULL && ip != NULL; pp = pp->p_link) {
if (IS_IPV6(&pp->srcadr)) {
if (!client_v6_capable)
continue;
ip->addr6 = SOCK_ADDR6(&pp->srcadr);
ip->v6_flag = 1;
} else {
ip->addr = NSRCADR(&pp->srcadr);
if (client_v6_capable)
ip->v6_flag = 0;
}
ip->port = NSRCPORT(&pp->srcadr);
ip->hmode = pp->hmode;
ip->flags = 0;
if (pp->flags & FLAG_CONFIG)
ip->flags |= INFO_FLAG_CONFIG;
if (pp == sys_peer)
ip->flags |= INFO_FLAG_SYSPEER;
if (pp->status == CTL_PST_SEL_SYNCCAND)
ip->flags |= INFO_FLAG_SEL_CANDIDATE;
if (pp->status >= CTL_PST_SEL_SYSPEER)
ip->flags |= INFO_FLAG_SHORTLIST;
ip = (struct info_peer_list *)more_pkt();
} /* for pp */
/*
* do_conf - add a peer to the configuration list
*/
static void
do_conf(
sockaddr_u *srcadr,
endpt *inter,
struct req_pkt *inpkt
)
{
u_short items;
size_t item_sz;
u_int fl;
char * datap;
struct conf_peer temp_cp;
sockaddr_u peeraddr;
/*
* Do a check of everything to see that it looks
* okay. If not, complain about it. Note we are
* very picky here.
*/
items = INFO_NITEMS(inpkt->err_nitems);
item_sz = INFO_ITEMSIZE(inpkt->mbz_itemsize);
datap = inpkt->u.data;
if (item_sz > sizeof(temp_cp)) {
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
while (items-- > 0) {
ZERO(temp_cp);
memcpy(&temp_cp, datap, item_sz);
ZERO_SOCK(&peeraddr);
fl = 0;
if (temp_cp.flags & CONF_FLAG_PREFER)
fl |= FLAG_PREFER;
if (temp_cp.flags & CONF_FLAG_BURST)
fl |= FLAG_BURST;
if (temp_cp.flags & CONF_FLAG_IBURST)
fl |= FLAG_IBURST;
#ifdef AUTOKEY
if (temp_cp.flags & CONF_FLAG_SKEY)
fl |= FLAG_SKEY;
#endif /* AUTOKEY */
if (client_v6_capable && temp_cp.v6_flag) {
AF(&peeraddr) = AF_INET6;
SOCK_ADDR6(&peeraddr) = temp_cp.peeraddr6;
} else {
AF(&peeraddr) = AF_INET;
NSRCADR(&peeraddr) = temp_cp.peeraddr;
/*
* Make sure the address is valid
*/
if (!ISREFCLOCKADR(&peeraddr) &&
ISBADADR(&peeraddr)) {
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
/* check mode value: 0 <= hmode <= 6
*
* There's no good global define for that limit, and
* using a magic define is as good (or bad, actually) as
* a magic number. So we use the highest possible peer
* mode, and that is MODE_BCLIENT.
*
* [Bug 3009] claims that a problem occurs for hmode > 7,
* but the code in ntp_peer.c indicates trouble for any
* hmode > 6 ( --> MODE_BCLIENT).
*/
if (temp_cp.hmode > MODE_BCLIENT) {
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
/* Any more checks on the values? Unchecked at this
* point:
* - version
* - ttl
* - keyid
*
* - minpoll/maxpoll, but they are treated properly
* for all cases internally. Checking not necessary.
*
* Note that we ignore any previously-specified ippeerlimit.
* If we're told to create the peer, we create the peer.
*/
/*
* do_unconf - remove a peer from the configuration list
*/
static void
do_unconf(
sockaddr_u * srcadr,
endpt * inter,
struct req_pkt *inpkt
)
{
u_short items;
size_t item_sz;
char * datap;
struct conf_unpeer temp_cp;
struct peer * p;
sockaddr_u peeraddr;
int loops;
/*
* This is a bit unstructured, but I like to be careful.
* We check to see that every peer exists and is actually
* configured. If so, we remove them. If not, we return
* an error.
*
* [Bug 3011] Even if we checked all peers given in the request
* in a dry run, there's still a chance that the caller played
* unfair and gave the same peer multiple times. So we still
* have to be prepared for nasty surprises in the second run ;)
*/
/* now do two runs: first a dry run, then a busy one */
for (loops = 0; loops != 2; ++loops) {
items = INFO_NITEMS(inpkt->err_nitems);
datap = inpkt->u.data;
while (items-- > 0) {
/* copy from request to local */
ZERO(temp_cp);
memcpy(&temp_cp, datap, item_sz);
/* get address structure */
ZERO_SOCK(&peeraddr);
if (client_v6_capable && temp_cp.v6_flag) {
AF(&peeraddr) = AF_INET6;
SOCK_ADDR6(&peeraddr) = temp_cp.peeraddr6;
} else {
AF(&peeraddr) = AF_INET;
NSRCADR(&peeraddr) = temp_cp.peeraddr;
}
SET_PORT(&peeraddr, NTP_PORT);
#ifdef ISC_PLATFORM_HAVESALEN
peeraddr.sa.sa_len = SOCKLEN(&peeraddr);
#endif
DPRINTF(1, ("searching for %s\n",
stoa(&peeraddr)));
/* search for matching configred(!) peer */
p = NULL;
do {
p = findexistingpeer(
&peeraddr, NULL, p, -1, 0, NULL);
} while (p && !(FLAG_CONFIG & p->flags));
if (!loops && !p) {
/* Item not found in dry run -- bail! */
req_ack(srcadr, inter, inpkt,
INFO_ERR_NODATA);
return;
} else if (loops && p) {
/* Item found in busy run -- remove! */
peer_clear(p, "GONE");
unpeer(p);
}
datap += item_sz;
}
}
if (flags & SYS_FLAG_BCLIENT)
proto_config(PROTO_BROADCLIENT, set, 0., NULL);
if (flags & SYS_FLAG_PPS)
proto_config(PROTO_PPS, set, 0., NULL);
if (flags & SYS_FLAG_NTP)
proto_config(PROTO_NTP, set, 0., NULL);
if (flags & SYS_FLAG_KERNEL)
proto_config(PROTO_KERNEL, set, 0., NULL);
if (flags & SYS_FLAG_MONITOR)
proto_config(PROTO_MONITOR, set, 0., NULL);
if (flags & SYS_FLAG_FILEGEN)
proto_config(PROTO_FILEGEN, set, 0., NULL);
if (flags & SYS_FLAG_AUTH)
proto_config(PROTO_AUTHENTICATE, set, 0., NULL);
if (flags & SYS_FLAG_CAL)
proto_config(PROTO_CAL, set, 0., NULL);
req_ack(srcadr, inter, inpkt, INFO_OKAY);
}
/* There have been some issues with the restrict list processing,
* ranging from problems with deep recursion (resulting in stack
* overflows) and overfull reply buffers.
*
* To avoid this trouble the list reversal is done iteratively using a
* scratch pad.
*/
typedef struct RestrictStack4 RestrictStack4T;
struct RestrictStack4 {
RestrictStack4T *link;
size_t fcnt;
const struct restrict_4 *pres[63];
};
ir = (struct info_restrict *)prepare_pkt(srcadr, inter, inpkt,
v6sizeof(struct info_restrict));
/*
* The restriction lists are kept sorted in the reverse order
* than they were originally. To preserve the output semantics,
* dump each list in reverse order. The workers take care of that.
*/
list_restrict4(restrictlist4, &ir);
if (client_v6_capable)
list_restrict6(restrictlist6, &ir);
flush_pkt();
}
/*
* Do a check of the flags to make sure that only
* the NTPPORT flag is set, if any. If not, complain
* about it. Note we are very picky here.
*/
items = INFO_NITEMS(inpkt->err_nitems);
item_sz = INFO_ITEMSIZE(inpkt->mbz_itemsize);
datap = inpkt->u.data;
if (item_sz > sizeof(cr)) {
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
bad = 0;
while (items-- > 0 && !bad) {
memcpy(&cr, datap, item_sz);
cr.flags = ntohs(cr.flags); /* XXX */
cr.mflags = ntohs(cr.mflags);
if (~RESM_NTPONLY & cr.mflags)
bad |= 1;
if (~RES_ALLFLAGS & cr.flags)
bad |= 2;
if (INADDR_ANY != cr.mask) {
if (client_v6_capable && cr.v6_flag) {
if (IN6_IS_ADDR_UNSPECIFIED(&cr.addr6))
bad |= 4;
} else {
if (INADDR_ANY == cr.addr)
bad |= 8;
}
}
datap += item_sz;
}
if (bad) {
msyslog(LOG_ERR, "%s: bad = 0x%x", __func__, bad);
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
/*
* Looks okay, try it out. Needs to reload data pointer and
* item counter. (Talos-CAN-0052)
*/
ZERO_SOCK(&matchaddr);
ZERO_SOCK(&matchmask);
items = INFO_NITEMS(inpkt->err_nitems);
datap = inpkt->u.data;
/*
* Module entry points and the flags they correspond with
*/
struct reset_entry {
int flag; /* flag this corresponds to */
void (*handler)(void); /* routine to handle request */
};
/*
* do_setclr_trap - do the grunge work of (un)configuring a trap
*/
static void
do_setclr_trap(
sockaddr_u *srcadr,
endpt *inter,
struct req_pkt *inpkt,
int set
)
{
register struct conf_trap *ct;
register endpt *linter;
int res;
sockaddr_u laddr;
/*
* Validate a request packet for a new request or control key:
* - only one item allowed
* - key must be valid (that is, known, and not in the autokey range)
*/
static void
set_keyid_checked(
keyid_t *into,
const char *what,
sockaddr_u *srcadr,
endpt *inter,
struct req_pkt *inpkt
)
{
keyid_t *pkeyid;
keyid_t tmpkey;
/* restrict ourselves to one item only */
if (INFO_NITEMS(inpkt->err_nitems) > 1) {
msyslog(LOG_ERR, "set_keyid_checked[%s]: err_nitems > 1",
what);
req_ack(srcadr, inter, inpkt, INFO_ERR_FMT);
return;
}
/* plug the new key from the packet */
pkeyid = (keyid_t *)&inpkt->u;
tmpkey = ntohl(*pkeyid);
/* validate the new key id, claim data error on failure */
if (tmpkey < 1 || tmpkey > NTP_MAXKEY || !auth_havekey(tmpkey)) {
msyslog(LOG_ERR, "set_keyid_checked[%s]: invalid key id: %ld",
what, (long)tmpkey);
req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA);
return;
}
/* if we arrive here, the key is good -- use it */
*into = tmpkey;
req_ack(srcadr, inter, inpkt, INFO_OKAY);
}
/*
* set_request_keyid - set the keyid used to authenticate requests
*/
static void
set_request_keyid(
sockaddr_u *srcadr,
endpt *inter,
struct req_pkt *inpkt
)
{
set_keyid_checked(&info_auth_keyid, "request",
srcadr, inter, inpkt);
}
/*
* set_control_keyid - set the keyid used to authenticate requests
*/
static void
set_control_keyid(
sockaddr_u *srcadr,
endpt *inter,
struct req_pkt *inpkt
)
{
set_keyid_checked(&ctl_auth_keyid, "control",
srcadr, inter, inpkt);
}
/*
* get_ctl_stats - return some stats concerning the control message module
*/
static void
get_ctl_stats(
sockaddr_u *srcadr,
endpt *inter,
struct req_pkt *inpkt
)
{
register struct info_control *ic;
ic = (struct info_control *)prepare_pkt(srcadr, inter, inpkt,
sizeof(struct info_control));
ic->clockadr = NSRCADR(&addr);
i = bug.nvalues;
if (i > NUMCBUGVALUES)
i = NUMCBUGVALUES;
ic->nvalues = (u_char)i;
ic->svalues = htons((u_short) (bug.svalues & ((1<<i)-1)));
while (--i >= 0)
ic->values[i] = htonl(bug.values[i]);
i = bug.ntimes;
if (i > NUMCBUGTIMES)
i = NUMCBUGTIMES;
ic->ntimes = (u_char)i;
ic->stimes = htonl(bug.stimes);
while (--i >= 0) {
HTONL_FP(&bug.times[i], &ic->times[i]);
}
ic = (struct info_clkbug *)more_pkt();
}
flush_pkt();
}
#endif