Mailing List Archive

NessusClient/nessus plugin_cache.h, NONE, 1.1 plugin_cache.c, NONE, 1.1 nessus_plugin.h, 1.1.1.1, 1.2 nessus_plugin.c, 1.1.1.1, 1.2 nessus.h, 1.1.1.1, 1.2 comm.c, 1.12, 1.13 Makefile, 1.10, 1.11
Update of /usr/local/cvs/NessusClient/nessus
In directory raccoon.nessus.org:/tmp/cvs-serv70899/nessus

Modified Files:
nessus_plugin.h nessus_plugin.c nessus.h comm.c Makefile
Added Files:
plugin_cache.h plugin_cache.c
Log Message:
Implement a cache for plugin information

The bulk of the caching code is in the new file
nessus/nessus_plugin.c. Most of the other changes are in comm.c
to extend the protocol to ask the server for plugin md5sums and
to update the information read from the cache.

Currently the cache is always active.


--- NEW FILE: plugin_cache.h ---
/* NessusClient -- the Nessus Client
* Copyright (C) 2005 Renaud Deraison
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#ifndef _NESSUSC_PLUGIN_CACHE_H
#define _NESSUSC_PLUGIN_CACHE_H

int plugin_cache_write(struct context*, const char *);
int plugin_cache_read(struct context *, const char *);


#endif

--- NEW FILE: plugin_cache.c ---
/* NessusClient -- the Nessus Client
* Copyright (C) 2005, 2006 Renaud Deraison
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/* Cache for the plugin information read from the server
*
* File format
* -----------
*
* A plugin cache file consists of a number of lines, each holding a
* number of fields. The fields are separated by vertical bar
* characters ('|'). The first field is a keyword looking like a
* C-language identifier describing the type of the line, the rest of
* the fields are %-escaped strings. Percent escaped means that
* non-printable ascii characters, i.e. anything with ascii code < 32 or
* >= 127, as well as % and | are replaced with a % followed by two
* hexadecimal digits giving the ascii code of the original character.
* E.g. "%" is turned into "%25".
*
* Since the | is escaped too, the only |-characters occurring in a line
* are the field separators. So decoding can be done by first replacing
* all | with NUL and then %-unescaping each field. Unescaping never
* makes the string longer and thus can be done in place.
*
* There are several kinds of lines, distinguished by the keyword:
*
* NessusClientPluginCache
*
* This is only and always used on the first line and also serves as
* a magic string identifying the file. The following fields are in
* order:
*
* - the version number of the file format in decimal. Currently
* there is only version 0.
*
* - the hex-encoded md5sum that was reported by the server for the
* set of plugins saved in the cache.
*
* plugin
*
* Plugin desription. The rest of the fields are: id, md5sum, name,
* category, copyright, description, summary, family, version, cve,
* bid, xrefs.
*
* end
*
* No further fields. This line marks the end of the file. If it's
* missing, the cache hasn't been written properly and should be
* considered outdated.
*
*
* Files used
* ----------
*
* A cache file is specific for a given context and is stored in the
* same directory as the nessusrc file for the context. The cache for
* the global context is ~/.nessus_plugin_cache. If an alternate
* nessurc file was given on the command line, no caching is done.
*/

#include <includes.h>

#include "nessus_plugin.h"
#include "error_dialog.h"
#include "context.h"
#include "comm.h"
#include "preferences.h"
#include "globals.h"
#include "plugin_cache.h"


/* file format constants */
#define MAX_HEADER_ITEMS 3
#define MAX_LINE_ITEMS 13
#define HEADER_MAGIC "NessusClientPluginCache"
#define FILE_FORMAT_VERSION 0
#define PLUGIN_KEYWORD "plugin"
#define END_KEYWORD "end"

/* Determine the cache file to use for the context.
*
* The cache file will be in the same directory as the nessusrc file of
* the context. The return value has to be free's with efree.
*/
char *
plugin_cache_get_filename(struct context *context)
{
char *filename = NULL;

if (context->dir)
{
filename = emalloc(strlen(context->dir) + strlen("/nessus_plugin_cache")+1);
sprintf(filename, "%s/nessus_plugin_cache", context->dir);
}
else
{
if (Alt_rcfile)
/* if an alternate rc file was given on the command line, we do
* not know where to store the plugin cache, so we don't cache the
* plugin descriptions at all */
filename = NULL;
else
{
char *home = prefs_get_nessushome();

filename = emalloc(strlen(home) + strlen("/.nessus_plugin_cache") + 1);
sprintf(filename, "%s/.nessus_plugin_cache", home);
}
}

return filename;
}


