Coverage Report

Created: 2023-09-25 06:33

/src/qubes-os/qubes-core-qubesdb/daemon/db-cmds.c
Line
Count
Source (jump to first uncovered line)
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
#include <errno.h>
5
#include <fcntl.h>
6
#include <assert.h>
7
#ifdef WIN32
8
#include <windows.h>
9
#include <strsafe.h>
10
11
#include <qubes-io.h>
12
#else
13
#include <unistd.h>
14
#endif
15
16
#include <libvchan.h>
17
18
#include "buffer.h"
19
#include <qubesdb.h>
20
#include "qubesdb_internal.h"
21
22
#if defined(_MSC_VER)
23
#if !defined(size_t)
24
#define size_t SIZE_T
25
#endif
26
#if !defined(ssize_t)
27
#define ssize_t SSIZE_T
28
#endif
29
#endif
30
31
/** Check if given string matches path specification (i.e. have only allowed
32
 * characters). This function allows for '/' at the end, so if particular
33
 * command doesn't permit so, appropriate handle_* function should additionally
34
 * check for it.
35
 * @param path String to check
36
 * @return 1 if everything is OK, 0 if path is invalid
37
 */
38
0
static int verify_path(char *path) {
39
0
    int i;
40
0
    int path_len;
41
42
0
    path_len = (int)strlen(path);
43
0
    if (path_len >= QDB_MAX_PATH)
44
0
        return 0;
45
0
    for (i = 0; i < path_len; i++) {
46
0
        if (path[i] == 0)
47
0
            break;
48
0
        if (path[i] >= 'a' && path[i] <= 'z')
49
0
            continue;
50
0
        if (path[i] >= 'A' && path[i] <= 'Z')
51
0
            continue;
52
0
        if (path[i] >= '0' && path[i] <= '9')
53
0
            continue;
54
0
        switch (path[i]) {
55
0
            case '-':
56
0
                if (i)
57
0
                    break;
58
                /* leading dash forbidden */
59
0
                return 0;
60
0
            case '_':
61
0
            case '/':
62
0
            case '.':
63
0
            case ':':
64
0
            case '@':
65
0
                break;
66
0
            default:
67
                /* forbidden character in path */
68
0
                return 0;
69
0
        }
70
0
    }
71
0
    return 1;
72
0
}
73
74
/** Check if given data doesn't contains forbidden characters (currently only
75
 * \0).
76
 * @param data Data buffer.
77
 * @param len Data size
78
 * @return 1 if everything is OK, 0 if invalid data detected
79
 */
80
0
static int verify_data(char *data, int len) {
81
0
    int i;
82
83
0
    for (i = 0; i < len; i++) {
84
        /* forbid NULL byte */
85
0
        if (data[i] == 0)
86
0
            return 0;
87
0
    }
88
0
    return 1;
89
0
}
90
91
/** Sanitize message header.
92
 * @param untrusted_hdr Header to be checked
93
 * @param vchan Does message was received via vchan link?
94
 * @return 1 if OK, 0 if some invalid field value was detected
95
 */
96
0
static int verify_hdr(struct qdb_hdr *untrusted_hdr, int vchan) {
97
0
    switch (untrusted_hdr->type) {
98
0
        case QDB_CMD_READ:
99
0
        case QDB_CMD_LIST:
100
0
        case QDB_CMD_WATCH:
101
0
        case QDB_CMD_UNWATCH:
102
            /* those messages allowed only on local daemon interface */
103
0
            if (vchan)
104
0
                return 0;
105
            /* fallthrough */
106
0
        case QDB_CMD_MULTIREAD:
107
0
        case QDB_CMD_WRITE:
108
0
        case QDB_CMD_RM:
109
0
            break;
110
0
        case QDB_RESP_OK:
111
0
        case QDB_RESP_ERROR:
112
0
        case QDB_RESP_MULTIREAD:
113
            /* those messages expected only on vchan daemon interface */
114
0
            if (vchan)
115
0
                break;
116
0
            else
117
0
                return 0;
118
0
        default:
119
            /* invalid command */
120
0
            return 0;
121
0
    }
122
    /* ensure path is null terminated */
123
0
    untrusted_hdr->path[sizeof(untrusted_hdr->path)-1] = '\0';
124
0
    if (!verify_path(untrusted_hdr->path))
125
0
        return 0;
126
0
    if (untrusted_hdr->data_len >= QDB_MAX_DATA)
127
0
        return 0;
128
0
    return 1;
129
0
}
130
131
/** Simulate non-blocking vchan write by checking available space
132
 * @param vchan vchan connection
133
 * @param buf Data to send
134
 * @param len Amount of data
135
 * @return written amount of data, or -1 on error (error code in errno)
136
 *
137
 * FIXME: this is suspectible to race condition if remote side is malicious,
138
 * proper solution require exposing blocking flag on libvchan API.
139
 */
