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