/* System monitor daemon: sends to remote machines information on the system */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int sysinfo(struct sysinfo *info) { int res; asm("int $0x80" : "=a" (res) : "a" (__NR_sysinfo), "b" (info) ); if (res < 0) { errno = -res; return -1; } else { return 0; } } #include "sysmon.h" /*************************************************************************/ /* (Encrypted) password. This is compared to the encrypted version of the * password sent in the first packet on a connection. */ #define PASSWORD "xxxxxxxxxxxxx" /* Copy of command-line arguments and environment for execve() */ char **argv, **env; /* Run in debug mode? */ int debug = 0; /* Name of configuration file */ char *config_file = "/etc/sysmond.conf"; /* List of allowed remote IP addresses. NOTE: This is not IPV6-aware. */ int allowed_ip_count = 0; int *allowed_ips; /* List of remote IPs that can modify the system. */ int modify_ip_count = 0; int *modify_ips; /*************************************************************************/ /* Information about connections. */ typedef struct { /* General stuff */ int num; /* Index into clients[] */ int sock; /* Socket descriptor */ int flags; /* Connection flags; see CF_* below */ int silenttime; /* When the socket last went silent */ /* For reading queries */ int nread; /* Number of bytes read so far */ char intbuf[4]; /* For reading ints */ int query_len; /* Length of query */ char *query; /* Buffer for query */ int query_bufsize; /* malloc'd size of query buffer */ int seen_magic; /* Have we seen a QUERY_MAGIC value? */ int seen_len; /* Have we seen a query length? */ /* For writing responses */ int nwritten; /* Number of bytes written so far */ int resp_len; /* Length of response to write */ char *response; /* Response to write */ int resp_bufsize; /* Size of response buffer */ int wrote_magic; /* Have we written the magic value? */ int wrote_len; /* Have we written the response length? */ } Connection; #define CF_OPEN 1 /* Connection is open */ #define CF_MODIFY 2 /* Connection may modify system */ #define CF_REGISTERED 4 /* Connection sent correct password */ #define CF_WRITING 0x10000 /* Write in progress */ #define CF_DELAYED_CLOSE 0x20000 /* Close after write finishes */ /* Maximum size for query/response buffers in connection structures. If we * have to allocate a larger one, then we free it when we're done with it * and reallocate later. */ #define MAX_BUFFER 4096 /* Maximum number of connections to allow. */ #define MAX_CONNECTIONS 128 /* Array of connections. */ Connection clients[MAX_CONNECTIONS]; /* Number of active connections. */ int nclients = 0; /* Socket descriptor for listen socket. -1 means this is closed. */ int listen_sock = -1; /* Port to listen on */ int listen_port = DEFAULT_PORT; /* Maximum length of the listen queue */ #define LISTEN_QUEUE 5 /*************************************************************************/ /*************************************************************************/ /* Special debugging stuff to save what we read and write. */ static inline int my_read(int fd, char *buf, size_t size) { int n = read(fd, buf, size); if (debug >= 3) { printf("read %d of %d bytes from fd %d\n", n, size, fd); if (n > 0) { int fd = open("input", O_WRONLY | O_APPEND | O_CREAT, 0600); write(fd, buf, n); close(fd); } } return n; } #define read my_read static inline int my_write(int fd, char *buf, size_t size) { int n = write(fd, buf, size); if (debug >= 3) { printf("wrote %d of %d bytes to fd %d\n", n, size, fd); if (n > 0) { int fd = open("output", O_WRONLY | O_APPEND | O_CREAT, 0600); write(fd, buf, n); close(fd); } } return n; } #define write my_write /*************************************************************************/ /********************* Connection-handling routines **********************/ /*************************************************************************/ /* Open the listen socket, if possible and if it's not open already. * Return whether it was opened. */ int open_listener() { struct sockaddr_in sin; struct protoent *pe; int on = 1; if (listen_sock >= 0) return 1; sin.sin_family = AF_INET; memset(&sin.sin_addr, 0, sizeof(sin.sin_addr)); sin.sin_port = htons(listen_port); pe = getprotobyname("tcp"); if (!pe) return 0; if ((listen_sock = socket(AF_INET, SOCK_STREAM, pe->p_proto)) < 0) return 0; fcntl(listen_sock, F_SETFD, 1); if (setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0) { int errno_save = errno; close(listen_sock); listen_sock = -1; errno = errno_save; return 0; } if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { int errno_save = errno; close(listen_sock); listen_sock = -1; errno = errno_save; return 0; } if (listen(listen_sock, LISTEN_QUEUE) < 0) { int errno_save = errno; close(listen_sock); listen_sock = -1; errno = errno_save; return 0; } return 1; } /*************************************************************************/ /* Close the listener socket if it's open. */ void close_listener() { if (listen_sock < 0) return; close(listen_sock); listen_sock = -1; } /*************************************************************************/ /* Accept a connection on the listener socket. If it's not from an * authorized source, drop it immediately. */ void accept_conn() { struct sockaddr_in sin; int len, sock, ip, i; /* Accept connection, set close-on-exec and non-blocking flags, and get * remote IP address. */ len = sizeof(sin); sock = accept(listen_sock, (struct sockaddr *)&sin, &len); if (sock < 0) return; fcntl(sock, F_SETFD, 1); fcntl(sock, F_SETFL, O_NONBLOCK); ip = *(int *)&sin.sin_addr; /* Is it allowed? */ for (i = 0; i < allowed_ip_count; i++) { if (ip == allowed_ips[i]) break; } if (i == allowed_ip_count) { /* i.e. not in allowed list */ close(sock); return; } /* Find an open connection. If there isn't one, drop the connection * and close the listen socket. */ for (i = 0; i < MAX_CONNECTIONS; i++) { if (!(clients[i].flags & CF_OPEN)) break; } if (i == MAX_CONNECTIONS) { close(sock); close_listener(); return; } if (debug) printf("Client %d (socket %d) is %s\n", i, sock, inet_ntoa(sin.sin_addr)); /* Set up the connection structure. */ memset(&clients[i], 0, sizeof(Connection)); clients[i].num = i; clients[i].flags = CF_OPEN; clients[i].sock = sock; clients[i].silenttime = -1; /* Increment the number of active connections. If this reaches the * maximum number of connections, close the listen socket. */ nclients++; if (nclients == MAX_CONNECTIONS) close_listener(); } /*************************************************************************/ /* Close the given connection. */ void close_conn(Connection *conn) { if (!(conn->flags & CF_OPEN)) return; if (debug) printf("Closing connection to client %d\n", conn-clients); close(conn->sock); if (conn->query) free(conn->query); if (conn->response) free(conn->response); memset(conn, 0, sizeof(*conn)); nclients--; /* Reopen the listener if it was closed due to too many connections */ open_listener(); } /*************************************************************************/ /* Attempt to read a query from the given connection. Return 1 if we read * a complete query (or finish reading one that was incomplete), else 0. */ int read_query(Connection *conn) { struct timeval tv = {0,0}; fd_set fds; int sock = conn->sock; int magic, len, nread; FD_ZERO(&fds); FD_SET(sock, &fds); while (select(sock+1, &fds, NULL, NULL, &tv) == 1) { if (!conn->seen_magic) { nread = read(sock, conn->intbuf+conn->nread, 4-conn->nread); if (nread <= 0 && errno != EINTR) { /* Supposedly select() will keep us from trying to read off * a silent socket. It doesn't seem to be doing that, * though, so don't kill a client connection if we get * EAGAIN, unless it keeps doing that for >10 seconds. */ if (errno == EAGAIN) { if (conn->silenttime < 0) conn->silenttime = time(NULL); else if (time(NULL) >= conn->silenttime+10) close_conn(conn); } else close_conn(conn); return 0; } conn->silenttime = -1; conn->nread += nread; if (conn->nread != 4) continue; magic = ntohl(*(int *)conn->intbuf); if (magic == QUERY_MAGIC) { conn->seen_magic = 1; conn->nread = 0; } else { /* Try one byte farther in the stream next time; don't try * it this time--this keeps us from getting too bogged down * trying to process a stream of garbage */ conn->nread--; memmove(conn->intbuf, conn->intbuf+1, 3); return 0; } } if (!conn->seen_len) { nread = read(sock, conn->intbuf+conn->nread, 4-conn->nread); if (nread <= 0 && errno != EINTR) { if (errno == EAGAIN) { if (conn->silenttime < 0) conn->silenttime = time(NULL); else if (time(NULL) >= conn->silenttime+10) close_conn(conn); } else close_conn(conn); return 0; } conn->nread += nread; if (conn->nread != 4) continue; len = conn->query_len = ntohl(*(int *)conn->intbuf); conn->seen_len = 1; conn->nread = 0; if (len > conn->query_bufsize) { if (conn->query) free(conn->query); conn->query = malloc(len); if (!conn->query) { /* Abort reading this query and return failure */ conn->query_bufsize = 0; conn->seen_magic = 0; conn->seen_len = 0; return 0; } conn->query_bufsize = len; } } nread = read(sock, conn->query+conn->nread, conn->query_len-conn->nread); if (nread <= 0 && errno != EINTR) { if (errno == EAGAIN) { if (conn->silenttime < 0) conn->silenttime = time(NULL); else if (time(NULL) >= conn->silenttime+10) close_conn(conn); } else close_conn(conn); return 0; } conn->nread += nread; if (conn->nread == conn->query_len) { /* Finished this query; clear flags for next time around, and * return success */ conn->seen_magic = 0; conn->seen_len = 0; conn->nread = 0; return 1; } } /* while (select() == 1) */ /* If we got here, we didn't finish reading the query */ return 0; } /*************************************************************************/ /* Attempt to write any current response to the given communication * channel. */ void write_response(Connection *conn) { struct timeval tv = {0,0}; fd_set fds; int sock = conn->sock; char intbuf[4]; int nwritten; FD_ZERO(&fds); FD_SET(sock, &fds); while (select(sock+1, NULL, &fds, NULL, &tv) == 1) { if (!conn->wrote_magic) { *(int *)intbuf = htonl(RESPONSE_MAGIC); nwritten = write(sock, intbuf+conn->nwritten, 4-conn->nwritten); if (nwritten == 0 || (nwritten < 0 && errno != EINTR)) { close_conn(conn); return; } conn->nwritten += nwritten; if (conn->nwritten != 4) continue; conn->wrote_magic = 1; conn->nwritten = 0; } if (!conn->wrote_len) { *(int *)intbuf = htonl(conn->resp_len); nwritten = write(sock, intbuf+conn->nwritten, 4-conn->nwritten); if (nwritten == 0 || (nwritten < 0 && errno != EINTR)) { close_conn(conn); return; } conn->nwritten += nwritten; if (conn->nwritten != 4) continue; conn->wrote_len = 1; conn->nwritten = 0; } nwritten = write(sock, conn->response + conn->nwritten, conn->resp_len - conn->nwritten); if (nwritten == 0 || (nwritten < 0 && errno != EINTR)) { close_conn(conn); return; } conn->nwritten += nwritten; if (conn->nwritten == conn->resp_len) { /* Finished this response; clear flags for next time around */ conn->wrote_magic = 0; conn->wrote_len = 0; conn->nwritten = 0; conn->flags &= ~CF_WRITING; /* Close if there's a delayed close pending */ if (conn->flags & CF_DELAYED_CLOSE) close_conn(conn); return; } } /* while (select() == 1) */ } /*************************************************************************/ /* Send a response to a command. This takes a format string somewhat like * printf(), but only "%d", "%c", and "%s" are allowed; %d translates into * a 32-bit network-order integer, "%c" into a single character (byte), and * %s into a null-terminated string. */ void reply(Connection *conn, unsigned char query, const char *format, ...) { const char *s; unsigned char *t; int len = 0; va_list args; 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 */ if (len > conn->resp_bufsize) { if (conn->response) free(conn->response); conn->response = malloc(len); if (!conn->response) { conn->resp_bufsize = 0; return; } } t = (unsigned char *)conn->response; *t++ = query; va_start(args, format); s = format; while (*s) { if (*s == '%') { s++; switch (*s++) { case 'd': { unsigned int n = va_arg(args, unsigned int); *t++ = n>>24; *t++ = n>>16; *t++ = n>> 8; *t++ = n; break; } /* case 'd' */ case 'c': { unsigned int n = va_arg(args, unsigned int); *t++ = n; break; } /* case 'c' */ case 's': { const char *str = va_arg(args, const char *); do { *t++ = *str; } while (*str++); break; } /* case 's' */ } /* switch (*s++) */ } else { *t++ = *s++; } } conn->resp_len = len; conn->flags |= CF_WRITING; } /*************************************************************************/ /**************************** Query handlers *****************************/ /*************************************************************************/ /* All query handlers take the connection which generated the query and the * parameter buffer (i.e. the query buffer with the first byte stripped * off), and return a boolean (1 or 0) success value. This is used only by * the daemon and not sent to the client. Most routines should just return * 1; the only one that needs to return a correct value is the handler for * the PASSWORD query, which should return whether the password matched. */ /*************************************************************************/ /* Quick routines to get an int or string out of the parameter buffer. * Pass a pointer to the buffer pointer, which will be updated. */ int getint(char **param) { register unsigned char *s = (unsigned char *) *param; register int val; val = *s++; val = val<<8 | *s++; val = val<<8 | *s++; val = val<<8 | *s++; *param = (char *)s; return val; } char *getstr(char **param) { register char *s = *param; char *ret = s; while (*s++) ; *param = s; return ret; } /*************************************************************************/ int do_password(Connection *conn, char *param) { char *s = crypt(param, PASSWORD); if (strcmp(s, PASSWORD) == 0) { if (debug) printf("[%d] password -> ok\n", conn->num); reply(conn, Q_PASSWORD, "%d", 1); return 1; } else { if (debug) printf("[%d] password -> bad\n", conn->num); reply(conn, Q_PASSWORD, "%d", 0); return 0; } } /*************************************************************************/ int do_close(Connection *conn, char *param) { if (debug >= 1) printf("[%d] close\n", conn->num); close_conn(conn); return 1; } /*************************************************************************/ int do_uptime(Connection *conn, char *param) { struct sysinfo si; int nusers = 0; FILE *f; struct utmp ut; /* Get uptime, number of processes, and load averages. */ sysinfo(&si); /* Multiply load averages by 100 and convert to integer. */ si.loads[0] = (si.loads[0] * 100 + 0x8000) >> 16; si.loads[1] = (si.loads[1] * 100 + 0x8000) >> 16; si.loads[2] = (si.loads[2] * 100 + 0x8000) >> 16; /* Count users. */ f = fopen("/etc/utmp", "r"); if (f) { nusers = 0; while (fread(&ut, sizeof(ut), 1, f) == 1) { if (ut.ut_type == USER_PROCESS && *ut.ut_user) nusers++; } fclose(f); } else { nusers = -1; /* Signal error */ } /* Send info. */ if (debug >= 1) printf("[%d] uptime -> %ld %ld %ld %ld %d %d\n", conn->num, si.uptime, si.loads[0], si.loads[1], si.loads[2], nusers, si.procs); reply(conn, Q_UPTIME, "%d%d%d%d%d%d", si.uptime, si.loads[0], si.loads[1], si.loads[2], nusers, si.procs); return 1; } /*************************************************************************/ int do_diskfree(Connection *conn, char *param) { struct statfs fs; if (statfs(param, &fs) < 0) { if (debug >= 1) printf("[%d] diskfree %s -> error %s\n", conn->num, param, strerror(errno)); reply(conn, Q_ERROR, "%c%s", Q_DISKFREE, strerror(errno)); } else { int blocks = fs.f_blocks, bfree = fs.f_bfree, bavail = fs.f_bavail, inodes = fs.f_files, ifree = fs.f_ffree; if (debug >= 1) printf("[%d] diskfree %s -> %s %d %d %d %d %d %d %d\n", conn->num, param, param, fs.f_bsize, blocks, blocks-bfree, bfree-bavail, inodes, inodes-ifree, 0); reply(conn, Q_DISKFREE, "%s%d%d%d%d%d%d%d", param, fs.f_bsize, blocks, blocks-bfree, bfree-bavail, inodes, inodes-ifree, 0); } return 1; } /*************************************************************************/ int do_quota(Connection *conn, char *param) { if (debug >= 1) printf("[%d] quota\n", conn->num); return 1; } /*************************************************************************/ int do_memory(Connection *conn, char *param) { int memtotal, memfree, shared, buffers, cached, swaptotal, swapfree; char buf[256]; FILE *f; int temp; f = fopen("/proc/meminfo", "r"); if (f) { memtotal = memfree = shared = buffers = cached = 0; swaptotal = swapfree = 0; fgets(buf, sizeof(buf), f); if (*buf == ' ') { fscanf(f, "%*s %d %*d %d %d %d %d ", &memtotal, &memfree, &shared, &buffers, &cached); fscanf(f, "%*s %d %*d %d ", &swaptotal, &swapfree); fgets(buf, sizeof(buf), f); } if (sscanf(buf, "%*s %d kB", &temp) != 0) { do { char *s = strchr(buf, ':'); if (!s) { continue; } *s++ = 0; s += strspn(s, " \t"); if (strcmp(buf, "MemTotal") == 0) { memtotal = strtol(s, NULL, 10); } else if (strcmp(buf, "MemFree") == 0) { memfree = strtol(s, NULL, 10); } else if (strcmp(buf, "Buffers") == 0) { buffers = strtol(s, NULL, 10); } else if (strcmp(buf, "Cached") == 0) { cached = strtol(s, NULL, 10); } else if (strcmp(buf, "SwapTotal") == 0) { swaptotal = strtol(s, NULL, 10); } else if (strcmp(buf, "SwapFree") == 0) { swapfree = strtol(s, NULL, 10); } } while (fgets(buf, sizeof(buf), f)); } else { memtotal /= 1024; memfree /= 1024; shared /= 1024; buffers /= 1024; cached /= 1024; swaptotal /= 1024; swapfree /= 1024; } fclose(f); if (debug >= 1) printf("[%d] memory -> %d %d %d %d %d %d %d\n", conn->num, memtotal, memfree, shared, buffers, cached, swaptotal, swapfree); reply(conn, Q_MEMORY, "%d%d%d%d%d%d%d", memtotal, memfree, shared, buffers, cached, swaptotal, swapfree); return 1; } else { char errbuf[256]; snprintf(errbuf, sizeof(errbuf), "Unable to open /proc/meminfo: %s", strerror(errno)); if (debug >= 1) printf("[%d] memory -> error %s\n", conn->num, errbuf); reply(conn, Q_ERROR, "%c%s", Q_MEMORY, errbuf); return 0; } } /*************************************************************************/ int do_procstat(Connection *conn, char *param) { FILE *f; char fname[PATH_MAX], buf[4096]; int pid = getint(¶m); snprintf(fname, sizeof(fname), "/proc/%d/stat", pid); f = fopen(fname, "r"); if (f) { if (fgets(buf, sizeof(buf), f)) { if (debug >= 1) printf("[%d] procstat %d -> %s\n", conn->num, pid, buf); reply(conn, Q_PROCSTAT, "%s", buf); return 1; } else { if (debug >= 1) printf("[%d] procstat %d -> error Unable to read from %s\n", conn->num, pid, fname); reply(conn, Q_ERROR, "%cUnable to read from %s", Q_PROCSTAT,fname); return 0; } } else { char errbuf[256]; snprintf(errbuf, sizeof(errbuf), "Unable to open %s: %s", buf, strerror(errno)); if (debug >= 1) printf("[%d] procstat %d -> error %s\n", conn->num, pid, errbuf); reply(conn, Q_ERROR, "%c%s", Q_PROCSTAT, errbuf); return 0; } } /*************************************************************************/ int do_procquery(Connection *conn, char *param) { DIR *d; struct dirent *de; char *ptr = conn->response; int size = conn->resp_bufsize; int count = 0; if (!(d = opendir("/proc"))) { if (debug >= 1) printf("[%d] procquery -> error opendir(/proc): %s\n", conn->num, strerror(errno)); reply(conn, Q_ERROR, "%copendir(/proc): %s", Q_PROCQUERY, strerror(errno)); return 0; } /* We create our own response string because we don't know in advance * how long it's going to be */ if (!ptr || size < 6+strlen(param)) { if (ptr) free(ptr); size = 256+strlen(param); ptr = conn->response = malloc(size); if (!ptr) { conn->resp_bufsize = 0; if (debug >= 1) printf("[%d] procquery -> error Out of memory\n", conn->num); reply(conn, Q_ERROR, "%c%s", Q_PROCQUERY, "Out of memory"); return 0; } conn->resp_bufsize = size; } strcpy(ptr, param); ptr += strlen(ptr)+1; *ptr++ = Q_PROCQUERY; ptr += 4; /* Leave space for count of PIDs */ while ((de = readdir(d))) { char buf[256]; FILE *f; int pid, i, j; if (de->d_name[strspn(de->d_name, "0123456789")] != 0) continue; pid = atoi(de->d_name); snprintf(buf, sizeof(buf), "/proc/%s/stat", de->d_name); if (!(f = fopen(buf, "r"))) continue; fgets(buf, sizeof(buf), f); fclose(f); i = 0; while (buf[i] != 0 && buf[i] != '(') i++; if (buf[i] == 0) continue; i++; j = i; while (buf[j] != 0 && buf[j] != ')') j++; if (buf[j] == 0) continue; buf[j] = 0; if (strcmp(buf+i, param) != 0) continue; if (ptr - conn->response > size-4) { int len = ptr - conn->response; conn->response = realloc(conn->response, size+64); if (!conn->response) { conn->resp_bufsize = 0; if (debug >= 1) printf("[%d] procquery -> error Out of memory\n", conn->num); reply(conn, Q_ERROR, "%c%s", Q_PROCQUERY, "Out of memory"); return 0; } size += 64; conn->resp_bufsize = size; ptr = conn->response + len; } *ptr++ = pid>>24; *ptr++ = pid>>16; *ptr++ = pid>> 8; *ptr++ = pid; count++; } closedir(d); conn->resp_len = ptr - conn->response; ptr = conn->response + strlen(param) + 2; *ptr++ = count>>24; *ptr++ = count>>16; *ptr++ = count>> 8; *ptr++ = count; conn->flags |= CF_WRITING; if (debug >= 1) printf("[%d] procquery -> ok\n", conn->num); return 1; } /*************************************************************************/ int do_proclist(Connection *conn, char *param) { DIR *d; struct dirent *de; char *ptr = conn->response; int size = conn->resp_bufsize; int count = 0; if (!(d = opendir("/proc"))) { if (debug >= 1) printf("[%d] proclist -> error opendir(/proc): %s\n", conn->num, strerror(errno)); reply(conn, Q_ERROR, "%copendir(/proc): %s", Q_PROCLIST, strerror(errno)); return 0; } if (!ptr || size < 5) { if (ptr) free(ptr); size = 257; ptr = conn->response = malloc(size); if (!ptr) { conn->resp_bufsize = 0; if (debug >= 1) printf("[%d] proclist -> error Out of memory\n", conn->num); reply(conn, Q_ERROR, "%c%s", Q_PROCLIST, "Out of memory"); return 0; } conn->resp_bufsize = size; } *ptr++ = Q_PROCLIST; ptr += 4; while ((de = readdir(d))) { int pid; if (de->d_name[strspn(de->d_name, "0123456789")] != 0) continue; pid = atoi(de->d_name); if (ptr - conn->response > size-4) { int len = ptr - conn->response; conn->response = realloc(conn->response, size+64); if (!conn->response) { conn->resp_bufsize = 0; if (debug >= 1) printf("[%d] proclist -> error Out of memory\n", conn->num); reply(conn, Q_ERROR, "%c%s", Q_PROCLIST, "Out of memory"); return 0; } size += 64; conn->resp_bufsize = size; ptr = conn->response + len; } *ptr++ = pid>>24; *ptr++ = pid>>16; *ptr++ = pid>> 8; *ptr++ = pid; count++; } closedir(d); conn->resp_len = ptr - conn->response; ptr = conn->response + 1; *ptr++ = count>>24; *ptr++ = count>>16; *ptr++ = count>> 8; *ptr++ = count; conn->flags |= CF_WRITING; if (debug >= 1) printf("[%d] proclist -> ok\n", conn->num); return 1; } /*************************************************************************/ int do_kill(Connection *conn, char *param) { int pid, signal; if (!conn->flags & CF_MODIFY) { if (debug >= 1) printf("[%d] kill %d %d -> error Permission denied\n", conn->num, getint(¶m), getint(¶m)); reply(conn, Q_ERROR, "%c%s", Q_KILL, "Permission denied"); return 0; } pid = getint(¶m); signal = getint(¶m); if (kill(pid, signal) < 0) { if (debug >= 1) printf("[%d] kill %d %d -> error %s\n", conn->num, pid, signal, strerror(errno)); reply(conn, Q_ERROR, "%c%s", Q_KILL, strerror(errno)); return 0; } if (debug >= 1) printf("[%d] kill %d %d -> ok\n", conn->num, pid, signal); reply(conn, Q_KILL, ""); return 1; } /*************************************************************************/ int do_exec(Connection *conn, char *param) { char *path, **argv; int argc, i; if (!conn->flags & CF_MODIFY) { if (debug >= 1) printf("[%d] exec %s %d ... -> error Permission denied\n", conn->num, getstr(¶m), getint(¶m)); reply(conn, Q_ERROR, "%c%s", Q_EXEC, "Permission denied"); return 0; } path = getstr(¶m); argc = getint(¶m); argv = calloc(argc+1, sizeof(char *)); if (!argv) { if (debug >= 1) printf("[%d] exec %s %d ... -> error Out of memory\n", conn->num, path, argc); reply(conn, Q_ERROR, "%c%s", Q_EXEC, "Out of memory"); return 0; } for (i = 0; i < argc; i++) argv[i] = getstr(¶m); switch (i = fork()) { case -1: if (debug >= 1) printf("[%d] exec %s %d ... -> error fork(): %s\n", conn->num, path, argc, strerror(errno)); reply(conn, Q_ERROR, "%cfork(): %s", Q_EXEC, strerror(errno)); return 0; case 0: execv(path, argv); exit(-1); } if (debug >= 1) printf("[%d] exec %s %d ... -> %d\n", conn->num, path, argc, i); reply(conn, Q_EXEC, "%d", i); return 1; } /*************************************************************************/ int do_reboot(Connection *conn, char *param) { int quick = getint(¶m); if (!conn->flags & CF_MODIFY) { if (debug >= 1) printf("[%d] reboot %d -> error Permission denied\n", conn->num, quick); reply(conn, Q_ERROR, "%c%s", Q_EXEC, "Permission denied"); return 0; } if (debug >= 1) printf("[%d] reboot %d -> ok\n", conn->num, quick); if (!quick) { struct mntent *mnt; FILE *f; char *mntlist[256]; int mntcnt = 0, i; /* First just try to do a reboot */ execl("/sbin/reboot", "reboot", "-t5", NULL); /* If that fails, do what we can to clean things up */ f = setmntent("/etc/mtab", "r"); if (f) { while ((mnt = getmntent(f))) { if (strcmp(mnt->mnt_type, "nfs") != 0 && mntcnt < 256) mntlist[mntcnt++] = strdup(mnt->mnt_fsname); } endmntent(f); for (i = 0; i < mntcnt; i++) { if (mntlist[i]) umount(mntlist[i]); } } sync(); sleep(3); /* Give the writes time to be flushed out */ } /* The "return" is only needed to make the compiler happy. */ return reboot(RB_AUTOBOOT); } /*************************************************************************/ int do_restart(Connection *conn, char *param) { if (!conn->flags & CF_MODIFY) { if (debug >= 1) printf("[%d] restart -> error Permission denied\n", conn->num); reply(conn, Q_ERROR, "%c%s", Q_RESTART, "Permission denied"); return 0; } if (debug >= 1) printf("[%d] restart -> ok\n", conn->num); raise(SIGHUP); return 0; } /*************************************************************************/ /****************************** Main stuff *******************************/ /*************************************************************************/ void sighandler(int sig) { if (sig == SIGINT || sig == SIGQUIT || sig == SIGTERM || sig == SIGHUP) { int i; close(listen_sock); for (i = 0; i < 128; i++) { if (clients[i].flags & CF_OPEN) close_conn(&clients[i]); } if (sig == SIGHUP) { if (*argv[0] == '/') execve(argv[0], argv, env); else execve("/usr/local/sbin/sysmond", argv, env); } exit(0); } signal(sig, sighandler); } /*************************************************************************/ void usage(const char *progname) { fprintf(stderr, "Usage: %s [-p port]\n", progname); exit(1); } /*************************************************************************/ int read_config() { FILE *f; char buf[1024], *s; int line = 0; f = fopen(config_file, "r"); if (!f) { perror(config_file); return 0; } while (fgets(buf, sizeof(buf), f)) { line++; if (*buf == '#') continue; s = strtok(buf, " \t\r\n"); if (!s) continue; if (strcasecmp(s, "allow") == 0) { int ip0, ip1, ip2, ip3; s = strtok(NULL, " \t\r\n"); if (!s) { fprintf(stderr, "%s:%d: Missing IP address for `allow'\n", config_file, line); return 0; } if (sscanf(s, "%d.%d.%d.%d", &ip0, &ip1, &ip2, &ip3) != 4) { fprintf(stderr, "%s:%d: Malformed IP address for `allow'\n", config_file, line); return 0; } allowed_ips = realloc(allowed_ips, sizeof(int) * (allowed_ip_count+1)); if (!allowed_ips) { fprintf(stderr, "%s:%d: Out of memory\n", config_file, line); return 0; } allowed_ips[allowed_ip_count] = htonl(ip0<<24 | ip1<<16 | ip2<< 8 | ip3 ); while ((s = strtok(NULL, " \t\r\n"))) { if (strcasecmp(s, "modify") == 0) { modify_ips = realloc(modify_ips, sizeof(int) * (modify_ip_count+1)); if (!modify_ips) { fprintf(stderr, "%s:%d: Out of memory\n", config_file, line); return 0; } modify_ips[modify_ip_count] = allowed_ips[allowed_ip_count]; modify_ip_count++; } else { fprintf(stderr, "%s:%d: Unknown option `%s' for `allow'\n", config_file, line, s); return 0; } } allowed_ip_count++; } else { fprintf(stderr, "%s:%d: Unknown directive `%s'\n", config_file, line, s); return 0; } } fclose(f); return 1; } /*************************************************************************/ void init(int ac, char **av, char **envp) { int c; argv = av; env = envp; while ((c = getopt(ac, av, "hp:df:")) != EOF) { switch (c) { case 'p': listen_port = atoi(optarg); break; case 'd': debug++; break; case 'f': config_file = optarg; break; default : usage(av[0]); } } if (!read_config()) exit(1); if (!open_listener()) { perror("Unable to open listen socket"); exit(1); } for (c = 1; c <= NSIG; c++) signal(c, sighandler); if (!debug) { switch (fork()) { case -1: perror("fork()"); exit(1); case 0: if (isatty(0)) { close(0); close(1); close(2); } setpgid(0, 0); break; default: exit(0); } } } /*************************************************************************/ /*************************************************************************/ /* Handle a query on a connection. */ void handle_query(Connection *conn) { unsigned char query; char *param; if (conn->query_len <= 0) return; param = conn->query; query = *param++; if (debug >= 2) printf("Query from connection %d is %d\n", conn->num, query); if (!(conn->flags & CF_REGISTERED) && query != Q_PASSWORD) { if (debug >= 1) printf("[%d] %02X -> error No password supplied\n", conn->num, query); reply(conn, Q_ERROR, "%c%s", query, "No password supplied"); conn->flags |= CF_DELAYED_CLOSE; return; } switch (query) { case Q_PASSWORD: if (do_password(conn, param)) conn->flags |= CF_REGISTERED; else conn->flags |= CF_DELAYED_CLOSE; break; case Q_CLOSE: do_close(conn, param); break; case Q_UPTIME: do_uptime(conn, param); break; case Q_DISKFREE: do_diskfree(conn, param); break; case Q_QUOTA: do_quota(conn, param); break; case Q_MEMORY: do_memory(conn, param); break; case Q_PROCSTAT: do_procstat(conn, param); break; case Q_PROCQUERY: do_procquery(conn, param); break; case Q_PROCLIST: do_proclist(conn, param); break; case Q_KILL: do_kill(conn, param); break; case Q_EXEC: do_exec(conn, param); break; case Q_REBOOT: do_reboot(conn, param); break; case Q_RESTART: do_restart(conn, param); break; default: if (debug >= 1) printf("[%d] %02X -> error Unknown query ID\n", conn->num, query); reply(conn, Q_ERROR, "%c%s", query, "Unknown query ID"); break; } } /*************************************************************************/ /* Check for socket activity, and do things when we find it. */ void check_sockets() { fd_set readfds, writefds; int i, maxfd = 0; FD_ZERO(&readfds); FD_ZERO(&writefds); if (listen_sock >= 0) { FD_SET(listen_sock, &readfds); maxfd = listen_sock; } for (i = 0; i < MAX_CONNECTIONS; i++) { if (clients[i].flags & CF_OPEN) { if (clients[i].flags & CF_WRITING) FD_SET(clients[i].sock, &writefds); else FD_SET(clients[i].sock, &readfds); if (clients[i].sock > maxfd) maxfd = clients[i].sock; } } if (select(maxfd+1, &readfds, &writefds, NULL, NULL) > 0) { if (FD_ISSET(listen_sock, &readfds)) { if (debug >= 2) printf("Accepting connection\n"); accept_conn(); } for (i = 0; i < MAX_CONNECTIONS; i++) { if (!(clients[i].flags & CF_OPEN)) continue; if (FD_ISSET(clients[i].sock, &readfds)) { if (debug >= 2) printf("Reading from client %d\n", i); if (read_query(&clients[i])) { if (debug >= 2) printf("Handling query from client %d\n", i); handle_query(&clients[i]); } } else if (FD_ISSET(clients[i].sock, &writefds)) { if (debug >= 2) printf("Writing to client %d\n", i); write_response(&clients[i]); } } } } /*************************************************************************/ /*************************************************************************/ int main(int ac, char **av, char **envp) { init(ac, av, envp); for (;;) check_sockets(); /* not reached */ } /*************************************************************************/