140
0
static int vchan_write_nonblock(libvchan_t *vchan, char *buf, size_t size) {
141
0
    size_t avail = libvchan_buffer_space(vchan);
142
0
    ssize_t ret;
143
0
    if (avail < size)
144
0
        size = avail;
145
0
    ret = libvchan_write(vchan, buf, size);
146
0
    if (ret == 0) {
147
0
        ret = -1;
148
0
        errno = EWOULDBLOCK;
149
0
    }
150
0
    return ret;
151
0
}
152
153
/* write to either client given by fd parameter or vchan if
154
 * fd == NULL
155
 * writes could be buffered for vchan, or if client FD is set to non-blocking
156
 * mode
157
 */
158
int write_vchan_or_client(struct db_daemon_data *d, struct client *c,
159
0
        char *data, int data_len) {
160
0
    int ret, count;
161
0
    struct buffer *write_queue = NULL;
162
0
    int buf_datacount;
163
164
0
    if (c == NULL) {
165
        /* vchan */
166
0
        if (!d->vchan)
167
            /* if vchan not connected, just do nothing */
168
0
            return 1;
169
0
        write_queue = d->vchan_buffer;
170
0
    } else {
171
0
#ifndef WIN32
172
0
        write_queue = c->write_queue;
173
0
#endif
174
0
    }
175
176
#ifdef WIN32
177
    if (c) {
178
        DWORD status = QpsWrite(d->pipe_server, c->id, data, data_len);
179
        if (status != ERROR_SUCCESS)
180
            return 0;
181
        return 1;
182
    }
183
#endif
184
185
    /* now it's either vchan, or local client on Linux */
186
0
    while ((buf_datacount = buffer_datacount(write_queue))) {
187
0
#ifndef WIN32
188
0
        if (c)
189
0
            ret = write(c->fd, buffer_data(write_queue), buf_datacount);
190
0
        else
191
0
#endif
192
0
            ret = vchan_write_nonblock(d->vchan, buffer_data(write_queue), buf_datacount);
193
0
        if (ret < 0) {
194
0
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
195
                /* receiver doesn't have space for more data,
196
                 * buffer actual requested data and exit */
197
0
                buffer_append(write_queue, data, data_len);
198
0
                return 1;
199
0
            }
200
0
            perror("vchan/client write");
201
0
            return 0;
202
0
        }
203
0
        buffer_substract(write_queue, ret);
204
0
    }
205
206
0
    count = 0;
207
0
    while (count < data_len) {
208
0
#ifndef WIN32
209
0
        if (c)
210
0
            ret = write(c->fd, data+count, data_len-count);
211
0
        else
212
0
#endif
213
0
            ret = vchan_write_nonblock(d->vchan, data+count, data_len-count);
214
0
        if (ret < 0) {
215
0
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
216
                /* receiver doesn't have space for more data,
217
                 * buffer remaining requested data and exit */
218
0
                buffer_append(write_queue, data+count, data_len-count);
219
0
                return 1;
220
0
            }
221
0
            perror("vchan/client write");
222
0
            return 0;
223
0
        }
224
0
        count += ret;
225
0
    }
226
0
    return 1;
