cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
db_sync.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
4
5#include <sqlite3.h>
6
7#include <arpa/inet.h>
8#include <errno.h>
9#include <netdb.h>
10#include <inttypes.h>
11#include <stdint.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/socket.h>
16#include <sys/types.h>
17#include <unistd.h>
18
24static const unsigned char SYNC_MAGIC[4] = {0x43, 0x57, 0x53, 0x59}; /* "CWSY" */
25
33static int db_sync_write_all(int fd, const void *buf, size_t len) {
34 const unsigned char *p = (const unsigned char *)buf;
35 while (len > 0) {
36 ssize_t n = write(fd, p, len);
37 if (n <= 0) return -1;
38 p += (size_t)n;
39 len -= (size_t)n;
40 }
41 return 0;
42}
43
51static int read_all(int fd, void *buf, size_t len) {
52 unsigned char *p = (unsigned char *)buf;
53 while (len > 0) {
54 ssize_t n = read(fd, p, len);
55 if (n <= 0) return -1;
56 p += (size_t)n;
57 len -= (size_t)n;
58 }
59 return 0;
60}
61
69int cwist_db_sync_serve(sqlite3 *db, const cwist_db_crypt_ctx_t *ctx, int port) {
70 if (!db || !ctx) return CWIST_DB_SYNC_ERR_ARGS;
71 if (port <= 0) port = CWIST_DB_SYNC_DEFAULT_PORT;
72
73 /* 1. Serialise the database to a byte buffer. */
75 unsigned char *db_bytes = sqlite3_serialize(db, "main", &db_size, 0);
76 if (!db_bytes || db_size <= 0) {
77 fprintf(stderr, "[db_sync] serialize failed\n");
79 }
80
81 /* 2. Seal the bytes. */
82 size_t blob_len = 0;
83 unsigned char *blob = cwist_db_crypt_seal(ctx, db_bytes, (size_t)db_size, &blob_len);
85
86 if (!blob) {
87 fprintf(stderr, "[db_sync] seal failed\n");
89 }
90
91 /* 3. Create a listening TCP socket. */
93 if (srv_fd < 0) {
94 free(blob);
96 }
97
98 int opt = 1;
100
101 struct sockaddr_in addr;
102 memset(&addr, 0, sizeof(addr));
103 addr.sin_family = AF_INET;
104 addr.sin_addr.s_addr = INADDR_ANY;
105 addr.sin_port = htons((uint16_t)port);
106
107 if (bind(srv_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
108 fprintf(stderr, "[db_sync] bind port %d failed: %s\n", port, strerror(errno));
109 close(srv_fd);
110 free(blob);
112 }
113
114 if (listen(srv_fd, 1) < 0) {
115 close(srv_fd);
116 free(blob);
118 }
119
120 /* 4. Accept one connection. */
123 int cli_fd = accept(srv_fd, (struct sockaddr *)&client_addr, &client_len);
124 close(srv_fd);
125
126 if (cli_fd < 0) {
127 free(blob);
129 }
130
131 /* 5. Read client magic. */
132 unsigned char magic_buf[4];
133 if (read_all(cli_fd, magic_buf, 4) < 0 || memcmp(magic_buf, SYNC_MAGIC, 4) != 0) {
134 fprintf(stderr, "[db_sync] bad client magic\n");
135 close(cli_fd);
136 free(blob);
138 }
139
140 /* 6. Send length (little-endian uint64) then blob. */
141 unsigned char len_buf[8];
142 for (int i = 0; i < 8; i++) len_buf[i] = (unsigned char)((uint64_t)blob_len >> (8 * i));
143 if (db_sync_write_all(cli_fd, len_buf, 8) < 0 || db_sync_write_all(cli_fd, blob, blob_len) < 0) {
144 close(cli_fd);
145 free(blob);
147 }
148
149 close(cli_fd);
150 free(blob);
151 return CWIST_DB_SYNC_OK;
152}
153
163int cwist_db_sync_pull(const char *host, int port, const cwist_db_crypt_ctx_t *ctx,
164 unsigned char **out_bytes, size_t *out_len) {
165 if (!host || !ctx || !out_bytes || !out_len) return CWIST_DB_SYNC_ERR_ARGS;
166 if (port <= 0) port = CWIST_DB_SYNC_DEFAULT_PORT;
167
168 /* 1. Resolve host. */
169 struct addrinfo hints, *res = NULL;
170 memset(&hints, 0, sizeof(hints));
171 hints.ai_family = AF_UNSPEC;
172 hints.ai_socktype = SOCK_STREAM;
173
174 char port_str[16];
175 snprintf(port_str, sizeof(port_str), "%d", port);
176
177 if (getaddrinfo(host, port_str, &hints, &res) != 0 || !res) {
178 fprintf(stderr, "[db_sync] getaddrinfo failed for %s\n", host);
180 }
181
182 /* 2. Connect. */
183 int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
184 if (fd < 0) {
185 freeaddrinfo(res);
187 }
188
189 if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
190 fprintf(stderr, "[db_sync] connect to %s:%d failed: %s\n", host, port, strerror(errno));
191 close(fd);
192 freeaddrinfo(res);
194 }
195 freeaddrinfo(res);
196
197 /* 3. Send magic. */
198 if (db_sync_write_all(fd, SYNC_MAGIC, 4) < 0) {
199 close(fd);
201 }
202
203 /* 4. Read blob length (little-endian uint64). */
204 unsigned char len_buf[8];
205 if (read_all(fd, len_buf, 8) < 0) {
206 close(fd);
208 }
210 for (int i = 0; i < 8; i++) blob_len_u64 |= ((uint64_t)len_buf[i]) << (8 * i);
211 if (blob_len_u64 == 0) {
212 close(fd);
214 }
215
216 /* Reject unreasonably large blobs before allocating to prevent OOM from a
217 * rogue or malicious sync server. 4 GiB is well above any realistic
218 * SQLite snapshot; adjust CWIST_DB_SYNC_MAX_BLOB_SIZE if needed. */
219#ifndef CWIST_DB_SYNC_MAX_BLOB_SIZE
220#define CWIST_DB_SYNC_MAX_BLOB_SIZE ((uint64_t)4 * 1024 * 1024 * 1024)
221#endif
223 fprintf(stderr, "[db_sync] blob_len %" PRIu64 " exceeds limit\n", blob_len_u64);
224 close(fd);
226 }
227 size_t blob_len = (size_t)blob_len_u64;
228 unsigned char *blob = (unsigned char *)malloc(blob_len);
229 if (!blob) {
230 close(fd);
232 }
233
234 /* 5. Read blob. */
235 if (read_all(fd, blob, blob_len) < 0) {
236 free(blob);
237 close(fd);
239 }
240 close(fd);
241
242 /* 6. Decrypt. */
243 size_t pt_len = 0;
244 unsigned char *pt = cwist_db_crypt_open(ctx, blob, blob_len, &pt_len);
245 free(blob);
246
247 if (!pt) {
248 fprintf(stderr, "[db_sync] decrypt failed\n");
250 }
251
252 *out_bytes = pt;
253 *out_len = pt_len;
254 return CWIST_DB_SYNC_OK;
255}
Two-level key hierarchy for encrypting an SQLite database blob.
unsigned char * cwist_db_crypt_seal(const cwist_db_crypt_ctx_t *ctx, const unsigned char *sqlite_bytes, size_t sqlite_len, size_t *out_len)
Encrypt an SQLite database blob.
Definition db_crypt.c:68
unsigned char * cwist_db_crypt_open(const cwist_db_crypt_ctx_t *ctx, const unsigned char *blob, size_t blob_len, size_t *out_len)
Decrypt a sealed blob produced by cwist_db_crypt_seal().
Definition db_crypt.c:160
#define CWIST_DB_SYNC_MAX_BLOB_SIZE
int cwist_db_sync_pull(const char *host, int port, const cwist_db_crypt_ctx_t *ctx, unsigned char **out_bytes, size_t *out_len)
Connect to a sync server, download the encrypted snapshot, and decrypt it.
Definition db_sync.c:163
int cwist_db_sync_serve(sqlite3 *db, const cwist_db_crypt_ctx_t *ctx, int port)
Serialize and encrypt a SQLite database, then serve it to one TCP client.
Definition db_sync.c:69
Server-to-server SQLite database synchronisation over TCP.
#define CWIST_DB_SYNC_ERR_PROTO
Definition db_sync.h:34
#define CWIST_DB_SYNC_ERR_ARGS
Definition db_sync.h:30
#define CWIST_DB_SYNC_OK
Return codes.
Definition db_sync.h:29
#define CWIST_DB_SYNC_DEFAULT_PORT
Definition db_sync.h:26
#define CWIST_DB_SYNC_ERR_NET
Definition db_sync.h:31
#define CWIST_DB_SYNC_ERR_CRYPTO
Definition db_sync.h:32
#define CWIST_DB_SYNC_ERR_MEM
Definition db_sync.h:33
size_t cap
Definition otel.c:56
Caller-managed key pair.
Definition db_crypt.h:53