/* Write a string to the cache file
*
* The parameter FILE is the stream to be used to write to the cache
* file. S is the NUL-terminated string to be written. The bytes of S
* are percent escaped (see file format description above).
*
* The return value is 0 on success and != 0 otherwise.
*/
static int
write_string(FILE *file, const char *s)
{
const char *p;

for (p = s; *p; p++)
{
if (*p >= 0x20 && *p < 127 && *p != '|' && *p != '%')
putc(*p, file);
else
fprintf(file, "%%%02x", *(unsigned char*)p);
}

return ferror(file) ? -1 : 0;
}

/* Write a record to the cache file
*
* The parameter FILE is be the stream to be used to write to the cache
* file. FORMAT is a string containing one character for each of the
* fields following the format parameter. Two field types are
* supported:
*
* i The value must be an int. It written in a decimal.
* s The value must be a NUL-terminated string.
* The string percent escaped while being written
* k The value must be a NUL-terminated string.
* The string is copied literally to the file
* and is not percent escaped.
*
* The values are written in percent-escaped form, separated by vertical
* bars, followed by a newline to end the line/record.
*
* The return value is 0 on success and != 0 otherwise.
*/
/* TODO: check for write errors */
static int
write_record(FILE *file, const char *format, ...)
{
va_list ap;
const char *p;
int failure = 0;
int write_delimiter = 0;

va_start(ap, format);

for (p = format; *p && !failure; p++)
{
if (write_delimiter)
{
failure = putc('|', file) == EOF;
if (failure)
break;
}

switch (*p) {
case 's':
failure = write_string(file, va_arg(ap, const char *));
break;
case 'k':
/* Ideally we should check whether the contents of the string
* match the definition of "keyword" in the fileformat */
failure = fputs(va_arg(ap, const char *), file) == EOF;
break;
case 'i':
failure = fprintf(file, "%d", va_arg(ap, int)) < 0;
break;

default:
/* Error because format character is unsupported */
failure = -1;
}
write_delimiter = 1;
}
if (!failure)
failure = putc('\n', file) == EOF;

va_end(ap);

return failure;
}


/* Write a plugin to the cache file
*
* The parameter PLUGIN is the plugin to write and FILE is be the stream
* to be used to write to the cache file. The plugin is written as a
* single record as described in the file format description.
*
* The return value is 0 on success and != 0 otherwise.
*/
static int
write_plugin(struct nessus_plugin *plugin, FILE *file)
{
char * md5sum = plugin->md5sum;
if (md5sum == NULL)
md5sum = "";

return write_record(file, "kisssssssssss", PLUGIN_KEYWORD,
plugin->id, md5sum, plugin->name, plugin->category, plugin->copyright,
nessus_plugin_get_description(plugin), plugin->summary, plugin->family,
plugin->version, plugin->cve, plugin->bid, plugin->xrefs);
}


/* Write all plugins in the linked list PLUGINS
*
* The parameter PLUGINS is the first plugin in the list of plugins to
* write and FILE is be the stream to be used to write to the cache
* file. Each plugin is written with write_plugin.
*
* The return value is 0 on success and != 0 otherwise.
*/
static int
write_plugin_list(struct nessus_plugin *plugins, FILE *file)
{
while (plugins != NULL)
{
if (write_plugin(plugins, file) != 0)
return -1;
plugins = plugins->next;
}

return 0;
}


/* Write the plugins in CONTEXT to a cache
*
* The parameter CONTEXT is the context whose plugins are to be written.
* The filename for the cache is determined with
* plugin_cache_get_filename.
*
* The return value is 0 on success and != 0 otherwise.
*
* If an error occurs when writing the file, the file is removed to
* avoid incorrect caches lying around.
*/
int
plugin_cache_write(struct context * context, const char * server_md5sum)
{
char *filename;
FILE *file;
int failure = 0;

filename = plugin_cache_get_filename(context);
if (!filename)
{
/* the only failure cause currently is that the user has specified a
* different nessusrc file. In that case no caching takes place.
*/
return 1;
}

file = fopen(filename, "w");
if (!file)
{
show_error("Could not open file '%s' for writing", filename);
efree(&filename);
return -1;
}

failure = (write_record(file, "kis", HEADER_MAGIC, FILE_FORMAT_VERSION,
server_md5sum)
|| write_plugin_list(context->scanners, file)
|| write_plugin_list(context->plugins, file)
|| write_record(file, "k", END_KEYWORD));

if (fclose(file) != 0)
failure = -1;

if (failure)
unlink(filename);
efree(&filename);

return failure;
}


