#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_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; }