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 |
--------------------------------------------------------------------------------