227
0
}
228
229
static int read_vchan_or_client(struct db_daemon_data *d, struct client *c,
230
0
        char *data, int data_len) {
231
0
#ifndef WIN32
232
0
    int ret, count;
233
0
#endif
234
235
0
    if (data_len == 0)
236
        /* nothing to do */
237
0
        return 1;
238
239
0
    if (c == NULL) {
240
        /* vchan */
241
0
        if (!d->vchan)
242
            /* if vchan not connected, return error */
243
0
            return 0;
244
0
        if (libvchan_recv(d->vchan, data, data_len) < 0) {
245
0
            perror("closing vchan. vchan read");
246
0
            libvchan_close(d->vchan);
247
0
            d->vchan = NULL;
248
0
            return 0;
249
0
        }
250
0
        return 1;
251
0
    } else {
252
0
#ifndef WIN32
253
0
        count = 0;
254
0
        while (count < data_len) {
255
0
            ret = read(c->fd, data + count, data_len - count);
256
0
            if (ret < 0) {
257
0
                if (errno == ECONNRESET)
258
0
                    return 0;
259
0
                perror("client read");
260
0
                return 0;
261
0
            }
262
            /* EOF */
263
0
            if (ret == 0)
264
0
                return 0;
265
0
            count += ret;
266
0
        }
267
#else // !WIN32
268
        DWORD status = QpsRead(d->pipe_server, c->id, data, data_len);
269
        if (status != ERROR_SUCCESS)
270
            return 0;
271
#endif
272
0
        return 1;
273
0
    }
274
0
}
275
276
/** Discard specified amount of data on given communication channel
277
 * @param d Daemon global data
278
 * @param fd From which client discard data. NULL means vchan.
279
 * @param amount Size of data to discard in bytes.
280
 * @return 1 on success, 0 on failure
281
     */
282
0
static int discard_data(struct db_daemon_data *d, struct client *client, int amount) {
283
0
    char buf[256];
284
0
    int data_to_read;
285
286
0
    while (amount) {
287
0
        data_to_read = amount < sizeof(buf) ? amount : sizeof(buf);
288
0
        if (!read_vchan_or_client(d, client, buf, data_to_read))
289
0
            return 0;
290
0
        amount -= data_to_read;
291
0
    }
292
0
    return 1;
293
0
}
294
295
/** Discard 'data' part of message and send QDB_RESP_ERROR. To be used when
296
 * invalid header detected.
297
 * @param d Daemon global data
298
 * @param client Client connection (NULL means vchan)
299
 * @param hdr Original header received from client. hdr->data_len bytes will be
300
 * discarded. WARNING: This struct will be modified to send response.
301
 * @return 1 on success, 0 on failure (recovery failed and client should be
302
 * disconnected).
303
 */
304
static int discard_data_and_send_error(struct db_daemon_data *d, struct client *client,
305
0
        struct qdb_hdr *hdr) {
306
0
    if (discard_data(d, client, hdr->data_len)) {
307
0
        hdr->type = QDB_RESP_ERROR;
308
0
        hdr->data_len = 0;
309
0
        if (write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
310
0
            return 1;
311
0
    }
312
0
    return 0;
313
0
}
314
315
#ifndef WIN32
316
/** Send data to a client, but in non-blocking way - if write would block,
317
 * buffer the data instead
318
 * @param client Client connection
319
 * @param buf Data to send
320
 * @param len Amount of data
321
 * @return 1 on when everything was sent, 0 otherwise (errors, some data buffered)
322
 */
323
0
int write_client_buffered(struct client *client, char *buf, size_t size) {
324
0
    int ret, buf_datacount, written;
325
326
0
    assert(client);
327
328
0
    if (fcntl(client->fd, F_SETFL, O_NONBLOCK) < 0) {
329
0
        perror("fcntl");
330
0
        return 0;
331
0
    }
332
0
    while ((buf_datacount = buffer_datacount(client->write_queue))) {
333
0
        ret = write(client->fd, buffer_data(client->write_queue), buf_datacount);
334
0
        if (ret < 0) {
335
0
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
336
0
                buffer_append(client->write_queue, buf, size);
337
0
                ret = 0;
338
0
                goto out;
339
0
            } else {
340
                /* discard the data, then probably close socket */
341
0
                buffer_substract(client->write_queue, buf_datacount);
342
0
                ret = 0;
343
0
                goto out;
344
0
            }
345
0
            break;
346
0
        }
347
0
        buffer_substract(client->write_queue, ret);
348
0
    }
349
350
0
    written = 0;
351
0
    while (written < size) {
352
0
        ret = write(client->fd, buf+written, size-written);
353
0
        if (ret < 0) {
354
0
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
355
0
                buffer_append(client->write_queue, buf+written, size-written);
356
0
                goto out;
357
0
            } else {
358
0
                perror("client write");
359
0
                ret = 0;
360
0
                goto out;
361
0
            }
362
0
        }
363
0
        written += ret;
364
0
    }
