#include #include #include #include #include #include #include #include #include #include #include #include #include "netconsole.h" #include "netdumpclient.h" #include "configuration.h" #include "server.h" #define USE_SAME_DIR_TIMEOUT 60 typedef struct { time_t time; char *path; } CrashDir; GHashTable *clients; /* Maps ip -> NetdumpClient */ GHashTable *dirs; /* Maps ip -> time + directory */ int master_socket; #define BUFLEN 10000 #define IP(a,b,c,d) ((d) | ((c) << 8) | ((b) << 16) | ((a) << 24)) void remove_client (NetdumpClient *client) { g_hash_table_remove (clients, client); } static const char * get_dir_from_ip (guint32 ip, gboolean create_new) { CrashDir *dir; time_t now; int res; now = time (NULL); dir = g_hash_table_lookup (dirs, GUINT_TO_POINTER (ip)); if (dir && (now - dir->time) > USE_SAME_DIR_TIMEOUT && create_new) { g_hash_table_remove (dirs, GUINT_TO_POINTER(ip)); g_free (dir->path); g_free (dir); dir = NULL; } if (dir == NULL) { struct tm *tm; char date[128]; tm = localtime (&now); strftime (date, sizeof (date), "%Y-%m-%d-%H:%M", tm); dir = g_new (CrashDir, 1); dir->time = now; dir->path = g_strdup_printf ("%s/%d.%d.%d.%d-%s", config.dumpdirprefix, (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip) & 0xff, date); res = mkdir(dir->path, S_IRWXU); if (res) { char *msg = g_strdup_printf ("Error making crashdump directory %s", dir->path); syslog (LOG_ERR, msg); g_free (msg); g_free (dir->path); g_free (dir); return NULL; } g_hash_table_insert (dirs, GUINT_TO_POINTER(ip), dir); } return dir->path; } int execute_script (const char *script, guint32 ip, const char *dir) { struct stat sbuf; char *filename; char *cmdline; int res = -1; filename = g_strconcat (config.dumpdirprefix, "/scripts/", script, NULL); if (stat (filename, &sbuf) == 0) { cmdline = g_strdup_printf ("%s %d.%d.%d.%d '%s'", filename, (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip >> 0) & 0xff, (dir)?dir:""); res = system (cmdline); g_free (cmdline); } g_free (filename); return res; } guint hex_value (gchar c) { if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; return c - '0'; } static gboolean load_magic (guint32 ip, guchar magic[MAGIC_SIZE]) { char *path; FILE *file; char buffer[1024]; size_t size; int i; if (!config.secure) { memset(magic, 1, MAGIC_SIZE); return TRUE; } path = g_strdup_printf ("%s/magic/%d.%d.%d.%d", config.dumpdirprefix, (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip) & 0xff); file = fopen (path, "r"); g_free(path); if (file == NULL) return FALSE; size = fread(buffer, 1, sizeof(buffer), file); fclose (file); if (size < MAGIC_SIZE*2) return FALSE; for (i=0;i 0) just_reboot = TRUE; else just_reboot = FALSE; if (!just_reboot && (g_hash_table_size (clients) > config.max_concurrent_dumps)) { char *msg = g_strdup_printf ("Too many concurrent netdumps, ignoring dump request from %d.%d.%d.%d\n", (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, (ip >> 0) & 0xff); syslog (LOG_WARNING, msg); g_free (msg); just_reboot = TRUE; } client = netdump_client_new (ip, dir, magic, just_reboot, reply, msg); g_hash_table_insert (clients, client, client); } } /* * Here we use offset to tell if this is a fresh system boot. The client * will always start at 0 and increment from there. If 0, we create a new * directory for this log (and potential crash). If non-zero, we use the * existing directory in /var/crash if it exists. */ static void write_log (guint32 ip, char *logline, int len, unsigned int offset) { const char *dir; dir = get_dir_from_ip (ip, !offset); if (dir) { char *file; int fd; int res; file = g_strconcat (dir, "/log", NULL); fd = open (file, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR| S_IWUSR); g_free (file); if (fd != -1) { res = write (fd, logline, len); close (fd); } } } static gboolean master_socket_packet (GIOChannel *channel, GIOCondition cond, gpointer callback_data) { NetdumpClient *client, key; unsigned char buf[BUFLEN]; int len; struct sockaddr_in addr; int addr_len = sizeof(addr); int fd = g_io_channel_unix_get_fd (channel); len = recvfrom (fd, buf, BUFLEN, 0, (struct sockaddr *)&addr, &addr_len); if (len < 0) { syslog (LOG_ERR, "Error receiving from master socket: recvfrom returned: %d", len); return TRUE; } key.ip = ntohl (addr.sin_addr.s_addr); #if 0 g_print ("got packet from %d.%d.%d.%d\n", (key.ip >> 24) & 0xff, (key.ip >> 16) & 0xff, (key.ip >> 8) & 0xff, (key.ip >> 0) & 0xff); #endif client = g_hash_table_lookup (clients, &key); if (client) { if (client->process_packet) (client->process_packet) (client, &addr, buf, len); } else { reply_t reply; guchar magic[MAGIC_SIZE]; if (!load_magic(key.ip, magic)) { /* don't log anything, as an attacker could fill up the logs */ return TRUE; } if (len < HEADER_LEN || !VERSION_VERIFIED(buf[0])) { syslog (LOG_WARNING, "Got strange packet from ip %d.%d.%d.%d\n", (key.ip >> 24) & 0xff, (key.ip >> 16) & 0xff, (key.ip >> 8) & 0xff, (key.ip >> 0) & 0xff); return TRUE; } /* first byte is the netconsole version number */ memcpy ((char *)&reply, buf + 1, sizeof(reply_t)); if (ntohl (reply.code) == REPLY_START_NETDUMP) { handle_new_client (key.ip, magic, &reply, &buf[HEADER_LEN]); } else if (ntohl (reply.code) == REPLY_LOG) { char *startup_message = "[...network console startup...]\n"; if ((len - HEADER_LEN) == strlen(startup_message) && strncmp (startup_message, buf + HEADER_LEN, strlen(startup_message)) == 0) execute_script ("netdump-start", key.ip, NULL); write_log(key.ip, buf + HEADER_LEN, len - HEADER_LEN, reply.info); } else { syslog (LOG_WARNING, "Got unexpected packet type %d from ip %d.%d.%d.%d\n", ntohl (reply.code), (key.ip >> 24) & 0xff, (key.ip >> 16) & 0xff, (key.ip >> 8) & 0xff, (key.ip >> 0) & 0xff); return TRUE; } } return TRUE; } static void setup_pidfile(void) { if (config.pidfile) { char pid[32]; /* more than enough for an int... */ int pidfd; sprintf(pid, "%d\n", getpid()); pidfd = open(config.pidfile, O_RDWR|O_CREAT|O_TRUNC, 0644); write(pidfd, pid, strlen(pid)); close(pidfd); } } static void delete_pidfile(void) { if (config.pidfile) { unlink(config.pidfile); } } static void cleanup_and_exit(int signal) { delete_pidfile(); exit(0); } int main (int argc, char *argv[]) { struct sockaddr_in saddr; char *str; GIOChannel *channel; GMainLoop *loop; struct sigaction sact;/* used to initialize the signal handler */ config_init (argc, argv); openlog("netdump", LOG_PID, LOG_DAEMON); if (chdir (config.dumpdirprefix) == -1) { syslog (LOG_ERR, "can't cd to %s", config.dumpdirprefix); fprintf (stderr, "can't cd to %s\n", config.dumpdirprefix); exit (1); } if (config.daemon) { daemon(1, 0); } setup_pidfile(); /* Do after daemon() call */ /* set the signal handler to quit cleanly. */ sact.sa_handler = cleanup_and_exit; sigemptyset (&sact.sa_mask); sact.sa_flags = 0; sigaction(SIGINT, &sact, NULL); sigaction(SIGTERM, &sact, NULL); clients = g_hash_table_new (netdump_client_hash, netdump_client_compare); dirs = g_hash_table_new (g_direct_hash, g_direct_equal); master_socket = socket (PF_INET, SOCK_DGRAM, IPPROTO_IP); if (master_socket < 0) { syslog (LOG_ERR, "Couldn't allocate master socket"); exit (1); } memset (&saddr, 0, sizeof (saddr)); saddr.sin_family = PF_INET; saddr.sin_port = htons (config.port); saddr.sin_addr.s_addr = INADDR_ANY; if (bind (master_socket, (struct sockaddr *) &saddr, sizeof (saddr)) < 0) { str = g_strdup_printf ("Couldn't bind master socket to port %d", config.port); syslog (LOG_ERR, str); g_free (str); exit (1); } channel = g_io_channel_unix_new (master_socket); g_io_add_watch (channel, G_IO_IN | G_IO_HUP, master_socket_packet, NULL); g_io_channel_unref (channel); loop = g_main_new (TRUE); g_main_run (loop); if (config.pidfile != NULL) free(config.pidfile); if (config.dumpdirprefix != NULL) free(config.dumpdirprefix); return 0; } /* * Verify that a new client's netdump_magic number matches * its associated /var/crash/magic/ value. */ gboolean verify_magic (NetdumpClient *client) { int i; u64 value; unsigned char check_magic[MAGIC_SIZE]; if (!config.secure) return TRUE; value = client->netdump_magic; for (i = MAGIC_SIZE-1; i >= 0; i--) { check_magic[i] = value & 0xffULL; value >>= 8; } for (i = 0; i < MAGIC_SIZE; i++) { if (check_magic[i] != client->magic_value[i]) { syslog (LOG_ERR, "Got invalid magic number (%llx) from client %s", (long long)client->netdump_magic, client->ip_addr); return FALSE; } } return TRUE; } /* * Allow a back-door to avoid the filesystem space check. */ gboolean perform_space_check(void) { return config.space_check; }