/* Read one line from FILE
*
* The return value is a NUL-terminated string including the trailing
* newline of the line if any. The return value is allocated with
* emalloc and has to be freed by the caller with efree.
*
* If an error occurrs this function returns NULL. When the end of the
* file is reached the function returns an empty string.
*/
static char *
read_line(FILE *file)
{
char *buf;
char *end;
char *cur;
int c;
size_t buf_size;
size_t len;

buf_size = 1024;
buf = emalloc(buf_size);
end = buf + buf_size - 1;
cur = buf;

for (;;)
{
while ((c = getc(file)) != EOF && (*cur++ = c) != '\n' && cur != end)
;
if (c == '\n')
break;
if (c == EOF)
{
if (ferror(file))
goto fail;
break;
}
/* line doesn't fit into the buffer. Realloc and continue */
len = cur - buf;
buf_size += 1024;
buf = erealloc(buf, buf_size);
if (!buf)
return NULL;
end = buf + buf_size - 1;
cur = buf + len;
}

*cur = '\0';
return buf;

fail:
efree(&buf);
return NULL;
}


/* Split LINE at the vertical bars
*
* The NUL-terminated string LINE is split into fields at the vertical
* bar characters ('|') by replacing the vertical bars with NUL
* characters and writing pointers to the start of each field into the
* array ITEMS. The ITEMS array must be provided by the caller. The
* parameter NITEMS should be the length of ITEMS and it must be at
* least 1. No more than NITEMS values will be put into ITEMS by this
* function. If LINE ends in a newline, that newline character will
* also be replaced by NUL.
*
* The return value is the number of fields found in the line. This
* number may be larger than NITEMS in which case the fields beyond the
* first NITEMS fields won't be accesssible to the caller.
*/
static int
split_line(char * line, char **items, int nitems)
{
int found = 0;
char *cur = line;

/* the first item is always at the beginning of the line */
items[0] = line;
found = 1;

while (*cur)
{
if (*cur == '|')
{
*cur = '\0';
if (found < nitems)
{
items[found] = cur + 1;
}
found++;
}
cur++;
}

/* cur now points at the final NUL character. If there's a newline
* just in front of it, remove it */
if (cur > line && cur[-1] == '\n')
{
cur[-1] = '\0';
}

return found;
}

/* Macro that determines the numerical value of a hex digit. */
#define HEXDECODE(c) \
(('0' <= (c) && c <= '9') ? (c) - '0' \
: ('A' <= (c) && c <= 'F') ? (c) - 'A' + 10\
: ('a' <= (c) && c <= 'f') ? (c) - 'a' + 10\
: -1)

/* percent-unquote the NUL-terminated string S.
*
* Unquoting is done in place.
* The return value is 0 on success and != 0 otherwise.
*/
static int
unquote(char * s)
{
char * from = s;
char * to = s;

while ((*to++ = *from++))
{
if (from[-1] == '%')
{
/* we copied a '%'. decode it. */
int hi, lo;

if ( (hi = HEXDECODE(from[0])) >= 0
&& (lo = HEXDECODE(from[1])) >= 0)
{
to[-1] = hi << 4 | lo;
from += 2;
}
else
{
/* TODO: error */
return -1;
}
}
}

return 0;
}


/* Read a line from the cache file and decode it
*
* The parameter FILE is the stread to the read the cache line from.
* The parameters ITEMS and NITEMS are passed through to split_line and
* have the same semantics.
*
* This function reads a line from FILE with read_line and splits it
* using split_line. Each item is then decoded with unquote.
*
* When the function is successful, the return value is the actual
* number of fields in the line as determined by split_line and thus may
* be larger than NITEMS. items[0] is always the same as the pointer
* returned by read_line and the caller has to release this memory as
* documented for read_line.
*
* When end of file is reached, the return value is 0. ITEMS will not
* be modified.
*
* When an error occurs, the return value is -1. The ITEMS array may
* have been modified already in that case, but the memory the items now
* point to is invalid.
*/
static int
read_cache_line(FILE * file, char **items, int nitems)
{
int real_nitems;
char *line = read_line(file);
int i;

if (line == NULL)
return -1;

/* if the line is not empty, decode it */
if (line[0])
{
real_nitems = split_line(line, items, nitems);

/* The keyword is not quoted, so do not unquote it */
for (i = 1; i < real_nitems; i++)
{
if (unquote(items[i]) != 0)
{
real_nitems = -1;
break;
}
}
}
else
/* empty string, i.e. EOF */
real_nitems = 0;

if (real_nitems <= 0)
efree(&line);

return real_nitems;
}