365
0
    ret = 1;
366
0
out:
367
0
    if (fcntl(client->fd, F_SETFL, 0) < 0) {
368
0
        perror("fcntl");
369
0
        return 0;
370
0
    }
371
0
    return ret;
372
0
}
373
#else // !WIN32
374
int send_watch_notify(struct client *c, char *buf, size_t len, PIPE_SERVER ps)
375
{
376
    DWORD status = QpsWrite(ps, c->id, buf, (DWORD)len);
377
    if (status != ERROR_SUCCESS)
378
        return 0;
379
    return 1;
380
}
381
#endif
382
383
/** Handle 'write' command. Modify the database and send notification to other
384
 * vchan side (if command received from local client). After modification (and
385
 * sending response+notification) appropriate watches are fired.
386
 * This command is valid on both client socket and vchan, so input data must be
387
 * handled with special care.
388
 * @param d Daemon global data
389
 * @param client Client connection (NULL means vchan)
390
 * @param hdr Original header received from client.
391
 *        WARNING: This struct will be modified to send response.
392
 * @return 1 on success (message handled and responded, even if response is
393
 *           error message), 0 if fatal error occured and client should be
394
 *           disconnected.
395
 */
396
static int handle_write(struct db_daemon_data *d, struct client *client,
397
0
        struct qdb_hdr *hdr) {
398
0
    char untrusted_data[QDB_MAX_DATA];
399
0
    char *data;
400
401
0
    if (!read_vchan_or_client(d, client, untrusted_data, hdr->data_len)) {
402
0
        return 0;
403
0
    }
404
405
0
    if (!verify_data(untrusted_data, hdr->data_len)) {
406
0
        fprintf(stderr, "invalid data received from peer\n");
407
        /* recovery path */
408
0
        hdr->data_len = 0; // data already received
409
0
        return discard_data_and_send_error(d, client, hdr);
410
0
    }
411
0
    data = untrusted_data;
412
413
0
    if (!qubesdb_write(d->db, hdr->path, data, hdr->data_len)) {
414
0
        fprintf(stderr, "failed to write path %s\n", hdr->path);
415
0
        hdr->type = QDB_RESP_ERROR;
416
0
        hdr->data_len = 0;
417
0
        write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr));
418
0
        return 0;
419
0
    } else {
420
0
        if (client != NULL && d->remote_connected) {
421
            /* if write was from local client, duplicate it through vchan */
422
0
            write_vchan_or_client(d, NULL,
423
0
                    (char*)hdr, sizeof(*hdr));
424
0
            write_vchan_or_client(d, NULL,
425
0
                    data, hdr->data_len);
426
0
        }
427
0
        hdr->type = QDB_RESP_OK;
428
0
        hdr->data_len = 0;
429
0
        if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
430
0
            return 0;
431
0
        qubesdb_fire_watches(d->db, hdr->path);
432
0
        return 1;
433
0
    }
434
0
}
435
436
/** Handle 'rm' command. Modify the database and send notification to other
437
 * vchan side (if command received from local client). After modification (and
438
 * sending response+notification) appropriate watches are fired.
439
 * This command is valid on both client socket and vchan, so input data must be
440
 * handled with special care.
441
 * @param d Daemon global data
442
 * @param client Client connection (NULL means vchan)
443
 * @param hdr Original header received from client.
444
 *        WARNING: This struct will be modified to send response.
445
 * @return 1 on success (message handled and responded, even if response is
446
 *           error message), 0 if fatal error occured and client should be
447
 *           disconnected.
448
 */
