From f70ce379de6261733dc71279c76fc76a210285e1 Mon Sep 17 00:00:00 2001 From: Daniel Roberson Date: Sun, 13 Sep 2026 07:44:32 -0700 Subject: initial commit --- pam_bloom_create.c | 236 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 pam_bloom_create.c (limited to 'pam_bloom_create.c') diff --git a/pam_bloom_create.c b/pam_bloom_create.c new file mode 100644 index 0000000..fb59a24 --- /dev/null +++ b/pam_bloom_create.c @@ -0,0 +1,236 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "bloom.h" + + +static const size_t PAM_BLOOM_MAX_ENTRY_LENGTH = 4096; + + +static uint64_t +count_lines(FILE *fp) +{ + char *line = NULL; + size_t cap = 0; + uint64_t count = 0; + + while (getline(&line, &cap, fp) != -1) { + count++; + } + + free(line); + + if (ferror(fp)) { + perror("getline"); + exit(EXIT_FAILURE); + } + + return count; +} + +static size_t +trim_newline(char *line, size_t len) +{ + if (len > 0 && line[len - 1] == '\n') { + len--; + + if (len > 0 && line[len - 1] == '\r') { + len--; + } + } + + return len; +} + +static int +validate_entry(const char *line, size_t len, uint64_t line_number) +{ + if (len == 0) { + fprintf(stderr, "line %llu: empty entry\n", (unsigned long long)line_number); + return -1; + } + + if (len > PAM_BLOOM_MAX_ENTRY_LENGTH) { + fprintf( + stderr, + "line %llu: entry exceeds maximum length of %zu bytes\n", + (unsigned long long)line_number, + PAM_BLOOM_MAX_ENTRY_LENGTH + ); + + return -1; + } + + if (memchr(line, '\0', len) != NULL) { + fprintf(stderr, "line %llu: entry contains NUL byte\n", (unsigned long long)line_number); + return -1; + } + + return 0; +} + +int +main(int argc, char **argv) +{ + double fpr = 0.001; + int opt; + + while ((opt = getopt(argc, argv, "p:")) != -1) { + switch (opt) { + case 'p': { + char *end = NULL; + + errno = 0; + + double value = strtod(optarg, &end); + + if (errno != 0 || end == optarg || *end != '\0' || + !isfinite(value) || value <= 0.0 || value >= 1.0) { + fprintf(stderr, "invalid false-positive rate: %s\n", optarg); + return EXIT_FAILURE; + } + + fpr = value; + break; + } + + default: + fprintf(stderr, "usage: %s [-p FPR] WORDLIST OUTPUT NAME\n", argv[0]); + return EXIT_FAILURE; + } + } + + if (argc - optind != 3) { + fprintf(stderr, "usage: %s [-p FPR] WORDLIST OUTPUT NAME\n", argv[0]); + return EXIT_FAILURE; + } + + const char *input = argv[optind]; + const char *output = argv[optind + 1]; + const char *name = argv[optind + 2]; + + if (!bloom_valid_filter_name((const uint8_t *)name, strlen(name) + 1)) { + fprintf(stderr, "invalid filter name\n"); + return EXIT_FAILURE; + } + + FILE *fp = fopen(input, "rb"); + if (!fp) { + perror(input); + return EXIT_FAILURE; + } + + uint64_t count = count_lines(fp); + + if (count == 0) { + fprintf(stderr, "%s: empty wordlist\n", input); + fclose(fp); + return EXIT_FAILURE; + } + + printf("entries: %llu\n", (unsigned long long)count); + + if (fseeko(fp, 0, SEEK_SET) == -1) { + perror("fseeko"); + fclose(fp); + return EXIT_FAILURE; + } + + if (count > SIZE_MAX) { + fprintf(stderr, "%s: too many entries for this platform\n", input); + fclose(fp); + return EXIT_FAILURE; + } + + bloomfilter bf = {0}; + bloom_error_t err = bloom_init(&bf, (size_t)count, fpr); + + if (err != BF_SUCCESS) { + fprintf(stderr, "bloom_init: %s\n", bloom_strerror(err)); + fclose(fp); + return EXIT_FAILURE; + } + + if (!bloom_set_name(&bf, name)) { + fprintf(stderr, "filter name too long\n"); + bloom_destroy(&bf); + fclose(fp); + return EXIT_FAILURE; + } + + char *line = NULL; + size_t capacity = 0; + ssize_t nread; + + uint64_t inserted = 0; + uint64_t line_number = 0; + + while ((nread = getline(&line, &capacity, fp)) != -1) { + line_number++; + + size_t len = trim_newline(line, (size_t)nread); + + if (validate_entry(line, len, line_number) < 0) { + free(line); + bloom_destroy(&bf); + fclose(fp); + + return EXIT_FAILURE; + } + + bloom_add(&bf, line, len); + + inserted++; + } + + if (ferror(fp)) { + perror("getline"); + + free(line); + bloom_destroy(&bf); + fclose(fp); + + return EXIT_FAILURE; + } + + free(line); + fclose(fp); + + if (inserted != count) { + fprintf(stderr, + "input changed while processing (counted %llu, read %llu)\n", + (unsigned long long)count, + (unsigned long long)inserted); + + bloom_destroy(&bf); + return EXIT_FAILURE; + } + + err = bloom_save(&bf, output); + + if (err != BF_SUCCESS) { + fprintf(stderr, "bloom_save: %s\n", bloom_strerror(err)); + + bloom_destroy(&bf); + return EXIT_FAILURE; + } + + printf("filter: %s\n", name); + printf("elements: %llu\n", (unsigned long long)inserted); + printf("bits: %zu\n", bf.size); + printf("bytes: %zu\n", bf.bitmap_size); + printf("hashes: %zu\n", bf.hashcount); + printf("fpr: %.10g\n", fpr); + printf("saturation: %.2f%%\n", bloom_saturation(&bf)); + + bloom_destroy(&bf); + + return EXIT_SUCCESS; +} -- cgit v1.2.3