/* Nessus * Copyright (C) 1998 - 2006 Tenable Network Security, Inc. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * * save_kb : * save the knowledge base about a remote host * */ #include #include "log.h" #include "comm.h" #include "users.h" #include "locks.h" #ifdef ENABLE_SAVE_KB #ifndef MAP_FAILED #define MAP_FAILED (void*)(-1) #endif #include "save_kb.h" /*========================================================================= Private functions ===========================================================================*/ static char * filter_odd_name(name) char * name; { char * ret = name; while(name[0]) { /* * A host name should never contain any slash. But we never * know */ if(name[0]=='/')name[0]='_'; name++; } return ret; } /*----------------------------------------------------------------- Name of the directory which contains the sessions of the current user (/path/to/var/nessus//kbs/) ------------------------------------------------------------------*/ static char * kb_dirname(globals) struct arglist * globals; { char * home = user_home(globals); char * dir = emalloc(strlen(home) + strlen("kbs") + 2); sprintf(dir, "%s/kbs", home); efree(&home); return(dir); } /*---------------------------------------------------------------- Create a kb directory. XXXXX does not check for the existence of a directory and does not check any error ------------------------------------------------------------------*/ static int kb_mkdir(dir) char * dir; { char *t; int ret = 0; dir = estrdup(dir); t = strchr(dir+1, '/'); while(t) { t[0] = '\0'; mkdir(dir, 0700); t[0] = '/'; t = strchr(t+1, '/'); } if ((ret = mkdir(dir, 0700)) < 0) { if(errno != EEXIST) log_write("mkdir(%s) failed : %s\n", dir, strerror(errno)); efree(&dir); return ret; } efree(&dir); return ret; } /*---------------------------------------------------------------- From , return /path/to/var/nessus//kb/ ------------------------------------------------------------------*/ static char* kb_fname(globals, hostname) struct arglist * globals; char * hostname; { char * dir = kb_dirname(globals); char * ret; char * hn = strdup(hostname); hn = filter_odd_name(hn); ret = emalloc(strlen(dir) + strlen(hn) + 2); sprintf(ret, "%s/%s", dir, hn); efree(&dir); efree(&hn); return ret; } /* * mmap() tends to sometimes act weirdly */ static char* map_file(file) int file; { struct stat st; char *ret; int i = 0; int len; bzero(&st, sizeof(st)); fstat(file, &st); len = (int)st.st_size; if ( len == 0 ) return NULL; lseek(file, 0, SEEK_SET); ret = emalloc(len + 1); while(i < len ) { int e = read(file, ret + i, len - i); if(e > 0) i+=e; else { log_write("read(%d, buf, %d) failed : %s\n", file, len, strerror(errno)); efree(&ret); lseek(file, len, SEEK_SET); return NULL; } } lseek(file, len, SEEK_SET); return ret; } static int save_kb_entry_present_already(globals, hostname, name, value) struct arglist * globals; char * hostname; char * name; char * value; { char * buf; int fd; char* req; int ret; fd = (int)arg_get_value(globals, "save_kb"); if(fd <= 0) return -1; buf = map_file(fd); if(buf) { req = emalloc(strlen(name) + strlen(value) + 2); sprintf(req, "%s=%s",name, value); if(strstr(buf, req)) ret = 1; else ret = 0; efree(&buf); efree(&req); return ret; } return -1; } static int save_kb_rm_entry_value(globals, hostname, name, value) struct arglist * globals; char * hostname; char * name; char * value; { char * buf; char * t; int fd; char * req; fd = (int)arg_get_value(globals, "save_kb"); if(fd <= 0) return -1; buf = map_file(fd); if(buf) { if(value) { req = emalloc(strlen(name) + strlen(value) + 2); sprintf(req, "%s=%s", name, value); } else req = estrdup(name); t = strstr(buf, req); if(t) { char * end; while(t[0] != '\n') { if(t == buf)break; else t--; } if(t[0] == '\n')t++; end = strchr(t, '\n'); t[0] = '\0'; if(end){ end[0] = '\0'; end++; } if((lseek(fd, 0, SEEK_SET))<0) { log_write("lseek() failed - %s\n", strerror(errno)); } if((ftruncate(fd, 0))<0) { log_write("ftruncate() failed - %s\n", strerror(errno)); } if(write(fd, buf, strlen(buf)) < 0) { log_write("write() failed - %s\n", strerror(errno)); } if(end){ if((write(fd, end, strlen(end)))<0) log_write("write() failed - %s\n", strerror(errno)); } } efree(&buf); efree(&req); lseek(fd, 0, SEEK_END); } return 0; } static int save_kb_rm_entry(globals, hostname, name) struct arglist * globals; char * hostname; char * name; { return save_kb_rm_entry_value(globals, hostname, name, NULL); } /* * Write data * * We want to avoid duplicates for : * * Successful/... * SentData/... * Launched/... * * We don't want to save /tmp/... */ static int save_kb_write(globals, hostname, name, value, type) struct arglist * globals; char * hostname, * name, * value; int type; { int fd; char * str; int e; struct timeval now; if(!globals || !hostname || !name || !value) return -1; fd = (int)arg_get_value(globals, "save_kb"); if(fd <= 0) { log_write("user %s : Can not find KB fd for %s\n", (char*)arg_get_value(globals, "user"), hostname); return -1; } /* * Don't save temporary KB entries */ if(!strncmp(name, "/tmp/", 4) || !strncmp(name, "NIDS/", 5) || !strncmp(name, "Settings/", 9)) return 0; /* Don't save sensitive information */ if (strncmp(name, "Secret/", 7) == 0) return 0; /* * Avoid duplicates for these families */ if(!strncmp(name, "Success/", strlen("Success/")) || !strncmp(name, "Launched/", strlen("Launched/")) || !strncmp(name, "SentData/", strlen("SentData/"))) { save_kb_rm_entry(globals, hostname, name); } if(save_kb_entry_present_already(globals, hostname, name, value)) { save_kb_rm_entry_value(globals, hostname, name, value); } str = emalloc(strlen(name) + strlen(value) + 25); gettimeofday(&now, NULL); sprintf(str, "%ld %d %s=%s\n", (long)now.tv_sec, type, name, value); e = write(fd, str, strlen(str)); if(e < 0) { log_write("user %s : write kb error - %s\n", (char*)arg_get_value(globals, "user"), strerror(errno)); } efree(&str); return 0; } /*====================================================================== Public functions =======================================================================*/ /*------------------------------------------------------------------ Initialize a new KB that will be saved The indexes of all the opened KB are in a hashlist in globals, saved under the name "save_kb". This makes no sense at this time, as the test of each host is done in a separate process, but this allows us to regroup easily these in the future -------------------------------------------------------------------*/ int save_kb_new(globals, hostname) struct arglist * globals; char * hostname; { char * fname; char * dir; char * user = arg_get_value(globals, "user"); int ret = 0; int f; if( hostname == NULL ) return -1; dir = kb_dirname(globals); kb_mkdir(dir); efree(&dir); fname = kb_fname(globals, hostname); if(file_locked(fname)) { efree(&fname); return 0; } unlink(fname); /* delete the previous kb */ f = open(fname, O_CREAT|O_RDWR|O_EXCL, 0640); if(f < 0) { log_write("user %s : Can not save KB for %s - %s", user, hostname, strerror(errno)); ret = -1; efree(&fname); return ret; } else { file_lock(fname); log_write("user %s : new KB will be saved as %s", user, fname); if(arg_get_value(globals, "save_kb")) arg_set_value(globals, "save_kb", sizeof(int), (void*)f); else arg_add_value(globals, "save_kb", ARG_INT, sizeof(int),(void*)f); } return 0; } void save_kb_close(globals, hostname) struct arglist * globals; char * hostname; { int fd = (int)arg_get_value(globals, "save_kb"); char* fname = kb_fname(globals, hostname); if(fd > 0)close(fd); file_unlock(fname); efree(&fname); } /* * Returns <1> if we already saved a KB for this host, * less than seconds ago. If is * equal to zero, then the age is not taken in account * (returns true if a knowledge base exists) */ int save_kb_exists(globals, hostname) struct arglist * globals; char * hostname; { char * fname = kb_fname(globals, hostname); FILE *f; if(file_locked(fname)) { efree(&fname); return 0; } f = fopen(fname, "r"); efree(&fname); if(!f) return 0; else { fclose(f); return 1; } } int save_kb_write_str(globals, hostname, name, value) struct arglist * globals; char * hostname, * name, * value; { char * newvalue = addslashes(value); int e; e = save_kb_write(globals, hostname, name, newvalue, ARG_STRING); efree(&newvalue); return e; } int save_kb_write_int(globals, hostname, name, value) struct arglist * globals; char * hostname, * name; int value; { static char asc_value[25]; int e; sprintf(asc_value, "%d", value); e = save_kb_write(globals, hostname, name, asc_value, ARG_INT); bzero(asc_value, sizeof(asc_value)); return e; } /* * Restores a copy of the knowledge base */ int save_kb_restore_backup(globals, hostname) struct arglist * globals; char*hostname; { char * fname = kb_fname(globals, hostname); char * bakname; int fd; bakname = emalloc(strlen(fname) + 5); strcat(bakname, fname); strcat(bakname, ".bak"); unlink(fname); if((fd = open(bakname, O_RDONLY)) >= 0) { close(fd); rename(bakname, fname); } return 0; } /* * Makes a copy of the knowledge base */ int save_kb_backup(globals, hostname) struct arglist * globals; char* hostname; { char * fname = kb_fname(globals, hostname); char * newname = NULL; int fd_src = -1, fd_dst = -1; if(file_locked(fname)) { log_write("%s is locked\n", fname); goto failed1; } file_lock(fname); newname = emalloc(strlen(fname) + 5); strcat(newname, fname); strcat(newname, ".bak"); if((fd_src = open(fname, O_RDONLY)) >= 0) { char buf[4096]; int n; fd_dst = open(newname, O_WRONLY|O_CREAT|O_TRUNC, 0640); if(fd_dst < 0) { log_write("save_kb_backup failed : %s", strerror(errno)); close(fd_src); goto failed; } bzero(buf, sizeof(buf)); while((n = read(fd_src, buf, sizeof(buf))) > 0) { int m = 0; while(m != n) { int e = write(fd_dst, &(buf[m]), n-m); if(e < 0) { log_write("save_kb_backup failed : %s", strerror(errno)); close(fd_src); close(fd_dst); goto failed; } m+=e; } bzero(buf, sizeof(buf)); } } else log_write("save_kb_backup failed : %s\n", strerror(errno)); close(fd_src); close(fd_dst); efree(&newname); file_unlock(fname); efree(&fname); return 0; failed: file_unlock(fname); failed1: efree(&fname); efree(&newname); return -1; } /* * Restores a previously saved knowledge base * * The KB entry 'Host/dead' is ignored, as well as all the * entries starting by '/tmp/' */ struct kb_item ** save_kb_load_kb(globals, hostname) struct arglist * globals; char * hostname; { char * fname = kb_fname(globals, hostname); FILE * f; int fd; struct kb_item ** kb; char buf[4096]; long max_age = save_kb_max_age(globals); if(file_locked(fname)) { efree(&fname); return NULL; } f = fopen(fname, "r"); if(!f) { log_write("user %s : Could not open %s - kb won't be restored for %s\n", (char*)arg_get_value(globals, "user"), fname, hostname); efree(&fname); return NULL; } bzero(buf, sizeof(buf)); fgets(buf, sizeof(buf) - 1, f); kb = kb_new(); /* * Ignore the date */ bzero(buf, sizeof(buf)); while(fgets(buf, sizeof(buf) - 1, f)) { int type; char * name, * value, *t; struct timeval then, now; buf[strlen(buf)-1]='\0'; /* chomp(buf) */ t = strchr(buf, ' '); if(!t)continue; t[0] = '\0'; then.tv_sec = atol(buf); t[0] = ' ';t++; type = atoi(t); t = strchr(t, ' '); if(!t) continue; t[0] = ' ';t++; name = t; t = strchr(name, '='); if(!t)continue; t[0] = '\0'; name = strdup(name); t[0] = ' '; t++; value = strdup(t); if(strcmp(name, "Host/dead") && strncmp(name, "/tmp/", 4) && strcmp(name, "Host/ping_failed")) { gettimeofday(&now, NULL); if(now.tv_sec - then.tv_sec > max_age) { /* log_write("discarding %s because it's too old\n", name, (now.tv_sec - then.tv_sec)); */ } else { if(type == ARG_STRING) { char * tmp = rmslashes(value); kb_item_add_str(kb, name, tmp); efree(&tmp); } else if(type == ARG_INT) kb_item_add_int(kb, name, atoi(value)); } } efree(&value); efree(&name); bzero(buf, sizeof(buf)); } fclose(f); /* * Re-open the file */ fd = open(fname, O_RDWR); efree(&fname); if(fd > 0) { lseek(fd, 0, SEEK_END); if(arg_get_value(globals, "save_kb")) arg_set_value(globals, "save_kb", ARG_INT, (void*)fd); else arg_add_value(globals, "save_kb", ARG_INT, sizeof(int), (void*)fd); } else log_write("user %s : ERROR - %s\n", (char*)arg_get_value(globals, "user"), strerror(errno)); return kb; } /*------------------------------------------------------------------- * Preferences set by the user *-------------------------------------------------------------------*/ /* * Returns <1> if the user wants us the save the knowledge base */ int save_kb(globals) struct arglist * globals; { struct arglist * preferences; char * value; if(!globals) return 0; preferences = arg_get_value(globals, "preferences"); if(!preferences) return 0; value = arg_get_value(preferences, "save_knowledge_base"); if(value && !strcmp(value, "yes")) return 1; return 0; } /* * Returns <1> if we should only test hosts whose knowledge base we * already have */ int save_kb_pref_tested_hosts_only(globals) struct arglist * globals; { struct arglist * preferences = arg_get_value(globals, "preferences"); char * value; value = arg_get_value(preferences, "only_test_hosts_whose_kb_we_have"); if(value && !strcmp(value, "yes")) return 1; return 0; } /* * Returns <1> if we should only test hosts whose kb we DO NOT have */ int save_kb_pref_untested_hosts_only(globals) struct arglist * globals; { struct arglist * preferences = arg_get_value(globals, "preferences"); char * value; value = arg_get_value(preferences, "only_test_hosts_whose_kb_we_dont_have"); if(value && !strcmp(value, "yes")) return 1; return 0; } /* * Returns <1> if we should restore the KB for the tests */ int save_kb_pref_restore(globals) struct arglist * globals; { struct arglist * preferences = arg_get_value(globals, "preferences"); char * value; value = arg_get_value(preferences, "kb_restore"); if(value && !strcmp(value, "yes")) return 1; return 0; } /* * Return <1> if this type of plugin can be executed */ int save_kb_replay_check(globals, type) struct arglist * globals; int type; { struct arglist * preferences = arg_get_value(globals, "preferences"); char * name = NULL; char * value; switch(type) { case ACT_SCANNER: name = "kb_dont_replay_scanners"; break; case ACT_GATHER_INFO: name = "kb_dont_replay_info_gathering"; break; case ACT_MIXED_ATTACK: case ACT_DESTRUCTIVE_ATTACK: case ACT_ATTACK: name = "kb_dont_replay_attacks"; break; case ACT_DENIAL: case ACT_KILL_HOST: case ACT_FLOOD: name = "kb_dont_replay_denials"; break; /* ACT_SETTINGS and ACT_INIT should always be executed */ } if(name) { value = arg_get_value(preferences, name); if(value && !strcmp(value, "yes"))return 0; } return 1; } /* * Returns the max. age of the KB, in seconds, as set * by the user */ long save_kb_max_age(globals) struct arglist * globals; { struct arglist * prefs = arg_get_value(globals, "preferences"); long ret = atol(arg_get_value(prefs, "kb_max_age")); if(!ret) return 3600; else return ret; } /* * Differential scans * * * The idea of a differential scan is to only show the user what * has changed in the report. To do this, libnessus relies on the content * of the Success/... and Failures/... KB entries that record if a test * was sucessful or failed in the past. * * Note that the KB now contain the full text of the messages sent * back to the client, so libnessus will be able to determine if a message * has changed or not (such as a newer FTP version for instance). * * * TODO : * Add 'DataSent/PluginID/Num' entries */ int diff_scan(globals) struct arglist * globals; { struct arglist * prefs = arg_get_value(globals, "preferences"); char * v = arg_get_value(prefs, "diff_scan"); if(v && !strcmp(v, "yes")) return 1; else return 0; } void diff_scan_enable(pluginargs) struct arglist * pluginargs; { arg_add_value(pluginargs, "DIFF_SCAN", ARG_INT, sizeof(int), (void*)1); } #endif