449
/* this command is valid on both client socket and vchan */
450
static int handle_rm(struct db_daemon_data *d, struct client *client,
451
0
        struct qdb_hdr *hdr) {
452
0
    if (hdr->data_len > 0) {
453
0
        fprintf(stderr, "CMD_RM shouldn't have data field\n");
454
        /* recovery path */
455
0
        return discard_data_and_send_error(d, client, hdr);
456
0
    }
457
458
0
    if (!qubesdb_remove(d->db, hdr->path)) {
459
0
        hdr->type = QDB_RESP_ERROR_NOENT;
460
0
        hdr->data_len = 0;
461
0
        if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
462
0
            return 0;
463
        /* failed rm received from vchan is fatal - means some database
464
         * de-synchronization */
465
0
        if (client == NULL)
466
0
            return 0;
467
0
    } else {
468
0
        if (client != NULL && d->remote_connected) {
469
            /* if rm was from local client, duplicate it through vchan */
470
0
            write_vchan_or_client(d, NULL,
471
0
                    (char*)hdr, sizeof(*hdr));
472
0
        }
473
0
        hdr->type = QDB_RESP_OK;
474
0
        hdr->data_len = 0;
475
0
        if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
476
0
            return 0;
477
0
        qubesdb_fire_watches(d->db, hdr->path);
478
0
    }
479
0
    return 1;
480
0
}
481
482
/** Handle 'read' command.
483
 * This command is only valid local socket.
484
 * @param d Daemon global data
485
 * @param client Client connection (NULL means vchan)
486
 * @param hdr Original header received from client.
487
 *        WARNING: This struct will be modified to send response.
488
 * @return 1 on success (message handled and responded, even if response is
489
 *           error message), 0 if fatal error occured and client should be
490
 *           disconnected.
491
 */
492
static int handle_read(struct db_daemon_data *d, struct client *client,
493
0
        struct qdb_hdr *hdr) {
494
0
    struct qubesdb_entry *db_entry;
495
496
0
    if (hdr->data_len > 0) {
497
0
        fprintf(stderr, "CMD_READ shouldn't have data field\n");
498
0
        return 0;
499
0
    }
500
501
0
    db_entry = qubesdb_search(d->db, hdr->path, 1);
502
0
    if (!db_entry) {
503
0
        hdr->type = QDB_RESP_ERROR_NOENT;
504
0
        hdr->data_len = 0;
505
0
        if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
506
0
            return 0;
507
0
    } else {
508
0
        hdr->type = QDB_RESP_READ;
509
0
        hdr->data_len = db_entry->value_len;
510
0
        if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
511
0
            return 0;
512
0
        if (!write_vchan_or_client(d, client,
513
0
                    db_entry->value, hdr->data_len))
514
0
            return 0;
515
0
    }
516
0
    return 1;
517
0
}
518
519
/** Handle 'multiread' command. Send all mathing entries. This command is used
520
 * for initial database synchronization by VM client part.
521
 * vchan side (if command received from local client).
522
 * This command is valid on both client socket and vchan, so input data must be
523
 * handled with special care.
524
 * @param d Daemon global data
525
 * @param client Client connection (NULL means vchan)
526
 * @param hdr Original header received from client.
527
 *        WARNING: This struct will be modified to send response.
528
 * @return 1 on success (message handled and responded, even if response is
529
 *           error message), 0 if fatal error occured and client should be
530
 *           disconnected.
531
 */
532
static int handle_multiread(struct db_daemon_data *d, struct client *client,
533
0
        struct qdb_hdr *hdr) {
534
0
    struct qubesdb_entry *db_entry;
535
0
    char search_path[QDB_MAX_PATH];
536
0
    int search_path_len;
537
538
0
    if (hdr->data_len > 0) {
539
0
        fprintf(stderr, "CMD_MULTIREAD shouldn't have data field\n");
540
        /* recovery path */
541
0
        return discard_data_and_send_error(d, client, hdr);
542
0
    }
543
544
0
#ifndef WIN32
545
0
    strncpy(search_path, hdr->path, QDB_MAX_PATH);
546
#else
547
    StringCbCopyA(search_path, sizeof(search_path), hdr->path);
548
#endif
549
0
    search_path_len = (int)strlen(search_path);
550
551
0
    hdr->type = QDB_RESP_MULTIREAD;
552
553
0
    if (search_path_len) {
554
0
        db_entry = qubesdb_search(d->db, search_path, 0);
555
0
    } else {
556
        /* if full database requested, dump in reverser order so insertion-sort
557
         * on the other side will be more efficient */
558
0
        db_entry = d->db->entries->prev;
559
0
    }
560
0
    while (db_entry != d->db->entries &&
561
0
             strncmp(db_entry->path, search_path, search_path_len) == 0) {
562
0
#ifndef WIN32
563
0
        strncpy(hdr->path, db_entry->path, sizeof(hdr->path));
564
#else
565
        StringCbCopyA(hdr->path, sizeof(hdr->path), db_entry->path);
566
#endif
567
0
        hdr->data_len = db_entry->value_len;
568
0
        if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
569
0
            return 0;
570
0
        if (!write_vchan_or_client(d, client,
571
0
                    db_entry->value, hdr->data_len))
572
0
            return 0;
573
0
        if (search_path_len)
574
0
            db_entry = db_entry->next;
575
0
        else
576
0
            db_entry = db_entry->prev;
577
0
    }
578
    /* end of data */
579
0
    hdr->data_len = 0;
580
0
    hdr->path[0] = 0;
581
0
    if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
582
0
        return 0;
583
0
    return 1;
584
0
}
585
586
/** Handle 'list' command. Send list of paths matching given prefix.
587
 * This command is only valid local socket.
588
 * @param d Daemon global data
589
 * @param client Client connection (NULL means vchan)
590
 * @param hdr Original header received from client.
591
 *        WARNING: This struct will be modified to send response.
592
 * @return 1 on success (message handled and responded, even if response is
593
 *           error message), 0 if fatal error occured and client should be
594
 *           disconnected.
595
 */
