File: scanself.cc

package info (click to toggle)
signalbackup-tools 20250313.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 3,752 kB
  • sloc: cpp: 47,042; sh: 477; ansic: 399; ruby: 19; makefile: 3
file content (285 lines) | stat: -rw-r--r-- 15,207 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/*
  Copyright (C) 2021-2025  Selwin van Dijk

  This file is part of signalbackup-tools.

  signalbackup-tools is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  signalbackup-tools is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with signalbackup-tools.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "signalbackup.ih"

long long int SignalBackup::scanSelf() const
{
  if (!d_database.containsTable("recipient"))
    return false;

  ///// FIRST TRY BY GETTING KEY 'account.pni_identity_public_key' FROM KeyValues, and matching it to uuid from identites-table
  std::string identity_public_key;
  for (auto const &kv : d_keyvalueframes)
    if (kv->key() == "account.pni_identity_public_key" && !kv->value().empty())
    {
      identity_public_key = kv->value();
      break;
    }
  if (!identity_public_key.empty())
  {
    long long int selfid = d_database.getSingleResultAs<long long int>("SELECT _id FROM recipient WHERE " + d_recipient_aci + " IN "
                                                                       "(SELECT address FROM identities WHERE identity_key IS ?)",
                                                                       identity_public_key, -1);
    if (selfid != -1)
      return selfid;
  }

  ///// NEXT TRY BY GETTING KEY 'account.aci_identity_public_key' FROM KeyValues, and matching it to uuid from identites-table
  identity_public_key.clear();
  for (auto const &kv : d_keyvalueframes)
    if (kv->key() == "account.aci_identity_public_key" && !kv->value().empty())
    {
      identity_public_key = kv->value();
      break;
    }
  if (!identity_public_key.empty())
  {
    long long int selfid = d_database.getSingleResultAs<long long int>("SELECT _id FROM recipient WHERE " + d_recipient_aci + " IN "
                                                                       "(SELECT address FROM identities WHERE identity_key IS ?)",
                                                                       identity_public_key, -1);
    if (selfid != -1)
      return selfid;
  }


  // only 'works' on 'newer' versions
  if (!d_database.tableContainsColumn("thread", d_thread_recipient_id) ||
      !d_database.tableContainsColumn(d_mms_table, "quote_author") ||
      (!d_database.tableContainsColumn(d_mms_table, "reactions") && !d_database.containsTable("reaction")))
    return -1;


  // in newer databases (>= dbv185), message.from_recipient_id should always be set to self on outgoing messages.
  long long int selfid = d_database.getSingleResultAs<long long int>("SELECT DISTINCT " + d_mms_recipient_id + " FROM " + d_mms_table + " WHERE (" + d_mms_type + " & 0x1f) IN (?, ?, ?, ?, ?, ?, ?, ?)",
                                                                     {Types::BASE_OUTBOX_TYPE, Types::BASE_SENT_TYPE,
                                                                      Types::BASE_SENDING_TYPE, Types::BASE_SENT_FAILED_TYPE,
                                                                      Types::BASE_PENDING_SECURE_SMS_FALLBACK,Types:: BASE_PENDING_INSECURE_SMS_FALLBACK ,
                                                                      Types::OUTGOING_CALL_TYPE, Types::OUTGOING_VIDEO_CALL_TYPE}, -1);
  if (selfid != -1)
    return selfid;

  // get thread ids of all 1-on-1 conversations
  SqliteDB::QueryResults res;
  if (!d_database.exec("SELECT _id, " + d_thread_recipient_id + " FROM thread WHERE " + d_thread_recipient_id + " IN (SELECT _id FROM recipient WHERE group_id IS NULL)", &res))
    return -1;

  std::set<long long int> options;

  for (unsigned int i = 0; i < res.rows(); ++i)
  {
    // try to find another recipient in this one-on-one thread

    long long int tid = res.getValueAs<long long int>(i, "_id");
    long long int rid = bepaald::toNumber<long long int>(res.valueAsString(i, d_thread_recipient_id));
    SqliteDB::QueryResults res2;

    //std::cout << "Dealing with thread: " << tid << " (recipient: " << rid << ")" << std::endl;

    // in earlier versions, it was possible to quote someone cross-thread. So we need to limit
    // this query to quotes with quote_id referencing a quote in the same thread.
    if (!d_database.exec("SELECT DISTINCT quote_author FROM " + d_mms_table + " "
                         "WHERE thread_id IS ? AND quote_id IS NOT 0 AND quote_id IS NOT NULL "
                         "AND quote_author IS NOT NULL AND quote_author IS NOT ? "
                         "AND (quote_id IN (SELECT " + d_mms_date_sent + " FROM " + d_mms_table + " WHERE thread_id = ?)" +
                         (d_database.containsTable("sms") ? " OR quote_id IN (SELECT date_sent FROM sms WHERE thread_id = " + bepaald::toString(tid) + "))" : ")"), {tid, rid, tid}, &res2))
      continue;
    for (unsigned int j = 0; j < res2.rows(); ++j)
    {
      //std::cout << "  From quote:" << res2.valueAsString(j, "quote_author") << std::endl;
      options.insert(bepaald::toNumber<long long int>(res2.valueAsString(j, "quote_author")));
    }

    for (auto const &t : {"sms"s, d_mms_table}) // OLD STYLE REACTIONS
    {
      if (d_database.tableContainsColumn(t, "reactions"))
      {
        if (!d_database.exec("SELECT reactions FROM " + t + " WHERE thread_id IS ? AND reactions IS NOT NULL", tid, &res2))
          continue;
        for (unsigned int j = 0; j < res2.rows(); ++j)
        {
          ReactionList reactions(res2.getValueAs<std::pair<std::shared_ptr<unsigned char []>, size_t>>(j, "reactions"));
          for (unsigned int k = 0; k < reactions.numReactions(); ++k)
          {
            if (reactions.getAuthor(k) != static_cast<uint64_t>(rid))
            {
              //std::cout << "  From " + t + ".reaction (old): " << reactions.getAuthor(k) << std::endl;
              options.insert(reactions.getAuthor(k));
            }
          }
        }
      }
    }
    if (d_database.containsTable("reaction")) // NEW STYLE REACTIONS
    {
      if (d_database.containsTable("sms"))
      {
        if (!d_database.exec("SELECT DISTINCT author_id FROM reaction WHERE is_mms IS 0 AND "s +
                             "author_id IS NOT ? AND message_id IN (SELECT DISTINCT _id FROM sms WHERE thread_id = ?)", {rid, tid}, &res2))
          continue;
        for (unsigned int j = 0; j < res2.rows(); ++j)
        {
          //std::cout << "  From reaction (new):" << res2.valueAsString(j, "author_id") << std::endl;
          options.insert(bepaald::toNumber<long long int>(res2.valueAsString(j, "author_id")));
        }
      }

      if (!d_database.exec("SELECT DISTINCT author_id FROM reaction WHERE "s +
                           (d_database.tableContainsColumn("reaction", "is_mms") ? "is_mms IS 1 AND " : "") +
                           "author_id IS NOT ? AND message_id IN (SELECT DISTINCT _id FROM " + d_mms_table + " WHERE thread_id = ?)", {rid, tid} , &res2))
          continue;
      for (unsigned int j = 0; j < res2.rows(); ++j)
      {
        //std::cout << "  From reaction (new):" << res2.valueAsString(j, "author_id") << std::endl;
        options.insert(bepaald::toNumber<long long int>(res2.valueAsString(j, "author_id")));
      }
    }
  }

  // get thread ids of all group conversations
  if (!d_database.exec("SELECT _id FROM groups WHERE active IS NOT 0", &res))
    return -1;
  //res.prettyPrint();

  // for each group-thread
  for (unsigned int i = 0; i < res.rows(); ++i)
  {
    long long int gid = res.getValueAs<long long int>(i, "_id");

    SqliteDB::QueryResults res2;
    // skip groups without thread
    if (!d_database.exec("SELECT _id from thread WHERE " + d_thread_recipient_id + " IS (SELECT _id FROM recipient WHERE group_id IS (SELECT group_id from groups WHERE _id IS ?))", gid, &res2))
      continue;
    if (res2.rows() == 0)
    {
      //std::cout << "Skipping group: " << gid << " (no thread)" << std::endl;
      continue;
    }
    long long int tid = res2.getValueAs<long long int>(0, "_id");

    //std::cout << "Dealing with group: " << gid << " (thread: " << tid << ")" << std::endl;

    SqliteDB::QueryResults res3;
    if (d_database.tableContainsColumn("groups", "members")) // old style
    {
      // this prints all group members that never appear as recipient in a message (in groups, the recipient ('address') is always the sender, except for self, who has the groups id as address)
      if (d_database.containsTable("sms"))
      {
        if (!d_database.exec("WITH split(word, str) AS (SELECT '',members||',' FROM groups WHERE _id IS ?1 UNION ALL SELECT substr(str, 0, instr(str, ',')), substr(str, instr(str, ',')+1) FROM split WHERE str!='') SELECT word FROM split WHERE word!='' AND word NOT IN (SELECT DISTINCT " + d_mms_recipient_id +" FROM " + d_mms_table + " WHERE thread_id IS (SELECT _id FROM thread WHERE " + d_thread_recipient_id + " IS (SELECT _id FROM recipient WHERE group_id IS (SELECT group_id FROM groups WHERE _id IS ?1)))) AND word NOT IN (SELECT DISTINCT " + d_sms_recipient_id + " FROM sms WHERE thread_id IS (SELECT _id FROM thread WHERE " + d_thread_recipient_id + " IS (SELECT _id FROM recipient WHERE group_id IS (SELECT group_id FROM groups WHERE _id IS ?1))))", gid, &res3))
          continue;
      }
      else
        if (!d_database.exec("WITH split(word, str) AS (SELECT '',members||',' FROM groups WHERE _id IS ?1 UNION ALL SELECT substr(str, 0, instr(str, ',')), substr(str, instr(str, ',')+1) FROM split WHERE str!='') SELECT word FROM split WHERE word!='' AND word NOT IN (SELECT DISTINCT " + d_mms_recipient_id +" FROM " + d_mms_table + " WHERE thread_id IS (SELECT _id FROM thread WHERE " + d_thread_recipient_id + " IS (SELECT _id FROM recipient WHERE group_id IS (SELECT group_id FROM groups WHERE _id IS ?1))))", gid, &res3))
          continue;

      for (unsigned int j = 0; j < res3.rows(); ++j)
      {
        //std::cout << "  From group membership:" << res3.valueAsString(j, "word") << std::endl;
        options.insert(bepaald::toNumber<long long int>(res3.valueAsString(j, "word")));
      }
    }
    else if (d_database.containsTable("group_membership")) // modern style
    {
      if (!d_database.tableContainsColumn(d_mms_table, "to_recipient_id")) // < dbv185
      {
        // this prints all group members that never appear as recipient in a message (in groups, the recipient ('address') is always the sender, except for self, who has the groups id as address)
        if (!d_database.exec("SELECT DISTINCT recipient_id FROM group_membership WHERE group_id IN (SELECT group_id FROM groups WHERE _id = ?) AND "
                             "recipient_id NOT IN (SELECT DISTINCT " + d_mms_recipient_id + " FROM " + d_mms_table + " WHERE thread_id IS ? AND type IS NOT ?)",
                             {gid, tid, Types::GROUP_CALL_TYPE}, &res3))
          continue;
        else
        {
          for (unsigned int j = 0; j < res3.rows(); ++j)
          {
            //std::cout << "  From group membership (<185):" << res3.valueAsString(j, "recipient_id") << std::endl;
            options.insert(bepaald::toNumber<long long int>(res3.valueAsString(j, "recipient_id")));
          }
        }
      }
      else
      {
        // in the newer style, ([from/to]_recipient_id), self CAN appear as recipient (to_ when incoming, from_ when outgoing).
        // for incoming messages 'self' will never appear in from_, but could (for msgs arriving since 185) appear in to_.
        // for outgoing messages 'self' will never appear in to_ (= always rec._id of group), but always (if migration succesfull) in from_.

        // // outgoing
        // if (!d_database.exec("SELECT DISTINCT recipient_id FROM group_membership WHERE group_id IN (SELECT group_id FROM groups WHERE _id = ?) AND "
        //                      "recipient_id NOT IN ("
        //                      "SELECT DISTINCT to_recipient_id FROM " + d_mms_table + " WHERE thread_id IS ? AND type IN (?, ?, ?, ?, ?, ?, ?, ?)"
        //                      ")",
        //                      {gid, tid,
        //                       Types::BASE_OUTBOX_TYPE, Types::BASE_SENT_TYPE, Types::BASE_SENDING_TYPE, Types::BASE_SENT_FAILED_TYPE,
        //                       Types::BASE_PENDING_SECURE_SMS_FALLBACK,Types:: BASE_PENDING_INSECURE_SMS_FALLBACK , Types::OUTGOING_CALL_TYPE, Types::OUTGOING_VIDEO_CALL_TYPE}, &res3))
        //   for (unsigned int j = 0; j < res3.rows(); ++j)
        //   {
        //     std::cout << "  From group membership (NEW):" << res3(j, "recipeint_id") << std::endl;
        //     options.insert(bepaald::toNumber<long long int>(res3(j, "recipient_id")));
        //   }

        // incoming
        if (!d_database.exec("SELECT DISTINCT recipient_id FROM group_membership WHERE group_id IN (SELECT group_id FROM groups WHERE _id = ?) AND "
                             "recipient_id NOT IN ("
                             "SELECT DISTINCT from_recipient_id FROM " + d_mms_table + " WHERE thread_id IS ? AND type IS NOT ? AND type NOT IN (?, ?, ?, ?, ?, ?, ?, ?)"
                             ")",
                             {gid, tid, Types::GROUP_CALL_TYPE,
                              Types::BASE_OUTBOX_TYPE, Types::BASE_SENT_TYPE, Types::BASE_SENDING_TYPE, Types::BASE_SENT_FAILED_TYPE,
                              Types::BASE_PENDING_SECURE_SMS_FALLBACK,Types:: BASE_PENDING_INSECURE_SMS_FALLBACK , Types::OUTGOING_CALL_TYPE, Types::OUTGOING_VIDEO_CALL_TYPE}, &res3))
          continue;
        else
        {
          for (unsigned int j = 0; j < res3.rows(); ++j)
          {
            //std::cout << "  From group membership (NEW):" << res3(j, "recipient_id") << std::endl;
            options.insert(res3.valueAsInt(j, "recipient_id"));
          }
        }
      }
    }
  }

  // std::cout << "OPTIONS:" << std::endl;
  // for (auto const &o: options)
  //   std::cout << "Option: " << o << std::endl;

  if (options.size() == 1)
    return *options.begin();

  return -1;
}


/*
  Another possible option, maybe also for older (but not oldest) databases:

  SELECT address FROM identites WHERE first_use = 1 AND verified = 1 AND nonblocking_approval = 1;

  This seems to return 1 or two 'address'es, which seem to all point to the same recipient (='self');

  dbv10 : not working
  dbv23 : address == e164 of self
  dbv27 - dbv99 : address == recipient._id of self
  dbv123 - dbv198 : address == recipient.uuid (/aci) of self
  dbv201 - dbv231 : address (result 1) == recipient.uuid (/aci) of self
                    address (result 2) == recipient.pni of self

  In my databases it only has 1 questionable result for DEVDBV23 where it returns 2 distinct e164s,
  One is self, the other is unknown (does not appear in recipient_preferences or elsewhere). Possible
  number change, or backup restore after new SIM card placed?

*/