/* Read the file header and check the md5sum
*
* The file header is the first line of the file with the md5sum for the
* md5sum originally reported by the server.
*
* If the header could not be read successfully, the function returns a
* value < 0.
*
* If the header could be read, but the md5sums are different, the
* function returns 1.
*
* if the header could be read and the md5sums are equal, the function
* returns 0.
*/
static int
check_header(FILE * file, const char * server_md5sum)
{
char *items[MAX_HEADER_ITEMS];
int nitems = read_cache_line(file, items, MAX_HEADER_ITEMS);
int result = 0;

if (nitems <=0)
{
/* error or EOF. A file without a header is always an error,
* though. */
return -1;
}

/* the header line always has exactly three fields with the first
* being the magic string, so anything else is an error */
if (nitems == 3)
{
char *tail;
long version = strtol(items[1], &tail, 10);
if (*tail != '\0')
{
version = -1;
}

if (strcmp(items[0], HEADER_MAGIC) != 0)
{
result = -1;
}
else if (version != FILE_FORMAT_VERSION)
{
result = -1;
}
else
{
/* compare the md5sums */
if (strcmp(server_md5sum, items[2]) == 0)
{
result = 0;
}
else
{
result = 1;
}
}
}
else
{
result = -1;
}

efree(&items[0]);
return result;
}

/* Read the plugin cache if it's still current
*
* CONTEXT is the context into which the cache should be read.
* SERVER_MD5SUM should be the string with the hex-encoded md5sum
* reported by the server while connecting. This md5sum is compared to
* the md5sum read from the plugin cache for the context. If they match
* the plugins of the context are read from the cache. Otherwise the
* context object is not changed at all.
*
* If the return value is 0 the cache was current and the plugin
* information has been read from the cache successfully. If the return
* value is <0 and error occurred. If it's >0 the cache was not current
* and the plugins have not been read.
*/
int
plugin_cache_read(struct context * context, const char * server_md5sum)
{
int result = 0;
int is_current = 0;
int end_found = 0;
char *filename;
FILE *file;

filename = plugin_cache_get_filename(context);
if (!filename)
{
/* the only failure cause currently is that the user has specified a
* different nessusrc file. In that case no caching takes place.
*/
return 1;
}

file = fopen(filename, "r");
if (!file)
{
/* the file could not be opened for reading. This is normal if the
* cache simply doesn't exist yet, so we don't give an error
* message.
*/
result = -1;
goto fail;
}

/* check the header and compare md5sums */
result = is_current = check_header(file, server_md5sum);
if (result < 0)
goto fail;

/* read the cache even if it's outdated so that we hopefully only have
* to fetch individual plugins later */
result = 0;

/* read the rest of the lines */
while (result == 0)
{
char *items[MAX_LINE_ITEMS];
int nitems = read_cache_line(file, items, MAX_LINE_ITEMS);

if (nitems < 0)
{
/* error */
result = -1;
break;
}

if (nitems == 0)
{
/* EOF. If the "end" line hasn't been read yet it's an error */
if (!end_found)
{
result = -1;
}
break;
}

/* If the line is not empty but we've already read the end-line,
* it's an error */
if (end_found)
{
result = -1;
efree(&items[0]);
break;
}

if (nitems == 13 && strcmp(items[0], PLUGIN_KEYWORD) == 0)
{
/* items[1] is the plugin's md5sum which is not used at the moment */
struct nessus_plugin *plugin = nessus_plugin_new(items[1] /*id*/,
items[3] /*name*/, items[4] /*category*/, items[5] /*copyright*/,
items[6] /*description*/, items[7] /*summary*/,
items[8] /*family*/, items[9] /*version*/, items[10] /*cve*/,
items[11] /*bid*/, items[12] /*xref*/);

/* add the md5sum */
nessus_plugin_set_md5sum(plugin, items[2]);

context_add_plugin(context, plugin);
}
else if (nitems == 1 && strcmp(items[0], END_KEYWORD) == 0)
{
end_found = 1;
}
else
{
/* TODO: free plugins in case of error */
result = -1;
}

efree(&items[0]);
}

fail:
efree(&filename);
if (file != NULL)
fclose(file);
return result ? result : is_current;
}