596
static int handle_list(struct db_daemon_data *d, struct client *client,
597
0
        struct qdb_hdr *hdr) {
598
0
    struct qubesdb_entry *db_entry;
599
0
    char search_path[QDB_MAX_PATH];
600
0
    int search_path_len;
601
602
0
    if (hdr->data_len > 0) {
603
0
        fprintf(stderr, "CMD_LIST shouldn't have data field\n");
604
        /* recovery path */
605
0
        return discard_data_and_send_error(d, client, hdr);
606
0
    }
607
608
0
#ifndef WIN32
609
0
    strncpy(search_path, hdr->path, QDB_MAX_PATH);
610
#else
611
    StringCbCopyA(search_path, sizeof(search_path), hdr->path);
612
#endif
613
0
    search_path_len = (int)strlen(search_path);
614
615
0
    hdr->type = QDB_RESP_LIST;
616
617
0
    db_entry = qubesdb_search(d->db, search_path, 0);
618
0
    while (db_entry != d->db->entries &&
619
0
             strncmp(db_entry->path, search_path, search_path_len) == 0) {
620
0
#ifndef WIN32
621
0
        strncpy(hdr->path, db_entry->path, sizeof(hdr->path));
622
#else
623
        StringCbCopyA(hdr->path, sizeof(hdr->path), db_entry->path);
624
#endif
625
0
        hdr->data_len = 0;
626
0
        if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
627
0
            return 0;
628
0
        db_entry = db_entry->next;
629
0
    }
630
    /* end of data */
631
0
    hdr->data_len = 0;
632
0
    hdr->path[0] = 0;
633
0
    if (!write_vchan_or_client(d, client, (char*)hdr, sizeof(*hdr)))
634
0
        return 0;
635
0
    return 1;
636
0
}
637
638
/** Handle single response to 'multiread' command. This incoming message is
639
 * valid only on vchan and is used only for initial database synchronization.
640
 * Modify the database but do not send any notigications nor fire watches.
641
 * @param d Daemon global data
642
 * @param client Client connection (NULL means vchan)
643
 * @param hdr Original header received from client.
644
 * @return 1 on success, 0 if fatal error occured and client should be
645
 *           disconnected.
646
 */
647
0
static int handle_vchan_multiread_resp(struct db_daemon_data *d, struct qdb_hdr *hdr) {
648
0
    char data[QDB_MAX_DATA];
649
650
0
    if (hdr->data_len && libvchan_recv(d->vchan, data, hdr->data_len) < 0) {
651
0
        perror("vchan read");
652
0
        return 0;
653
0
    }
654
655
0
    if (hdr->path[0] == '\0') {
656
        /* empty path - end of data */
657
0
        d->multiread_requested = 0;
658
0
        return 1;
659
0
    }
660
661
0
    if (!verify_data(data, hdr->data_len)) {
662
0
        fprintf(stderr, "invalid data received from peer\n");
663
0
        return 0;
664
0
    }
665
666
0
    if (!qubesdb_write(d->db, hdr->path, data, hdr->data_len)) {
667
0
        fprintf(stderr, "failed to insert entry\n");
668
0
        return 0;
669
0
    }
670
0
    qubesdb_fire_watches(d->db, hdr->path);
671
0
    return 1;
672
0
}
673
674
/** Handle new vchan command. This functions is called every time when any new
675
 * vchan command is detected (but not yet read). It receives data from other
676
 * vchan peer, carefully verify its contents and call appropriate handle
677
 * function. Any error in processing vchan data should be considered fatal.
678
 * @param d Daemon global data
679
 * @return 1 on success (message handled and responded), 0 if error
680
 *           occured and client should be disconnected, 2 if message not yet
681
 *           handled (waiting for more data)
682
 */
