Commit e76ff9e7 authored by Anders Blomdell's avatar Anders Blomdell
Browse files

Combined md5toc and rust-hashtoc

parents
*~
sha512toc
sha512toc.spec
*.o
*.tar
*.src.rpm
This diff is collapsed.
TARGET=sha512toc
SHA512TOC_VERSION=$(shell git describe | sed -e 's/^v//;s/-/_/g')
SOURCES=Makefile sha512toc.c sha512toc.spec
CFLAGS=-Wall -Werror -I.
VERSION=-DSHA512TOC_VERSION='"$(SHA512TOC_VERSION)"'
all: sha512toc
sha512toc: sha512toc.c sha512.o Makefile
gcc $(CFLAGS) $(VERSION) -o $@ $(filter %.c %.o, $^) -lcrypto
%.o: %.c Makefile
gcc $(CFLAGS) -o $@ -c $<
sha512toc.tar: $(SOURCES)
tar -cvf $@ --transform 's|.*|sha512toc/&|' $^
sha512toc.spec: sha512toc.spec.template Makefile
sed -e 's/__SHA512TOC_VERSION__/$(SHA512TOC_VERSION)/' $< > $@
echo $(SHA512TOC_VERSION:%-=%-)
srpm: sha512toc.spec sha512toc.tar Makefile
rpmbuild --define "_topdir $$(pwd)" \
--define "_builddir $$(pwd)" \
--define "_rpmdir $$(pwd)" \
--define "_sourcedir $$(pwd)" \
--define "_specdir $$(pwd)" \
--define "_srcrpmdir $$(pwd)" \
--define "_buildrootdir $$(pwd)" \
-bs sha512toc.spec
clean:
rm -f *.o *~ $(TARGET) sha512toc.tar sha512toc.spec *.src.rpm
# sha512toc #
Traverses directory paths generating sha512 hashes of all files and
symlinks within those directories.
usage: sha512toc [options*] [--] { file | directory }*
optional arguments:
-h, --help show this help message and exit
-x, --xattr read/write sha512 extended attribute
-r, --read-xattr read sha512 extended attribute
(remove if mtime mismatch detected)
-w, --write-xattr write sha512 extended attribute
-c, --clear-xattr remove sha512 extended attribute
-m, --max-age SECONDS max age of sha512 extended attribute
-v, --verbose be verbose
/*
Copyright (C) 2018 Anders Blomdell <anders.blomdell@control.lth.se>
This file is part of hashtoc.
Hashtoc is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3.
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, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <openssl/sha.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include "sha512toc.h"
#define BLOCKSIZE 4096
#define XATTR_SHA512HASH "trusted.sha512.control.lth.se"
struct __attribute__((__packed__)) hash_attr {
time_t hash_time;
time_t mtime;
uint32_t mtime_nsec;
uint8_t hash[SHA512_DIGEST_LENGTH];
};
void hash_sha512_buf(char *buf,
size_t length,
unsigned char *hash)
{
SHA512_CTX ctx;
SHA512_Init(&ctx);
SHA512_Update(&ctx, buf, length);
SHA512_Final(hash, &ctx);
}
int hash_sha512_file(char *filename,
time_t mtime,
int64_t mtime_nsec,
int flags,
time_t maxage,
unsigned char *hash)
{
int fd;
int result = 0, recalc = 1;
struct hash_attr hash_attr;
if (flags & FLAGS_CLEAR_XATTR) {
if (removexattr(filename, XATTR_SHA512HASH) != 0) {
if (errno != ENODATA) {
result = -1;
fprintf(stderr, "Failed to remove xattr '%s'\n", filename);
goto out;
} else if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) {
fprintf(stderr, "No xattr for '%s'\n", filename);
}
} else {
if ((flags & FLAGS_VERBOSE_MASK) > FLAGS_VERBOSE2) {
fprintf(stderr, "Removed xattr for '%s'\n", filename);
}
}
}
if (flags & FLAGS_NO_CALC_HASH) {
// We should not calculate hash
goto out;
}
if (flags & FLAGS_READ_XATTR) {
int err;
err = getxattr(filename, XATTR_SHA512HASH, &hash_attr, sizeof(hash_attr));
if (err < 0 && errno != ENODATA) {
result = -1;
goto out;
}
if (err != sizeof(hash_attr)) {
recalc = 1;
} else if (le64toh(hash_attr.mtime) != mtime) {
recalc = 1;
} else if (le32toh(hash_attr.mtime_nsec) != mtime_nsec) {
recalc = 1;
} else if (flags & FLAGS_MAX_AGE && le64toh(hash_attr.hash_time) < maxage) {
recalc = 1;
} else {
recalc = 0;
memcpy(hash, hash_attr.hash, SHA512_DIGEST_LENGTH);
}
}
if (recalc == 0) {
goto out;
}
fd = open (filename, O_RDONLY);
if (fd < 0) {
result = -1;
goto out;
} else {
SHA512_CTX ctx;
unsigned char buffer[BLOCKSIZE];
int count;
time_t now;
time(&now);
SHA512_Init(&ctx);
while ((count = read(fd, buffer, BLOCKSIZE)) > 0) {
SHA512_Update(&ctx, buffer, count);
}
SHA512_Final(hash, &ctx);
if (count < 0) {
result = -1;
fremovexattr(fd, XATTR_SHA512HASH);
goto out_close;
}
if (flags & FLAGS_WRITE_XATTR) {
int err;
hash_attr.hash_time = htole64(now);
hash_attr.mtime = htole64(mtime);
hash_attr.mtime_nsec = htole32(mtime_nsec);
memcpy(hash_attr.hash, hash, SHA512_DIGEST_LENGTH);
err = fsetxattr(fd, XATTR_SHA512HASH, &hash_attr, sizeof(hash_attr), 0);
if (err < 0) {
result = -1;
fremovexattr(fd, XATTR_SHA512HASH);
goto out_close;
}
} else if (flags & FLAGS_READ_XATTR) {
fremovexattr(fd, XATTR_SHA512HASH);
}
}
out_close:
close(fd);
out:
return result;
}
extern int hash_sha512_file(char *filename,
time_t mtime,
int64_t mtime_nsec,
int flags,
time_t maxage,
unsigned char *hash);
extern void hash_sha512_buf(char *buf,
size_t length,
unsigned char *hash);
/*
Copyright (©) 2003-2015 Anders Blomdell <anders.blomdell@control.lth.se>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE /* See feature_test_macros(7) */
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <openssl/sha.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <time.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <fcntl.h> /* Definition of AT_* constants */
#include "sha512toc.h"
#include "sha512.h"
#define XATTR_sha512SUM "trusted.sha512sum.control.lth.se"
/*
#define FLAGS_READ_XATTR 0x01
#define FLAGS_WRITE_XATTR 0x02
#define FLAGS_CLEAR_XATTR 0x04
#define FLAGS_MAX_AGE 0x08
#define FLAGS_VERBOSE 0x10
#define FLAGS_NUL_TERMINATED 0x20
*/
struct options {
int flags;
time_t max_age;
};
void usage()
{
fprintf(stderr, "# sha512toc version: %s\n", SHA512TOC_VERSION);
fprintf(stderr, "usage: sha512toc [options*] [--] { file | directory }*\n");
fprintf(stderr, "optional arguments:\n");
fprintf(stderr,
" -h, --help show this help message and exit\n"
" -x, --xattr read/write sha512 extended attribute\n"
" -r, --read-xattr read sha512 extended attribute\n"
" (remove if mtime mismatch detected)\n"
" -w, --write-xattr write sha512 extended attribute\n"
" -c, --clear-xattr remove sha512 extended attribute\n"
" -m, --max-age SECONDS max age of sha512 extended attribute\n"
" -v, --verbose be verbose\n"
" -z, --zero-terminated zero terminated lines\n");
exit(1);
}
void dofile(char *name, dev_t *dev, struct options *options);
void dodir(char *name, dev_t *dev, struct options *options);
void trim(char *s) {
int last_was_slash, r, w;
last_was_slash = 0;
r = 0;
w = 0;
/* Trim of leading .[/]+ */
while (s[r] == '.' && s[r + 1] == '/') {
r = r + 2;
while (s[r] == '/') { r++; }
}
/* Replace [/]+ with [/] */
for ( ; s[r] != 0 ; r++) {
switch (s[r]) {
case '/': {
if (! last_was_slash) { s[w++] = s[r]; }
last_was_slash = 1;
} break;
default: {
last_was_slash = 0;
s[w++] = s[r];
} break;
}
}
s[w] = 0;
w--;
while (w >= 0) {
if (w > 0 && s[w] == '/') {
/* Trim trailing '/', but keep it if it's first in name */
s[w] = 0;
w--;
} else if (w >= 1 && s[w - 1] == '/' && s[w] == '.') {
/* Replace trailing '/.' with '/' */
s[w] = 0;
w--;
} else if (w == 0 && s[w] == '.') {
/* Replace '.' with '' */
s[w] = 0;
w--;
} else {
break;
}
}
}
void printentry(long long size, int mtime,
int uid, int gid, int mode,
char *sha512, char kind, char *name,
struct options *options)
{
trim(name);
if (name[0]) {
/* Don't print empty filenames */
if (kind != 'D') {
printf("%Ld:%d:%d:%d:%o:%s:%c:%s%c",
size, mtime, uid, gid, mode & ~S_IFMT, sha512, kind, name,
(options->flags & FLAGS_NUL_TERMINATED) ? '\0' : '\n');
} else {
printf("::%d:%d:%o:%s:%c:%s%c",
uid, gid, mode & ~S_IFMT, sha512, kind, name,
(options->flags & FLAGS_NUL_TERMINATED) ? '\0' : '\n');
}
}
}
int dotfilter(const struct dirent *entry)
{
const char *p = entry->d_name;
if ((p[0] == '.') && ((p[1] == 0) ||
(p[1] == '.' && p[2] == 0))) {
return 0;
}
return 1;
}
void dodir(char *dirname, dev_t *dev, struct options *options)
{
struct dirent **entry = NULL;
int i, n;
if (getuid() != 0 &&
faccessat(AT_FDCWD, dirname, R_OK|X_OK, AT_SYMLINK_NOFOLLOW) != 0) {
/* Bail out if no access */
fprintf(stderr, "Failed to access '%s'\n", dirname);
goto out;
}
n = scandir(dirname, &entry, dotfilter, alphasort);
for (i = 0 ; i < n ; i++) {
const char *d_name = entry[i]->d_name;
char *filename;
filename = malloc(strlen(dirname) + strlen(d_name) + 2);
filename[0] = 0;
strcat(filename, dirname);
strcat(filename, "/");
strcat(filename, d_name);
dofile(filename, dev, options);
free(filename);
free(entry[i]);
}
free(entry);
out:
;
}
int filenameislegal(char *filename)
{
int result = 1;
char *p = filename;
while (*p && result) {
if (*p > 0 && *p < ' ') {
int pos;
result = 0;
fprintf(stderr, "Filename '");
for (pos = 0 ; filename[pos] ; pos++) {
if (filename[pos] > 0 && filename[pos] < ' ') {
fprintf(stderr, "?");
} else {
fprintf(stderr, "%c", filename[pos]);
}
}
fprintf(stderr, "' contains illegal characters\n");
for (pos = 0 ; filename[pos] ; pos++) {
if (filename[pos] > 0 && filename[pos] < ' ') {
int i;
for (i = 0 ; i < pos + 10 ; i++) {
fprintf(stderr, " ");
}
fprintf(stderr, "^ = 0x%2.2x\n", filename[pos]);
}
}
}
p++;
}
return result;
}
void dofile(char *filename, dev_t *dev, struct options *options)
{
struct stat64 buf;
int err;
if (getuid() != 0 &&
faccessat(AT_FDCWD, filename, R_OK, AT_SYMLINK_NOFOLLOW) != 0) {
/* Bail out if no access */
fprintf(stderr, "Failed to access '%s'\n", filename);
goto out;
}
err = lstat64(filename, &buf);
if (err == 0 && dev == 0) { dev = &buf.st_dev; }
if (err != 0) {
fprintf(stderr, "Failed to lstat '%s'\n", filename);
} else if (*dev != buf.st_dev) {
fprintf(stderr, "skipping mountpoint '%s'\n", filename);
} else if (options->flags & FLAGS_NUL_TERMINATED ||
filenameislegal(filename)) {
if (S_ISREG(buf.st_mode)) {
unsigned char hash[SHA512_DIGEST_LENGTH];
char sha512[SHA512_DIGEST_LENGTH * 2 + 1];
int i;
hash_sha512_file(filename,
buf.st_mtim.tv_sec, buf.st_mtim.tv_nsec,
options->flags, options->max_age,
hash);
for (i = 0 ; i < SHA512_DIGEST_LENGTH ; i++) {
sprintf(&sha512[i*2], "%2.2x", hash[i]);
}
printentry(buf.st_size, buf.st_mtime,
buf.st_uid, buf.st_gid, buf.st_mode,
sha512, 'F', filename, options);
} else if (S_ISDIR(buf.st_mode)) {
if (! (options->flags & FLAGS_CLEAR_XATTR)) {
printentry(0, 0, buf.st_uid, buf.st_gid, buf.st_mode,
"", 'D', filename, options);
}
if (filename[0] == 0) { filename = "."; }
dodir(filename, dev, options);
} else if (options->flags & FLAGS_CLEAR_XATTR) {
goto out;
} else if (S_ISLNK(buf.st_mode)) {
char *link = malloc(buf.st_size + 1);
err = readlink(filename, link, buf.st_size);
if (err != buf.st_size) {
fprintf(stderr, "failed to readlink: %s\n", filename);
} else {
unsigned char hash[SHA512_DIGEST_LENGTH];
char sha512[SHA512_DIGEST_LENGTH * 2 + 1];
int i;
hash_sha512_buf(link, buf.st_size, hash);
for (i = 0 ; i < SHA512_DIGEST_LENGTH ; i++) {
sprintf(&sha512[i*2], "%2.2x", hash[i]);
}
printentry(buf.st_size, buf.st_mtime,
buf.st_uid, buf.st_gid, buf.st_mode,
sha512, 'L', filename, options);
}
free(link);
} else {
char kind;
switch(buf.st_mode & S_IFMT) {
case S_IFSOCK: { kind = 'S'; } break;
case S_IFBLK: { kind = 'B'; } break;
case S_IFCHR: { kind = 'C'; } break;
case S_IFIFO: { kind = 'F'; } break;
default: { kind = 'U'; }
}
printentry(buf.st_size, buf.st_mtime,
buf.st_uid, buf.st_gid, buf.st_mode,
"", kind, filename, options);
}
}
out:
;
}
int main(int argc, char *argv[])
{
int i;
struct options options = {
.flags = 0,
.max_age = 0
};
int verbosity = 0;
static struct option long_options[] = {
{"help", no_argument, NULL, 'h' },
{"xattr", no_argument, NULL, 'x' },
{"read-xattr", no_argument, NULL, 'r' },
{"write-xattr", no_argument, NULL, 'w' },
{"clear-xattr", no_argument, NULL, 'c' },
{"max-age", required_argument, NULL, 'm' },
{"verbose", no_argument, NULL, 'v' },
{"zero-terminated", no_argument, NULL, 'z' },
{0, 0, NULL, 0 }
};
while (1) {
int v, i = 0;
v = getopt_long(argc, argv, "hxrwcm:v", long_options, &i);
if (v == -1) break;
switch (v) {
case 'h':
case ':':
case '?': {
usage();
} break;
case 'x': {
options.flags |= FLAGS_READ_XATTR | FLAGS_WRITE_XATTR;
} break;
case 'r': {
options.flags |= FLAGS_READ_XATTR;
} break;
case 'w': {
options.flags |= FLAGS_WRITE_XATTR;
} break;
case 'c': {
options.flags |= FLAGS_CLEAR_XATTR;
} break;
case 'm': {
options.flags |= FLAGS_MAX_AGE;
time(&options.max_age);
options.max_age -= atol(optarg);
} break;
case 'v': {
verbosity += 1;
} break;
case 'z': {
options.flags |= FLAGS_NUL_TERMINATED;
} break;
}
}
switch (verbosity) {
case 0: options.flags |= FLAGS_VERBOSE0; break;
case 1: options.flags |= FLAGS_VERBOSE1; break;
case 2: options.flags |= FLAGS_VERBOSE2; break;
default: options.flags |= FLAGS_VERBOSE3; break;
}
if (options.flags & (FLAGS_READ_XATTR | FLAGS_WRITE_XATTR |
FLAGS_CLEAR_XATTR)) {
if (geteuid() != 0) {
fprintf(stderr, "Not suid or root, not using xattr\n");
options.flags &= ~(FLAGS_READ_XATTR | FLAGS_WRITE_XATTR |
FLAGS_CLEAR_XATTR);
}
}
printf("#fields: size:mtime:uid:gid:mode:sha512:kind:name%c",
(options.flags & FLAGS_NUL_TERMINATED) ? '\0' : '\n');
for (i = optind ; i < argc ; i++) {
printf("#path: %s%c", argv[i],
(options.flags & FLAGS_NUL_TERMINATED) ? '\0' : '\n');
dofile(argv[i], 0, &options);
}
printf("#endTOC%c",
(options.flags & FLAGS_NUL_TERMINATED) ? '\0' : '\n');
return 0;
}
enum {
FLAGS_READ_XATTR=0x0001,
FLAGS_WRITE_XATTR=0x0002,
FLAGS_CLEAR_XATTR=0x0004,
FLAGS_MAX_AGE=0x0008,
FLAGS_NO_CALC_HASH=0x0010,
FLAGS_NUL_TERMINATED=0x00,
FLAGS_VERBOSE_MASK=0xc000,
FLAGS_VERBOSE0=0x0000,
FLAGS_VERBOSE1=0x4000,
FLAGS_VERBOSE2=0x8000,
FLAGS_VERBOSE3=0xc000,