Index: nessus_plugin.h
===================================================================
RCS file: /usr/local/cvs/NessusClient/nessus/nessus_plugin.h,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -d -r1.1.1.1 -r1.2
--- nessus_plugin.h 10 Sep 2005 17:36:42 -0000 1.1.1.1
+++ nessus_plugin.h 3 Feb 2006 21:00:49 -0000 1.2
@@ -3,7 +3,7 @@
int id;
char * asc_id;
char * name;
-
+ char * md5sum;

char * category;
char * copyright;
@@ -25,6 +25,7 @@

struct nessus_plugin * next;
int enabled:1;
+ int is_current:1; /* used for the cache */
};


@@ -34,6 +35,7 @@
struct nessus_plugin * nessus_plugin_get_by_name( struct nessus_plugin * plugins, char * name);
struct nessus_plugin * nessus_plugin_get_by_id( struct nessus_plugin * plugins, int id);

+void nessus_plugin_set_md5sum(struct nessus_plugin * plugin, const char * md5sum);

void nessus_plugin_free(struct nessus_plugin * plugins);


Index: nessus_plugin.c
===================================================================
RCS file: /usr/local/cvs/NessusClient/nessus/nessus_plugin.c,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -d -r1.1.1.1 -r1.2
--- nessus_plugin.c 10 Sep 2005 17:36:42 -0000 1.1.1.1
+++ nessus_plugin.c 3 Feb 2006 21:00:49 -0000 1.2
@@ -15,6 +15,7 @@
bzero(np, sizeof(*np));
np->id = atoi(id);
np->asc_id = cache_inc(id);
+ np->md5sum = NULL;

np->name = cache_inc(name);
np->category = cache_inc(category);
@@ -40,6 +41,17 @@
np->xrefs = cache_inc(xrefs);
np->next = NULL;
return np;
+}
+
+
+/* set the md5 sum of the plugin. This function makes a copy of the
+ * md5sum */
+void
+nessus_plugin_set_md5sum(struct nessus_plugin * plugin, const char * md5sum)
+{
+ efree(&plugin->md5sum);
+ if (md5sum != NULL)
+ plugin->md5sum = estrdup(md5sum);
}



Index: nessus.h
===================================================================
RCS file: /usr/local/cvs/NessusClient/nessus/nessus.h,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -d -r1.1.1.1 -r1.2
--- nessus.h 10 Sep 2005 17:36:42 -0000 1.1.1.1
+++ nessus.h 3 Feb 2006 21:00:49 -0000 1.2
@@ -28,7 +28,7 @@
#endif

#define DEFAULT_SERVER "localhost"
-#define PROTO_NAME "< NTP/1.2 >< plugins_cve_id plugins_version plugins_bugtraq_id plugins_xrefs timestamps dependencies >\n"
+#define PROTO_NAME "< NTP/1.2 >< plugins_cve_id plugins_version plugins_bugtraq_id plugins_xrefs timestamps dependencies md5_caching >\n"
#ifdef NESSUS_ON_SSL
# define SSL_VER_DEF_NAME "TLSv1"
# define SSL_VER_DEF_METH TLSv1_client_method

Index: comm.c
===================================================================
RCS file: /usr/local/cvs/NessusClient/nessus/comm.c,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- comm.c 3 Feb 2006 20:43:54 -0000 1.12
+++ comm.c 3 Feb 2006 21:00:49 -0000 1.13
@@ -45,6 +45,7 @@
#include "parser.h"
#include "globals.h"
#include "error_dialog.h"
+#include "plugin_cache.h"

#ifndef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
@@ -816,13 +817,294 @@
}