683
26
int handle_vchan_data(struct db_daemon_data *d) {
684
26
    struct qdb_hdr untrusted_hdr;
685
26
    struct qdb_hdr hdr;
686
687
26
    if (d->vchan_pending_hdr.type == QDB_INVALID_CMD) {
688
        /* no previous header, retrieve it */
689
0
        if (libvchan_data_ready(d->vchan) < sizeof(untrusted_hdr)) {
690
            /* not enough data in vchan, wait for more */
691
0
            return 2;
692
0
        }
693
0
        if (libvchan_recv(d->vchan, &untrusted_hdr, sizeof(untrusted_hdr)) < 0) {
694
0
            perror("vchan read");
695
0
            return 0;
696
0
        }
697
0
        if (!verify_hdr(&untrusted_hdr, 1)) {
698
0
            fprintf(stderr, "invalid message received from peer\n");
699
0
            return 0;
700
0
        }
701
0
        hdr = untrusted_hdr;
702
26
    } else {
703
26
        hdr = d->vchan_pending_hdr;
704
26
        d->vchan_pending_hdr.type = QDB_INVALID_CMD;
705
26
    }
706
707
    /* This check is correct only because the whole message (up to
708
     * QDB_MAX_DATA) can fit into a vchan buffer. Otherwise it could cause a
709
     * deadlock. */
710
26
    if (libvchan_data_ready(d->vchan) < hdr.data_len) {
711
        /* save the header, but process the message when the rest is available */
712
0
        d->vchan_pending_hdr = hdr;
713
0
        return 2;
714
0
    }
715
716
26
    switch (hdr.type) {
717
0
        case QDB_CMD_WRITE:
718
0
            if (!handle_write(d, NULL, &hdr))
719
0
                return 0;
720
0
            break;
721
0
        case QDB_CMD_MULTIREAD:
722
0
            if (d->remote_connected) {
723
0
                fprintf(stderr, "received spurious QDB_CMD_MULTIREAD on vchan: "
724
0
                                "only one after connection allowed\n");
725
0
                discard_data_and_send_error(d, NULL, &hdr);
726
0
                return 0;
727
0
            }
728
0
            if (!handle_multiread(d, NULL, &hdr))
729
0
                return 0;
730
            /* remote have synchronized database, send furher updates */
731
0
            d->remote_connected = 1;
732
0
            break;
733
734
0
        case QDB_CMD_RM:
735
            /* if there is no space for response, drop the command - remote
736
             * side seems unresponsive */
737
0
            if (libvchan_buffer_space(d->vchan) < sizeof(hdr)) {
738
0
                fprintf(stderr, "got QDB_CMD_RM from remote domain, "
739
0
                                "but there is no space in vchan for the reponse; dropping\n");
740
0
                return 0;
741
0
            }
742
0
            if (!handle_rm(d, NULL, &hdr))
743
0
                return 0;
744
0
            break;
745
746
0
        case QDB_RESP_OK:
747
0
            break;
748
0
        case QDB_RESP_ERROR:
749
0
            fprintf(stderr, "received error from peer\n");
750
0
            if (hdr.data_len) {
751
0
                fprintf(stderr, "FATAL: error packet contains some unexpected data\n");
752
0
                return 0;
753
0
            }
754
0
            break;
755
0
        case QDB_RESP_MULTIREAD:
756
0
            if (d->multiread_requested) {
757
0
                if (!handle_vchan_multiread_resp(d, &hdr))
758
0
                    return 0;
759
0
            } else {
760
0
                fprintf(stderr, "spurious MULTIREAD response\n");
761
0
                return 0;
762
0
            }
763
0
            break;
764
26
        default:
765
26
            fprintf(stderr, "unexpected command from peer: %d\n", hdr.type);
766
26
            return 0;
767
26
    }
768
0
    return 1;
769
26
}
770
771
/** Handle data from client; if some data already received by caller, pass it
772
 * via data+data_len parameters.
773
 * @param d Daemon global data
774
 * @param client Client socket from which handle command
775
 * @param data Data buffer already received from client. Must be no more than
776
 *             sizeof(struct qdb_hdr).
777
 * @param data_len Size of filled buffer in 'data'
778
 * @return 1 on success (message handled and responded, even if response is
779
 *           error message), 0 if fatal error occured and client should be
780
 *           disconnected.
781
 */
