/* alv.c : ver 2.2b 19/3/13 */ /* * Copyright (C) 2012 University of Liverpool */ /* Author: D.Nixon e-mail d.j.nixon@csc.liv.ac.uk */ /* * Oracle archived log sequence verification tool. */ /* The sequence of archived log files in the current directory, or a specified one, is output showing status codes for each log and any gaps in the sequence. This utility makes various checks on the integrity and identity of a log file; including size, file type and redo record block checksums. Syntax: alv {-q} {archive log directory} Options: -q quick checks - skip doing checksums Environment: ORACLE_SID - identify a file's DBNAME ALV_LNAME - specify custom prefix for log name Retval: 0 - completed checks 1 - syntax 2 - couldn't find directory 3 - memory alloc failed 4 - couldn't read directory 5 - completed checks without checksums Output single char log status codes: ' ' - missing in sequence '*' - verified 'b' - file open or mapping failure 'c' - corrupt block 'o' - not an Oracle file 'r' - not an archive log 's' - wrong size 'e' - empty file 'i' - file dbname doesnt't match identity specified by ORACLE_SID 'a' - aborted; incomplete archive log */ /* ============================ Notes ===================================== * 'alv' has been tested using HP-UX Oracle 10gR2 & 11gR1 logs under both HP-UX and Linux. * The structure of the various file headers and the checksum algorithm used in the code are based, in part, on information from the in-depth Oracle security paper: "Oracle Forensics Part 1: Dissecting the Redo Logs" by David Litchfield (davidl@ngssoftware.com) * To adjust the maximun log sequence number allowed set the following defines: #define MAXSEQ 65536 #define SQLEN 5 To compile under HP-UX with an ANSI C compiler: $ cc -o alv -Ae +O4 alv.c for Linux : $ gcc -O3 -o alv -O3 alv.c There should not be any compiler warnings. * To run full checks on a repository of archived logs from a DB instance named "PROD": $ export ORACLE_SID="PROD" $ cd /mnt/raid01/backup/PROD $ alv | grep -v \* To run a quick check on an active system: $ alv -q /mnt/raid01/app/oracle/admin/PROD/arch * The "-q" option can be used to skip per-block checks: in this only the size and identity of each archive file is checked. * Complete block checks are both CPU and I/O intensive. =========================================================================== */ #include #include #include #include #include #include #include #include #include #include #include #ifndef MAP_FAILED #define MAP_FAILED (void *) -1 #endif #undef _10gR2 #define _11gR1 #if defined _10gR2 || defined _11gR1 #define MAGIC 0x7a7b7c7d #define ARC_REDO 0x0042 #define LOGNAME "arch" /* default log name prefix */ #endif #define MAXSEQ 65536 /* maximun log sequence number */ #define SQLEN 5 /* log status codes */ #define GOOD_arch '*' /* file redo blocks validated - if checksummed */ #define ABORT_arch 'a' /* records incomplete */ #define NULL_arch 'e' /* file size is zero */ #define TRUNC_arch 's' /* file size is wrong */ #define BAD_arch 'b' /* bad dir entry or I/O error on file */ #define CORRUPT_arch 'c' /* corrupt redo block */ #define NOT_oracle 'o' /* not an oracle file */ #define NOT_redo 'r' /* not a redo log */ #define NOT_sid 'i' /* not generated by this DB */ /* formatted output messages */ #define GOOD_a "%c %05d\n",'*', seq #define ABORT_a "%c %05d\n",'a', seq #define NULL_a "%c %05d\n",'e', seq #define TRUNC_a "%c %05d\n",'s', seq #define BAD_a "%c %05d\n",'b', seq #define CORRUPT_a "%c %05d\n",'c', seq #define NOT_o "%c %05d\n",'o', seq #define NOT_r "%c %05d\n",'r', seq #define NOT_i "%c %05d\n",'i', seq #define GONE_a " %05d\n", seq /* missing case */ /* global data */ char *version="@(#)Version 2.2b : 22/3/13", *lgname, *oracle_sid; unsigned int qflag=0, /* command line option */ iflag=0; /* check SID identity */ size_t lnamelen; struct oracle_file_header { u_short type; u_char fil1[20]; u_short blksz; u_int numblks; /* ex block 0 */ u_int magic; } ; struct oracle_block_header { u_short type; u_char fil1[2]; u_int blknum; u_int sequence; /* log sequence */ u_char fil2[2]; u_short checksum; u_char fil3[12]; u_char dbname[8]; } ; /* Functions */ int verify(); int afcks(); main(argc, argv) int argc; char *argv[]; { int retval=0; char cwd[2]={'.','\0'}, *dirpath=cwd; /* default archive is current directory */ /* Syntax */ if (argc > 3) { fputs("Usage: alv {-q} {log dir path}\n",stderr); retval=1; } else { /* parse command line options */ if (argc == 2) if (strcmp("-q", argv[1])) dirpath=argv[1]; else ++qflag; /* skips block tests */ if (argc == 3) { if (strcmp("-q", argv[1])) dirpath=argv[1]; else ++qflag; if (strcmp("-q", argv[2])) dirpath=argv[2]; else ++qflag; } /* check shell environment */ if ((oracle_sid=getenv("ORACLE_SID"))) ++iflag; if (!(lgname=getenv("ALV_LNAME"))) lgname=LOGNAME; lnamelen=strlen(lgname); if (chdir(dirpath) == -1) { perror("Could not chdir"); fprintf(stderr,"dir: (%s)\n",dirpath); retval=2; } else { char *status_map=(char *)calloc(MAXSEQ + 4096, 1); int lseq; /* flag file names containing too many sequence digits */ register int dgcnt, seq=1, hiseq=0, loseq=MAXSEQ+1; register char *sptr,*sptr2,*sptr3; off_t size; if((sptr=status_map) == NULL) { fputs("Out of memory\n",stderr); retval=3; } else { char *seqstr=status_map+(MAXSEQ + 4095 - 1023); struct stat sbuf; DIR *dirp; register struct dirent *dp; if ((dirp=opendir(".")) == NULL) { perror("Could not open directory for reading"); fprintf(stderr,"dir: (%s)\n",dirpath); retval=4; } else { while ((dp=readdir(dirp)) != NULL) { if (strncmp(dp->d_name, lgname, lnamelen)) continue; /* get log sequence number */ sptr2=dp->d_name; sptr3=seqstr; while (*sptr2++ != '_') {}; while (*sptr2++ != '_') ; dgcnt=0; lseq=0; while (*sptr2 != '_') { *sptr3++ = *sptr2++; if (++dgcnt > SQLEN) { ++lseq; break; } } *sptr3='\0'; /* checks on sequence number */ if (lseq) { fprintf(stderr,"Sequence out of range: %s\n",seqstr); continue; } if ((seq=strtol(seqstr, (char **)NULL, 10)) == 0) { fprintf(stderr,"Sequence mangled: %s\n",seqstr); continue; } if (seq > hiseq) hiseq=seq; if (seq < loseq) loseq=seq; /* assign status of archived log */ if (stat(dp->d_name, &sbuf) == -1) { perror("Could not stat"); fprintf(stderr,"file: (%s)\n",dp->d_name); *(sptr+seq)=BAD_arch; } else if ((size=sbuf.st_size) == 0) *(sptr+seq)=NULL_arch; else switch (verify(dp->d_name, size)) { case 0: *(sptr+seq)=GOOD_arch; break; case 1: case 2: *(sptr+seq)=BAD_arch; break; case 3: *(sptr+seq)=TRUNC_arch; break; case 4: *(sptr+seq)=NOT_oracle; break; case 5: *(sptr+seq)=NOT_redo; break; case 6: *(sptr+seq)=CORRUPT_arch; break; case 9: *(sptr+seq)=GOOD_arch; retval=5; break; case 10: *(sptr+seq)=NOT_sid; break; case 11: *(sptr+seq)=ABORT_arch; } if(retval == 3) { fputs("Out of memory\n",stderr); break; } } } /* print any output */ for (seq=loseq,sptr=(sptr + loseq); seq <= hiseq; ++seq,++sptr) switch(*sptr) { case GOOD_arch: printf(GOOD_a); break; case NULL_arch: printf(NULL_a); break; case CORRUPT_arch: printf(CORRUPT_a); break; case TRUNC_arch: printf(TRUNC_a); break; case NOT_oracle: printf(NOT_o); break; case NOT_redo: printf(NOT_r); break; case NOT_sid: printf(NOT_i); break; case BAD_arch: printf(BAD_a); break; case ABORT_arch: printf(ABORT_a); break; default: printf(GONE_a); } } } } return(retval); } /* Verify the redo blocks of a puported archived redo log file after first establishing that it is a redo log. Retval: 0 - verified 1 - open failure 2 - mapping error 3 - wrong size 4 - not Oracle file 5 - not redo log 6 - corrupt (bad checksum) 9 - no checksum check 10 - wrong SID 11 - block not written out */ int verify(arcfile, size) const char *arcfile; off_t size; { int afile, retval=0; #if defined _10gR2 || defined _11gR1 int sublocks; u_short blksz; struct oracle_file_header *ofilehdr; /* per-file header */ struct oracle_block_header *oblkhdr; /* block header */ register char *ofileaddr; /* file memory mapping */ char *ofaddr; /* preliminary checks on file header */ if ((afile=open(arcfile, O_RDONLY)) == -1 ) retval=1; else if ((ofileaddr=mmap((caddr_t)0, size, PROT_READ, MAP_SHARED, afile, (off_t)0)) == MAP_FAILED) retval=2; else { ofilehdr=(struct oracle_file_header *)ofileaddr; blksz=htons(ofilehdr->blksz); ofaddr=ofileaddr; /* save for unmapping */ if (htonl(ofilehdr->magic) != MAGIC) { retval=4; } else if (htons(ofilehdr->type) != ARC_REDO) { retval=5; } else if (size != blksz * (htonl(ofilehdr->numblks) + 1)) { retval=3; } else { register int n=htonl(ofilehdr->numblks)+1,i; /* no. of blks less block 0 */ sublocks=blksz / 64; /* 64 byte sub-blocks per block */ /* scan from block 1 onwards */ for (i=1; i < n; ++i) { ofileaddr+=blksz; oblkhdr=(struct oracle_block_header*)ofileaddr; /* block header */ /* DBNAME check on block 1 */ if (i == 1 && iflag == 1) { register int n; register char *osid=oracle_sid; for (n=0; n<8; ++n) { if (oblkhdr->dbname[n] == '\0'||*osid == '\0') break;/* end check*/ if (oblkhdr->dbname[n] != *osid++) { retval=10; break; } } if (retval == 10) break; } /* ..completed quick checks */ if (qflag) { retval=9; break; } /* incomplete block check */ if (htonl(oblkhdr->blknum) == 0) { retval=11; break; } /* verify block checksum */ if (afcks(ofileaddr, sublocks) == -1) { retval=6; break; } } } } munmap(ofaddr, size); close(afile); #endif return(retval); } /* Checksum a redo block. Retval: 0 - ok -1 - error */ afcks(oblk, sblks) unsigned char *oblk; int sblks; { register unsigned char *sbptr=oblk, /* divide block into 64 byte sub-blocks */ *scptr1,*scptr2,*scptr3,*scptr4; unsigned char res1[16],res2[16],res3[16],res4[16],res5[4]; register int n,i=sblks,j; int retval=0; memset(res4, 0, 16); /* divide block into 64 byte sub-blocks and generate a 16 byte XORing result */ for (n=0; n < i; ++n) { scptr1=sbptr; scptr2=sbptr+16; scptr3=sbptr+32; scptr4=sbptr+48; for (j=0; j < 16; ++j) { *(res1+j) = (*(scptr1+j) ^ *(scptr2+j)); *(res2+j) = (*(scptr3+j) ^ *(scptr4+j)); *(res3+j) = (*(res1+j) ^ *(res4+j)); *(res4+j) = (*(res2+j) ^ *(res3+j)); } sbptr+=64; } /* create 4 byte "word" frpm 16 byte result */ scptr1=res4; scptr2=scptr1+4; scptr3=scptr2+4; scptr4=scptr3+4; for (j=0; j < 4; ++j) { *(res5+j)=(*(scptr1+j) ^ *(scptr2+j)); *(res5+j)=(*(res5+j) ^ *(scptr3+j)); *(res5+j)=(*(res5+j) ^ *(scptr4+j)); } /* checksum valid if high order bytes match low order bytes */ if (((*res5) ^ (*(res5+2))) | ((*(res5+1)) ^ (*(res5+3)))) retval=-1; return(retval); }