package de.vanitasvitae.sync_client; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.HashMap; import java.util.List; import java.util.Scanner; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smackx.omemo.OmemoFingerprint; import org.jivesoftware.smackx.omemo.OmemoManager; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; import org.jivesoftware.smackx.omemo.internal.OmemoDevice; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.FullJid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.stringprep.XmppStringprepException; public class Main { private static Options prepareOptions() { Options options = new Options(); Option username = Option.builder("u").longOpt("username").required().argName("user@server.tld").hasArg().desc("Barejid of the XMPP account.").build(); Option password = Option.builder("p").longOpt("password").required().argName("swordfish").hasArg().desc("Password of the XMPP account.").build(); Option debug = Option.builder("d").longOpt("debug").desc("Print debug messages to the console.").build(); Option directory = Option.builder("D").longOpt("directory").hasArg().required().desc("Directory which will be synced.").build(); Option master = Option.builder("m").longOpt("master").desc("Start in master mode. When this flag is missing, the client will be started in slave mode.").build(); Option help = Option.builder("h").longOpt("help").desc("Display this help text.").build(); options.addOption(username); options.addOption(password); options.addOption(debug); options.addOption(directory); options.addOption(master); options.addOption(help); return options; } public static Slave slave(String username, String password, String dir) throws IOException, InterruptedException, XMPPException, SmackException, NoSuchPaddingException, CorruptedOmemoKeyException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException, InvalidAlgorithmParameterException { return new Slave(username, password, dir); } public static Master master(String username, String password, String dir) throws IOException, InterruptedException, XMPPException, SmackException, CorruptedOmemoKeyException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException, NoSuchProviderException, InvalidAlgorithmParameterException { return new Master(username, password, dir); } public static void main(String[] args) throws Exception { Options options = prepareOptions(); HelpFormatter formatter = new HelpFormatter(); CommandLineParser parser = new DefaultParser(); CommandLine cmd = null; try { cmd = parser.parse(options, args); } catch (ParseException e) { formatter.printHelp("client", options, true); return; } if (cmd.hasOption('h')) { formatter.printHelp("client", options, true); return; } String username = cmd.getOptionValue('u'); String password = cmd.getOptionValue('p'); String dir = cmd.getOptionValue('D'); if (cmd.hasOption('d')) { SmackConfiguration.DEBUG = true; } Client client; if (cmd.hasOption('m')) { client = master(username, password, dir); } else { client = slave(username, password, dir); } client.login(); System.out.println("Ready!"); Scanner scanner = new Scanner(System.in); outerloop: while (true) { String line = scanner.nextLine(); if (line == null || line.isEmpty()) { continue; } String[] split = line.split(" "); if (split.length == 0) { continue; } Roster roster = Roster.getInstanceFor(client.connection); OmemoManager omemoManager = OmemoManager.getInstanceFor(client.connection); switch (split[0]) { case "/add": if (split.length != 2) { System.out.println("Usage: /add user@server.tld"); for (RosterEntry r : roster.getEntries()) { System.out.println(r.getJid()); } continue outerloop; } try { BareJid bareJid = getJid(split[1]); roster.createEntry(getJid(split[1]), bareJid.getLocalpartOrNull().toString(), null); } catch (XmppStringprepException e) { System.out.println("Illegal BareJid."); } break; case "/sync": if (split.length != 2) { System.out.println("Usage: /sync user@server.tld"); for (FullJid fullJid : client.remotes) { System.out.println(fullJid.toString()); } continue outerloop; } List presences = roster.getPresences(getJid(split[1])); if (presences.size() == 0) { System.out.println("No device online!"); continue outerloop; } else if (presences.size() == 1) { client.addRemote(presences.get(0).getFrom().asFullJidOrThrow()); } else { while (true) { System.out.println("Please select a resource:"); for (int i = 0; i < presences.size(); i++) { System.out.println(i + " " + presences.get(i).getFrom().asFullJidOrThrow().toString()); } int selection = Integer.parseInt(scanner.nextLine()); if (selection < 0 || selection >= presences.size()) { System.out.println("Invalid selection."); continue; } client.addRemote(presences.get(selection).getFrom().asFullJidOrThrow()); continue outerloop; } } break; case "/trust": if (split.length != 2) { System.out.println("Usage: /trust user@server.tld"); continue outerloop; } BareJid jid = getJid(split[1]); omemoManager.requestDeviceListUpdateFor(jid); omemoManager.requestDeviceListUpdateFor(client.connection.getUser().asBareJid()); HashMap theirFPs = omemoManager.getActiveFingerprints(jid); HashMap ourFPs = omemoManager.getActiveFingerprints(client.connection.getUser().asBareJid()); System.out.println("Their fingerprints: : : "); for (OmemoDevice d : theirFPs.keySet()) { OmemoFingerprint f = theirFPs.get(d); System.out.println(d.getDeviceId() + ": " + f + ": " + omemoManager.isTrustedOmemoIdentity(d, f)); System.out.println("Trust? (y/n)"); String a = scanner.nextLine(); if (a.isEmpty()) { continue; } else if (a.toLowerCase().equals("y")) { omemoManager.trustOmemoIdentity(d, f); System.out.println("Trusted."); } else { omemoManager.distrustOmemoIdentity(d, f); System.out.println("Distrusted."); } } System.out.println("Our fingerprints: : : "); for (OmemoDevice d : ourFPs.keySet()) { OmemoFingerprint f = ourFPs.get(d); System.out.println(d.getDeviceId() + ": " + f + ": " + omemoManager.isTrustedOmemoIdentity(d, f)); System.out.println("Trust? (y/n)"); String a = scanner.nextLine(); if (a.isEmpty()) { continue; } else if (a.toLowerCase().equals("y")) { omemoManager.trustOmemoIdentity(d, f); System.out.println("Trusted."); } else { omemoManager.distrustOmemoIdentity(d, f); System.out.println("Distrusted."); } } break; case "/help": System.out.println("Available commands: \n" + "/add : Add user to roster, or display current roster.\n" + "/sync : Add a users device to synced devices or display currently synced devices.\n" + "/trust user@sever.tld: Trust a users OMEMO identities.\n" + "/help: Show this help text.\n" + "/quit: Quit the application."); break; case "/quit": ((AbstractXMPPConnection) client.connection).disconnect(new Presence(Presence.Type.unavailable, "Shutdown", 100, Presence.Mode.away)); break outerloop; } } scanner.close(); System.exit(0); } public static BareJid getJid(String string) throws XmppStringprepException { return JidCreate.bareFrom(string); } }