782
int handle_client_data(struct db_daemon_data *d, struct client *client,
783
0
        char *data, int data_len) {
784
0
    struct qdb_hdr hdr;
785
0
    int ret = 1;
786
787
0
    if (data_len > sizeof(hdr)) {
788
0
        fprintf(stderr, "BUG(handle_client_data): caller passed more data than "
789
0
                "header size, cannot continue\n");
790
0
        exit(1);
791
0
    }
792
0
    memcpy(&hdr, data, data_len);
793
0
    if (!read_vchan_or_client(d, client,
794
0
                    ((char*)&hdr)+data_len, sizeof(hdr)-data_len)) {
795
0
        return 0;
796
0
    }
797
798
0
    if (!verify_hdr(&hdr, 0)) {
799
0
        fprintf(stderr, "invalid message received from client "
800
0
#ifndef WIN32
801
0
                CLIENT_SOCKET_FORMAT "\n", client->fd);
802
#else
803
                CLIENT_SOCKET_FORMAT "\n", client->id);
804
#endif
805
        /* recovery path */
806
0
        return discard_data_and_send_error(d, client, &hdr);
807
0
    }
808
809
0
    switch (hdr.type) {
810
0
        case QDB_CMD_READ:
811
0
            ret = handle_read(d, client, &hdr);
812
0
            break;
813
814
0
        case QDB_CMD_WRITE:
815
0
            ret = handle_write(d, client, &hdr);
816
0
            break;
817
818
0
        case QDB_CMD_MULTIREAD:
819
0
            ret = handle_multiread(d, client, &hdr);
820
0
            break;
821
822
0
        case QDB_CMD_LIST:
823
0
            ret = handle_list(d, client, &hdr);
824
0
            break;
825
826
0
        case QDB_CMD_RM:
827
0
            ret = handle_rm(d, client, &hdr);
828
0
            break;
829
830
0
        case QDB_CMD_WATCH:
831
0
            ret = qubesdb_add_watch(d->db, hdr.path, client);
832
0
            hdr.type = ret ? QDB_RESP_OK : QDB_RESP_ERROR;
833
0
            hdr.data_len = 0;
834
0
            if (!write_vchan_or_client(d, client, (char*)&hdr, sizeof(hdr)))
835
0
                ret = 0;
836
0
            break;
837
838
0
        case QDB_CMD_UNWATCH:
839
0
            ret = qubesdb_remove_watch(d->db, hdr.path, client);
840
0
            hdr.type = ret ? QDB_RESP_OK : QDB_RESP_ERROR_NOENT;
841
0
            hdr.data_len = 0;
842
            /* NOENT isn't fatal */
843
0
            ret = 1;
844
0
            if (!write_vchan_or_client(d, client, (char*)&hdr, sizeof(hdr)))
845
0
                ret = 0;
846
0
            break;
847
848
0
        default:
849
0
            fprintf(stderr, "unexpected command from peer: %d\n", hdr.type);
850
            /* recovery path */
851
0
            return discard_data_and_send_error(d, client, &hdr);
852
0
    }
853
0
    return ret;
854
0
}
855
856
0
int handle_client_connect(struct db_daemon_data *d, struct client *client) {
857
    /* currently nothing */
858
0
    return 1;
859
0
}
860
861
0
int handle_client_disconnect(struct db_daemon_data *d, struct client *client) {
862
    /* remove all watches owned by this client */
863
0
    qubesdb_remove_watch(d->db, NULL, client);
864
0
    return 1;
865
0
}
866
867
0
int request_full_db_sync(struct db_daemon_data *d) {
868
0
    struct qdb_hdr hdr;
869
870
0
    hdr.type = QDB_CMD_MULTIREAD;
871
0
    hdr.path[0] = 0;
872
0
    hdr.data_len = 0;
873
874
0
    return write_vchan_or_client(d, NULL, (char*)&hdr, sizeof(hdr));
875
0
}