/*
* Copyright (c) 1985 Corporation for Research and Educational Networking
* Copyright (c) 1988 University of Illinois Board of Trustees, Steven
* Dorner, and Paul Pomes
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the Corporation for
* Research and Educational Networking (CREN), the University of
* Illinois at Urbana, and their contributors.
* 4. Neither the name of CREN, the University nor the names of their
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE TRUSTEES AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TRUSTEES OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* This program checks the integrity of a CSO nameserver database. It makes
* sure almost everything is "on the up-and-up". Specifically, it detects
* the following problems:
*
* + existence and permissions of all database files
* - non-unique aliases
* - no pointers to null leaves in the .bdx file
* - entries in .bdx file in proper order
* - no bad pointers into .seq file from .bdx file
* - entries in .seq file in proper order
* - proper counts in all files
* + no bad pointers into .dir file from .idx file
* + no pointers to deleted entries in .dir file from .idx file
* + no bad pointers into .iov file from .idx file
* + no shared pointers into .iov file from .idx and .iov files
* + no bad pointers into .dov file from .dir file
* + no shared pointers into .dov file from .dir file
* + no bad pointers from .dov file into .dov file
* + no shared pointers from .dov file into .dov file
* - no entries whose data is longer than their entry chain
*
* Note that it DETECTS only; it does not (yet) correct problems. Note
* also that it does NOT check to see that each word the index claims to be
* in an entry is in fact in that entry, nor does it check to see that every
* word in indexed fields actually appear in the index with the proper
* pointers. The reason it does not do so is that such would amount to
* doing a full reindex of the database, and would take hours. Perhaps
* at some time nsck will be extended to perform some or all of these
* functions.
*
* Note also that this program avoids many of the normal database
* operations, since they may give wrong information in the presence
* of flaws. That means that changes made in database structure will
* require changes in this program.
*/
#include "protos.h"
#include <sys/types.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <grp.h>
#include <pwd.h>
#include "conf.h"
/*
* important definitions
*/
#define FATAL 2 /* an error that makes progress impossible */
#define ERROR 1 /* error that does not stop processing */
#define FINE 0 /* clean bill of health */
#define DEAD 1 /* for dead .dir entries */
#define REFERENCED 2 /* .dir entries that are referenced in .idx */
typedef char string[2048]; /* for various strings */
/*
* some functions that have to be declared...
*/
/*
* some global variables
*/
static char *Me; /* the name of this program */
int WantInfo = 0; /* do we want info messages? */
int WantStatus = 0; /* do we want status messages? */
extern char *sys_errlist[]; /* system errors */
extern int sys_nerr; /* how many of them there are */
extern int errno; /* the current error */
/*
* this structure holds names and functions for all the checks
*/
typedef struct
{
char *name; /* name (for humans) of check */
int (*func) (); /* function implementing check */
} CHECK;
/*
* Check that all the proper files exist, and that they have all the
* proper owner, group, and modes
*/
#define RIGHT_MODE (0660)
Files()
{
int trouble = 0;
static char *extensions[] =
{"bdx", "seq", "idx", "iov", "dir", "dov", 0};
string filename;
char **x;
struct stat s;
static int nsuid = -1;
static int nsgid = -1;
/* figure out proper uid and gid */
if (nsuid < 0)
{
struct passwd *pwent;
struct group *grent;
if (!(pwent = getpwnam(OWNER)))
{
printf("Proper owner \"%s\" does not exist!\n", OWNER);
trouble = ERROR;
nsuid = 0;
} else
nsuid = pwent->pw_uid;
if (!(grent = getgrnam(GROUP)))
{
printf("Proper group \"%s\" does not exist!\n", GROUP);
trouble = ERROR;
nsgid = 0;
} else
nsgid = grent->gr_gid;
}
/* now check the files */
for (x = extensions; *x; x++)
{
sprintf(filename, "%s.%s", Database, *x);
if (stat(filename, &s))
{
PERR(filename);
trouble = FATAL;
} else
{
if (s.st_uid != nsuid)
T("%s: owner %d not %d\n", filename, s.st_uid, nsuid);
if (s.st_gid != nsgid)
T("%s: group %d not %d\n", filename, s.st_gid, nsgid);
if (s.st_mode != RIGHT_MODE)
T("%s: mode %o not %o\n", filename, s.st_mode, RIGHT_MODE);
}
}
return (trouble);
}
/*
* make sure all the pointers in the .idx file point to non-deleted,
* valid entries in the .dir file.
*/
IdxDir()
{
int trouble = FINE;
int dirCount, idxCount;
int i;
char *ents = NULL;
string dirName, idxName, iovName;
string buffer;
int dfd = 0, ifd = 0, iod = 0;
register int *ptr;
struct stat s;
int aDir;
register char *d;
/*
* first, we find out how many entries are in the .dir file,
* and collect the numbers of all the deleted entries. Along
* the way, we test the size of the .dir file
*/
sprintf(dirName, "%s.%s", Database, "dir");
if ((dfd = open(dirName, O_RDONLY)) == 0)
{
PERR(dirName);
return (FATAL);
}
if (read(dfd, buffer, sizeof (int)) < 0)
{
PERR(dirName);
trouble = FATAL;
goto done;
}
dirCount = *((int *) buffer);
fstat(dfd, &s);
i = s.st_size / DRECSIZE;
if (s.st_size % DRECSIZE)
T("%s: size is not a multiple of %d.\n", dirName, DRECSIZE);
if (dirCount != i)
{
T("%s:number of entries is %d; file size indicates %d.\n", dirCount, i);
dirCount = i;
}
/* now, gather entries */
maybef(WantStatus, "Gathering entry information.\n");
if (dirCount)
{
ents = malloc(dirCount);
if (!ents)
{
printf("malloc FAILED! Sheesh!\n");
trouble = FATAL;
goto done;
}
for (d = ents; d < ents + dirCount; d++)
*d = 0;
/* entries found. now, wade through .idx file, checking each ptr */
maybef(WantStatus, "Checking idx file against .dir list.\n");
sprintf(idxName, "%s.%s", Database, "idx");
sprintf(iovName, "%s.%s", Database, "iov");
if ((ifd = open(idxName, O_RDONLY)) == 0)
{
PERR(idxName);
trouble = FATAL;
goto done;
}
if ((iod = open(iovName, O_RDONLY)) == 0)
{
PERR(iovName);
trouble = FATAL;
goto done;
}
fstat(ifd, &s);
if (s.st_size % sizeof (struct iindex))
T("%s: size is not a multiple of %d.\n", idxName, sizeof (struct iindex));
idxCount = s.st_size / sizeof (struct iindex);
for (i = 1; i < idxCount; i++)
{
if (i % REP_INT == REP_INT - 1)
maybef(WantStatus, "%-10d\r", i);
if (lseek(ifd, i * sizeof (struct iindex), L_SET) < 0)
{
PERR(idxName);
printf("idx %x\n", i);
trouble = FATAL;
goto done;
}
if (read(ifd, buffer, sizeof (struct iindex)) < 0)
{
PERR(idxName);
printf("idx %x\n", i);
trouble = FATAL;
goto done;
}
if (!*buffer || *buffer == '\377')
continue; /* empty... */
for (ptr = (int *) buffer; ptr < (int *) (buffer + sizeof (struct iindex)) - 1; ptr++)
if (HASNULL(*ptr))
{
for (ptr++; ptr < (int *) (buffer + sizeof (struct iindex)) - 1; ptr++)
{
if (!*ptr)
break;
if (ents[*ptr - 1] & DEAD)
T("Index entry %x references dead entry %x.\n", i, *ptr);
ents[*ptr - 1] |= REFERENCED;
}
}
for (ptr = (int *) (buffer + sizeof (struct iindex)) - 1; *ptr;)
{
if (lseek(iod, *ptr * NOPTRS * PTRSIZE, L_SET) < 0)
{
PERR(iovName);
T("Bad overflow pointer (%x) in idx chain %x.\n", *ptr, i);
break;
}
if (read(iod, buffer, NOPTRS * PTRSIZE) < 0)
{
PERR(iovName);
printf("idx %x\n", i);
trouble = FATAL;
goto done;
}
for (ptr = (int *) buffer; ptr < (int *) (buffer + NOPTRS * PTRSIZE) - 1; ptr++)
{
if (!*ptr)
break;
if (ents[*ptr - 1] & DEAD)
T("Index chain %x references dead entry %x.\n", i, *ptr);
ents[*ptr - 1] |= REFERENCED;
}
}
}
/* report on any "funny" dir entries */
for (d = ents; d < &ents[dirCount]; d++)
switch (*d)
{
case REFERENCED:
break;
case DEAD:
maybef(WantInfo, "INFO: entry %x is dead.\n", d - ents + 1);
break;
case DEAD | REFERENCED:
T("Dead entry %x referenced.\n", d - ents + 1);
break;
case 0:
T("Entry %x unreferenced.\n", d - ents + 1);
break;
default:
printf("Internal error, entry %x (%x).\n", d - ents + 1, *d);
break;
}
/* finished. clean up and return */
done:
if (ifd)
close(ifd);
if (iod)
close(iod);
if (dfd)
close(dfd);
if (ents)
free(ents);
return (trouble);
}
/*
* print a message (maybe)
*/
maybef(yes, fmt, a, b, c, d, e, f, g, h, i, j, l, m, n, o, p)
int yes;
char *fmt;
unsigned a, b, c, d, e, f, g, h, i, j, l, m, n, o, p;
{
if (yes)
printf(fmt, a, b, c, d, e, f, g, h, i, j, l, m, n, o, p);
fflush(stdout);
}
/*
* Check the consistency of the Idx and Iov files
* - no unreferenced IOV entries
* - no doubly-referenced IOV entries
*/
IdxIov()
{
int idx = 0, iov = 0;
string idxName, iovName;
string buffer;
int *iovEnts = NULL;
int trouble = FINE;
int idxCount, iovCount;
struct stat s;
int i;
register int *ent;
sprintf(iovName, "%s.iov", Database);
sprintf(idxName, "%s.idx", Database);
if ((idx = open(idxName, O_RDONLY)) == 0)
{
PERR(idxName);
trouble = FATAL;
goto done;
}
if ((iov = open(iovName, O_RDONLY)) == 0)
{
PERR(iovName);
trouble = FATAL;
goto done;
}
/* set up and array to keep track of iov entries and references */
fstat(iov, &s);
if (s.st_size % (PTRSIZE * NOPTRS))
T("%s: size not a multiple of %d.\n", iovName, PTRSIZE * NOPTRS);
iovCount = s.st_size / (PTRSIZE * NOPTRS);
if (!(iovEnts = (int *) malloc(iovCount * sizeof (int))))
{
PERR("malloc iovEnts");
trouble = FATAL;
goto done;
}
for (ent = iovEnts; ent - iovEnts < iovCount; ent++)
*ent = 0;
/* look through idx file for bad things */
fstat(idx, &s);
idxCount = s.st_size / sizeof (struct iindex);
/* put a NULL word at end of iindex buffer as a placemarker */
*(int *) (buffer + sizeof (struct iindex)) = 0;
*(int *) (buffer + sizeof (struct iindex) + sizeof (int)) = 0;
/* read the iindex entries */
maybef(WantStatus, "Checking idx for bad iov pointers.\n");
for (i = 0; i < idxCount; i++)
{
if (i % REP_INT == REP_INT - 1)
maybef(WantStatus, "%-10d\r", i);
if (read(idx, buffer, sizeof (struct iindex)) < 0)
{
PERR(idxName);
trouble = ERROR;
break;
}
if (!*buffer || *buffer == '\377')
continue; /* empty... */
/* look for a NULL byte */
for (ent = (int *) buffer; !HASNULL(*ent); ent++) ;
/* look for a NULL word */
for (ent++; *ent; ent++) ;
if (ent - (int *) buffer == sizeof (struct iindex) / sizeof (int))
{
ent--;
/* we have an overflow pointer */
if (*ent < 0 || *ent >= iovCount)
T("idx %x: overflow pointer out of range (%x).\n", i, *ent);
else if (iovEnts[*ent])
T("idx %x: shares overflow %x with %x.\n", i, *ent, iovEnts[*ent]);
else
iovEnts[*ent] = i;
}
}
close(idx);
idx = 0;
/* now for the iov entries */
maybef(WantStatus, "Checking iov for bad iov pointers.\n");
*(int *) (buffer + PTRSIZE * NOPTRS) = 0;
for (i = 0; i < iovCount; i++)
{
if (i % REP_INT == REP_INT - 1)
maybef(WantStatus, "%-10d\r", i);
if (read(iov, buffer, PTRSIZE * NOPTRS) < 0)
{
PERR(iovName);
trouble = ERROR;
break;
}
/* look for a NULL word */
for (ent = (int *) buffer; *ent; ent++) ;
if (ent - (int *) buffer == PTRSIZE * NOPTRS / sizeof (int))
{
ent--;
/* we have an overflow pointer */
if (*ent < 0 || *ent >= iovCount)
T("iov %x: overflow pointer out of range (%x).\n", i, *ent);
else if (iovEnts[*ent])
T("iov %x: shares overflow %x with %x.\n", i, *ent, iovEnts[*ent]);
else
iovEnts[*ent] = -i;
}
}
/* let's have a look for unused iov entries */
if (WantInfo)
for (i = 1; i < iovCount; i++)
if (iovEnts[i] == 0)
printf("%s: overflow block %x unused.\n", iovName, i);
done:
if (idx)
close(idx);
if (iov)
close(iov);
if (iovEnts)
free(iovEnts);
return (trouble);
}
/*
* Check the consistency of the dir and dov files
* - no unreferenced dov entries
* - no doubly-referenced dov entries
*/
DirDov()
{
int dir = 0, dov = 0;
string dirName, dovName;
string buffer;
int *dovEnts = NULL;
int trouble = FINE;
int dirCount, dovCount;
struct stat s;
int i;
register int *ent;
sprintf(dovName, "%s.dov", Database);
sprintf(dirName, "%s.dir", Database);
if ((dir = open(dirName, O_RDONLY)) == 0)
{
PERR(dirName);
trouble = FATAL;
goto done;
}
if ((dov = open(dovName, O_RDONLY)) == 0)
{
PERR(dovName);
trouble = FATAL;
goto done;
}
/* set up and array to keep track of dov entries and references */
fstat(dov, &s);
if (s.st_size % DOVRSIZE)
T("%s: size not a multiple of %d.\n", dovName, DOVRSIZE);
dovCount = s.st_size / (DOVRSIZE);
if (!(dovEnts = (int *) malloc(dovCount * sizeof (int))))
{
PERR("malloc dovEnts");
trouble = FATAL;
goto done;
}
for (ent = dovEnts; ent - dovEnts < dovCount; ent++)
*ent = 0;
/* look through dir file for bad things */
fstat(dir, &s);
dirCount = s.st_size / DRECSIZE;
/* put a NULL word at end of iindex buffer as a placemarker */
*(int *) (buffer + DRECSIZE) = 0;
*(int *) (buffer + DRECSIZE + sizeof (int)) = 0;
/* read the dir entries */
maybef(WantStatus, "Checking dir for bad dov pointers.\n");
ent = (int *) buffer;
for (i = 0; i < dirCount; i++)
{
if (i % REP_INT == REP_INT - 1)
maybef(WantStatus, "%-10d\r", i);
if (read(dir, buffer, DRECSIZE) < 0)
{
PERR(dirName);
trouble = ERROR;
break;
}
if (buffer[16] || buffer[17])
continue; /* dead... */
/* look for a NULL byte */
if (*ent)
{
/* we have an overflow pointer */
if (*ent < 0 || *ent >= dovCount)
T("dir %x: overflow pointer out of range (%x).\n", i, *ent);
else if (dovEnts[*ent])
T("dir %x: shares overflow %x with %x.\n", i, *ent, dovEnts[*ent]);
else
dovEnts[*ent] = i;
}
}
close(dir);
dir = 0;
/* now for the dov entries */
maybef(WantStatus, "Checking dov for bad dov pointers.\n");
ent = ((int *) (buffer + DOVRSIZE)) - 1;
for (i = 0; i < dovCount; i++)
{
if (i % REP_INT == REP_INT - 1)
maybef(WantStatus, "%-10d\r", i);
if (read(dov, buffer, DOVRSIZE) < 0)
{
PERR(dovName);
trouble = ERROR;
break;
}
/* look for a NULL word */
if (*ent)
{
/* we have an overflow pointer */
if (*ent < 0 || *ent >= dovCount)
T("dov %x: overflow pointer out of range (%x).\n", i, *ent);
else if (dovEnts[*ent])
{
if (dovEnts[*ent] > 0)
T("dov %x: shares overflow %x with %x.\n", i, *ent, dovEnts[*ent]);
else
T("dov %x: shares overflow %x with dov %x.\n", i, *ent, -dovEnts[*ent]);
} else
dovEnts[*ent] = -i;
}
}
/* let's have a look for unused dov entries */
if (WantInfo)
for (i = 1; i < dovCount; i++)
if (dovEnts[i] == 0)
printf("%s: overflow block %x unused.\n", dovName, i);
done:
if (dir)
close(dir);
if (dov)
close(dov);
if (dovEnts)
free(dovEnts);
return (trouble);
}