/* Sysmon routines for handling connections to servers */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "etree.h" #include "server.h" #include "socketio.h" #include "sysmon.h" /*************************************************************************/ /* List of all servers. */ static Server *serverlist; /*************************************************************************/ /*************************************************************************/ /* Send a query. See sysmond.c/reply() for details on the format string. * Return 0 if the write failed (e.g. connection closed), else 1. */ static int send_query(int sock, unsigned char query, const char *format, ...) { const char *s; int len = 0; va_list args; char buf[4]; /* for writing integers */ va_start(args, format); s = format; while (*s) { if (*s++ == '%') { switch (*s++) { case 'd': (void) va_arg(args, int); len += 4; break; case 'c': (void) va_arg(args, int); len++; break; case 's': len += strlen(va_arg(args, char *)) + 1; break; } } else { len++; } } len++; /* Query code */ buf[0] = QUERY_MAGIC>>24; buf[1] = QUERY_MAGIC>>16 & 0xFF; buf[2] = QUERY_MAGIC>> 8 & 0xFF; buf[3] = QUERY_MAGIC & 0xFF; if (sock_write(sock, buf, 4) != 4) return 0; buf[0] = len>>24; buf[1] = len>>16; buf[2] = len>> 8; buf[3] = len; if (sock_write(sock, buf, 4) != 4) return 0; buf[0] = query; if (sock_write(sock, buf, 1) != 1) return 0; va_start(args, format); while (*format) { if (*format == '%') { format++; switch (*format++) { case 'd': { unsigned int n = va_arg(args, unsigned int); buf[0] = n>>24; buf[1] = n>>16; buf[2] = n>> 8; buf[3] = n; if (sock_write(sock, buf, 4) != 4) return 0; break; } /* case 'd' */ case 'c': { unsigned int n = va_arg(args, unsigned int); *buf = n; if (sock_write(sock, buf, 1) != 1) return 0; break; } /* case 'c' */ case 's': { s = va_arg(args, const char *); if (sock_write(sock, s, strlen(s)+1) != strlen(s)+1) return 0; break; } /* case 's' */ } /* switch (*format++) */ } else { if (sock_write(sock, format++, 1) != 1) return 0; } } return 1; } /*************************************************************************/ /* Send a query given the raw query buffer and length. Return 0 and close * the socket if the write failed (e.g. connection broken), else 1. */ static int send_raw_query(int sock, unsigned char query, const char *buffer, int len) { char buf[4]; /* for writing integers */ Server *s; for (s = serverlist; s; s = s->next) { if (s->sock == sock) break; } if (!s) { sock_close(sock); return 0; } buf[0] = QUERY_MAGIC>>24; buf[1] = QUERY_MAGIC>>16 & 0xFF; buf[2] = QUERY_MAGIC>> 8 & 0xFF; buf[3] = QUERY_MAGIC & 0xFF; if (sock_write(sock, buf, 4) != 4) { sock_close(sock); s->sock = NO_SOCK; return 0; } buf[0] = (len+1)>>24; buf[1] = (len+1)>>16; buf[2] = (len+1)>> 8; buf[3] = (len+1); if (sock_write(sock, buf, 4) != 4) { sock_close(sock); s->sock = NO_SOCK; return 0; } buf[0] = query; if (sock_write(sock, buf, 1) != 1) { sock_close(sock); s->sock = NO_SOCK; return 0; } if (sock_write(sock, buffer, len) != len) { sock_close(sock); s->sock = NO_SOCK; return 0; } return 1; } /*************************************************************************/ /* Read a response on the socket. Return NULL if no response is ready; if * an error was encountered, close the socket and return NULL. */ static char *read_response(int sock, unsigned char *query_ret) { int magic; char intbuf[8]; Server *s; for (s = serverlist; s; s = s->next) { if (s->sock == sock) break; } if (!s) { sock_close(sock); s->sock = NO_SOCK; return NULL; } if (s->response_len < 0) { if (s->response_buf) { free(s->response_buf); s->response_buf = NULL; } if (sock_read(sock, intbuf, 8) != 8) { if (errno != EAGAIN) { sock_close(sock); s->sock = NO_SOCK; } return NULL; } magic = ntohl(*(int *)intbuf); if (magic != RESPONSE_MAGIC) { sock_close(sock); s->sock = NO_SOCK; return NULL; } s->response_len = ntohl(((int *)intbuf)[1]); s->response_buf = malloc(s->response_len); if (!s->response_buf) { s->response_len = -1; sock_close(sock); s->sock = NO_SOCK; return NULL; } } if (sock_read(sock, s->response_buf, s->response_len) != s->response_len) { if (errno != EAGAIN) { sock_close(sock); s->sock = NO_SOCK; } return NULL; } s->response_len = -1; if (query_ret) *query_ret = s->response_buf[0]; return s->response_buf+1; } /*************************************************************************/ /* Quick routines to get an int or string out of the result buffer. Pass * a pointer to the buffer pointer, which will be updated. */ static int getint(unsigned char **result) { register unsigned char *s = *result; register int val; val = *s++; val = val<<8 | *s++; val = val<<8 | *s++; val = val<<8 | *s++; *result = (char *)s; return val; } static char *getstr(unsigned char **result) { register unsigned char *s = *result; char *ret = (char *) s; while (*s++) ; *result = s; return ret; } /*************************************************************************/ /*************************************************************************/ /* External routines. */ /*************************************************************************/ /* Create a new server record. */ Server *server_new(void) { Server *s = calloc(sizeof(Server), 1); s->next = serverlist; if (serverlist) serverlist->prev = s; serverlist = s; s->sock = NO_SOCK; /* Flag: not connected */ s->response_len = -1; return s; } /*************************************************************************/ /* Return a pointer to the Server structure corresponding to the given name * or alias, or NULL if no such server exists. */ Server *server_find(const char *name) { Server *s; for (s = serverlist; s; s = s->next) { if (s->name && strcasecmp(s->name, name) == 0) return s; } for (s = serverlist; s; s = s->next) { if (s->alias && strcasecmp(s->alias, name) == 0) return s; } return NULL; } /*************************************************************************/ /* Callback for connection success/failure */ static void connect_callback(int sock, int status) { Server *s; for (s = serverlist; s; s = s->next) { if (s->sock == ~sock) break; } if (!s) { sock_close(sock); return; } if (!(status & SOCKSTAT_OPEN)) { close(sock); s->sock = NO_SOCK; return; } s->sock = sock; send_query(sock, Q_PASSWORD, "%s", s->password); s->valid = 0; } /* Attempt to connect to the server. */ void server_conn(Server *s) { struct hostent *he; struct protoent *pe; struct sockaddr_in sin; int sock; if (s->sock >= 0) return; if (!s->name || !s->password) return; if (s->port == 0) s->port = DEFAULT_PORT; if (s->sock == NO_SOCK) { /* Haven't yet tried to connect */ pe = getprotobyname("tcp"); if (!pe) return; he = gethostbyname(s->name); if (!he) return; sock = socket(AF_INET, SOCK_STREAM, pe->p_proto); if (sock < 0) return; sin.sin_family = AF_INET; memcpy(&sin.sin_addr, he->h_addr_list[0], sizeof(sin.sin_addr)); sin.sin_port = htons(s->port); s->sock = ~sock; /* So the callback knows which Server we are */ } else { sock = ~s->sock; } sock_connect(sock, &sin, connect_callback); } /*************************************************************************/ /* Retrieve information about the given server. Tries to connect to the * server if not already connected. Returns 0 if the information could not * be retrieved, else 1. The information is stored in the server record. */ int server_query(Server *s, int query, int nargs, ETree **args) { unsigned char *result; unsigned char q; /* Returned query value */ char *str; /* Connect to the server (if we aren't already) */ server_conn(s); /* Send our request to the server */ switch (query) { case Q_UPTIME: if (!send_query(s->sock, query, "")) { sock_close(s->sock); s->sock = NO_SOCK; return 0; } break; case Q_DISKFREE: if (nargs < 1) return 0; if (!send_query(s->sock, query, "%s", etree_eval_s(args[0]))) { sock_close(s->sock); s->sock = NO_SOCK; return 0; } break; case Q_MEMORY: if (!send_query(s->sock, query, "")) { sock_close(s->sock); s->sock = NO_SOCK; return 0; } break; case Q_EXEC: if (nargs < 1) { return 0; } { char buf[4096], *ptr, *countptr; int count = 0; strncpy(buf, etree_eval_s(args[0]), sizeof(buf)-5); buf[sizeof(buf)-5] = 0; ptr = buf + strlen(buf) + 1; nargs--; args++; countptr = ptr; ptr += 4; while (count < nargs && ptr-buf < sizeof(buf)) { strncpy(ptr, etree_eval_s(args[count]), sizeof(buf) - (ptr-buf)); buf[sizeof(buf)-1] = 0; ptr += strlen(ptr) + 1; count++; } *countptr++ = count>>24; *countptr++ = count>>16; *countptr++ = count>> 8; *countptr++ = count; if (!send_raw_query(s->sock, query, buf, ptr-buf)) { sock_close(s->sock); s->sock = NO_SOCK; return 0; } } break; case Q_REBOOT: if (nargs < 1) return 0; if (!send_query(s->sock, query, "%d", etree_eval_i(args[0]))) { sock_close(s->sock); s->sock = NO_SOCK; return 0; } break; case Q_RESTART: if (!send_query(s->sock, query, "")) { sock_close(s->sock); s->sock = NO_SOCK; return 0; } break; default: return 0; } /* switch (query) */ /* Read any response from the server */ if (!(result = read_response(s->sock, &q))) return 0; if (!s->valid) { if (q != Q_PASSWORD || !getint(&result)) { sock_close(s->sock); s->sock = NO_SOCK; return 0; } s->valid = 1; if (!(result = read_response(s->sock, &q))) return 0; } switch (q) { case Q_UPTIME: gettimeofday(&s->last_uptime_update, NULL); s->uptime = getint(&result); s->load1 = getint(&result); s->load5 = getint(&result); s->load15 = getint(&result); s->nusers = getint(&result); s->nprocs = getint(&result); break; case Q_DISKFREE: { DiskInfo *di; int i; str = getstr(&result); for (i = 0, di = s->disks; i < MAX_DISKS; i++, di++) { if (strcmp(di->name, str) == 0) break; } if (i == MAX_DISKS) { for (i = 0, di = s->disks; i < MAX_DISKS; i++, di++) { if (!*di->name) break; } } if (i == MAX_DISKS) { memmove(s->disks, s->disks+1, sizeof(s->disks)-sizeof(*di)); i--; } gettimeofday(&di->last_update, NULL); strncpy(di->name, str, sizeof(di->name)-1); di->name[sizeof(di->name)-1] = 0; di->bsize = getint(&result); di->blocks = getint(&result); di->bused = getint(&result); di->bresv = getint(&result); di->inodes = getint(&result); di->iused = getint(&result); di->iresv = getint(&result); break; } case Q_MEMORY: gettimeofday(&s->last_memory_update, NULL); s->memtotal = getint(&result); s->memfree = getint(&result); s->shared = getint(&result); s->buffers = getint(&result); s->cached = getint(&result); s->swaptotal = getint(&result); s->swapfree = getint(&result); break; case Q_EXEC: break; default: return 0; } /* switch (q) */ return 1; } /*************************************************************************/