summaryrefslogtreecommitdiff
path: root/pam_bloom_create.c
diff options
context:
space:
mode:
authorDaniel Roberson <daniel@badoperation.net>2026-09-13 07:44:32 -0700
committerDaniel Roberson <daniel@badoperation.net>2026-09-13 07:44:32 -0700
commitf70ce379de6261733dc71279c76fc76a210285e1 (patch)
tree07704c8156c03df696460b6c0edae6673174ecdf /pam_bloom_create.c
initial commit
Diffstat (limited to 'pam_bloom_create.c')
-rw-r--r--pam_bloom_create.c236
1 files changed, 236 insertions, 0 deletions
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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <math.h>
+
+#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;
+}