2 # -*- coding: UTF-8 -*-
4 ########################################################################
5 # WhisperBack - Send feedback in an encrypted mail
6 # Copyright (C) 2009-2018 Tails developers <tails@boum.org>
8 # This file is part of WhisperBack
10 # WhisperBack is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or (at
13 # your option) any later version.
15 # This program is distributed in the hope that it will be useful, but
16 # WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 # General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ########################################################################
24 """various WhisperBack utility functions
33 from textwrap
import TextWrapper
35 LOG
= logging
.getLogger(__name__
)
36 # Ugly pathes finder utilities
40 """Tries to guess the prefix
42 @return The guessed prefix"""
44 # XXX: hardcoded path !
45 if os
.path
.exists ("/usr/local/share/whisperback"):
47 elif os
.path
.exists ("/usr/share/whisperback"):
54 """Tries to guess the shared data directiry
56 @return The guessed shared data directiry"""
59 return os
.path
.join (guess_prefix(), "share")
65 """Tries to guess the datadir
67 @return The guessed datadir"""
69 return os
.path
.join (get_sharedir(), "whisperback")
75 """Tries to guess the pixmapdir
77 @return The guessed pixmapdir"""
80 return os
.path
.join (get_sharedir(), "pixmaps")
84 # Input validation fuctions
87 def is_valid_link(candidate
):
88 """Check if candidate seems to be a internet link
90 @param candidate the URL to be checked
92 @returns true if candidate is an URL with:
93 - an hostname of the form domain.tld
94 - a scheme http(s) or ftp(S)
96 LOG
.debug("Validating link %s", candidate
)
97 parseresult
= urllib
.parse
.urlparse(candidate
)
98 #pylint: disable=E1101
99 if (re
.search(r
'^(ht|f)tp(s)?$', parseresult
.scheme
) and
100 re
.search(r
'^(\w{1,}\.){1,}\w{1,}$', parseresult
.hostname
)):
106 def is_valid_pgp_block(candidate
):
107 """Check if candidate seems to be a PGP public key block
109 @param candidate the string to be checked
111 @returns true if candidate starts with `-----BEGIN PGP PUBLIC KEY BLOCK----`
112 and ends with `-----END PGP PUBLIC KEY BLOCK-----`
114 LOG
.debug("Validating pgp block %s", candidate
)
115 #pylint: disable=C0301
116 if re
.search(r
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n(?:.*\n)+-----END PGP PUBLIC KEY BLOCK-----",
123 def is_valid_pgp_id(candidate
):
124 """Check if candidate looks like a pgp key ID
126 @param candidate the string to be checked
128 @returns true if candidate is either an 8 or 16 digit hex number or a 40
129 digit hex fingerprint
131 #pylint: disable=C0301
132 LOG
.debug("Validating pgp id %s", candidate
)
133 if re
.search(r
"(?:^(?:0x)?(?:[0-9a-fA-F]{8}){1,2}$)|(?:^(?:[0-9f-zA-F]{4} {0,2}){10}$)",
140 def is_valid_email(candidate
):
141 """Check if candidate looks like an email address
143 @param candidate the string to be checked
145 @returns true if candidate is in the form test@example.com
147 LOG
.debug("Validating email %s", candidate
)
148 if re
.search(r
"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", candidate
):
154 def is_valid_port(candidate
):
155 """Check if candidate is a valid port number (integer between 1 and 65535)
157 @param candidate the port number to be checked
159 LOG
.debug("Validating port %s", candidate
)
165 if candidate
>= 1 and candidate
< 65535:
171 def is_valid_hostname_or_ipv4(candidate
):
172 """Check if candidate is a valid hostname or IPv4 address
174 pySocks is not compatible with IPv6
175 hostname specs follow RFC 1123
177 @param candidate the hostname or IPv4 address to validate
179 LOG
.debug("Validating host or IP %s", candidate
)
180 # XXX: must be updated once IPv6 is enabled
182 if not isinstance(candidate
, str):
184 if len(candidate
) > 255:
187 # regex from http://stackoverflow.com/a/106223
188 ip_address_regex
= re
.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
189 hostname_regex
= re
.compile("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$");
191 if ip_address_regex
.match(candidate
) or hostname_regex
.match(candidate
):
197 def sanitize_hardware_info(log_string
):
198 """Sanitize hardware-identifying info from a string
202 - labeled as serial numbers and UUID;
203 - looking like IPs or MAC addresses.
205 @param log_string the string to be sanitized
207 @returns a sanitized version of log_string
209 # XXX: must be updated once IPv6 is enabled
212 log_string
= re
.sub(r
'(DMI:)[\s].*',
217 log_string
= re
.sub(r
'(Serial Number:?[\s]+|'
219 'iSerial[\s]+[\d]+\s+|'
220 'SerialNumber:[\s]+|'
229 log_string
= re
.sub(r
'(UUID:[\s]+)[^\s].+',
234 log_string
= re
.sub(r
'([\d]{1,3}\.){3}[\d]{1,3}',
238 log_string
= re
.sub(r
'(?i)[0-9A-F]{4}(:[0-9A-F]{4}){7}',
242 log_string
= re
.sub(r
'(?i)([0-9A-F]{2}:){5,}[0-9A-F]{2}',
249 """Wraps long lines of text to a width of 70 chars
250 @param text the string to be wrapped
252 @return The wrapped text"""
253 LOG
.debug("Wrapping text")
255 wrapper
= TextWrapper()
256 wrapped
= [wrapper
.fill(line
) for line
in text
.split('\n')]
257 return '\n'.join(wrapped
)