/* -*- Mode:C; c-basic-offset:4; tab-width:8; indent-tabs-mode:t -*- */ /* * Copyright (C) 2004-2024 by the University of Southern California * $Id: a91d5228a362749a1ccff71f0fbdc092ac7ebdcc $ * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * 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, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * */ /* setup environment */ #define _GNU_SOURCE 1 #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include //xxx need to check in autoconf #include #include "cryptopANT.h" //autoconfigured #include "config.h" //long options #define PASS4 256 #define PASS6 257 #define KTYPE 258 #define IPPREFIX 259 #define IPSUFFIX 260 #define IP4MAXLEN 15 //strlen("xxx.xxx.xxx.xxx") #define IP6MAXLEN 39 //strlen("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx") #define CBUF_SZ (IP6MAXLEN*2) #define INITBUFSIZE 4096 //xxx need more space in case text expands due to IP scrambling #if HAVE__U6_ADDR32 #define s6_addr32 __u6_addr.__u6_addr32 #endif #if HAVE_REGEX_H static const char REGEX4[]="((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))"; static const char REGEX6[]="(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:)))(%.+)?)"; #endif //HAVE_REGEX_H static int pass_bits4 = 0; static int pass_bits6 = 0; static int reverse_mode = 0; //buffers for in/out processing static char *linebuf1 = NULL,*linebuf2 = NULL; static size_t linebuf1_sz = 0, linebuf2_sz = 0; static char cbuf[CBUF_SZ]; void usage(const char *pname) { fprintf(stderr, "Read IP addresses from stdin and print scrambled addresses on stdout\n" "USAGE:\n" "\t%s [OPTIONS] [-r] key_file\n" "\tWhere OPTIONS are:\n" "\t\t--newkey|-G generate a new key key_file\n" "\t\t--type=TYPE crypto to use for key generation (valid only with --newkey option)\n" "\t\t supported choices are: blowfish (default), aes, sha1, md5\n" "\t\t--pass4= pass higher bits of ipv4 addresses through unchanged\n" "\t\t--pass6= pass higher bits of ipv6 addresses through unchanged\n" #if HAVE_REGEX_H "\t\t--ip-prefix= all ips must be prefixed by this regex prefix (both ipv4 and ipv6)\n" "\t\t--ip-suffix= all ips must be followed by this regex suffix (both ipv4 and ipv6)\n" "\t\t-t text mode: read text from stdin and scramble all addresses\n" "\t\t that can be found using regex (use with caution)\n" #endif //HAVE_REGEX_H "\t\t-r reverse-mode i.e. for unscrambling ip addresses\n" , pname); exit(1); } int anon_ip4_txt(const char *oldip, char *newip) { struct in_addr ip4, ip4s; if (inet_pton(AF_INET, oldip, &ip4) <= 0) { fprintf(stderr, "don't understand address (%s)\n", oldip); #ifdef HAVE_STRLCPY strlcpy(newip, oldip, INITBUFSIZE); #else strcpy(newip, oldip); //copy without changing #endif } else { ip4s.s_addr = (reverse_mode) ? unscramble_ip4(ip4.s_addr, pass_bits4) : scramble_ip4(ip4.s_addr, pass_bits4); if (newip != inet_ntop(AF_INET, &ip4s, newip, 256)) { perror("Error: can't print new address"); exit(1); } } return strlen(newip); } int anon_ip6_txt(const char *oldip, char *newip) { struct in6_addr ip6; if (inet_pton(AF_INET6, oldip, &ip6) <= 0) { fprintf(stderr, "don't understand address (%s)\n", oldip); #ifdef HAVE_STRLCPY strlcpy(newip, oldip, INITBUFSIZE); #else strcpy(newip, oldip); //copy without changing #endif } else { if (reverse_mode) { unscramble_ip6(&ip6, pass_bits6); } else { scramble_ip6(&ip6, pass_bits6); } if (newip != inet_ntop(AF_INET6, &ip6, newip, 256)) { perror("Error: can't print new address"); exit(1); } } return strlen(newip); } //return the pointer in 'in' buffer where processing stopped const char* search_replace_ip(const char *in, char *out, const regex_t *r, int (*anonf)(const char*, char*), size_t *outsz) { const char *c = in; char *c2 = out; regmatch_t re_match[2]; //0th element is the whole thing (including prefix and suffix) //1st element is the IP address while (0 == regexec(r, c, 2, re_match, 0)) { const char *cc=c; if (re_match[1].rm_so != -1) { const char *ipbeg = cc + re_match[1].rm_so; const char *ipend = cc + re_match[1].rm_eo; size_t copylen = ipbeg - c; //from prev match to this one, pass-thru, including prefix //copy from c..(beg-1), advance both pointers while (c2 - out + copylen >= *outsz) { *outsz *= 2; out = realloc(out, *outsz); if (out == NULL) { fprintf(stderr, "Error: run out of buffer space\n"); exit(1); } } memcpy(c2, c, copylen); c += copylen; c2+= copylen; char hold = *ipend; //store last character, we'll need temporarly replace it with '\0' *(char*)ipend = '\0'; size_t anoniplen = anonf(c, c2); *(char*)ipend = hold; //restore c = ipend; c2 += anoniplen; } } //copy the rest size_t copylen = strlen(c) + 1; while (c2 - out + copylen >= *outsz) { *outsz *= 2; out = realloc(out, *outsz); if (out == NULL) { fprintf(stderr, "Error: run out of buffer space\n"); exit(1); } } memcpy(c2, c, copylen); return out; } int main(int argc, char *argv[]) { FILE *keyfile = NULL; const char *keyfn = NULL; const char *pname = argv[0]; int opt; int text_mode = 0; int opt_newkey = 0; char *opt_keytype = NULL; #if HAVE_REGEX_H char *opt_ipprefix = NULL; char *opt_ipsuffix = NULL; char regex4[4096]; char regex6[4096]; #endif //HAVE_REGEX_H scramble_crypt_t key_crypto = SCRAMBLE_BLOWFISH; struct option long_options[] = { {"newkey",0, NULL, 'G'}, {"help", 0, NULL, 'h'}, {"pass4", 1, NULL, PASS4}, {"pass6", 1, NULL, PASS6}, {"type", 1, NULL, KTYPE}, #if HAVE_REGEX_H {"text", 0, NULL, 't'}, {"ip-prefix", 1, NULL, IPPREFIX}, {"ip-suffix", 1, NULL, IPSUFFIX}, #endif //HAVE_REGEX_H }; while((opt = getopt_long(argc, argv, "Ghr" #if HAVE_REGEX_H "t" #endif //HAVE_REGEX_H , long_options, NULL)) != EOF) { switch(opt) { /* long options first: */ case PASS4: pass_bits4 = atoi(optarg); if (pass_bits4 < 0 || pass_bits4 > 32) { fprintf(stderr, "Error: --pass4 option argument must be within [0..32]\n"); exit(1); } break; case PASS6: pass_bits6 = atoi(optarg); if (pass_bits6 < 0 || pass_bits6 > 128) { fprintf(stderr, "Error: --pass6 option argument must be within [0..128]\n"); exit(1); } break; case KTYPE: opt_keytype = optarg; if (strcmp(opt_keytype, "blowfish") == 0) { key_crypto = SCRAMBLE_BLOWFISH; } else if (strcmp(opt_keytype, "aes") == 0) { key_crypto = SCRAMBLE_AES; } else if (strcmp(opt_keytype, "sha1") == 0) { key_crypto = SCRAMBLE_SHA1; } else if (strcmp(opt_keytype, "md5") == 0) { key_crypto = SCRAMBLE_MD5; } else { fprintf(stderr, "Error: unsupported crypto key type: '%s' (can be one of: blowfish, aes, sha1, md5)\n", opt_keytype); exit(1); } break; #if HAVE_REGEX_H case IPPREFIX: opt_ipprefix = optarg; break; case IPSUFFIX: opt_ipsuffix = optarg; break; case 't': text_mode = 1; break; #endif //HAVE_REGEX_H /* short options: */ case 'G': opt_newkey = 1; break; case 'h': usage(pname); /* never returns */ break; case 'r': reverse_mode = 1; break; default: usage(pname); } } argc -= optind; argv += optind; if (argc != 1) { usage(pname); } keyfn = argv[0]; if (opt_newkey && (text_mode || reverse_mode || pass_bits4 || pass_bits6)) { fprintf(stderr, "Error: --newkey or -G is mutually exclusive with other options.\n"); exit(1); } if (opt_keytype && !opt_newkey) { fprintf(stderr, "Error: --type requires --newkey (-G) option.\n"); exit(1); } #if HAVE_REGEX_H if (!text_mode && (opt_ipprefix != NULL || opt_ipsuffix != NULL)) { fprintf(stderr, "--ip-prefix and --ipsuffix require text mode (-t).\n"); exit(1); } #endif //HAVE_REGEX_H if ((keyfile = fopen(keyfn, "r")) == NULL) { if (!opt_newkey) { /* no keyfile, but supposed to exist */ fprintf(stderr, "Error: cannot open the key_file: '%s': %s\n", keyfn, strerror(errno)); exit(1); } } else { if (opt_newkey) { /* keyfile exists, but asked to make new one */ fprintf(stderr, "Error: keyfile '%s' already exists, remove it before trying to generate a new one.\n", keyfn); exit(1); } } if (keyfile) fclose(keyfile); if (scramble_init_from_file(keyfn, key_crypto, key_crypto, NULL) < 0) { fprintf(stderr, "Error: can't initialize from keyfile '%s'\n", keyfn); exit(1); } if (opt_newkey) { //only generating mode exit(0); } if (!text_mode && setvbuf(stdout, NULL, _IOLBF, 0) < 0) { fprintf(stderr, "Error: setting line buffering: %s\n", strerror(errno)); exit(1); } #if 1 //HAVE_REGEX_H if (text_mode) { linebuf1_sz = INITBUFSIZE; linebuf2_sz = INITBUFSIZE; linebuf1 = malloc(linebuf1_sz); linebuf2 = malloc(linebuf2_sz); memset(regex4, 0, sizeof(regex4)); memset(regex6, 0, sizeof(regex6)); if (opt_ipprefix != NULL) { strncat(regex4, opt_ipprefix, sizeof(regex4)-1); strncat(regex6, opt_ipprefix, sizeof(regex6)-1); } strncat(regex4, REGEX4, sizeof(regex4)-1); strncat(regex6, REGEX6, sizeof(regex6)-1); if (opt_ipsuffix != NULL) { strncat(regex4, opt_ipsuffix, sizeof(regex4)-1); strncat(regex6, opt_ipsuffix, sizeof(regex6)-1); } if (regex4[sizeof(regex4)-1] != '\0' || regex6[sizeof(regex6)-1] != '\0') { fprintf(stderr, "Error: regex for ip addresses is too long."); exit(1); } regex_t r4, r6; if (0 != regcomp(&r4, regex4, REG_EXTENDED|REG_NEWLINE)) exit(1); if (0 != regcomp(&r6, regex6, REG_EXTENDED|REG_NEWLINE)) exit(1); linebuf1[linebuf1_sz-1]='x'; //anything but zero //xxx todo: use buffered reading/writing while (fgets(linebuf1, linebuf1_sz, stdin) != NULL) { //fgets adds a terminating '\0', reading stops after an EOF or a newline if (linebuf1[linebuf1_sz-1] == '\0' && linebuf1[linebuf1_sz-2] != '\n') { //filled the buffer, but no newline; xxx remove newline dependency fprintf(stderr, "Error: input lines are too long; maximum supported is %zd.\n", linebuf1_sz-1); exit(1); } //read a full line, can be anonymized in full linebuf2 = (char*)search_replace_ip(linebuf1, linebuf2, &r4, &anon_ip4_txt, &linebuf2_sz); linebuf1 = (char*)search_replace_ip(linebuf2, linebuf1, &r6, &anon_ip6_txt, &linebuf1_sz); //output fputs(linebuf1, stdout); } regfree(&r4); regfree(&r6); free(linebuf1); free(linebuf2); return 0; } #endif for (;;) { int af; int i; int prefix = 0; char *plen = NULL; char *c; struct in_addr ip4, ip4s; struct in6_addr ip6, ip6s; void *new = NULL; // char *c2; if (fgets(cbuf, CBUF_SZ, stdin) == NULL) break; for (i = strnlen(cbuf, CBUF_SZ-1)-1; i >= 0; --i) { if (!isgraph(cbuf[i])) cbuf[i] = '\0'; } plen = NULL; /* first see if this is a network */ if (NULL != (c = strchr(cbuf, '/'))) { /* this is a network */ plen = c + 1; if (!isdigit(*plen)) { fprintf(stderr, "can't parse network prefix (%s)\n", cbuf); continue; } *c = '\0'; prefix = atoi(plen); } /* first try ipv4 */ af= AF_INET; if (inet_pton(af, cbuf, &ip4) <= 0) { /* next try ipv6 */ af= AF_INET6; if (inet_pton(af, cbuf, &ip6) <= 0) { fprintf(stderr, "don't understand address (%s)\n", cbuf); continue; } ip6s = ip6; if (reverse_mode) { unscramble_ip6(&ip6s, pass_bits6); } else { scramble_ip6(&ip6s, pass_bits6); } new = &ip6s; /* if it was a network, zero out host bits */ if (plen && prefix < 128) { int hostbits = 128 - prefix; i = 3; while (hostbits >= 32) { ip6s.s6_addr32[i] = 0; --i; hostbits -= 32; } if (hostbits > 0) { ip6s.s6_addr32[i] = htonl(ntohl(ip6s.s6_addr32[i]) & (0xffffffffUL << hostbits)); } } } else { ip4s.s_addr = (reverse_mode) ? unscramble_ip4(ip4.s_addr, pass_bits4) : scramble_ip4(ip4.s_addr, pass_bits4); new = &ip4s; /* if it was a network, zero out host bits */ if (plen && prefix < 32) { int hostbits = 32 - prefix; ip4s.s_addr = ntohl(ip4s.s_addr); if (hostbits == 32) ip4s.s_addr = 0; else ip4s.s_addr &= 0xffffffffUL << hostbits; ip4s.s_addr = htonl(ip4s.s_addr); } } if (cbuf != inet_ntop(af, new, cbuf, CBUF_SZ)) { perror("Error: can't print new address"); exit(1); } printf("%s", cbuf); if (plen) printf("/%d", prefix); printf("\n"); } scramble_cleanup(); return 0; }