Account.java

  1package eu.siacs.conversations.entities;
  2
  3import android.content.ContentValues;
  4import android.database.Cursor;
  5import android.os.SystemClock;
  6import android.util.Pair;
  7
  8import eu.siacs.conversations.crypto.PgpDecryptionService;
  9import net.java.otr4j.crypto.OtrCryptoEngineImpl;
 10import net.java.otr4j.crypto.OtrCryptoException;
 11
 12import org.json.JSONException;
 13import org.json.JSONObject;
 14
 15import java.security.PublicKey;
 16import java.security.interfaces.DSAPublicKey;
 17import java.util.Collection;
 18import java.util.HashSet;
 19import java.util.List;
 20import java.util.concurrent.CopyOnWriteArrayList;
 21import java.util.concurrent.CopyOnWriteArraySet;
 22
 23import eu.siacs.conversations.Config;
 24import eu.siacs.conversations.R;
 25import eu.siacs.conversations.crypto.OtrService;
 26import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 27import eu.siacs.conversations.services.XmppConnectionService;
 28import eu.siacs.conversations.xmpp.XmppConnection;
 29import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 30import eu.siacs.conversations.xmpp.jid.Jid;
 31
 32public class Account extends AbstractEntity {
 33
 34	public static final String TABLENAME = "accounts";
 35
 36	public static final String USERNAME = "username";
 37	public static final String SERVER = "server";
 38	public static final String PASSWORD = "password";
 39	public static final String OPTIONS = "options";
 40	public static final String ROSTERVERSION = "rosterversion";
 41	public static final String KEYS = "keys";
 42	public static final String AVATAR = "avatar";
 43	public static final String DISPLAY_NAME = "display_name";
 44	public static final String HOSTNAME = "hostname";
 45	public static final String PORT = "port";
 46	public static final String STATUS = "status";
 47	public static final String STATUS_MESSAGE = "status_message";
 48
 49	public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
 50
 51	public static final int OPTION_USETLS = 0;
 52	public static final int OPTION_DISABLED = 1;
 53	public static final int OPTION_REGISTER = 2;
 54	public static final int OPTION_USECOMPRESSION = 3;
 55	public static final int OPTION_MAGIC_CREATE = 4;
 56	public final HashSet<Pair<String, String>> inProgressDiscoFetches = new HashSet<>();
 57
 58	public boolean httpUploadAvailable(long filesize) {
 59		return xmppConnection != null && xmppConnection.getFeatures().httpUpload(filesize);
 60	}
 61
 62	public boolean httpUploadAvailable() {
 63		return httpUploadAvailable(0);
 64	}
 65
 66	public void setDisplayName(String displayName) {
 67		this.displayName = displayName;
 68	}
 69
 70	public String getDisplayName() {
 71		return displayName;
 72	}
 73
 74	public XmppConnection.Identity getServerIdentity() {
 75		if (xmppConnection == null) {
 76			return XmppConnection.Identity.UNKNOWN;
 77		} else {
 78			return xmppConnection.getServerIdentity();
 79		}
 80	}
 81
 82	public enum State {
 83		DISABLED,
 84		OFFLINE,
 85		CONNECTING,
 86		ONLINE,
 87		NO_INTERNET,
 88		UNAUTHORIZED(true),
 89		SERVER_NOT_FOUND(true),
 90		REGISTRATION_FAILED(true),
 91		REGISTRATION_CONFLICT(true),
 92		REGISTRATION_SUCCESSFUL,
 93		REGISTRATION_NOT_SUPPORTED(true),
 94		SECURITY_ERROR(true),
 95		INCOMPATIBLE_SERVER(true),
 96		TOR_NOT_AVAILABLE(true),
 97		BIND_FAILURE(true),
 98		HOST_UNKNOWN(true);
 99
100		private final boolean isError;
101
102		public boolean isError() {
103			return this.isError;
104		}
105
106		private State(final boolean isError) {
107			this.isError = isError;
108		}
109
110		private State() {
111			this(false);
112		}
113
114		public int getReadableId() {
115			switch (this) {
116				case DISABLED:
117					return R.string.account_status_disabled;
118				case ONLINE:
119					return R.string.account_status_online;
120				case CONNECTING:
121					return R.string.account_status_connecting;
122				case OFFLINE:
123					return R.string.account_status_offline;
124				case UNAUTHORIZED:
125					return R.string.account_status_unauthorized;
126				case SERVER_NOT_FOUND:
127					return R.string.account_status_not_found;
128				case NO_INTERNET:
129					return R.string.account_status_no_internet;
130				case REGISTRATION_FAILED:
131					return R.string.account_status_regis_fail;
132				case REGISTRATION_CONFLICT:
133					return R.string.account_status_regis_conflict;
134				case REGISTRATION_SUCCESSFUL:
135					return R.string.account_status_regis_success;
136				case REGISTRATION_NOT_SUPPORTED:
137					return R.string.account_status_regis_not_sup;
138				case SECURITY_ERROR:
139					return R.string.account_status_security_error;
140				case INCOMPATIBLE_SERVER:
141					return R.string.account_status_incompatible_server;
142				case TOR_NOT_AVAILABLE:
143					return R.string.account_status_tor_unavailable;
144				case BIND_FAILURE:
145					return R.string.account_status_bind_failure;
146				case HOST_UNKNOWN:
147					return R.string.account_status_host_unknown;
148				default:
149					return R.string.account_status_unknown;
150			}
151		}
152	}
153
154	public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>();
155	public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>();
156
157	private static final String KEY_PGP_SIGNATURE = "pgp_signature";
158	private static final String KEY_PGP_ID = "pgp_id";
159
160	protected Jid jid;
161	protected String password;
162	protected int options = 0;
163	protected String rosterVersion;
164	protected State status = State.OFFLINE;
165	protected JSONObject keys = new JSONObject();
166	protected String avatar;
167	protected String displayName = null;
168	protected String hostname = null;
169	protected int port = 5222;
170	protected boolean online = false;
171	private OtrService mOtrService = null;
172	private AxolotlService axolotlService = null;
173	private PgpDecryptionService pgpDecryptionService = null;
174	private XmppConnection xmppConnection = null;
175	private long mEndGracePeriod = 0L;
176	private String otrFingerprint;
177	private final Roster roster = new Roster(this);
178	private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
179	private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
180	private Presence.Status presenceStatus = Presence.Status.ONLINE;
181	private String presenceStatusMessage = null;
182
183	public Account(final Jid jid, final String password) {
184		this(java.util.UUID.randomUUID().toString(), jid,
185				password, 0, null, "", null, null, null, 5222, Presence.Status.ONLINE, null);
186	}
187
188	private Account(final String uuid, final Jid jid,
189					final String password, final int options, final String rosterVersion, final String keys,
190					final String avatar, String displayName, String hostname, int port,
191					final Presence.Status status, String statusMessage) {
192		this.uuid = uuid;
193		this.jid = jid;
194		if (jid.isBareJid()) {
195			this.setResource("mobile");
196		}
197		this.password = password;
198		this.options = options;
199		this.rosterVersion = rosterVersion;
200		try {
201			this.keys = new JSONObject(keys);
202		} catch (final JSONException ignored) {
203			this.keys = new JSONObject();
204		}
205		this.avatar = avatar;
206		this.displayName = displayName;
207		this.hostname = hostname;
208		this.port = port;
209		this.presenceStatus = status;
210		this.presenceStatusMessage = statusMessage;
211	}
212
213	public static Account fromCursor(final Cursor cursor) {
214		Jid jid = null;
215		try {
216			jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)),
217					cursor.getString(cursor.getColumnIndex(SERVER)), "mobile");
218		} catch (final InvalidJidException ignored) {
219		}
220		return new Account(cursor.getString(cursor.getColumnIndex(UUID)),
221				jid,
222				cursor.getString(cursor.getColumnIndex(PASSWORD)),
223				cursor.getInt(cursor.getColumnIndex(OPTIONS)),
224				cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
225				cursor.getString(cursor.getColumnIndex(KEYS)),
226				cursor.getString(cursor.getColumnIndex(AVATAR)),
227				cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)),
228				cursor.getString(cursor.getColumnIndex(HOSTNAME)),
229				cursor.getInt(cursor.getColumnIndex(PORT)),
230				Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndex(STATUS))),
231				cursor.getString(cursor.getColumnIndex(STATUS_MESSAGE)));
232	}
233
234	public boolean isOptionSet(final int option) {
235		return ((options & (1 << option)) != 0);
236	}
237
238	public void setOption(final int option, final boolean value) {
239		if (value) {
240			this.options |= 1 << option;
241		} else {
242			this.options &= ~(1 << option);
243		}
244	}
245
246	public String getUsername() {
247		return jid.getLocalpart();
248	}
249
250	public void setJid(final Jid jid) {
251		this.jid = jid;
252	}
253
254	public Jid getServer() {
255		return jid.toDomainJid();
256	}
257
258	public String getPassword() {
259		return password;
260	}
261
262	public void setPassword(final String password) {
263		this.password = password;
264	}
265
266	public void setHostname(String hostname) {
267		this.hostname = hostname;
268	}
269
270	public String getHostname() {
271		return this.hostname == null ? "" : this.hostname;
272	}
273
274	public boolean isOnion() {
275		return getServer().toString().toLowerCase().endsWith(".onion");
276	}
277
278	public void setPort(int port) {
279		this.port = port;
280	}
281
282	public int getPort() {
283		return this.port;
284	}
285
286	public State getStatus() {
287		if (isOptionSet(OPTION_DISABLED)) {
288			return State.DISABLED;
289		} else {
290			return this.status;
291		}
292	}
293
294	public void setStatus(final State status) {
295		this.status = status;
296	}
297
298	public boolean errorStatus() {
299		return getStatus().isError();
300	}
301
302	public boolean hasErrorStatus() {
303		return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 3;
304	}
305
306	public void setPresenceStatus(Presence.Status status) {
307		this.presenceStatus = status;
308	}
309
310	public Presence.Status getPresenceStatus() {
311		return this.presenceStatus;
312	}
313
314	public void setPresenceStatusMessage(String message) {
315		this.presenceStatusMessage = message;
316	}
317
318	public String getPresenceStatusMessage() {
319		return this.presenceStatusMessage;
320	}
321
322	public String getResource() {
323		return jid.getResourcepart();
324	}
325
326	public boolean setResource(final String resource) {
327		final String oldResource = jid.getResourcepart();
328		if (oldResource == null || !oldResource.equals(resource)) {
329			try {
330				jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
331				return true;
332			} catch (final InvalidJidException ignored) {
333				return true;
334			}
335		}
336		return false;
337	}
338
339	public Jid getJid() {
340		return jid;
341	}
342
343	public JSONObject getKeys() {
344		return keys;
345	}
346
347	public String getKey(final String name) {
348		return this.keys.optString(name, null);
349	}
350
351	public boolean setKey(final String keyName, final String keyValue) {
352		try {
353			this.keys.put(keyName, keyValue);
354			return true;
355		} catch (final JSONException e) {
356			return false;
357		}
358	}
359
360	public boolean setPrivateKeyAlias(String alias) {
361		return setKey("private_key_alias", alias);
362	}
363
364	public String getPrivateKeyAlias() {
365		return getKey("private_key_alias");
366	}
367
368	@Override
369	public ContentValues getContentValues() {
370		final ContentValues values = new ContentValues();
371		values.put(UUID, uuid);
372		values.put(USERNAME, jid.getLocalpart());
373		values.put(SERVER, jid.getDomainpart());
374		values.put(PASSWORD, password);
375		values.put(OPTIONS, options);
376		values.put(KEYS, this.keys.toString());
377		values.put(ROSTERVERSION, rosterVersion);
378		values.put(AVATAR, avatar);
379		values.put(DISPLAY_NAME, displayName);
380		values.put(HOSTNAME, hostname);
381		values.put(PORT, port);
382		values.put(STATUS, presenceStatus.toShowString());
383		values.put(STATUS_MESSAGE, presenceStatusMessage);
384		return values;
385	}
386
387	public AxolotlService getAxolotlService() {
388		return axolotlService;
389	}
390
391	public void initAccountServices(final XmppConnectionService context) {
392		this.mOtrService = new OtrService(context, this);
393		this.axolotlService = new AxolotlService(this, context);
394		if (xmppConnection != null) {
395			xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
396		}
397		this.pgpDecryptionService = new PgpDecryptionService(context);
398	}
399
400	public OtrService getOtrService() {
401		return this.mOtrService;
402	}
403
404	public PgpDecryptionService getPgpDecryptionService() {
405		return pgpDecryptionService;
406	}
407
408	public XmppConnection getXmppConnection() {
409		return this.xmppConnection;
410	}
411
412	public void setXmppConnection(final XmppConnection connection) {
413		this.xmppConnection = connection;
414	}
415
416	public String getOtrFingerprint() {
417		if (this.otrFingerprint == null) {
418			try {
419				if (this.mOtrService == null) {
420					return null;
421				}
422				final PublicKey publicKey = this.mOtrService.getPublicKey();
423				if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
424					return null;
425				}
426				this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
427				return this.otrFingerprint;
428			} catch (final OtrCryptoException ignored) {
429				return null;
430			}
431		} else {
432			return this.otrFingerprint;
433		}
434	}
435
436	public String getRosterVersion() {
437		if (this.rosterVersion == null) {
438			return "";
439		} else {
440			return this.rosterVersion;
441		}
442	}
443
444	public void setRosterVersion(final String version) {
445		this.rosterVersion = version;
446	}
447
448	public int countPresences() {
449		return this.getRoster().getContact(this.getJid().toBareJid()).getPresences().size();
450	}
451
452	public String getPgpSignature() {
453		try {
454			if (keys.has(KEY_PGP_SIGNATURE) && !"null".equals(keys.getString(KEY_PGP_SIGNATURE))) {
455				return keys.getString(KEY_PGP_SIGNATURE);
456			} else {
457				return null;
458			}
459		} catch (final JSONException e) {
460			return null;
461		}
462	}
463
464	public boolean setPgpSignature(String signature) {
465		try {
466			keys.put(KEY_PGP_SIGNATURE, signature);
467		} catch (JSONException e) {
468			return false;
469		}
470		return true;
471	}
472
473	public boolean unsetPgpSignature() {
474		try {
475			keys.put(KEY_PGP_SIGNATURE, JSONObject.NULL);
476		} catch (JSONException e) {
477			return false;
478		}
479		return true;
480	}
481
482	public long getPgpId() {
483		if (keys.has(KEY_PGP_ID)) {
484			try {
485				return keys.getLong(KEY_PGP_ID);
486			} catch (JSONException e) {
487				return 0;
488			}
489		} else {
490			return 0;
491		}
492	}
493
494	public boolean setPgpSignId(long pgpID) {
495		try {
496			keys.put(KEY_PGP_ID, pgpID);
497		} catch (JSONException e) {
498			return false;
499		}
500		return true;
501	}
502
503	public Roster getRoster() {
504		return this.roster;
505	}
506
507	public List<Bookmark> getBookmarks() {
508		return this.bookmarks;
509	}
510
511	public void setBookmarks(final List<Bookmark> bookmarks) {
512		this.bookmarks = bookmarks;
513	}
514
515	public boolean hasBookmarkFor(final Jid conferenceJid) {
516		for (final Bookmark bookmark : this.bookmarks) {
517			final Jid jid = bookmark.getJid();
518			if (jid != null && jid.equals(conferenceJid.toBareJid())) {
519				return true;
520			}
521		}
522		return false;
523	}
524
525	public boolean setAvatar(final String filename) {
526		if (this.avatar != null && this.avatar.equals(filename)) {
527			return false;
528		} else {
529			this.avatar = filename;
530			return true;
531		}
532	}
533
534	public String getAvatar() {
535		return this.avatar;
536	}
537
538	public void activateGracePeriod() {
539		this.mEndGracePeriod = SystemClock.elapsedRealtime()
540			+ (Config.CARBON_GRACE_PERIOD * 1000);
541	}
542
543	public void deactivateGracePeriod() {
544		this.mEndGracePeriod = 0L;
545	}
546
547	public boolean inGracePeriod() {
548		return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
549	}
550
551	public String getShareableUri() {
552		final String fingerprint = this.getOtrFingerprint();
553		if (fingerprint != null) {
554			return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint;
555		} else {
556			return "xmpp:" + this.getJid().toBareJid().toString();
557		}
558	}
559
560	public boolean isBlocked(final ListItem contact) {
561		final Jid jid = contact.getJid();
562		return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid()));
563	}
564
565	public boolean isBlocked(final Jid jid) {
566		return jid != null && blocklist.contains(jid.toBareJid());
567	}
568
569	public Collection<Jid> getBlocklist() {
570		return this.blocklist;
571	}
572
573	public void clearBlocklist() {
574		getBlocklist().clear();
575	}
576
577	public boolean isOnlineAndConnected() {
578		return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
579	}
580}