├── .gitignore ├── AUTHORS ├── LICENSE ├── README ├── loganon ├── loganon.sh ├── logrotate ├── README └── mail ├── man ├── Makefile ├── loganon.1 └── loganon.1.txt ├── ordereddict.py └── rules ├── exchange.rules ├── fortigate.rules ├── infoblox.rules ├── mail.rules └── webproxy.rules /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | = Authors 2 | 3 | Christian Roessner 4 | cr@sys4.de 5 | Franziskanerstrasse 15 6 | 81669 Muenchen 7 | Deutschland 8 | 9 | Patrick Ben Koetter 10 | p@sys4.de 11 | Franziskanerstrasse 15 12 | 81669 Muenchen 13 | Deutschland 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | = Loganon 2 | 3 | loganon is a generic log anonymizer. It expects an input, an output and a rules 4 | file. A rules file contains search and replace patterns (regular expresssions). 5 | 6 | This repository contains 'loganon', the command to anonymize log files. It also 7 | contains `loganon.sh` a script to be called from a logrotate configuration and 8 | an example rules file `mail.rules` (for Postfix and Dovecot). 9 | 10 | == Contributions welcome! 11 | 12 | Each programm has its own way of logging. If you use 'loganon' and if you create 13 | a rule file for an application 'loganon' doesn't cover yet, consider to 14 | contribute it. Each contribution makes 'loganon' more useful. 15 | 16 | == License 17 | 18 | loganon is free software: you can redistribute it and/or modify it under the 19 | terms of the GNU Lesser General Public License as published by the Free 20 | Software Foundation, either version 3 of the License, or (at your option) any 21 | later version. 22 | 23 | loganon is distributed in the hope that it will be useful, 24 | but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | GNU Lesser General Public License for more details. 27 | 28 | You should have received a copy of the GNU Lesser General Public License 29 | along with loganon. If not, see . 30 | 31 | == Copyright 32 | 33 | Copyright sys4 AG 2015 34 | 35 | // vim: set ft=asciidoc: 36 | -------------------------------------------------------------------------------- /loganon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # copyright sys4 AG 2015 4 | 5 | # This file is part of loganon. 6 | # 7 | # loganon is free software: you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation, either version 3 of the License, or (at your option) any 10 | # later version. 11 | # 12 | # loganon is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Lesser General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with loganon. If not, see . 19 | 20 | import os 21 | import sys 22 | import re 23 | import yaml 24 | import magic 25 | import io 26 | 27 | from getopt import getopt 28 | from netaddr import IPAddress, IPNetwork 29 | from ipaddress import ip_address 30 | from netaddr.core import AddrFormatError 31 | import hashlib 32 | #import zlib 33 | #from random import randint 34 | 35 | try: 36 | from collections import OrderedDict 37 | except: 38 | from ordereddict import OrderedDict 39 | 40 | def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict): 41 | """Force YAML parser to use OrderedDict instead of dict() 42 | """ 43 | class OrderedLoader(Loader): 44 | pass 45 | 46 | OrderedLoader.add_constructor( 47 | yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, 48 | lambda loader, node: object_pairs_hook(loader.construct_pairs(node))) 49 | 50 | return yaml.load(stream, OrderedLoader) 51 | 52 | def usage(): 53 | """Print a simple usage to stdout 54 | """ 55 | print("""%s [options] 56 | 57 | -h, --help prints out this help 58 | -i, --input=file log file to read 59 | -o, --output=file output result to this file 60 | -r, --rules=file1,file2,... comma seperated list of rule files 61 | 62 | Optional: 63 | 64 | -4, --mask4=number number of bits to mask an IPv4 address 65 | -6, --mask6=number number of bits to mask an IPv6 address 66 | 67 | -t, --test test pattern and print output to stdout 68 | """ % os.path.basename(__file__)) 69 | 70 | def main(): 71 | """Main application 72 | """ 73 | # input file argument 74 | fdinarg = None 75 | 76 | # output file argument 77 | fdoutarg = None 78 | 79 | # A list of file names containing rule YAML definitions 80 | rules = None 81 | 82 | # Test mode 83 | test = False 84 | 85 | # A list of YAML parsed structures 86 | rules_collection = list() 87 | 88 | # Data structure for search and replace actions 89 | rule_data = OrderedDict() 90 | 91 | # Default IPv4 bit mask 92 | bitmask4 = IPAddress("255.255.0.0") 93 | 94 | # Default IPv6 bit mask 95 | bitmask6 = IPAddress("ffff:ffff:ff00::") 96 | 97 | # Pre-compile IPv4/IPv6 pattern 98 | ipv4 = re.compile("[1-9][0-9]{0,2}\.[0-9.]{3,7}\.[0-9]{1,3}") 99 | ipv6 = re.compile("([1-9a-fA-F][0-9a-fA-F]{3}):" 100 | "[0-9a-fA-F:]{2,29}[0-9a-fA-F]{1,4}") 101 | domain = re.compile("([^\s=\"\(\):]*\.)?[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}") 102 | syslog_prio = re.compile("(auth|cron|daemon|kern|local[0-7]|lpr|mail|news|user|uucp)\.(info|notice|warning|err|alert|warn|debug|emerg|crit)", re.IGNORECASE) 103 | 104 | def get_encoding(file): 105 | blob = open(file, "rb").read() 106 | m = magic.Magic(mime_encoding=True) 107 | return m.from_buffer(blob) 108 | 109 | # Read command line options 110 | try: 111 | opts = getopt(sys.argv[1:], 112 | "hi:o:r:4:6:t", 113 | ["help", 114 | "input=", 115 | "output=", 116 | "rules=", 117 | "mask4=", 118 | "mask6=", 119 | "test"])[0] 120 | 121 | for opt, optarg in opts: 122 | if opt in ("-h", "--help"): 123 | usage() 124 | sys.exit() 125 | elif opt in ("-i", "--input"): 126 | fdinarg = str(optarg) 127 | elif opt in ("-o", "--output"): 128 | fdoutarg = str(optarg) 129 | elif opt in ("-r", "--rules"): 130 | rules = str(optarg).split(",") 131 | elif opt in ("-4", "--mask4"): 132 | bitmask4 = IPNetwork("0.0.0.0/%i" % int(optarg)).netmask 133 | elif opt in ("-6", "--mask6"): 134 | bitmask6 = IPNetwork("::/%i" % int(optarg)).netmask 135 | elif opt in ("-t", "--test"): 136 | test = True 137 | else: 138 | usage() 139 | sys.exit(os.EX_USAGE) 140 | 141 | # Required: input file and pattern 142 | if fdinarg is None or rules is None: 143 | usage() 144 | sys.exit(os.EX_USAGE) 145 | 146 | # If we are not in test mode, an output file is required 147 | if test is False and fdoutarg is None: 148 | usage() 149 | sys.exit(os.EX_USAGE) 150 | 151 | except Exception as e: 152 | print >> sys.stderr, "Syntax error: %s" % e 153 | sys.exit(os.EX_USAGE) 154 | 155 | # Read all rules 156 | try: 157 | for rule in iter(rules): 158 | with open(rule, "r") as fd_rule: 159 | rules_collection.append(ordered_load(fd_rule, 160 | yaml.SafeLoader, 161 | OrderedDict)) 162 | 163 | except IOError as e: 164 | print >> sys.stderr, "IOError: %s" % e 165 | sys.exit(os.EX_IOERR) 166 | 167 | except Exception as e: 168 | print >> sys.stderr, "Unknown error: %s" % e 169 | sys.exit(os.EX_USAGE) 170 | 171 | # Build macro dictionary 172 | for rule_entity in iter(rules_collection): 173 | for service, ruledef in rule_entity.items(): 174 | for rulename, rulepattern in ruledef.items(): 175 | search = None 176 | replace = None 177 | for patterndef in iter(rulepattern): 178 | for actiondesc, actiondef in patterndef.items(): 179 | if actiondesc == "search": 180 | search = actiondef 181 | if actiondesc == "replace": 182 | replace = actiondef 183 | if search is None: 184 | print >> sys.stderr, "Missing tag" 185 | sys.exit(os.EX_USAGE) 186 | if replace is None: 187 | print >> sys.stderr, "Missing tag" 188 | sys.exit(os.EX_USAGE) 189 | try: 190 | rule_data[rulename] = (re.compile(search), replace) 191 | except Exception as e: 192 | print >> sys.stderr, ("Syntax error in or " 193 | " pattern: %s" % e) 194 | sys.exit(os.EX_USAGE) 195 | 196 | # Open input and output files 197 | try: 198 | fd_in = io.open(fdinarg, "r", encoding=get_encoding(fdinarg)) 199 | if not test: 200 | fd_out = open(fdoutarg, "w") 201 | 202 | except IOError as e: 203 | print("IOError: %s" % e, file=sys.stderr) 204 | sys.exit(os.EX_IOERR) 205 | 206 | except Exception as e: 207 | print("Unknown error: %s" % e, file=sys.stderr) 208 | sys.exit(os.EX_USAGE) 209 | 210 | def maybe_ip(matchobj): 211 | maybe_ip = False 212 | 213 | # simple tests 214 | 215 | if matchobj.group(0).startswith("127.0.0."): 216 | return matchobj.group(0) 217 | 218 | test = matchobj.group(0).split(".") 219 | if len(test) == 4: 220 | for octet in iter(test): 221 | try: 222 | if (":" in octet or 223 | int(octet) < 0 or int(octet) > 255): 224 | return matchobj.group(0) 225 | else: 226 | maybe_ip = True 227 | except ValueError: 228 | return matchobj.group(0) 229 | elif len(test) == 1: 230 | test = matchobj.group(0).split(":") 231 | if len(test) >= 2: 232 | maybe_ip = True 233 | return maybe_ip 234 | 235 | def reduce_ip(matchobj): 236 | if maybe_ip(matchobj): 237 | try: 238 | ip = IPAddress(matchobj.group(0)) 239 | except AddrFormatError: 240 | # might be something else than an IPv6 address 241 | return matchobj.group(0) 242 | 243 | if ip.version == 4: 244 | return str(bitmask4 & ip) 245 | else: 246 | return str(bitmask6 & ip) 247 | 248 | return str(ip) 249 | 250 | else: 251 | return matchobj.group(0) 252 | 253 | def ips(start, end): 254 | '''Return IPs in IPv4 range, inclusive.''' 255 | start_int = int(ip_address(start).packed.hex(), 16) 256 | end_int = int(ip_address(end).packed.hex(), 16) 257 | return [ip_address(ip).exploded for ip in range(start_int, end_int)] 258 | 259 | iprepo_global = ips('1.2.0.1', '1.2.20.254') 260 | iprepo_private = ips('10.10.0.1', '10.10.20.254') 261 | ip_map = {} 262 | 263 | def map_ip(matchobj): 264 | if maybe_ip(matchobj): 265 | try: 266 | ip = IPAddress(matchobj.group(0)) 267 | except AddrFormatError: 268 | # might be something else than an IPv6 address 269 | return matchobj.group(0) 270 | 271 | if ip.version == 4: 272 | if not str(ip) in ip_map: 273 | if ip.is_private(): 274 | ip_map[str(ip)] = iprepo_private.pop(0) 275 | else: 276 | ip_map[str(ip)] = iprepo_global.pop(0) 277 | return ip_map[str(ip)] 278 | #return str(bitmask4 & ip) 279 | else: 280 | return str(bitmask6 & ip) 281 | 282 | return str(ip) 283 | 284 | else: 285 | return matchobj.group(0) 286 | 287 | def string_hash(_str): 288 | _hash = list(hashlib.md5(_str.encode()).hexdigest()) 289 | ret = '' 290 | for i in range(0,len(_hash),2): 291 | ret += map_char(_hash[i] + _hash[i+1]) 292 | return ret 293 | 294 | domain_map = {} 295 | 296 | def map_domain(matchobj): 297 | _domain = matchobj.group(0) 298 | if re.match(syslog_prio, _domain) is not None: 299 | return _domain 300 | if not _domain in domain_map: 301 | parts = _domain.split('.') 302 | for idx in range(len(parts)): 303 | parts[idx] = string_hash(parts[idx]) 304 | domain_map[_domain] = '.'.join(parts) 305 | #print(_domain + '=' + domain_map[_domain]) 306 | return domain_map[_domain] 307 | 308 | def map_char(hex): 309 | ic = int(hex, 16) 310 | offset = int(ic / 255 * 50) 311 | if offset <= 25: 312 | return chr(65 + offset) 313 | else: 314 | return chr(72 + offset) 315 | 316 | string_map = {} 317 | 318 | def map_string(_str): 319 | if not _str in string_map: 320 | string_map[_str] = string_hash(_str) 321 | return string_map[_str] 322 | 323 | while True: 324 | line = fd_in.readline() 325 | if not line: 326 | break 327 | 328 | # Phase 1 - search and replace pattern 329 | for key, value in rule_data.items(): 330 | try: 331 | replace = value[1] 332 | if '_MAP_' in replace: 333 | found = value[0].search(line) 334 | if found: 335 | _str = found.group(1) 336 | replace = replace.replace('_MAP_', map_string(_str)) 337 | linenew = value[0].sub(replace, line) 338 | if linenew is not None: 339 | line = linenew 340 | except Exception as e: 341 | print >> sys.stderr, e 342 | 343 | # Phase 2 - find IPv4/IPv6 address 344 | line = re.sub(ipv4, map_ip, line) 345 | line = re.sub(ipv6, reduce_ip, line) 346 | 347 | # Phase 3 - search and replace domains 348 | line = re.sub(domain, map_domain, line) 349 | 350 | if test: 351 | print(line.strip()) 352 | else: 353 | fd_out.write(line) 354 | 355 | # Close input and output files 356 | fd_in.close() 357 | if not test: 358 | fd_out.close() 359 | 360 | if __name__ == "__main__": 361 | main() 362 | sys.exit(os.EX_OK) 363 | 364 | # vim: ts=4 sw=4 expandtab 365 | -------------------------------------------------------------------------------- /loganon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # copyright sys4 AG 2015 4 | 5 | # This file is part of loganon. 6 | # 7 | # loganon is free software: you can redistribute it and/or modify it under the 8 | # terms of the GNU Lesser General Public License as published by the Free 9 | # Software Foundation, either version 3 of the License, or (at your option) any 10 | # later version. 11 | # 12 | # loganon is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Lesser General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with loganon. If not, see . 19 | 20 | 21 | export PATH="$PATH:/usr/local/sbin" 22 | 23 | day=$(date +%Y%m%d -d "5 day ago") 24 | 25 | # mail.log: 26 | bunzip2 /var/log/mail/mail.log-$day.bz2 27 | loganon \ 28 | -i /var/log/mail/mail.log-$day \ 29 | -o /var/log/mail/mail.log-$day-conv \ 30 | -r /usr/local/etc/mail.rules 31 | mv /var/log/mail/mail.log-$day-conv /var/log/mail/mail.log-$day 32 | bzip2 /var/log/mail/mail.log-$day 33 | 34 | # dovecot.log: 35 | bunzip2 /var/log/mail/dovecot.log-$day.bz2 36 | loganon \ 37 | -i /var/log/mail/dovecot.log-$day \ 38 | -o /var/log/mail/dovecot.log-$day-conv \ 39 | -r /usr/local/etc/mail.rules 40 | mv /var/log/mail/dovecot.log-$day-conv /var/log/mail/dovecot.log-$day 41 | bzip2 /var/log/mail/dovecot.log-$day 42 | 43 | exit 0 44 | 45 | -------------------------------------------------------------------------------- /logrotate/README: -------------------------------------------------------------------------------- 1 | This directory holds examples for logrotation configurations. 2 | -------------------------------------------------------------------------------- /logrotate/mail: -------------------------------------------------------------------------------- 1 | /var/log/mail/mail.log 2 | /var/log/mail/dovecot.log 3 | { 4 | rotate 14 5 | daily 6 | missingok 7 | notifempty 8 | compress 9 | compresscmd /usr/bin/bzip2 10 | compressoptions -9 11 | compressext .bz2 12 | dateext 13 | delaycompress 14 | sharedscripts 15 | prerotate 16 | /usr/local/sbin/loganon.sh 17 | endscript 18 | } 19 | 20 | -------------------------------------------------------------------------------- /man/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile to create documentation 2 | # Patrick Ben Koetter, p@sys4.de 3 | # 4 | # Copyright (C) 2015, sys4 AG 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | SHELL = /bin/sh 20 | SOURCES = loganon.1.txt 21 | 22 | # Substitutions 23 | # HTMLS= $(patsubst %.txt,%.html,$(SOURCES)) 24 | MANS= $(patsubst %.txt,%.man,$(SOURCES)) 25 | 26 | # Build Targets 27 | all: man 28 | #html: $(HTMLS) 29 | man: $(MANS) 30 | 31 | # Build commands 32 | %.html: %.txt 33 | asciidoc -b html5 $< 34 | 35 | %.man: %.txt 36 | a2x --doctype manpage --format manpage $< 37 | 38 | # Defaults 39 | .PHONY: clean 40 | 41 | clean: 42 | rm -f *.html *.1 *.5 *.8 43 | 44 | -------------------------------------------------------------------------------- /man/loganon.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: loganon 3 | .\" Author: [see the "AUTHOR" section] 4 | .\" Generator: DocBook XSL Stylesheets v1.78.1 5 | .\" Date: 04/30/2015 6 | .\" Manual: loganon Manual 7 | .\" Source: loganon 0.1 8 | .\" Language: English 9 | .\" 10 | .TH "LOGANON" "1" "04/30/2015" "loganon 0\&.1" "loganon Manual" 11 | .\" ----------------------------------------------------------------- 12 | .\" * Define some portability stuff 13 | .\" ----------------------------------------------------------------- 14 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | .\" http://bugs.debian.org/507673 16 | .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html 17 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | .ie \n(.g .ds Aq \(aq 19 | .el .ds Aq ' 20 | .\" ----------------------------------------------------------------- 21 | .\" * set default formatting 22 | .\" ----------------------------------------------------------------- 23 | .\" disable hyphenation 24 | .nh 25 | .\" disable justification (adjust text to left margin only) 26 | .ad l 27 | .\" ----------------------------------------------------------------- 28 | .\" * MAIN CONTENT STARTS HERE * 29 | .\" ----------------------------------------------------------------- 30 | .SH "NAME" 31 | loganon \- anonymize log files 32 | .SH "SYNOPSIS" 33 | .sp 34 | \fBloganon\fR \fI\-i inputfile\fR \fI\-r rulefile\fR \fI\-o outputfile\fR [\fI\-4 xx\fR] [\fI\-6 xx\fR] [\fI\-t\fR] 35 | .SH "DESCRIPTION" 36 | .sp 37 | loganon is a log anonymizer\&. It takes log lines from an input file, processes them with search and replace patterns from a rules file and sends the result to an output file\&. 38 | .SH "OPTIONS" 39 | .PP 40 | \-i, \-\-input=file (mandatory) 41 | .RS 4 42 | Name of the file to read input from\&. 43 | .RE 44 | .PP 45 | \-o, \-\-output=file (manatory) 46 | .RS 4 47 | Name of the file to write anonymized output to\&. 48 | .RE 49 | .PP 50 | \-r, \-\-rules=file1,file2,\&.\&.\&. (mandatory) 51 | .RS 4 52 | Name of one or more rule files containing search and replace patterns for log anonymization\&. 53 | .RE 54 | .PP 55 | \-4, \-\-mask4=number (optional) 56 | .RS 4 57 | Number of bits to mask an IPv4 address\&. 58 | .RE 59 | .PP 60 | \-6, \-\-mask6=number (optional) 61 | .RS 4 62 | Number of bits to mask an IPv6 address\&. 63 | .RE 64 | .PP 65 | \-t, \-\-test (optional) 66 | .RS 4 67 | Test pattern and print output to stdout 68 | .RE 69 | .SH "BUGS" 70 | .sp 71 | Please submit BUGS to https://github\&.com/sys4/loganon/issues\&. 72 | .SH "AUTHOR" 73 | .sp 74 | Christian Roessner wrote the program\&. Patrick Ben Koetter wrote this man page\&. 75 | .SH "RESOURCES" 76 | .sp 77 | loganons home is at https://github\&.com/sys4/loganon\&. 78 | .SH "COPYING" 79 | .sp 80 | Copyright (C) 2015 sys4 AG\&. Free use of this software is granted under the terms of the GNU Lesser General Public License (GLPL)\&. 81 | -------------------------------------------------------------------------------- /man/loganon.1.txt: -------------------------------------------------------------------------------- 1 | loganon(1) 2 | ========== 3 | :doctype: manpage 4 | :man source: loganon 5 | :man version: 0.1 6 | :man manual: loganon Manual 7 | 8 | NAME 9 | ---- 10 | loganon - anonymize log files 11 | 12 | SYNOPSIS 13 | -------- 14 | *loganon* '-i inputfile' '-r rulefile' '-o outputfile' ['-4 xx'] ['-6 xx'] ['-t'] 15 | 16 | 17 | DESCRIPTION 18 | ----------- 19 | 20 | loganon is a log anonymizer. It takes log lines from an input file, processes 21 | them with search and replace patterns from a rules file and sends the result to 22 | an output file. 23 | 24 | OPTIONS 25 | ------- 26 | 27 | `-i, --input=file` (mandatory):: 28 | Name of the file to read input from. 29 | 30 | `-o, --output=file` (manatory):: 31 | Name of the file to write anonymized output to. 32 | 33 | `-r, --rules=file1,file2,...` (mandatory):: 34 | Name of one or more rule files containing search and replace patterns for log anonymization. 35 | 36 | `-4, --mask4=number` (optional):: 37 | Number of bits to mask an IPv4 address. 38 | 39 | `-6, --mask6=number` (optional):: 40 | Number of bits to mask an IPv6 address. 41 | 42 | `-t, --test` (optional):: 43 | Test pattern and print output to stdout 44 | 45 | BUGS 46 | ---- 47 | 48 | Please submit BUGS to . 49 | 50 | 51 | AUTHOR 52 | ------ 53 | 54 | Christian Roessner wrote the program. Patrick Ben Koetter wrote this man page. 55 | 56 | 57 | RESOURCES 58 | --------- 59 | 60 | loganons home is at . 61 | 62 | 63 | COPYING 64 | ------- 65 | 66 | Copyright \(C) 2015 sys4 AG. Free use of this software is granted under the terms of the GNU Lesser General Public License (GLPL). 67 | 68 | // vim: set ft=asciidoc: 69 | -------------------------------------------------------------------------------- /ordereddict.py: -------------------------------------------------------------------------------- 1 | ## {{{ http://code.activestate.com/recipes/576693/ (r9) 2 | # Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. 3 | # Passes Python2.7's test suite and incorporates all the latest updates. 4 | 5 | # Note from Christian Roessner: 6 | # As far as we know, this file is licensed under the MIT license. 7 | # We have not changed any code line here, ecept adding this comment. For 8 | # a discussion look at the provided URL above. 9 | 10 | try: 11 | from thread import get_ident as _get_ident 12 | except ImportError: 13 | from dummy_thread import get_ident as _get_ident 14 | 15 | try: 16 | from _abcoll import KeysView, ValuesView, ItemsView 17 | except ImportError: 18 | pass 19 | 20 | 21 | class OrderedDict(dict): 22 | 'Dictionary that remembers insertion order' 23 | # An inherited dict maps keys to values. 24 | # The inherited dict provides __getitem__, __len__, __contains__, and get. 25 | # The remaining methods are order-aware. 26 | # Big-O running times for all methods are the same as for regular dictionaries. 27 | 28 | # The internal self.__map dictionary maps keys to links in a doubly linked list. 29 | # The circular doubly linked list starts and ends with a sentinel element. 30 | # The sentinel element never gets deleted (this simplifies the algorithm). 31 | # Each link is stored as a list of length three: [PREV, NEXT, KEY]. 32 | 33 | def __init__(self, *args, **kwds): 34 | '''Initialize an ordered dictionary. Signature is the same as for 35 | regular dictionaries, but keyword arguments are not recommended 36 | because their insertion order is arbitrary. 37 | 38 | ''' 39 | if len(args) > 1: 40 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) 41 | try: 42 | self.__root 43 | except AttributeError: 44 | self.__root = root = [] # sentinel node 45 | root[:] = [root, root, None] 46 | self.__map = {} 47 | self.__update(*args, **kwds) 48 | 49 | def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 50 | 'od.__setitem__(i, y) <==> od[i]=y' 51 | # Setting a new item creates a new link which goes at the end of the linked 52 | # list, and the inherited dictionary is updated with the new key/value pair. 53 | if key not in self: 54 | root = self.__root 55 | last = root[0] 56 | last[1] = root[0] = self.__map[key] = [last, root, key] 57 | dict_setitem(self, key, value) 58 | 59 | def __delitem__(self, key, dict_delitem=dict.__delitem__): 60 | 'od.__delitem__(y) <==> del od[y]' 61 | # Deleting an existing item uses self.__map to find the link which is 62 | # then removed by updating the links in the predecessor and successor nodes. 63 | dict_delitem(self, key) 64 | link_prev, link_next, key = self.__map.pop(key) 65 | link_prev[1] = link_next 66 | link_next[0] = link_prev 67 | 68 | def __iter__(self): 69 | 'od.__iter__() <==> iter(od)' 70 | root = self.__root 71 | curr = root[1] 72 | while curr is not root: 73 | yield curr[2] 74 | curr = curr[1] 75 | 76 | def __reversed__(self): 77 | 'od.__reversed__() <==> reversed(od)' 78 | root = self.__root 79 | curr = root[0] 80 | while curr is not root: 81 | yield curr[2] 82 | curr = curr[0] 83 | 84 | def clear(self): 85 | 'od.clear() -> None. Remove all items from od.' 86 | try: 87 | for node in self.__map.itervalues(): 88 | del node[:] 89 | root = self.__root 90 | root[:] = [root, root, None] 91 | self.__map.clear() 92 | except AttributeError: 93 | pass 94 | dict.clear(self) 95 | 96 | def popitem(self, last=True): 97 | '''od.popitem() -> (k, v), return and remove a (key, value) pair. 98 | Pairs are returned in LIFO order if last is true or FIFO order if false. 99 | 100 | ''' 101 | if not self: 102 | raise KeyError('dictionary is empty') 103 | root = self.__root 104 | if last: 105 | link = root[0] 106 | link_prev = link[0] 107 | link_prev[1] = root 108 | root[0] = link_prev 109 | else: 110 | link = root[1] 111 | link_next = link[1] 112 | root[1] = link_next 113 | link_next[0] = root 114 | key = link[2] 115 | del self.__map[key] 116 | value = dict.pop(self, key) 117 | return key, value 118 | 119 | # -- the following methods do not depend on the internal structure -- 120 | 121 | def keys(self): 122 | 'od.keys() -> list of keys in od' 123 | return list(self) 124 | 125 | def values(self): 126 | 'od.values() -> list of values in od' 127 | return [self[key] for key in self] 128 | 129 | def items(self): 130 | 'od.items() -> list of (key, value) pairs in od' 131 | return [(key, self[key]) for key in self] 132 | 133 | def iterkeys(self): 134 | 'od.iterkeys() -> an iterator over the keys in od' 135 | return iter(self) 136 | 137 | def itervalues(self): 138 | 'od.itervalues -> an iterator over the values in od' 139 | for k in self: 140 | yield self[k] 141 | 142 | def iteritems(self): 143 | 'od.iteritems -> an iterator over the (key, value) items in od' 144 | for k in self: 145 | yield (k, self[k]) 146 | 147 | def update(*args, **kwds): 148 | '''od.update(E, **F) -> None. Update od from dict/iterable E and F. 149 | 150 | If E is a dict instance, does: for k in E: od[k] = E[k] 151 | If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] 152 | Or if E is an iterable of items, does: for k, v in E: od[k] = v 153 | In either case, this is followed by: for k, v in F.items(): od[k] = v 154 | 155 | ''' 156 | if len(args) > 2: 157 | raise TypeError('update() takes at most 2 positional ' 158 | 'arguments (%d given)' % (len(args),)) 159 | elif not args: 160 | raise TypeError('update() takes at least 1 argument (0 given)') 161 | self = args[0] 162 | # Make progressively weaker assumptions about "other" 163 | other = () 164 | if len(args) == 2: 165 | other = args[1] 166 | if isinstance(other, dict): 167 | for key in other: 168 | self[key] = other[key] 169 | elif hasattr(other, 'keys'): 170 | for key in other.keys(): 171 | self[key] = other[key] 172 | else: 173 | for key, value in other: 174 | self[key] = value 175 | for key, value in kwds.items(): 176 | self[key] = value 177 | 178 | __update = update # let subclasses override update without breaking __init__ 179 | 180 | __marker = object() 181 | 182 | def pop(self, key, default=__marker): 183 | '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. 184 | If key is not found, d is returned if given, otherwise KeyError is raised. 185 | 186 | ''' 187 | if key in self: 188 | result = self[key] 189 | del self[key] 190 | return result 191 | if default is self.__marker: 192 | raise KeyError(key) 193 | return default 194 | 195 | def setdefault(self, key, default=None): 196 | 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' 197 | if key in self: 198 | return self[key] 199 | self[key] = default 200 | return default 201 | 202 | def __repr__(self, _repr_running={}): 203 | 'od.__repr__() <==> repr(od)' 204 | call_key = id(self), _get_ident() 205 | if call_key in _repr_running: 206 | return '...' 207 | _repr_running[call_key] = 1 208 | try: 209 | if not self: 210 | return '%s()' % (self.__class__.__name__,) 211 | return '%s(%r)' % (self.__class__.__name__, self.items()) 212 | finally: 213 | del _repr_running[call_key] 214 | 215 | def __reduce__(self): 216 | 'Return state information for pickling' 217 | items = [[k, self[k]] for k in self] 218 | inst_dict = vars(self).copy() 219 | for k in vars(OrderedDict()): 220 | inst_dict.pop(k, None) 221 | if inst_dict: 222 | return (self.__class__, (items,), inst_dict) 223 | return self.__class__, (items,) 224 | 225 | def copy(self): 226 | 'od.copy() -> a shallow copy of od' 227 | return self.__class__(self) 228 | 229 | @classmethod 230 | def fromkeys(cls, iterable, value=None): 231 | '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S 232 | and values equal to v (which defaults to None). 233 | 234 | ''' 235 | d = cls() 236 | for key in iterable: 237 | d[key] = value 238 | return d 239 | 240 | def __eq__(self, other): 241 | '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive 242 | while comparison to a regular mapping is order-insensitive. 243 | 244 | ''' 245 | if isinstance(other, OrderedDict): 246 | return len(self)==len(other) and self.items() == other.items() 247 | return dict.__eq__(self, other) 248 | 249 | def __ne__(self, other): 250 | return not self == other 251 | 252 | # -- the following methods are only used in Python 2.7 -- 253 | 254 | def viewkeys(self): 255 | "od.viewkeys() -> a set-like object providing a view on od's keys" 256 | return KeysView(self) 257 | 258 | def viewvalues(self): 259 | "od.viewvalues() -> an object providing a view on od's values" 260 | return ValuesView(self) 261 | 262 | def viewitems(self): 263 | "od.viewitems() -> a set-like object providing a view on od's items" 264 | return ItemsView(self) 265 | ## end of http://code.activestate.com/recipes/576693/ }}} 266 | -------------------------------------------------------------------------------- /rules/exchange.rules: -------------------------------------------------------------------------------- 1 | # copyright sys4 AG 2015 2 | 3 | # This file is part of loganon. 4 | # 5 | # loganon is free software: you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, either version 3 of the License, or (at your option) any 8 | # later version. 9 | # 10 | # loganon is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with loganon. If not, see . 17 | 18 | 19 | exchange: 20 | sender-address: 21 | - search: "sender-address:([^@]+)@" 22 | - replace: "sender-address:_MAP_@" 23 | return-path: 24 | - search: "return-path:([^@]+)@" 25 | - replace: "return-path:_MAP_@" 26 | recipient-address: 27 | - search: "recipient-address:([^@]+)@" 28 | - replace: "recipient-address:_MAP_@" 29 | UserID: 30 | - search: "UserID:([^,]+)" 31 | - replace: "UserID:_MAP_" 32 | AccountName: 33 | - search: "AccountName:([^,]+)" 34 | - replace: "AccountName:_MAP_" 35 | Domain: 36 | - search: "Domain:([^,]+)" 37 | - replace: "Domain:_MAP_" 38 | # vim: syn=yaml ts=2 sw=2 expandtab 39 | -------------------------------------------------------------------------------- /rules/fortigate.rules: -------------------------------------------------------------------------------- 1 | # copyright sys4 AG 2015 2 | 3 | # This file is part of loganon. 4 | # 5 | # loganon is free software: you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, either version 3 of the License, or (at your option) any 8 | # later version. 9 | # 10 | # loganon is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with loganon. If not, see . 17 | 18 | fortigate: 19 | devname: 20 | - search: "devname=[^ ]+" 21 | - replace: "devname=DEVNAME" 22 | devid: 23 | - search: "devid=[^ ]+" 24 | - replace: "devid=DEVID" 25 | #srcip: 26 | # - search: "srcip=[^ ]+" 27 | # - replace: "srcip=IP" 28 | #dstip: 29 | # - search: "dstip=[^ ]+" 30 | # - replace: "dstip=IP" 31 | srcintf: 32 | - search: "srcintf=[^ ]+" 33 | - replace: "srcintf=\"SRCINTF\"" 34 | dstintf: 35 | - search: "dstintf=[^ ]+" 36 | - replace: "dstintf=\"DSTINTF\"" 37 | srccountry: 38 | - search: "srccountry=[^ ]+" 39 | - replace: "srccountry=\"COUNTRY\"" 40 | dstcountry: 41 | - search: "dstcountry=[^ ]+" 42 | - replace: "dstcountry=\"COUNTRY\"" 43 | 44 | # vim: syn=yaml ts=2 sw=2 expandtab 45 | -------------------------------------------------------------------------------- /rules/infoblox.rules: -------------------------------------------------------------------------------- 1 | # copyright sys4 AG 2015 2 | 3 | # This file is part of loganon. 4 | # 5 | # loganon is free software: you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, either version 3 of the License, or (at your option) any 8 | # later version. 9 | # 10 | # loganon is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with loganon. If not, see . 17 | 18 | fortigate: 19 | devname: 20 | - search: "devname=[^ ]+" 21 | - replace: "devname=DEVNAME" 22 | devid: 23 | - search: "devid=[^ ]+" 24 | - replace: "devid=DEVID" 25 | #srcip: 26 | # - search: "srcip=[^ ]+" 27 | # - replace: "srcip=IP" 28 | #dstip: 29 | # - search: "dstip=[^ ]+" 30 | # - replace: "dstip=IP" 31 | srcintf: 32 | - search: "srcintf=[^ ]+" 33 | - replace: "srcintf=SRCINTF" 34 | dstintf: 35 | - search: "dstintf=[^ ]+" 36 | - replace: "dstintf=DSTINTF" 37 | srccountry: 38 | - search: "srccountry=[^ ]+" 39 | - replace: "srccountry=\"COUNTRY\"" 40 | dstcountry: 41 | - search: "dstcountry=[^ ]+" 42 | - replace: "dstcountry=\"COUNTRY\"" 43 | 44 | # vim: syn=yaml ts=2 sw=2 expandtab 45 | -------------------------------------------------------------------------------- /rules/mail.rules: -------------------------------------------------------------------------------- 1 | # copyright sys4 AG 2015 2 | 3 | # This file is part of loganon. 4 | # 5 | # loganon is free software: you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, either version 3 of the License, or (at your option) any 8 | # later version. 9 | # 10 | # loganon is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with loganon. If not, see . 17 | 18 | 19 | mail: 20 | recipient: 21 | - search: "to=<[^ ]+>" 22 | - replace: "to=" 23 | sender: 24 | - search: "from=<[^ ]+>" 25 | - replace: "from=" 26 | user: 27 | - search: "user=<[^ ]+>" 28 | - replace: "user=" 29 | helo: 30 | - search: "helo=<[^ ]+>" 31 | - replace: "helo=" 32 | relay: 33 | - search: "relay=[^ ]+" 34 | - replace: "relay=hiddenhostname" 35 | cleanup_messageid: 36 | - search: "(?Pmessage-id=<[^ ]+@)[^ ]+>" 37 | - replace: "\\ghiddenhostname>\n" 38 | amavis_messageid: 39 | - search: "(?PMessage-ID: <[^ ]+@)[^ ]+>" 40 | - replace: "\\ghiddenhostname>" 41 | imap: 42 | - search: "imap\\([^ ]+\\)" 43 | - replace: "imap(user)" 44 | pop3: 45 | - search: "pop3\\([^ ]+\\)" 46 | - replace: "pop3(user)" 47 | lmtp: 48 | - search: "lmtp\\((?P[0-9]+), [^ ]+\\)" 49 | - replace: "lmtp(\\g, user)" 50 | indexer_worker: 51 | - search: "indexer-worker\\([^ ]+\\)" 52 | - replace: "indexer-worker(user)" 53 | sasl_username: 54 | - search: "sasl_username=[^ ]+" 55 | - replace: "sasl_username=username\n" 56 | statusok: 57 | - search: "(?P250 [.0-9]{5}) <[^ ]+>" 58 | - replace: "\\g " 59 | smtpd_from: 60 | - search: "(?P(connect|established)) from [^ ]+(?P\\[[a-fA-F0-9.:]{3,39}\\])" 61 | - replace: "\\g from hiddenhostname\\g" 62 | smtpd_client: 63 | - search: "client=[^ ]+(?P\\[[a-fA-F0-9.:]{3,39}\\])" 64 | - replace: "client=hiddenhostname\\g" 65 | smtpd_reject: 66 | - search: "NOQUEUE: reject: RCPT from [^ ]+(?P\\[[^ ]+\\]:[0-9]+: [45][0-9]{2} [0-9.]{5}) <[^ ]+>: (?P.+)" 67 | - replace: "NOQUEUE: reject: RCPT from hiddenhostname\\g : \\g" 68 | smtp_bounce: 69 | - search: "status=bounced \\(host [^ ]+\\[" 70 | - replace: "status=bounced (host hiddenhostname[" 71 | smtp_dnsblog_dnserr: 72 | - search: "Name service error for name=[^ ]+" 73 | - replace: "Name service error for name=hiddenhostname" 74 | dnsblog_dnserr: 75 | - search: "lookup error for DNS query [^ ]+" 76 | - replace: "lookup error for DNS query hiddenhostname" 77 | cleanup: 78 | - search: "milter-reject: END-OF-MESSAGE from [^ ]+\\[" 79 | - replace: "milter-reject: END-OF-MESSAGE from hiddenhostname[" 80 | opendkim: 81 | - search: "opendkim(?P\\[[0-9]+\\]: [a-zA-Z0-9]+): [^ ]+ \\[" 82 | - replace: "opendkim\\g: hiddenhostname [" 83 | opendkim_sd: 84 | - search: "opendkim(?P\\[[0-9]+\\]: [a-zA-Z0-9]+): s=[^ ]+ d=[^ ]+ (?P[^ ]+)" 85 | - replace: "opendkim\\g: s=hiddenselector d=hiddendomain \\g" 86 | opendmarc: 87 | - search: "opendmarc(?P\\[[0-9]+\\]: [a-zA-Z0-9]+): [^ ]+ (?P[^ ]+)" 88 | - replace: "opendmarc\\g: hiddenhostname \\g" 89 | amavis: 90 | - search: "<[^ ]+> -> <[^ ]+>" 91 | - replace: " -> " 92 | lookslikemailaddr: 93 | - search: "(?" 94 | - replace: " " 95 | 96 | # vim: syn=yaml ts=2 sw=2 expandtab 97 | -------------------------------------------------------------------------------- /rules/webproxy.rules: -------------------------------------------------------------------------------- 1 | # copyright sys4 AG 2015 2 | 3 | # This file is part of loganon. 4 | # 5 | # loganon is free software: you can redistribute it and/or modify it under the 6 | # terms of the GNU Lesser General Public License as published by the Free 7 | # Software Foundation, either version 3 of the License, or (at your option) any 8 | # later version. 9 | # 10 | # loganon is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with loganon. If not, see . 17 | 18 | webproxy: 19 | user: 20 | - search: "user=\"([^\"]+)\"" 21 | - replace: "user=\"_MAP_\"" 22 | 23 | # vim: syn=yaml ts=2 sw=2 expandtab 24 | --------------------------------------------------------------------------------