#include <glib.h>
#include <syslog.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <ctype.h>
#include <unistd.h>
#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<MAGIC_SIZE;i++)
{
if (!isxdigit(buffer[2*i]) ||
!isxdigit(buffer[2*i+1]))
return FALSE;
magic[i] = (hex_value (buffer[2*i]) << 4) + hex_value(buffer[2*i+1]);
}
return TRUE;
}
static void
handle_new_client (guint32 ip, guchar *magic, reply_t *reply, char *msg)
{
NetdumpClient *client;
const char *dir;
gboolean just_reboot;
int res;
dir = get_dir_from_ip (ip, 0);
if (dir)
{
res = execute_script ("netdump-crash", ip, dir);
if (res > 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/<ip> 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;
}
syntax highlighted by Code2HTML, v. 0.9.1