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