void dns_lookup(myconn_t *rec) { /* Push a normal DNS lookup into the DNS queue */ time_t updtime = 0; char *res = NULL; dbgprintf("dns_lookup(): Lookup %s, family %d\n", rec->netparams.lookupstring, rec->netparams.af_index-1); if (xymon_sqldb_dns_lookup_search(dns_lookup_sequence[rec->netparams.af_index], rec->netparams.lookupstring, &updtime, &res) != 0) { rec->netparams.lookupstatus = LOOKUP_FAILED; return; } if (!rec->netparams.lookupstring || (strlen(rec->netparams.lookupstring) == 0)) { errprintf("Invalid DNS lookup - string is %s\n", (rec->netparams.lookupstring ? "empty" : "null")); rec->netparams.lookupstatus = LOOKUP_FAILED; return; } if (!res) { /* No cache record, create one */ xymon_sqldb_dns_lookup_create(dns_lookup_sequence[rec->netparams.af_index], rec->netparams.lookupstring); } else if (res && ((time(NULL) - updtime) < 3600)) { /* We have a valid cache-record */ if ((strcmp(res, "-") != 0)) { /* Successfully resolved from cache */ dns_return_lookupdata(rec, res); } else { /* Continue with next address family */ rec->netparams.af_index++; rec->netparams.lookupstatus = LOOKUP_NEEDED; } } xymon_sqldb_dns_lookup_finish(); /* Cache data missing or invalid, do the DNS lookup */ if (rec->netparams.af_index == 0) getntimer(&rec->netparams.lookupstart); rec->netparams.lookupstatus = LOOKUP_ACTIVE; /* * Must increment af_index before calling ares_gethostbyname(), since the callback may be triggered * immediately (e.g. if the hostname is listed in /etc/hosts) */ rec->netparams.af_index++; /* Use ares_search() here, we want to use the whole shebang of name lookup options */ ares_gethostbyname(dns_lookupchannel, rec->netparams.lookupstring, dns_lookup_sequence[rec->netparams.af_index-1], dns_lookup_callback, rec); }
int ntimerus(struct timespec *start, struct timespec *now) { struct timespec tdiff; /* See how long the query took */ if (now) { memcpy(&tdiff, now, sizeof(struct timespec)); } else { getntimer(&tdiff); } if (tdiff.tv_nsec < start->tv_nsec) { tdiff.tv_sec--; tdiff.tv_nsec += 1000000000; } tdiff.tv_sec -= start->tv_sec; tdiff.tv_nsec -= start->tv_nsec; return (tdiff.tv_sec*1000000 + tdiff.tv_nsec/1000); }
void add_timestamp(const char *msg) { if (timing) { timestamp_t *newstamp = (timestamp_t *) malloc(sizeof(timestamp_t)); getntimer(&newstamp->eventtime); newstamp->eventtext = strdup(msg); if (stamphead == NULL) { newstamp->next = newstamp->prev = NULL; stamphead = newstamp; } else { newstamp->prev = stamptail; newstamp->next = NULL; stamptail->next = newstamp; } stamptail = newstamp; } }
unsigned char *get_xymond_message(enum msgchannels_t chnid, char *id, int *seq, struct timespec *timeout) { static unsigned int seqnum = 0; static char *idlemsg = NULL; static char *buf = NULL; static size_t bufsz = 0; static size_t maxmsgsize = 0; static int ioerror = 0; static char *startpos; /* Where our unused data starts */ static char *endpos; /* Where the first message ends */ static char *fillpos; /* Where our unused data ends (the \0 byte) */ int truncated = 0; struct timespec cutoff; int maymove, needmoredata; char *endsrch; /* Where in the buffer do we start looking for the end-message marker */ char *result; /* * The way this works is to read data from stdin into a * buffer. Each read fetches as much data as possible, * i.e. all that is available up to the amount of * buffer space we have. * * When the buffer contains a complete message, * we return a pointer to the message. * * Since a read into the buffer can potentially * fetch multiple messages, we need to keep track of * the start/end positions of the next message, and * where in the buffer new data should be read in. * As long as there is a complete message available * in the buffer, we just return that message - only * when there is no complete message do we read data * from stdin. * * A message is normally NOT copied, we just return * a pointer to our input buffer. The only time we * need to shuffle data around is if the buffer * does not have room left to hold a complete message. */ if (buf == NULL) { /* * Initial setup of the buffers. * We allocate a buffer large enough for the largest message * that can arrive on this channel, and add 4KB extra room. * The EXTRABUFSPACE is to allow the memmove() that will be * needed occasionally some room to work optimally. */ maxmsgsize = 1024*shbufsz(chnid); bufsz = maxmsgsize + EXTRABUFSPACE; buf = (char *)malloc(bufsz+1); *buf = '\0'; startpos = fillpos = buf; endpos = NULL; /* idlemsg is used to return the idle message in case of timeouts. */ idlemsg = strdup("@@idle\n"); /* We dont want to block when reading data. */ fcntl(inputfd, F_SETFL, O_NONBLOCK); } /* * If the start of the next message doesn't begin with "@" then * there's something rotten. */ if (*startpos && (*startpos != '@')) { errprintf("Bad data in channel, skipping it\n"); startpos = strstr(startpos, "\n@@"); endpos = (startpos ? strstr(startpos, "\n@@\n") : NULL); if (startpos && (startpos == endpos)) { startpos = endpos + 4; endpos = strstr(startpos, "\n@@\n"); } if (!startpos) { /* We're lost - flush the buffer and try to recover */ errprintf("Buffer sync lost, flushing data\n"); *buf = '\0'; startpos = fillpos = buf; endpos = NULL; } seqnum = 0; /* After skipping, we dont know what to expect */ } startagain: if (ioerror) { errprintf("get_xymond_message: Returning NULL due to previous i/o error\n"); return NULL; } if (timeout) { /* Calculate when the read should timeout. */ getntimer(&cutoff); cutoff.tv_sec += timeout->tv_sec; cutoff.tv_nsec += timeout->tv_nsec; if (cutoff.tv_nsec > 1000000000) { cutoff.tv_sec += 1; cutoff.tv_nsec -= 1000000000; } } /* * Start looking for the end-of-message marker at the beginning of * the message. The next scans will only look at the new data we've * got when reading data in. */ endsrch = startpos; /* * See if the current available buffer space is enough to hold a full message. * If not, then flag that we may do a memmove() of the buffer data. */ maymove = ((startpos + maxmsgsize) >= (buf + bufsz)); /* We only need to read data, if we do not have an end-of-message marker */ needmoredata = (endpos == NULL); while (needmoredata) { /* Fill buffer with more data until we get an end-of-message marker */ struct timespec now; struct timeval selecttmo; fd_set fdread; int res; size_t bufleft = bufsz - (fillpos - buf); size_t usedbytes = (fillpos - startpos); dbgprintf("Want msg %d, startpos %ld, fillpos %ld, endpos %ld, usedbytes=%ld, bufleft=%ld\n", (seqnum+1), (startpos-buf), (fillpos-buf), (endpos ? (endpos-buf) : -1), usedbytes, bufleft); if (usedbytes >= maxmsgsize) { /* Over-size message. Truncate it. */ errprintf("Got over-size message, truncating at %d bytes (max: %d)\n", usedbytes, maxmsgsize); endpos = startpos + usedbytes - 5; memcpy(endpos, "\n@@\n", 4); /* Simulate end-of-message and flush data */ needmoredata = 0; truncated = 1; } if (needmoredata) { if (maymove && (bufleft < EXTRABUFSPACE)) { /* Buffer is almost full - move data to accomodate a large message. */ dbgprintf("Moving %d bytes to start of buffer\n", usedbytes); memmove(buf, startpos, usedbytes); startpos = buf; fillpos = startpos + usedbytes; *fillpos = '\0'; endsrch = (usedbytes >= 4) ? (fillpos - 4) : startpos; maymove = 0; bufleft = bufsz - (fillpos - buf); } if (timeout) { /* How long time until the timeout ? */ getntimer(&now); selecttmo.tv_sec = cutoff.tv_sec - now.tv_sec; selecttmo.tv_usec = (cutoff.tv_nsec - now.tv_nsec) / 1000; if (selecttmo.tv_usec < 0) { selecttmo.tv_sec--; selecttmo.tv_usec += 1000000; } } FD_ZERO(&fdread); FD_SET(inputfd, &fdread); res = select(inputfd+1, &fdread, NULL, NULL, (timeout ? &selecttmo : NULL)); if (res < 0) { if (errno == EAGAIN) continue; if (errno == EINTR) { dbgprintf("get_xymond_message: Interrupted\n"); *seq = 0; return idlemsg; } /* Some error happened */ ioerror = 1; dbgprintf("get_xymond_message: Returning NULL due to select error %s\n", strerror(errno)); return NULL; } else if (res == 0) { /* * Timeout - return the "idle" message. * NB: If select() was not passed a timeout parameter, this cannot trigger */ *seq = 0; return idlemsg; } else if (FD_ISSET(inputfd, &fdread)) { res = read(inputfd, fillpos, bufleft); if (res < 0) { if ((errno == EAGAIN) || (errno == EINTR)) continue; ioerror = 1; dbgprintf("get_xymond_message: Returning NULL due to read error %s\n", strerror(errno)); return NULL; } else if (res == 0) { /* read() returns 0 --> End-of-file */ ioerror = 1; dbgprintf("get_xymond_message: Returning NULL due to EOF\n"); return NULL; } else { /* * Got data - null-terminate it, and update fillpos */ dbgprintf("Got %d bytes\n", res); *(fillpos+res) = '\0'; fillpos += res; /* Did we get an end-of-message marker ? Then we're done. */ endpos = strstr(endsrch, "\n@@\n"); needmoredata = (endpos == NULL); /* * If not done, update endsrch. We need to look at the * last 3 bytes of input we got - they could be "\n@@" so * all that is missing is the final "\n". */ if (needmoredata && (res >= 3)) endsrch = fillpos-3; } } } } /* We have a complete message between startpos and endpos */ result = startpos; *endpos = '\0'; if (truncated) { startpos = fillpos = buf; endpos = NULL; } else { startpos = endpos+4; /* +4 because we skip the "\n@@\n" end-marker from the previous message */ endpos = strstr(startpos, "\n@@\n"); /* To see if we already have a full message loaded */ /* fillpos stays where it is */ } /* Check that it really is a message, and not just some garbled data */ if (strncmp(result, "@@", 2) != 0) { errprintf("Dropping (more) garbled data\n"); goto startagain; } { /* * Get and check the message sequence number. * We dont do this for network based workers, since the * sequence number is globally generated (by xymond) * but a network-based worker may only see some of the * messages (those that are not handled by other network-based * worker modules). */ char *p = result + strcspn(result, "#/|\n"); if (*p == '#') { *seq = atoi(p+1); if (debug) { p = strchr(result, '\n'); if (p) *p = '\0'; dbgprintf("%s: Got message %u %s\n", id, *seq, result); if (p) *p = '\n'; } if ((seqnum == 0) || (*seq == (seqnum + 1))) { /* First message, or the correct sequence # */ seqnum = *seq; } else if (*seq == seqnum) { /* Duplicate message - drop it */ errprintf("%s: Duplicate message %d dropped\n", id, *seq); goto startagain; } else { /* Out-of-sequence message. Cant do much except accept it */ if (!locatorid) errprintf("%s: Got message %u, expected %u\n", id, *seq, seqnum+1); seqnum = *seq; } if (seqnum == 999999) seqnum = 0; } } /* Verify checksum - except for truncated messages, where it won't match since we overwrite bytes with the end-marker */ if (result && !truncated) { static struct digestctx_t *ctx = NULL; char *hashstr; if (!ctx) { ctx = (digestctx_t *) malloc(sizeof(digestctx_t)); ctx->digestname = strdup("md5"); ctx->digesttype = D_MD5; ctx->mdctx = (void *)malloc(myMD5_Size()); } hashstr = result + strcspn(result, ":#/|\n"); if (*hashstr == ':') { unsigned char md_value[16]; char md_string[2*16+1]; int i; char *p; myMD5_Init(ctx->mdctx); myMD5_Update(ctx->mdctx, result, (hashstr - result)); myMD5_Update(ctx->mdctx, hashstr + 33, strlen(hashstr + 33)); myMD5_Update(ctx->mdctx, "\n@@\n", 4); /* Stripped earlier */ myMD5_Final(md_value, ctx->mdctx); for(i = 0, p = md_string; (i < sizeof(md_value)); i++) p += sprintf(p, "%02x", md_value[i]); *p = '\0'; if (memcmp(hashstr+1, md_string, 32) != 0) { p = strchr(result, '\n'); if (p) *(p+1) = '\0'; errprintf("get_xymond_message: Invalid checksum, skipping message '%s'\n", result); result = NULL; goto startagain; } } } dbgprintf("startpos %ld, fillpos %ld, endpos %ld\n", (startpos-buf), (fillpos-buf), (endpos ? (endpos-buf) : -1)); return result; }
void do_tcp_tests(int timeout, int concurrency) { int selres; fd_set readfds, writefds; struct timespec timestamp; int absmaxconcurrency; int activesockets = 0; /* Number of allocated sockets */ int pending = 0; /* Total number of tests */ tcptest_t *nextinqueue; /* Points to the next item to start testing */ tcptest_t *firstactive; /* Points to the first item currently being tested */ /* Thus, active connections are between firstactive..nextinqueue */ tcptest_t *item; int sockok; int maxfd; int res; socklen_t connressize; char msgbuf[4096]; struct rlimit lim; /* If timeout or concurrency are 0, set them to reasonable defaults */ if (timeout == 0) timeout = 10; /* seconds */ /* * Decide how many tests to run in parallel. * If no --concurrency set by user, default to (FD_SETSIZE / 4) - typically 256. * But never go above the ressource limit that is set, or above FD_SETSIZE. * And we save 10 fd's for stdio, libs etc. */ absmaxconcurrency = (FD_SETSIZE - 10); getrlimit(RLIMIT_NOFILE, &lim); if ((lim.rlim_cur > 10) && ((lim.rlim_cur - 10) < absmaxconcurrency)) absmaxconcurrency = (lim.rlim_cur - 10); if (concurrency == 0) concurrency = (FD_SETSIZE / 4); if (concurrency > absmaxconcurrency) concurrency = absmaxconcurrency; dbgprintf("Concurrency evaluation: rlim_cur=%lu, FD_SETSIZE=%d, absmax=%d, initial=%d\n", lim.rlim_cur, FD_SETSIZE, absmaxconcurrency, concurrency); if (shuffletests) { struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); srandom(tv.tv_usec); } /* How many tests to do ? */ for (item = thead; (item); item = item->next) { if (shuffletests) item->randomizer = random(); pending++; } if (shuffletests) thead = msort(thead, tcptest_compare, tcptest_getnext, tcptest_setnext); firstactive = nextinqueue = thead; dbgprintf("About to do %d TCP tests running %d in parallel, abs.max %d\n", pending, concurrency, absmaxconcurrency); while (pending > 0) { int slowrunning, cclimit; time_t slowtimestamp = gettimer() - SLOWLIMSECS; /* * First, see if we need to allocate new sockets and initiate connections. */ /* * We start by counting the number of tests where the latest activity * happened more than SLOWLIMSECS seconds ago. These are ignored when counting * how many more tests we can start concurrenly. But never exceed the absolute * max. number of concurrently open sockets possible. */ for (item=firstactive, slowrunning = 0; (item != nextinqueue); item=item->next) { if ((item->fd > -1) && (item->lastactive < slowtimestamp)) slowrunning++; } cclimit = concurrency + slowrunning; if (cclimit > absmaxconcurrency) cclimit = absmaxconcurrency; sockok = 1; while (sockok && nextinqueue && (activesockets < cclimit)) { /* * We need to allocate a new socket that has O_NONBLOCK set. */ nextinqueue->fd = socket(PF_INET, SOCK_STREAM, 0); sockok = (nextinqueue->fd != -1); if (sockok) { /* Set the source address */ if (nextinqueue->srcaddr) { struct sockaddr_in src; int isip; memset(&src, 0, sizeof(src)); src.sin_family = PF_INET; src.sin_port = 0; isip = (inet_aton(nextinqueue->srcaddr, (struct in_addr *) &src.sin_addr.s_addr) != 0); if (!isip) { char *envaddr = getenv(nextinqueue->srcaddr); isip = (envaddr && (inet_aton(envaddr, (struct in_addr *) &src.sin_addr.s_addr) != 0)); } if (isip) { res = bind(nextinqueue->fd, (struct sockaddr *)&src, sizeof(src)); if (res != 0) errprintf("WARNING: Could not bind to source IP %s for test %s: %s\n", nextinqueue->srcaddr, nextinqueue->tspec, strerror(errno)); } else { errprintf("WARNING: Invalid source IP %s for test %s, using default\n", nextinqueue->srcaddr, nextinqueue->tspec); } } res = fcntl(nextinqueue->fd, F_SETFL, O_NONBLOCK); if (res == 0) { /* * Initiate the connection attempt ... */ getntimer(&nextinqueue->timestart); nextinqueue->lastactive = nextinqueue->timestart.tv_sec; nextinqueue->cutoff = nextinqueue->timestart.tv_sec + timeout + 1; res = connect(nextinqueue->fd, (struct sockaddr *)&nextinqueue->addr, sizeof(nextinqueue->addr)); /* * Did it work ? */ if ((res == 0) || ((res == -1) && (errno == EINPROGRESS))) { /* This is OK - EINPROGRES and res=0 pick up status in select() */ activesockets++; tcp_stats_connects++; } else if (res == -1) { /* connect() failed. Flag the item as "not open" */ nextinqueue->connres = errno; nextinqueue->open = 0; nextinqueue->errcode = CONTEST_ENOCONN; close(nextinqueue->fd); nextinqueue->fd = -1; pending--; switch (nextinqueue->connres) { /* These may happen if connection is refused immediately */ case ECONNREFUSED : break; case EHOSTUNREACH : break; case ENETUNREACH : break; case EHOSTDOWN : break; /* Not likely ... */ case ETIMEDOUT : break; /* These should not happen. */ case EBADF : errprintf("connect returned EBADF!\n"); break; case ENOTSOCK : errprintf("connect returned ENOTSOCK!\n"); break; case EADDRNOTAVAIL: errprintf("connect returned EADDRNOTAVAIL!\n"); break; case EAFNOSUPPORT : errprintf("connect returned EAFNOSUPPORT!\n"); break; case EISCONN : errprintf("connect returned EISCONN!\n"); break; case EADDRINUSE : errprintf("connect returned EADDRINUSE!\n"); break; case EFAULT : errprintf("connect returned EFAULT!\n"); break; case EALREADY : errprintf("connect returned EALREADY!\n"); break; default : errprintf("connect returned %d, errno=%d\n", res, errno); } } else { /* Should NEVER happen. connect returns 0 or -1 */ errprintf("Strange result from connect: %d, errno=%d\n", res, errno); } } else { /* Could net set to non-blocking mode! Hmmm ... */ sockok = 0; errprintf("Cannot set O_NONBLOCK\n"); } nextinqueue=nextinqueue->next; } else { int newconcurrency = ((activesockets > 5) ? (activesockets-1) : 5); /* Could not get a socket */ switch (errno) { case EPROTONOSUPPORT: errprintf("Cannot get socket - EPROTONOSUPPORT\n"); break; case EAFNOSUPPORT : errprintf("Cannot get socket - EAFNOSUPPORT\n"); break; case EMFILE : errprintf("Cannot get socket - EMFILE\n"); break; case ENFILE : errprintf("Cannot get socket - ENFILE\n"); break; case EACCES : errprintf("Cannot get socket - EACCESS\n"); break; case ENOBUFS : errprintf("Cannot get socket - ENOBUFS\n"); break; case ENOMEM : errprintf("Cannot get socket - ENOMEM\n"); break; case EINVAL : errprintf("Cannot get socket - EINVAL\n"); break; default : errprintf("Cannot get socket - errno=%d\n", errno); break; } if (newconcurrency != concurrency) { errprintf("Reducing --concurrency setting from %d to %d\n", concurrency, newconcurrency); concurrency = newconcurrency; } } } /* Ready to go - we have a bunch of connections being established */ dbgprintf("%d tests pending - %d active tests, %d slow tests\n", pending, activesockets, slowrunning); restartselect: /* * Setup the FDSET's */ FD_ZERO(&readfds); FD_ZERO(&writefds); maxfd = -1; for (item=firstactive; (item != nextinqueue); item=item->next) { if (item->fd > -1) { /* * WRITE events are used to signal that a * connection is ready, or it has been refused. * READ events are only interesting for sockets * that have already been found to be open, and * thus have the "readpending" flag set. * * So: On any given socket, we want either a * write-event or a read-event - never both. */ if (item->readpending) FD_SET(item->fd, &readfds); else FD_SET(item->fd, &writefds); if (item->fd > maxfd) maxfd = item->fd; } } if (maxfd == -1) { /* No active connections */ if (activesockets == 0) { /* This can happen, if we get an immediate CONNREFUSED on all connections. */ continue; } else { errprintf("contest logic error: No FD's, active=%d, pending=%d\n", activesockets, pending); continue; } } /* * Wait for something to happen: connect, timeout, banner arrives ... */ if (maxfd < 0) { errprintf("select - no active fd's found, but pending is %d\n", pending); selres = 0; } else { struct timeval tmo = { 1, 0 }; dbgprintf("Doing select with maxfd=%d\n", maxfd); selres = select((maxfd+1), &readfds, &writefds, NULL, &tmo); dbgprintf("select returned %d\n", selres); } if (selres == -1) { int selerr = errno; /* * select() failed - this is BAD! */ switch (selerr) { case EINTR : errprintf("select failed - EINTR\n"); goto restartselect; case EBADF : errprintf("select failed - EBADF\n"); break; case EINVAL: errprintf("select failed - EINVAL\n"); break; case ENOMEM: errprintf("select failed - ENOMEM\n"); break; default : errprintf("Unknown select() error %d\n", selerr); break; } /* Leave this mess ... */ errprintf("Aborting TCP tests with %d tests pending\n", pending); return; } /* selres == 0 (timeout) isn't special - just go through the list of active tests */ /* Fetch the timestamp so we can tell how long the connect took */ getntimer(×tamp); /* Now find out which connections had something happen to them */ for (item=firstactive; (item != nextinqueue); item=item->next) { if (item->fd > -1) { /* Only active sockets have this */ if (timestamp.tv_sec > item->cutoff) { /* * Request timed out. */ if (item->readpending) { /* Final read timeout - just shut this socket */ socket_shutdown(item); item->errcode = CONTEST_ETIMEOUT; } else { /* Connection timeout */ item->open = 0; item->errcode = CONTEST_ETIMEOUT; } get_totaltime(item, ×tamp); close(item->fd); item->fd = -1; activesockets--; pending--; if (item == firstactive) firstactive = item->next; } else { if (FD_ISSET(item->fd, &writefds)) { int do_talk = 1; unsigned char *outbuf = NULL; unsigned int outlen = 0; item->lastactive = timestamp.tv_sec; if (!item->open) { /* * First time here. * * Active response on this socket - either OK, or * connection refused. * We determine what happened by getting the SO_ERROR status. * (cf. select_tut(2) manpage). */ connressize = sizeof(item->connres); res = getsockopt(item->fd, SOL_SOCKET, SO_ERROR, &item->connres, &connressize); item->open = (item->connres == 0); if (!item->open) item->errcode = CONTEST_ENOCONN; do_talk = item->open; get_connectiontime(item, ×tamp); } if (item->open && (item->svcinfo->flags & TCP_SSL)) { /* * Setup the SSL connection, if not done already. * * NB: This can be triggered many times, as setup_ssl() * may need more data from the remote and return with * item->sslrunning == SSLSETUP_PENDING */ if (item->sslrunning == SSLSETUP_PENDING) { setup_ssl(item); if (item->sslrunning == 1) { /* * Update connectiontime to include * time for SSL handshake. */ get_connectiontime(item, ×tamp); } } do_talk = (item->sslrunning == 1); } /* * Connection succeeded - port is open, if SSL then the * SSL handshake is complete. * * If we have anything to send then send it. * If we want the banner, set the "readpending" flag to initiate * select() for read()'s. * NB: We want the banner EITHER if the GET_BANNER flag is set, * OR if we need it to match the expect string in the servicedef. */ item->readpending = (do_talk && !item->silenttest && ( (item->svcinfo->flags & TCP_GET_BANNER) || item->svcinfo->exptext )); if (do_talk) { if (item->telnetnegotiate && item->telnetbuflen) { /* * Return the telnet negotiate data response */ outbuf = item->telnetbuf; outlen = item->telnetbuflen; } else if (item->sendtxt && !item->silenttest) { outbuf = item->sendtxt; outlen = (item->sendlen ? item->sendlen : strlen(outbuf)); } if (outbuf && outlen) { /* * It may be that we cannot write all of the * data we want to. Tough ... */ res = socket_write(item, outbuf, outlen); tcp_stats_written += res; if (res == -1) { /* Write failed - this socket is done. */ dbgprintf("write failed\n"); item->readpending = 0; item->errcode = CONTEST_EIO; } else if (item->svcinfo->flags & TCP_HTTP) { /* * HTTP tests require us to send the full buffer. * So adjust sendtxt/sendlen accordingly. * If no more to send, switch to read-mode. */ item->sendtxt += res; item->sendlen -= res; item->readpending = (item->sendlen == 0); } } } /* If closed and/or no bannergrabbing, shut down socket */ if (item->sslrunning != SSLSETUP_PENDING) { if (!item->open || !item->readpending) { if (item->open) { socket_shutdown(item); } close(item->fd); get_totaltime(item, ×tamp); if (item->finalcallback) item->finalcallback(item->priv); item->fd = -1; activesockets--; pending--; if (item == firstactive) firstactive = item->next; } } } else if (FD_ISSET(item->fd, &readfds)) { /* * Data ready to read on this socket. Grab the * banner - we only do one read (need the socket * for other tests), so if the banner takes more * than one cycle to arrive, too bad! */ int wantmoredata = 0; int datadone = 0; item->lastactive = timestamp.tv_sec; /* * We may be in the process of setting up an SSL connection */ if (item->sslrunning == SSLSETUP_PENDING) setup_ssl(item); if (item->sslrunning == SSLSETUP_PENDING) break; /* Loop again waiting for more data */ /* * Connection is ready - plain or SSL. Read data. */ res = socket_read(item, msgbuf, sizeof(msgbuf)-1); tcp_stats_read += res; dbgprintf("read %d bytes from socket\n", res); if ((res > 0) && item->datacallback) { datadone = item->datacallback(msgbuf, res, item->priv); } if ((res > 0) && item->telnetnegotiate) { /* * telnet data has telnet options first. * We must negotiate the session before we * get the banner. */ item->telnetbuf = item->banner; item->telnetbuflen = res; /* * Safety measure: Dont loop forever doing * telnet options. * This puts a maximum on how many times * we go here. */ item->telnetnegotiate--; if (!item->telnetnegotiate) { dbgprintf("Max. telnet negotiation (%d) reached for host %s\n", MAX_TELNET_CYCLES, inet_ntoa(item->addr.sin_addr)); } if (do_telnet_options(item)) { /* Still havent seen the session banner */ item->banner = NULL; item->bannerbytes = 0; item->readpending = 0; wantmoredata = 1; } else { /* No more options - we have the banner */ item->telnetnegotiate = 0; } } if ((item->svcinfo->flags & TCP_HTTP) && ((res > 0) || item->sslagain) && (!datadone) ) { /* * HTTP : Grab the entire response. */ wantmoredata = 1; } if (!wantmoredata) { if (item->open) { socket_shutdown(item); } item->readpending = 0; close(item->fd); get_totaltime(item, ×tamp); if (item->finalcallback) item->finalcallback(item->priv); item->fd = -1; activesockets--; pending--; if (item == firstactive) firstactive = item->next; } } } } } /* end for loop */ } /* end while (pending) */ dbgprintf("TCP tests completed normally\n"); }
int dns_start_query(myconn_t *rec, char *targetserver) { struct ares_addr_node *srvr, *servers = NULL; struct ares_options options; int status, optmask; char *tdup, *tst; /* See what IP family we must use to communicate with the target DNS server */ srvr = malloc(sizeof(struct ares_addr_node)); append_addr_list(&servers, srvr); srvr->family = -1; #ifdef IPV4_SUPPORT if ((srvr->family == -1) && (inet_pton(AF_INET, targetserver, &srvr->addr.addr4) > 0)) srvr->family = AF_INET; #endif #ifdef IPV6_SUPPORT if ((srvr->family == -1) && (inet_pton(AF_INET6, targetserver, &srvr->addr.addr6) > 0)) srvr->family = AF_INET6; #endif if (srvr->family == -1) { errprintf("Unsupported IP family for DNS target IP %s, test %s\n", targetserver, rec->testspec); return 0; } /* Create a new ARES request channel */ rec->dnschannel = malloc(sizeof(ares_channel)); /* * The C-ARES timeout handling is a bit complicated. The timeout setting * here in the options only determines the timeout for the first query; * subsequent queries (up to the "tries" count) use a progressively * higher timeout setting. * So we cannot easily determine what combination of timeout/tries will * result in the full query timing out after the desired number of seconds. * Therefore, use a fixed set of values - the 2000 ms / 4 tries combination * results in a timeout after 23-24 seconds. */ optmask = ARES_OPT_FLAGS | ARES_OPT_SERVERS | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES; options.flags = ARES_FLAG_NOCHECKRESP; options.servers = NULL; options.nservers = 0; options.timeout = 2000; options.tries = 4; status = ares_init_options(rec->dnschannel, &options, optmask); if (status != ARES_SUCCESS) { errprintf("Cannot create ARES channel for DNS target %s, test %s\n", targetserver, rec->testspec); rec->dnsstatus = DNS_FINISHED; return 0; } /* Point the channel at the target server */ status = ares_set_servers(*((ares_channel *)rec->dnschannel), servers); destroy_addr_list(servers); if (status != ARES_SUCCESS) { errprintf("Cannot select ARES target DNS server %s, test %s\n", targetserver, rec->testspec); rec->dnsstatus = DNS_QUERY_COMPLETED; /* To reap the ARES channel later */ return 0; } /* Post the queries we want to perform */ tdup = strdup(rec->testspec); tst = strtok(tdup, ","); do { char *p, *tlookup; int atype = dns_atype; p = strchr(tst, ':'); tlookup = (p ? p+1 : tst); if (p) { *p = '\0'; atype = dns_name_type(tst); *p = ':'; } /* Use ares_query() here, since we dont want to get results from hosts file or other odd stuff. */ ares_query(*((ares_channel *)rec->dnschannel), tlookup, dns_aclass, atype, dns_query_callback, rec); tst = strtok(NULL, ","); } while (tst); xfree(tdup); rec->textlog = newstrbuffer(0); getntimer(&rec->dnsstarttime); rec->dnsstatus = DNS_QUERY_ACTIVE; return 1; }
void run_ldap_tests(service_t *ldaptest, int sslcertcheck, int querytimeout) { #ifdef HAVE_LDAP ldap_data_t *req; testitem_t *t; struct timespec starttime; struct timespec endtime; /* Pick a sensible default for the timeout setting */ if (querytimeout == 0) querytimeout = 30; for (t = ldaptest->items; (t); t = t->next) { LDAPURLDesc *ludp; LDAP *ld; int rc, finished; int msgID = -1; struct timeval ldaptimeout; struct timeval openldaptimeout; LDAPMessage *result; LDAPMessage *e; strbuffer_t *response; char buf[MAX_LINE_LEN]; req = (ldap_data_t *) t->privdata; if (req->skiptest) continue; ludp = (LDAPURLDesc *) req->ldapdesc; getntimer(&starttime); /* Initiate session with the LDAP server */ dbgprintf("Initiating LDAP session for host %s port %d\n", ludp->lud_host, ludp->lud_port); if( (ld = ldap_init(ludp->lud_host, ludp->lud_port)) == NULL ) { dbgprintf("ldap_init failed\n"); req->ldapstatus = XYMON_LDAP_INITFAIL; continue; } /* * There is apparently no standard way of defining a network * timeout for the initial connection setup. */ #if (LDAP_VENDOR == OpenLDAP) && defined(LDAP_OPT_NETWORK_TIMEOUT) /* * OpenLDAP has an undocumented ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv) */ openldaptimeout.tv_sec = querytimeout; openldaptimeout.tv_usec = 0; ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &openldaptimeout); #else /* * So using an alarm() to interrupt any pending operations * seems to be the least insane way of doing this. * * Note that we must do this right after ldap_init(), as * any operation on the session handle (ld) may trigger the * network connection to be established. */ connect_timeout = 0; signal(SIGALRM, ldap_alarmhandler); alarm(querytimeout); #endif /* * This is completely undocumented in the OpenLDAP docs. * But apparently it is documented in * http://www.ietf.org/proceedings/99jul/I-D/draft-ietf-ldapext-ldap-c-api-03.txt * * Both of these routines appear in the <ldap.h> file * from OpenLDAP 2.1.22. Their use to enable TLS has * been deciphered from the ldapsearch() utility * sourcecode. * * According to Manon Goo <*****@*****.**>, recent (Jan. 2005) * OpenLDAP implementations refuse to talk LDAPv2. */ #ifdef LDAP_OPT_PROTOCOL_VERSION { int protocol = LDAP_VERSION3; dbgprintf("Attempting to select LDAPv3\n"); if ((rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol)) != LDAP_SUCCESS) { dbgprintf("Failed to select LDAPv3, trying LDAPv2\n"); protocol = LDAP_VERSION2; if ((rc = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol)) != LDAP_SUCCESS) { req->output = strdup(ldap_err2string(rc)); req->ldapstatus = XYMON_LDAP_TLSFAIL; } continue; } } #endif if (req->usetls) { dbgprintf("Trying to enable TLS for session\n"); if ((rc = ldap_start_tls_s(ld, NULL, NULL)) != LDAP_SUCCESS) { dbgprintf("ldap_start_tls failed\n"); req->output = strdup(ldap_err2string(rc)); req->ldapstatus = XYMON_LDAP_TLSFAIL; continue; } } if (!connect_timeout) { msgID = ldap_simple_bind(ld, (t->host->ldapuser ? t->host->ldapuser : ""), (t->host->ldappasswd ? t->host->ldappasswd : "")); } /* Cancel any pending alarms */ alarm(0); signal(SIGALRM, SIG_DFL); /* Did we connect? */ if (connect_timeout || (msgID == -1)) { req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = "Cannot connect to server"; continue; } /* Wait for bind to complete */ rc = 0; finished = 0; ldaptimeout.tv_sec = querytimeout; ldaptimeout.tv_usec = 0L; while( ! finished ) { int rc2; rc = ldap_result(ld, msgID, LDAP_MSG_ONE, &ldaptimeout, &result); dbgprintf("ldap_result returned %d for ldap_simple_bind()\n", rc); if(rc == -1) { finished = 1; req->ldapstatus = XYMON_LDAP_BINDFAIL; if (result == NULL) { errprintf("LDAP library problem - NULL result returned\n"); req->output = strdup("LDAP BIND failed\n"); } else { rc2 = ldap_result2error(ld, result, 1); req->output = strdup(ldap_err2string(rc2)); } ldap_unbind(ld); } else if (rc == 0) { finished = 1; req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = strdup("Connection timeout"); ldap_unbind(ld); } else if( rc > 0 ) { finished = 1; if (result == NULL) { errprintf("LDAP library problem - got a NULL resultcode for status %d\n", rc); req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = strdup("LDAP library problem: ldap_result2error returned a NULL result for status %d\n"); ldap_unbind(ld); } else { rc2 = ldap_result2error(ld, result, 1); if(rc2 != LDAP_SUCCESS) { req->ldapstatus = XYMON_LDAP_BINDFAIL; req->output = strdup(ldap_err2string(rc)); ldap_unbind(ld); } } } } /* ... while() */ /* We're done connecting. If something went wrong, go to next query. */ if (req->ldapstatus != 0) continue; /* Now do the search. With a timeout */ ldaptimeout.tv_sec = querytimeout; ldaptimeout.tv_usec = 0L; rc = ldap_search_st(ld, ludp->lud_dn, ludp->lud_scope, ludp->lud_filter, ludp->lud_attrs, 0, &ldaptimeout, &result); if(rc == LDAP_TIMEOUT) { req->ldapstatus = XYMON_LDAP_TIMEOUT; req->output = strdup(ldap_err2string(rc)); ldap_unbind(ld); continue; } if( rc != LDAP_SUCCESS ) { req->ldapstatus = XYMON_LDAP_SEARCHFAILED; req->output = strdup(ldap_err2string(rc)); ldap_unbind(ld); continue; } getntimer(&endtime); response = newstrbuffer(0); sprintf(buf, "Searching LDAP for %s yields %d results:\n\n", t->testspec, ldap_count_entries(ld, result)); addtobuffer(response, buf); for(e = ldap_first_entry(ld, result); (e != NULL); e = ldap_next_entry(ld, e) ) { char *dn; BerElement *ber; char *attribute; char **vals; dn = ldap_get_dn(ld, e); sprintf(buf, "DN: %s\n", dn); addtobuffer(response, buf); /* Addtributes and values */ for (attribute = ldap_first_attribute(ld, e, &ber); (attribute != NULL); attribute = ldap_next_attribute(ld, e, ber) ) { if ((vals = ldap_get_values(ld, e, attribute)) != NULL) { int i; for(i = 0; (vals[i] != NULL); i++) { sprintf(buf, "\t%s: %s\n", attribute, vals[i]); addtobuffer(response, buf); } } /* Free memory used to store values */ ldap_value_free(vals); } /* Free memory used to store attribute */ ldap_memfree(attribute); ldap_memfree(dn); if (ber != NULL) ber_free(ber, 0); addtobuffer(response, "\n"); } req->ldapstatus = XYMON_LDAP_OK; req->output = grabstrbuffer(response); tvdiff(&starttime, &endtime, &req->duration); ldap_msgfree(result); ldap_unbind(ld); ldap_free_urldesc(ludp); } #endif }