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