+/* Get the md5sums for each plugin from the server. For each pair of
+ * plugin id and md5sum received from the server, this function calls
+ * the given callback function with the context, the plugin id, the
+ * md5sum, the plugin with the id (NULL if the plugin is not already
+ * known) and the data parameter. The callback should return 0 in case
+ * of success, non-zero otherwise. The md5sum parameter given to the
+ * callback is a pointer into a buffer maintained by
+ * comm_get_plugins_md5 so if the callback stores it somewhere it should
+ * make a copy.
+ *
+ * comm_get_plugins_md5 returns 0 on success, -1 on error.
+ */
+static int
+comm_get_plugins_md5(struct context *context, char * buf, int bufsz,
+ int (callback)(struct context *context, int id, const char * md5sum,
+ struct nessus_plugin * plugin, void * data), void * data)
+{
+ network_printf(context->socket, "CLIENT <|> SEND_PLUGINS_MD5 <|> CLIENT\n");
+ network_gets(context->socket, buf, 23);
+
+ if (strncmp(buf, "SERVER <|> PLUGINS_MD5", 22) == 0)
+ {
+ for(;;)
+ {
+ network_gets(context->socket, buf, bufsz);
+ if(buf[0] == '\0')
+ {
+ show_error(_("The daemon shut down the communication"));
+ break;
+ }
+ else if(!strncmp(buf, "<|> SERVER", 10))
+ break;
+ else
+ {
+ char * rest;
+ int id = strtol(buf, &rest, 10);
+ if (strncmp(rest, " <|> ", 5) == 0)
+ {
+ struct nessus_plugin * plugin = NULL;
+ char *md5sum = rest + 5;
+
+ /* the md5sum goes on until the end of the line. Strip the
+ * trailing newline */
+ int md5len = strlen(md5sum);
+ if (md5sum[md5len - 1] == '\n')
+ md5sum[md5len - 1] = '\0';
+
+ plugin = nessus_plugin_get_by_id(context->plugins, id);
+ if (plugin == NULL)
+ plugin = nessus_plugin_get_by_id(context->scanners, id);
+ if (callback(context, id, md5sum, plugin, data))
+ {
+ show_error(_("Error processing plugin information from the server"));
+ return -1;
+ }
+ }
+ else
+ {
+ show_error(_("Invalid SEND_PLUGINS_MD5 response from server"));
+ return -1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/* Manage a list (well actually an array) of missing or outdated
+ * plugins. The list contains the ids and md5sums of the plugins */
+
+struct missing_plugin {
+ int id;
+ char * md5sum;
+};
+
+struct missing_plugins_list {
+ int allocated;
+ int length;
+ struct missing_plugin * plugins;
+};
+
+/* initialize a missing_plugins_list struct */
+static void
+missing_plugins_list_init(struct missing_plugins_list * missing)
+{
+ missing->length = missing->allocated = 0;
+ missing->plugins = NULL;
+}
+
+/* Add an entry to the list of missing/outdated plugins */
+static void
+missing_plugins_list_add(struct missing_plugins_list * missing,
+ int id, const char * md5sum)
+{
+ if (missing->length == missing->allocated)
+ {
+ int new_length = missing->allocated + 1000;
+ if (missing->plugins != NULL)
+ missing->plugins = erealloc(missing->plugins,
+ sizeof(struct missing_plugin) * new_length);
+ else
+ missing->plugins = emalloc(sizeof(struct missing_plugin) * new_length);
+ missing->allocated = new_length;
+ }
+
+ missing->plugins[missing->length].id = id;
+ missing->plugins[missing->length].md5sum = estrdup(md5sum);
+ missing->length += 1;
+}
+
+
+/* free the list of missing plugins. Does not free the struct
+ * itself. */
+static void
+missing_plugins_list_free(struct missing_plugins_list * missing)
+{
+ int i;
+ for (i = 0; i < missing->length; i++)
+ efree(&missing->plugins[i].md5sum);
+ efree(&missing->plugins);
+ missing_plugins_list_init(missing);
+}
+
+
+/* callback for comm_get_plugins_md5 that checks the md5sum of an
+ * existing plugin.
+ *
+ * If plugin is given, i.e. if it's a known plugin, and the md5sums are
+ * equal, the plugin is up to date. In that case the plugin's
+ * is_current flag is set. Otherwise the flag is not modified (the code
+ * practically assumes it's false) and the id and md5sum are added to
+ * the missing plugins list which should be passed to this function as
+ * the data parameter.
+ */
+static int
+update_existing_plugin(struct context *context, int id, const char * md5sum,
+ struct nessus_plugin * plugin, void * data)
+{
+ if (plugin && strcmp(plugin->md5sum, md5sum) == 0)
+ {
+ plugin->is_current = 1;
+ }
+ else
+ {
+ missing_plugins_list_add((struct missing_plugins_list*)data, id, md5sum);
+ }
+
+ return 0;
+}
+
+
+/* Remove outdated plugins from the plugin list. The return value is a
+ * pointer to the first plugin that was not removed or NULL if all
+ * plugins have been removed.
+ *
+ * TODO: the plugins should also be removed from the pluginset. It
+ * should be noted, though, that items are never removed from the
+ * pluginset currently, even without the plugin cache.
+ */
+static struct nessus_plugin*
+remove_outdated_plugins(struct nessus_plugin* plugin)
+{
+ struct nessus_plugin * first = NULL;
+ struct nessus_plugin * prev = NULL;
+
+ while (plugin)
+ {
+ struct nessus_plugin * next = plugin->next;
+
+ if (!plugin->is_current)
+ {
+ if (prev != NULL)
+ {
+ prev->next = plugin->next;
+ }
+ nessus_plugin_free(plugin);
+ }
+ else
+ {
+ prev = plugin;
+ if (first == NULL)
+ first = plugin;
+ }
+
+ plugin = next;
+ }
+
+ return first;
+}
+
+
+/* Fetch the information for the plugins listed in missing.
+ * Return 0 on success, -1 on errors.
+ */
+static int
+fetch_new_plugins(struct context *context, struct missing_plugins_list* missing,
+ char * buf, int bufsz)
+{
+ int i;
+ struct nessus_plugin * plugin;
+
+ for (i = 0; i < missing->length; i++)
+ {
+ network_printf(context->socket,
+ "CLIENT <|> PLUGIN_INFO <|> %d <|> CLIENT\n", missing->plugins[i].id);
+ network_gets(context->socket, buf, bufsz);
+
+ plugin = parse_plugin(buf);
+ if (plugin != NULL)
+ {
+ nessus_plugin_set_md5sum(plugin, missing->plugins[i].md5sum);
+ context_add_plugin(context, plugin);
+ }
+ else
+ {
+ /* plugin information could not be parsed. Looks like a server
+ * error */
+ show_error(_("Invalid PLUGIN_INFO response from server"));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+/* Update the plugins in context by comparing them to the individual
+ * md5sums from the server. Missing and updated plugins are fetched
+ * from the server, plugins that no longer exist on the server are
+ * removed. If successful, the function returns 0 and a non-zero value
+ * otherwise */
+static int
+update_individual_plugins(struct context *context, char * buf, int bufsz)
+{
+ struct missing_plugins_list missing;
+ int result = 0;
+
+ missing_plugins_list_init(&missing);
+
+ result = comm_get_plugins_md5(context, buf, bufsz, update_existing_plugin,
+ &missing);
+ if (result)
+ goto fail;
+
+ context->plugins = remove_outdated_plugins(context->plugins);
+ context->scanners = remove_outdated_plugins(context->scanners);
+
+ result = fetch_new_plugins(context, &missing, buf, bufsz);
+
+fail:
+ missing_plugins_list_free(&missing);
+ return result;
+}
+
+
+/* callback for comm_get_plugins_md5 that simply adds the md5sum to a plugin
+ */
+static int
+add_md5sum_to_plugin(struct context *context, int id, const char * md5sum,
+ struct nessus_plugin * plugin, void * data)
+{
+ if (plugin != NULL)
+ {
+ nessus_plugin_set_md5sum(plugin, md5sum);
+ }
+ else
+ /* Since this function is used to fetch the md5sums for the plugins
+ * immediately after the full list of plugins has been fetched from
+ * the server, all plugins should be known, so we should never get
+ * here */
+ fprintf(stderr, "add_md5sum_to_plugin: Unknown plugin %d\n", id);
+
+ return 0;
+}
+

int
comm_get_plugins(context)
struct context *context;
{
+ int result = 0;
char *buf;
int bufsz;
+ char * server_md5sum = NULL;
+ int cache_status = -1;
+
#ifdef USE_GTK
context_reset_plugin_tree(context);
#endif
@@ -831,40 +1113,96 @@

bufsz = 1024 * 1024;
buf = emalloc(bufsz);
- network_gets(context->socket, buf, 27);
- if(strncmp(buf, "SERVER <|> PLUGIN_LIST <|>", 26))
+ network_gets(context->socket, buf, bufsz);
+
+ /* Valid data from the server at this can either start with "SERVER
+ * <|> PLUGINS_MD5 <|>" or "SERVER <|> PLUGIN_LIST <|>". In either
+ * case it starts with "SERVER <|> PLUGIN". Anything else is probably
+ * a login problem (wrong password, etc.)
+ */
+ if(strncmp(buf, "SERVER <|> PLUGIN", 17) != 0)
{
- efree(&buf);
- return (-1);
+ result = -1;
+ goto fail;
}

- for(;;)
+ /* if we requested md5sums, we get the md5sum over all the plugins
+ * now */
+ if(strncmp(buf, "SERVER <|> PLUGINS_MD5 <|> ", 27) == 0)
{
- comm_update_ui(context, COMM_GET_PLUGINS);
- network_gets(context->socket, buf, bufsz);
- if(buf[0] == '\0')
+ server_md5sum = parse_separator(buf + 22);
+ if (server_md5sum == NULL)
{
- show_error(_("The daemon shut down the communication"));
- break;
+ show_error(_("Invalid PLUGINS_MD5 information sent from server"));
+ result = -1;
+ goto fail;
}
- else if(!strncmp(buf, "<|> SERVER", 10))
- break;
- else
+
+ /* Read and check the cache */
+ cache_status = plugin_cache_read(context, server_md5sum);
+ if (cache_status < 0)
{
- struct nessus_plugin *plugin = parse_plugin(buf);
+ /* the cache could not be read for some reason. Most likely an
+ * error. Fetch the full list of plugins */
+ network_printf(context->socket, "CLIENT <|> COMPLETE_LIST <|> CLIENT\n");
+ network_gets(context->socket, buf, 27);
+ }
+ else if (cache_status > 0)
+ {
+ if (update_individual_plugins(context, buf, bufsz) < 0)
+ {
+ show_error(_("Error while updating the cached plugin information"));
+ result = -1;
+ goto fail;
+ }
+ }
+ }

- if ( plugin == NULL )
+ /* we may get a complete plugin list if we either did not request the
+ * md5sum in the first place or if the cache wasn't current. */
+ if (strncmp(buf, "SERVER <|> PLUGIN_LIST <|>", 26) == 0)
+ {
+ for(;;)
+ {
+ comm_update_ui(context, COMM_GET_PLUGINS);
+ network_gets(context->socket, buf, bufsz);
+ if(buf[0] == '\0')
{
- fprintf(stderr, "Could not parse %s\n", buf);
- continue;
+ show_error(_("The daemon shut down the communication"));
+ break;
}
+ else if(!strncmp(buf, "<|> SERVER", 10))
+ break;
+ else
+ {
+ struct nessus_plugin *plugin = parse_plugin(buf);

- context_add_plugin(context, plugin);
+ if ( plugin == NULL )
+ {
+ fprintf(stderr, "Could not parse %s\n", buf);
+ continue;
+ }
+
+ context_add_plugin(context, plugin);
+ }
}
+
+ if (server_md5sum != NULL)
+ comm_get_plugins_md5(context, buf, bufsz, add_md5sum_to_plugin, NULL);
}

+ /* if we requested md5sums, we need to tell the server explicitly that
+ * it should continue. Also, write the cache. */
+ if (server_md5sum != NULL)
+ {
+ plugin_cache_write(context, server_md5sum);
+ network_printf(context->socket, "CLIENT <|> GO ON <|> CLIENT\n");
+ }
+
+ fail:
+ efree(&server_md5sum);
efree(&buf);
- return (0);
+ return result;
}



Index: Makefile
===================================================================
RCS file: /usr/local/cvs/NessusClient/nessus/Makefile,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- Makefile 8 Dec 2005 18:05:31 -0000 1.10
+++ Makefile 3 Feb 2006 21:00:49 -0000 1.11
@@ -15,6 +15,7 @@
cli.o \
parser.o \
plugin_infos.o \
+ plugin_cache.o \
context.o \
preferences.o \
families.o \
@@ -182,6 +183,10 @@
plugin_infos.o : cflags plugin_infos.c plugin_infos.h globals.h context.h
$(CC) $(CFLAGS) $(NESSUS_INCLUDE) -c plugin_infos.c

+plugin_cache.o : cflags plugin_cache.c plugin_cache.h nessus_plugin.h \
+ error_dialog.h context.h preferences.h globals.h
+ $(CC) $(CFLAGS) $(NESSUS_INCLUDE) -c plugin_cache.c
+
main_window.o : cflags main_window.c main_window.h
$(CC) $(CFLAGS) $(NESSUS_INCLUDE) -c main_window.c

@@ -236,7 +241,7 @@
auth.o : cflags auth.c globals.h
$(CC) $(CFLAGS) $(NESSUS_INCLUDE) -c auth.c

-comm.o : cflags comm.c error_dialog.h globals.h context.h
+comm.o : cflags comm.c error_dialog.h globals.h context.h plugin_cache.h
$(CC) $(CFLAGS) $(NESSUS_INCLUDE) -c comm.c

report.o : cflags report.c error_dialog.h globals.h context.h \

_______________________________________________
Nessus-cvs mailing list
Nessus-cvs@list.nessus.org
http://mail.nessus.org/mailman/listinfo/nessus-cvs