Download
jcs
/subtext
/db.c
(View History)
jcs db: Add ftn_max_tossed_message_size and max_sysop_idle_minutes | Latest amendment: 469 on 2023-04-08 |
1 | /* |
2 | * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdio.h> |
18 | #include <string.h> |
19 | #include <time.h> |
20 | |
21 | #include "subtext.h" |
22 | #include "board.h" |
23 | #include "bile.h" |
24 | #include "db.h" |
25 | #include "folder.h" |
26 | #include "logger.h" |
27 | #include "mail.h" |
28 | #include "user.h" |
29 | #include "util.h" |
30 | |
31 | struct struct_field config_fields[] = { |
32 | { "BBS Name", CONFIG_TYPE_STRING, |
33 | offsetof(struct config, name), |
34 | 1, member_size(struct config, name) }, |
35 | { "Phone Number", CONFIG_TYPE_STRING, |
36 | offsetof(struct config, phone_number), |
37 | 1, member_size(struct config, phone_number) }, |
38 | { "Location", CONFIG_TYPE_STRING, |
39 | offsetof(struct config, location), |
40 | 1, member_size(struct config, location) }, |
41 | { "Timezone (Abbrev)", CONFIG_TYPE_STRING, |
42 | offsetof(struct config, timezone), |
43 | 1, member_size(struct config, timezone) }, |
44 | { "Timezone (UTC Offset)", CONFIG_TYPE_SHORT, |
45 | offsetof(struct config, timezone_utcoff), |
46 | -1200, 1400 }, |
47 | { "Allow Open Signup", CONFIG_TYPE_BOOLEAN, |
48 | offsetof(struct config, open_signup), |
49 | 0, 0 }, |
50 | |
51 | { "Hostname", CONFIG_TYPE_STRING, |
52 | offsetof(struct config, hostname), |
53 | 1, member_size(struct config, hostname) }, |
54 | { "Telnet Port", CONFIG_TYPE_SHORT, |
55 | offsetof(struct config, telnet_port), |
56 | 0, 65535 }, |
57 | |
58 | { "Telnet Trusted Proxy IP", CONFIG_TYPE_IP, |
59 | offsetof(struct config, trusted_proxy_ip), |
60 | 0, 1 }, |
61 | { "Telnet Trusted Proxy UDP Port", CONFIG_TYPE_SHORT, |
62 | offsetof(struct config, trusted_proxy_udp_port), |
63 | 0, 65535 }, |
64 | |
65 | { "Modem Port", CONFIG_TYPE_SHORT, |
66 | offsetof(struct config, modem_port), |
67 | 0, 2 }, |
68 | { "Modem Port Speed", CONFIG_TYPE_LONG, |
69 | offsetof(struct config, modem_speed), |
70 | 300, 115200 }, |
71 | { "Modem Bits/Parity/Stop", CONFIG_TYPE_STRING, |
72 | offsetof(struct config, modem_parity), |
73 | 1, member_size(struct config, modem_parity) }, |
74 | { "Modem Init String", CONFIG_TYPE_STRING, |
75 | offsetof(struct config, modem_init), |
76 | 1, member_size(struct config, modem_init) }, |
77 | |
78 | { "Max Idle Minutes", CONFIG_TYPE_SHORT, |
79 | offsetof(struct config, max_idle_minutes), |
80 | 0, USHRT_MAX }, |
81 | { "Max Sysop Idle Minutes", CONFIG_TYPE_SHORT, |
82 | offsetof(struct config, max_sysop_idle_minutes), |
83 | 0, USHRT_MAX }, |
84 | { "Max Login Seconds", CONFIG_TYPE_SHORT, |
85 | offsetof(struct config, max_login_seconds), |
86 | 1, USHRT_MAX }, |
87 | |
88 | { "Screen Blanker Idle Seconds", CONFIG_TYPE_SHORT, |
89 | offsetof(struct config, blanker_idle_seconds), |
90 | 0, USHRT_MAX }, |
91 | { "Screen Blanker Runtime Seconds", CONFIG_TYPE_SHORT, |
92 | offsetof(struct config, blanker_runtime_seconds), |
93 | 1, USHRT_MAX }, |
94 | |
95 | { "Prune Session Logs After N Days", CONFIG_TYPE_SHORT, |
96 | offsetof(struct config, session_log_prune_days), |
97 | 0, USHRT_MAX }, |
98 | { "Prune Mail After N Days", CONFIG_TYPE_SHORT, |
99 | offsetof(struct config, mail_prune_days), |
100 | 0, USHRT_MAX }, |
101 | |
102 | { "FTN Network Name", CONFIG_TYPE_STRING, |
103 | offsetof(struct config, ftn_network), |
104 | 0, member_size(struct config, ftn_network) }, |
105 | { "FTN Local Node Address", CONFIG_TYPE_STRING, |
106 | offsetof(struct config, ftn_node_addr), |
107 | 0, member_size(struct config, ftn_node_addr) }, |
108 | { "FTN Hub Node Address", CONFIG_TYPE_STRING, |
109 | offsetof(struct config, ftn_hub_addr), |
110 | 0, member_size(struct config, ftn_hub_addr) }, |
111 | { "FTN Hub Packet Password", CONFIG_TYPE_PASSWORD, |
112 | offsetof(struct config, ftn_hub_pkt_password), |
113 | 0, member_size(struct config, ftn_hub_pkt_password) }, |
114 | { "FTN Hub Binkp Hostname", CONFIG_TYPE_STRING, |
115 | offsetof(struct config, binkp_hostname), |
116 | 0, member_size(struct config, binkp_hostname) }, |
117 | { "FTN Hub Binkp Port", CONFIG_TYPE_SHORT, |
118 | offsetof(struct config, binkp_port), |
119 | 0, 65535 }, |
120 | { "FTN Hub Binkp Password", CONFIG_TYPE_PASSWORD, |
121 | offsetof(struct config, binkp_password), |
122 | 0, member_size(struct config, binkp_password) }, |
123 | { "FTN Hub Binkp Poll Seconds", CONFIG_TYPE_LONG, |
124 | offsetof(struct config, binkp_interval_seconds), |
125 | 1, LONG_MAX }, |
126 | { "FTN Delete Packets After Processing", CONFIG_TYPE_BOOLEAN, |
127 | offsetof(struct config, binkp_delete_done), |
128 | 0, 0 }, |
129 | { "FTN Max Tossed Message Size", CONFIG_TYPE_LONG, |
130 | offsetof(struct config, ftn_max_tossed_message_size), |
131 | 0, LONG_MAX }, |
132 | }; |
133 | size_t nconfig_fields = nitems(config_fields); |
134 | |
135 | struct db * db_init(Str255 file, short vrefnum, struct bile *bile); |
136 | short db_migrate(struct db *tdb, short is_new, Str255 basepath); |
137 | void db_config_load(struct db *tdb); |
138 | |
139 | struct db * |
140 | db_open(Str255 file, short vrefnum) |
141 | { |
142 | Str255 filepath; |
143 | struct stat sb; |
144 | Point pt = { 75, 100 }; |
145 | SFReply reply = { 0 }; |
146 | SFTypeList types; |
147 | StringHandle lastfileh; |
148 | struct db *ret; |
149 | |
150 | if (file[0]) { |
151 | getpath(vrefnum, file, filepath, true); |
152 | if (FStat(filepath, &sb) == 0) |
153 | return db_init(file, vrefnum, NULL); |
154 | |
155 | warn("Failed to open %s", PtoCstr(file)); |
156 | } |
157 | |
158 | if ((lastfileh = GetString(STR_LAST_DB))) { |
159 | HLock(lastfileh); |
160 | memcpy(filepath, *lastfileh, sizeof(filepath)); |
161 | HUnlock(lastfileh); |
162 | ReleaseResource(lastfileh); |
163 | if (FStat(filepath, &sb) == 0) { |
164 | ret = db_init(filepath, 0, NULL); |
165 | return ret; |
166 | } |
167 | } |
168 | |
169 | /* no file passed, no last file, prompt */ |
170 | types[0] = DB_TYPE; |
171 | SFGetFile(pt, NULL, NULL, 1, &types, NULL, &reply); |
172 | if (reply.good) { |
173 | ret = db_init(reply.fName, reply.vRefNum, NULL); |
174 | return ret; |
175 | } |
176 | |
177 | return db_create(); |
178 | } |
179 | |
180 | struct db * |
181 | db_create(void) |
182 | { |
183 | Point pt = { 75, 100 }; |
184 | SFReply reply; |
185 | struct bile *bile; |
186 | short error = 0; |
187 | |
188 | SFPutFile(pt, "\pCreate new Subtext DB:", "\p", NULL, &reply); |
189 | if (!reply.good) |
190 | return NULL; |
191 | |
192 | bile = bile_create(reply.fName, reply.vRefNum, SUBTEXT_CREATOR, |
193 | DB_TYPE); |
194 | if (bile == NULL && bile_error(NULL) == dupFNErr) { |
195 | error = FSDelete(reply.fName, reply.vRefNum); |
196 | if (error) |
197 | panic("Failed to re-create file %s: %d", PtoCstr(reply.fName), |
198 | error); |
199 | bile = bile_create(reply.fName, reply.vRefNum, SUBTEXT_CREATOR, |
200 | DB_TYPE); |
201 | } |
202 | |
203 | if (bile == NULL) |
204 | panic("Failed to create file %s: %d", PtoCstr(reply.fName), error); |
205 | |
206 | return db_init(reply.fName, reply.vRefNum, bile); |
207 | } |
208 | |
209 | struct db * |
210 | db_init(Str255 path, short vrefnum, struct bile *bile) |
211 | { |
212 | Str255 fullpath, newfullpath; |
213 | struct db *tdb; |
214 | Handle lastfileh; |
215 | short was_new; |
216 | |
217 | was_new = (bile != NULL); |
218 | |
219 | if (bile == NULL) { |
220 | bile = bile_open(path, vrefnum); |
221 | if (bile == NULL) { |
222 | if (ask("Attempt recovery with backup map?")) { |
223 | bile = bile_open_recover_map(path, vrefnum); |
224 | if (bile == NULL) |
225 | panic("Failed to recover DB file %s: %d", |
226 | PtoCstr(path), bile_error(NULL)); |
227 | } else { |
228 | panic("Failed to open DB file %s: %d", PtoCstr(path), |
229 | bile_error(NULL)); |
230 | } |
231 | } |
232 | } |
233 | |
234 | /* we got this far, store it as the last-accessed file */ |
235 | if (vrefnum == 0) |
236 | memcpy(&fullpath, path, sizeof(fullpath)); |
237 | else |
238 | getpath(vrefnum, path, fullpath, true); |
239 | lastfileh = Get1Resource('STR ', STR_LAST_DB); |
240 | if (lastfileh) |
241 | xSetHandleSize(lastfileh, fullpath[0] + 1); |
242 | else |
243 | lastfileh = xNewHandle(fullpath[0] + 1); |
244 | HLock(lastfileh); |
245 | memcpy(*lastfileh, fullpath, fullpath[0] + 1); |
246 | HUnlock(lastfileh); |
247 | if (HomeResFile(lastfileh) == -1) |
248 | AddResource(lastfileh, 'STR ', STR_LAST_DB, "\pSTR_LAST_DB"); |
249 | else |
250 | ChangedResource(lastfileh); |
251 | WriteResource(lastfileh); |
252 | DetachResource(lastfileh); |
253 | |
254 | tdb = xmalloczero(sizeof(struct db)); |
255 | if (tdb == NULL) |
256 | panic("Can't create db"); |
257 | tdb->bile = bile; |
258 | |
259 | if (db_migrate(tdb, was_new, fullpath) != 0) { |
260 | bile_close(tdb->bile); |
261 | xfree(&tdb); |
262 | return NULL; |
263 | } |
264 | |
265 | if (!was_new) |
266 | db_config_load(tdb); |
267 | |
268 | memcpy(newfullpath, fullpath, sizeof(newfullpath)); |
269 | PtoCstr(newfullpath); |
270 | strlcat((char *)&newfullpath, "-sessions", sizeof(newfullpath)); |
271 | CtoPstr(newfullpath); |
272 | |
273 | tdb->sessions_bile = bile_open(newfullpath, vrefnum); |
274 | if (tdb->sessions_bile == NULL) { |
275 | tdb->sessions_bile = bile_create(newfullpath, vrefnum, |
276 | SUBTEXT_CREATOR, SL_TYPE); |
277 | if (tdb->sessions_bile == NULL) |
278 | panic("Couldn't create %s: %d", PtoCstr(newfullpath), |
279 | bile_error(NULL)); |
280 | } |
281 | |
282 | memcpy(newfullpath, fullpath, sizeof(newfullpath)); |
283 | PtoCstr(newfullpath); |
284 | strlcat((char *)&newfullpath, "-mail", sizeof(newfullpath)); |
285 | CtoPstr(newfullpath); |
286 | |
287 | tdb->mail_bile = bile_open(newfullpath, vrefnum); |
288 | if (tdb->mail_bile == NULL) { |
289 | tdb->mail_bile = bile_create(newfullpath, vrefnum, |
290 | SUBTEXT_CREATOR, MAIL_SPOOL_TYPE); |
291 | if (tdb->mail_bile == NULL) |
292 | panic("Couldn't create %s: %d", PtoCstr(newfullpath), |
293 | bile_error(NULL)); |
294 | } |
295 | |
296 | return tdb; |
297 | } |
298 | |
299 | void |
300 | db_close(struct db *tdb) |
301 | { |
302 | short n; |
303 | |
304 | bile_close(tdb->bile); |
305 | xfree(&tdb->bile); |
306 | |
307 | if (tdb->sessions_bile != NULL) { |
308 | bile_close(tdb->sessions_bile); |
309 | xfree(&tdb->sessions_bile); |
310 | } |
311 | |
312 | if (tdb->mail_bile != NULL) { |
313 | bile_close(tdb->mail_bile); |
314 | xfree(&tdb->mail_bile); |
315 | } |
316 | |
317 | if (tdb->boards) { |
318 | for (n = 0; n < tdb->nboards; n++) { |
319 | if (tdb->boards[n].bile) |
320 | bile_close(tdb->boards[n].bile); |
321 | } |
322 | xfree(&tdb->boards); |
323 | } |
324 | |
325 | if (tdb->folders) { |
326 | for (n = 0; n < tdb->nfolders; n++) { |
327 | if (tdb->folders[n].bile) |
328 | bile_close(tdb->folders[n].bile); |
329 | } |
330 | xfree(&tdb->folders); |
331 | } |
332 | |
333 | xfree(&tdb); |
334 | } |
335 | |
336 | short |
337 | db_migrate(struct db *tdb, short is_new, Str255 fullpath) |
338 | { |
339 | struct user *suser; |
340 | struct db *olddb; |
341 | char ver; |
342 | |
343 | if (is_new) { |
344 | ver = DB_CUR_VERS; |
345 | |
346 | /* setup some defaults */ |
347 | sprintf(tdb->config.name, "Example Subtext BBS"); |
348 | sprintf(tdb->config.phone_number, "(555) 867-5309"); |
349 | sprintf(tdb->config.location, "Springfield"); |
350 | sprintf(tdb->config.hostname, "bbs.example.com"); |
351 | sprintf(tdb->config.timezone, "CT"); |
352 | tdb->config.modem_speed = 9600; |
353 | sprintf(tdb->config.modem_init, "ATQ0V1S0=2"); |
354 | sprintf(tdb->config.modem_parity, "8N1"); |
355 | tdb->config.max_idle_minutes = 5; |
356 | tdb->config.max_sysop_idle_minutes = 60; |
357 | tdb->config.max_login_seconds = 90; |
358 | tdb->config.blanker_idle_seconds = (60 * 60); |
359 | tdb->config.blanker_runtime_seconds = 30; |
360 | tdb->config.session_log_prune_days = 21; |
361 | tdb->config.binkp_port = 24554; |
362 | tdb->config.binkp_interval_seconds = (60 * 60 * 3); |
363 | tdb->config.mail_prune_days = 180; |
364 | tdb->config.ftn_max_tossed_message_size = 16 * 1024; |
365 | |
366 | db_config_save(tdb); |
367 | |
368 | /* create a default sysop user */ |
369 | suser = xmalloczero(sizeof(struct user)); |
370 | if (suser == NULL) |
371 | panic("Can't allocate new user"); |
372 | strncpy(suser->username, "sysop", sizeof(suser->username)); |
373 | suser->created_at = Time; |
374 | suser->is_enabled = DB_TRUE; |
375 | user_set_password(suser, "p4ssw0rd"); |
376 | suser->is_sysop = DB_TRUE; |
377 | /* user_save assumes db is already set */ |
378 | olddb = db; |
379 | db = tdb; |
380 | user_save(suser); |
381 | xfree(&suser); |
382 | user_cache_usernames(); |
383 | db = olddb; |
384 | } else { |
385 | if (bile_read(tdb->bile, DB_VERS_RTYPE, 1, &ver, 1) != 1) |
386 | ver = 1; |
387 | |
388 | if (ver == DB_CUR_VERS) |
389 | return 0; |
390 | |
391 | if (ask("Migrate this database from version %d to %d to open it?", |
392 | ver, DB_CUR_VERS) != ASK_YES) |
393 | return -1; |
394 | } |
395 | |
396 | /* per-version migrations */ |
397 | while (ver < DB_CUR_VERS) { |
398 | progress("Migrating from version %d to %d...", (short)ver, |
399 | (short)(ver + 1)); |
400 | |
401 | switch (ver) { |
402 | case 1: { |
403 | /* 1->2, added user is_enabled field */ |
404 | struct user user; |
405 | size_t nusers, n; |
406 | unsigned long *ids = NULL; |
407 | |
408 | nusers = bile_sorted_ids_by_type(tdb->bile, DB_USER_RTYPE, |
409 | &ids); |
410 | for (n = 0; n < nusers; n++) { |
411 | memset(&user, 0, sizeof(user)); |
412 | bile_read(tdb->bile, DB_USER_RTYPE, ids[n], (char *)&user, |
413 | sizeof(user)); |
414 | user.is_enabled = DB_TRUE; |
415 | bile_write(tdb->bile, DB_USER_RTYPE, ids[n], (char *)&user, |
416 | sizeof(user)); |
417 | } |
418 | if (ids) |
419 | xfree(&ids); |
420 | break; |
421 | } |
422 | case 2: { |
423 | /* 2->3, added config open_signup field */ |
424 | struct config new_config = { 0 }; |
425 | |
426 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
427 | sizeof(new_config)); |
428 | |
429 | new_config.open_signup = 0; |
430 | |
431 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
432 | sizeof(new_config)); |
433 | break; |
434 | } |
435 | case 3: { |
436 | /* 3->4, added max_login_seconds */ |
437 | struct config new_config = { 0 }; |
438 | |
439 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
440 | sizeof(new_config)); |
441 | |
442 | new_config.max_login_seconds = 90; |
443 | |
444 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
445 | sizeof(new_config)); |
446 | break; |
447 | } |
448 | case 4: { |
449 | /* 4->5, added blanker fields */ |
450 | struct config new_config = { 0 }; |
451 | |
452 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
453 | sizeof(new_config)); |
454 | |
455 | new_config.blanker_idle_seconds = (60 * 60); |
456 | new_config.blanker_runtime_seconds = 30; |
457 | |
458 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
459 | sizeof(new_config)); |
460 | break; |
461 | } |
462 | case 5: { |
463 | /* 5->6, added trusted_proxy_ip */ |
464 | struct config new_config = { 0 }; |
465 | |
466 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
467 | sizeof(new_config)); |
468 | |
469 | new_config.trusted_proxy_ip = 0; |
470 | |
471 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
472 | sizeof(new_config)); |
473 | break; |
474 | } |
475 | case 6: { |
476 | /* 6->7, added modem_speed */ |
477 | struct config new_config = { 0 }; |
478 | |
479 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
480 | sizeof(new_config)); |
481 | |
482 | new_config.modem_speed = 19200; |
483 | |
484 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
485 | sizeof(new_config)); |
486 | break; |
487 | } |
488 | case 7: { |
489 | /* 7->8, added trusted_proxy_udp_port */ |
490 | struct config new_config = { 0 }; |
491 | |
492 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
493 | sizeof(new_config)); |
494 | |
495 | new_config.trusted_proxy_udp_port = 0; |
496 | |
497 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
498 | sizeof(new_config)); |
499 | break; |
500 | } |
501 | case 8: { |
502 | /* 8->9, added user.last_motd_seen */ |
503 | struct user user; |
504 | size_t nusers, n; |
505 | unsigned long *ids = NULL; |
506 | |
507 | nusers = bile_sorted_ids_by_type(tdb->bile, DB_USER_RTYPE, |
508 | &ids); |
509 | for (n = 0; n < nusers; n++) { |
510 | memset(&user, 0, sizeof(user)); |
511 | bile_read(tdb->bile, DB_USER_RTYPE, ids[n], (char *)&user, |
512 | sizeof(user)); |
513 | user.last_motd_seen = 0; |
514 | bile_write(tdb->bile, DB_USER_RTYPE, ids[n], (char *)&user, |
515 | sizeof(user)); |
516 | } |
517 | if (ids) |
518 | xfree(&ids); |
519 | break; |
520 | } |
521 | case 9: { |
522 | /* 9->10, added board post via */ |
523 | size_t nposts, n, j, size; |
524 | unsigned long *ids = NULL; |
525 | struct board *board; |
526 | char *data; |
527 | struct board_post post; |
528 | short ret; |
529 | |
530 | db_cache_boards(tdb); |
531 | |
532 | for (n = 0; n < tdb->nboards; n++) { |
533 | board = &tdb->boards[n]; |
534 | nposts = bile_sorted_ids_by_type(board->bile, |
535 | BOARD_POST_RTYPE, &ids); |
536 | for (j = 0; j < nposts; j++) { |
537 | size = bile_read_alloc(board->bile, BOARD_POST_RTYPE, |
538 | ids[j], &data); |
539 | if (size == 0) |
540 | panic("failed fetching message %ld: %d", ids[j], |
541 | bile_error(board->bile)); |
542 | |
543 | /* pretend we read this much, just fill via with junk */ |
544 | size += member_size(struct board_post, via); |
545 | |
546 | bile_unmarshall_object(board->bile, |
547 | board_post_object_fields, nboard_post_object_fields, |
548 | data, size, &post, sizeof(post), true); |
549 | xfree(&data); |
550 | |
551 | /* zero out junk */ |
552 | memset(&post.via, 0, sizeof(post.via)); |
553 | |
554 | ret = bile_marshall_object(board->bile, |
555 | board_post_object_fields, nboard_post_object_fields, |
556 | &post, &data, &size); |
557 | if (ret != 0 || size == 0) |
558 | panic("failed to marshall new post object"); |
559 | if (bile_write(board->bile, BOARD_POST_RTYPE, |
560 | post.id, data, size) != size) |
561 | panic("bile_write of post failed! %d", |
562 | bile_error(board->bile)); |
563 | |
564 | xfree(&data); |
565 | } |
566 | if (ids != NULL) |
567 | xfree(&ids); |
568 | } |
569 | break; |
570 | } |
571 | case 10: { |
572 | /* 10->11, added session_log_prune_days */ |
573 | struct config new_config = { 0 }; |
574 | |
575 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
576 | sizeof(new_config)); |
577 | |
578 | new_config.session_log_prune_days = 21; |
579 | |
580 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
581 | sizeof(new_config)); |
582 | break; |
583 | } |
584 | case 11: { |
585 | /* 11->12, added modem_parity */ |
586 | struct config new_config = { 0 }; |
587 | |
588 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
589 | sizeof(new_config)); |
590 | |
591 | sprintf(new_config.modem_parity, "8N1"); |
592 | |
593 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
594 | sizeof(new_config)); |
595 | break; |
596 | } |
597 | case 12: { |
598 | /* 12->13, added ftn/binkp fields */ |
599 | struct config new_config = { 0 }; |
600 | |
601 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
602 | sizeof(new_config)); |
603 | |
604 | new_config.ftn_network[0] = '\0'; |
605 | new_config.ftn_node_addr[0] = '\0'; |
606 | new_config.ftn_hub_addr[0] = '\0'; |
607 | new_config.ftn_hub_pkt_password[0] = '\0'; |
608 | new_config.binkp_hostname[0] = '\0'; |
609 | new_config.binkp_port = 24554; |
610 | new_config.binkp_password[0] = '\0'; |
611 | new_config.binkp_interval_seconds = (60 * 60 * 3); |
612 | new_config.binkp_delete_done = 0; |
613 | |
614 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
615 | sizeof(new_config)); |
616 | break; |
617 | } |
618 | case 13: { |
619 | /* 13->14, move mail to separate db, add ftn fields */ |
620 | Str255 newfullpath; |
621 | struct bile *mail_bile; |
622 | size_t nids, n, size; |
623 | unsigned long *ids; |
624 | char *data; |
625 | |
626 | memcpy(&newfullpath, fullpath, sizeof(newfullpath)); |
627 | PtoCstr(newfullpath); |
628 | strlcat((char *)&newfullpath, "-mail", sizeof(newfullpath)); |
629 | CtoPstr(newfullpath); |
630 | |
631 | mail_bile = bile_create(newfullpath, tdb->bile->vrefnum, |
632 | SUBTEXT_CREATOR, MAIL_SPOOL_TYPE); |
633 | if (mail_bile == NULL) |
634 | panic("Couldn't create %s: %d", PtoCstr(newfullpath), |
635 | bile_error(NULL)); |
636 | |
637 | nids = bile_ids_by_type(tdb->bile, MAIL_SPOOL_MESSAGE_RTYPE, |
638 | &ids); |
639 | for (n = 0; n < nids; n++) { |
640 | progress("Copying mail message %ld...", ids[n]); |
641 | size = bile_read_alloc(tdb->bile, MAIL_SPOOL_MESSAGE_RTYPE, |
642 | ids[n], &data); |
643 | bile_write(mail_bile, MAIL_SPOOL_MESSAGE_RTYPE, ids[n], |
644 | data, size); |
645 | xfree(&data); |
646 | |
647 | size += (sizeof(struct mail_message) - |
648 | offsetof(struct mail_message, ftn_msgid)); |
649 | if (bile_resize(mail_bile, MAIL_SPOOL_MESSAGE_RTYPE, |
650 | ids[n], size) != size) |
651 | panic("failed resizing message %ld", ids[n]); |
652 | } |
653 | |
654 | bile_flush(mail_bile, true); |
655 | |
656 | /* now we can actually delete mail */ |
657 | for (n = 0; n < nids; n++) { |
658 | progress("Deleting old mail message %ld...", ids[n]); |
659 | bile_delete(tdb->bile, MAIL_SPOOL_MESSAGE_RTYPE, ids[n], 0); |
660 | } |
661 | bile_write_map(tdb->bile); |
662 | bile_close(mail_bile); |
663 | |
664 | break; |
665 | } |
666 | case 14: { |
667 | /* 14->15, boards get an ftn field */ |
668 | struct bile_object *o; |
669 | size_t nids, grow, size, n; |
670 | unsigned long *ids; |
671 | |
672 | grow = member_size(struct board, ftn_area); |
673 | |
674 | nids = bile_ids_by_type(tdb->bile, DB_BOARD_RTYPE, &ids); |
675 | for (n = 0; n < nids; n++) { |
676 | o = bile_find(tdb->bile, DB_BOARD_RTYPE, ids[n]); |
677 | progress("Growing board %ld/%ld...", n + 1, nids); |
678 | size = o->size + grow; |
679 | if (bile_resize(tdb->bile, o->type, o->id, size) != size) |
680 | panic("failed resizing board %ld", o->id); |
681 | xfree(&o); |
682 | } |
683 | xfree(&ids); |
684 | |
685 | db_cache_boards(tdb); |
686 | |
687 | break; |
688 | } |
689 | case 15: { |
690 | /* 15->16, timezone UTC offset */ |
691 | struct config new_config = { 0 }; |
692 | |
693 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
694 | sizeof(new_config)); |
695 | |
696 | new_config.timezone_utcoff = 0; |
697 | |
698 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
699 | sizeof(new_config)); |
700 | break; |
701 | } |
702 | case 16: { |
703 | /* 16->17, mail_prune_days */ |
704 | struct config new_config = { 0 }; |
705 | |
706 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
707 | sizeof(new_config)); |
708 | |
709 | new_config.mail_prune_days = 180; |
710 | |
711 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
712 | sizeof(new_config)); |
713 | break; |
714 | } |
715 | case 17: { |
716 | /* 17->18, more config fields */ |
717 | struct config new_config = { 0 }; |
718 | |
719 | bile_read(tdb->bile, DB_CONFIG_RTYPE, 1, (char *)&new_config, |
720 | sizeof(new_config)); |
721 | |
722 | new_config.ftn_max_tossed_message_size = 16 * 1024; |
723 | new_config.max_sysop_idle_minutes = 60; |
724 | |
725 | bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &new_config, |
726 | sizeof(new_config)); |
727 | break; |
728 | } |
729 | } |
730 | |
731 | ver++; |
732 | } |
733 | |
734 | progress(NULL); |
735 | |
736 | /* store new version */ |
737 | ver = DB_CUR_VERS; |
738 | if (bile_write(tdb->bile, DB_VERS_RTYPE, 1, &ver, 1) != 1) |
739 | panic("Failed writing new version: %d", bile_error(tdb->bile)); |
740 | |
741 | return 0; |
742 | } |
743 | |
744 | void |
745 | db_config_save(struct db *tdb) |
746 | { |
747 | if (bile_write(tdb->bile, DB_CONFIG_RTYPE, 1, &tdb->config, |
748 | sizeof(tdb->config)) != sizeof(tdb->config)) |
749 | panic("db_config_save: failed to write: %d", |
750 | bile_error(tdb->bile)); |
751 | } |
752 | |
753 | void |
754 | db_config_load(struct db *tdb) |
755 | { |
756 | size_t rlen; |
757 | char *newconfig; |
758 | |
759 | rlen = bile_read_alloc(tdb->bile, DB_CONFIG_RTYPE, 1, &newconfig); |
760 | if (rlen == 0 || bile_error(tdb->bile)) |
761 | panic("db_config_load: error reading config: %d", |
762 | bile_error(tdb->bile)); |
763 | if (rlen != sizeof(tdb->config)) |
764 | warn("db_config_load: read config of size %lu, but expected %lu", |
765 | rlen, sizeof(tdb->config)); |
766 | |
767 | memcpy(&tdb->config, newconfig, sizeof(tdb->config)); |
768 | xfree(&newconfig); |
769 | } |
770 | |
771 | void |
772 | db_cache_boards(struct db *tdb) |
773 | { |
774 | Str255 db_filename, board_filename; |
775 | struct board_id_time_map first_map; |
776 | size_t n, size; |
777 | unsigned long *ids; |
778 | struct bile_object *obj; |
779 | char *data = NULL; |
780 | |
781 | if (tdb->boards) { |
782 | for (n = 0; n < tdb->nboards; n++) { |
783 | if (tdb->boards[n].bile) |
784 | bile_close(tdb->boards[n].bile); |
785 | } |
786 | xfree(&tdb->boards); |
787 | } |
788 | |
789 | if (getpath(tdb->bile->vrefnum, tdb->bile->filename, db_filename, |
790 | false) != 0) |
791 | panic("getpath failed on %s", PtoCstr(tdb->bile->filename)); |
792 | PtoCstr(db_filename); |
793 | |
794 | tdb->nboards = bile_sorted_ids_by_type(tdb->bile, DB_BOARD_RTYPE, |
795 | &ids); |
796 | if (!tdb->nboards) |
797 | return; |
798 | tdb->boards = xcalloc(tdb->nboards, sizeof(struct board)); |
799 | if (tdb->boards == NULL) { |
800 | tdb->nboards = 0; |
801 | return; |
802 | } |
803 | |
804 | /* |
805 | * Read ids first so we have a consistent order, then try to open or |
806 | * fix/recreate each bile, which may change their order in the map |
807 | */ |
808 | for (n = 0; n < tdb->nboards; n++) { |
809 | obj = bile_find(tdb->bile, DB_BOARD_RTYPE, ids[n]); |
810 | if (obj == NULL) |
811 | break; |
812 | |
813 | size = bile_read_alloc(tdb->bile, DB_BOARD_RTYPE, obj->id, |
814 | &data); |
815 | bile_unmarshall_object(tdb->bile, board_object_fields, |
816 | nboard_object_fields, data, size, (char *)(&tdb->boards[n]), |
817 | sizeof(struct board), true); |
818 | xfree(&data); |
819 | } |
820 | |
821 | xfree(&ids); |
822 | |
823 | for (n = 0; n < tdb->nboards; n++) { |
824 | snprintf((char *)board_filename, sizeof(board_filename), |
825 | "%s:%lu.%s", db_filename, tdb->boards[n].id, BOARD_FILENAME_EXT); |
826 | CtoPstr(board_filename); |
827 | tdb->boards[n].bile = bile_open(board_filename, |
828 | tdb->bile->vrefnum); |
829 | if (tdb->boards[n].bile != NULL) |
830 | goto opened; |
831 | |
832 | PtoCstr(board_filename); |
833 | |
834 | if (bile_error(NULL) != fnfErr && |
835 | ask("Attempt recovery of %s with backup map?", board_filename)) { |
836 | CtoPstr(board_filename); |
837 | tdb->boards[n].bile = bile_open_recover_map(board_filename, |
838 | tdb->bile->vrefnum); |
839 | if (tdb->boards[n].bile != NULL) |
840 | continue; |
841 | warn("Failed to recover board file %s: %d", |
842 | PtoCstr(board_filename), bile_error(NULL)); |
843 | } |
844 | |
845 | if (ask("Failed to open board bile %s (error %d), recreate it?", |
846 | board_filename, bile_error(NULL)) == false) |
847 | panic("Can't open board file, exiting"); |
848 | |
849 | tdb->boards[n].bile = db_board_create(tdb, &tdb->boards[n], true); |
850 | if (tdb->boards[n].bile == NULL) |
851 | panic("Failed to create board file %s: %d", |
852 | board_filename, bile_error(NULL)); |
853 | |
854 | opened: |
855 | size = bile_read(tdb->boards[n].bile, |
856 | BOARD_SORTED_ID_MAP_RTYPE, 1, &first_map, sizeof(first_map)); |
857 | if (size != sizeof(first_map)) { |
858 | logger_printf("[db] Reindexing ids on board %s", |
859 | tdb->boards[n].name); |
860 | board_index_sorted_post_ids(&tdb->boards[n], NULL); |
861 | size = bile_read(tdb->boards[n].bile, |
862 | BOARD_SORTED_ID_MAP_RTYPE, 1, &first_map, sizeof(first_map)); |
863 | if (size != sizeof(first_map)) { |
864 | tdb->boards[n].last_post_at = 0; |
865 | continue; |
866 | } |
867 | } |
868 | tdb->boards[n].last_post_at = first_map.time; |
869 | } |
870 | } |
871 | |
872 | struct bile * |
873 | db_board_create(struct db *tdb, struct board *board, bool delete_first) |
874 | { |
875 | Str255 db_filename, board_filename; |
876 | struct bile *board_bile; |
877 | size_t size; |
878 | short ret; |
879 | char *data; |
880 | |
881 | ret = bile_marshall_object(tdb->bile, board_object_fields, |
882 | nboard_object_fields, board, &data, &size); |
883 | if (ret != 0 || size == 0) { |
884 | warn("db_board_create: failed to marshall object"); |
885 | return NULL; |
886 | } |
887 | |
888 | if (bile_write(tdb->bile, DB_BOARD_RTYPE, board->id, data, |
889 | size) != size) |
890 | panic("save of new board failed: %d", bile_error(tdb->bile)); |
891 | xfree(&data); |
892 | |
893 | if (getpath(tdb->bile->vrefnum, tdb->bile->filename, db_filename, |
894 | false) != 0) |
895 | panic("getpath failed on %s", PtoCstr(tdb->bile->filename)); |
896 | PtoCstr(db_filename); |
897 | |
898 | snprintf((char *)&board_filename, sizeof(board_filename), "%s:%lu.%s", |
899 | db_filename, board->id, BOARD_FILENAME_EXT); |
900 | CtoPstr(board_filename); |
901 | |
902 | if (delete_first) |
903 | FSDelete(board_filename, tdb->bile->vrefnum); |
904 | |
905 | board_bile = bile_create(board_filename, tdb->bile->vrefnum, |
906 | SUBTEXT_CREATOR, DB_BOARD_RTYPE); |
907 | if (board_bile == NULL) |
908 | panic("Failed creating new board bile at %s: %d", |
909 | PtoCstr(board_filename), bile_error(NULL)); |
910 | |
911 | return board_bile; |
912 | } |
913 | |
914 | void |
915 | db_board_delete(struct db *tdb, struct board *board) |
916 | { |
917 | if (bile_delete(tdb->bile, DB_BOARD_RTYPE, board->id, 0) != 0) { |
918 | warn("deletion of board %ld failed: %d", board->id, |
919 | bile_error(tdb->bile)); |
920 | return; |
921 | } |
922 | bile_close(board->bile); |
923 | } |
924 | |
925 | void |
926 | db_cache_folders(struct db *tdb) |
927 | { |
928 | Str255 db_filename, folder_filename, folder_dir; |
929 | size_t n, size; |
930 | unsigned long *ids, id; |
931 | struct bile_object *obj; |
932 | char *data = NULL; |
933 | short error; |
934 | |
935 | if (tdb->folders) { |
936 | for (n = 0; n < tdb->nfolders; n++) { |
937 | if (tdb->folders[n].bile) |
938 | bile_close(tdb->folders[n].bile); |
939 | } |
940 | xfree(&tdb->folders); |
941 | } |
942 | |
943 | if (getpath(tdb->bile->vrefnum, tdb->bile->filename, db_filename, |
944 | false) != 0) |
945 | panic("getpath failed on %s", PtoCstr(tdb->bile->filename)); |
946 | PtoCstr(db_filename); |
947 | |
948 | tdb->nfolders = bile_sorted_ids_by_type(tdb->bile, DB_FOLDER_RTYPE, |
949 | &ids); |
950 | if (!tdb->nfolders) |
951 | return; |
952 | tdb->folders = xcalloc(tdb->nfolders, sizeof(struct folder)); |
953 | if (tdb->folders == NULL) { |
954 | tdb->nfolders = 0; |
955 | return; |
956 | } |
957 | |
958 | /* |
959 | * Read ids first so we have a consistent order, then try to open or |
960 | * fix/recreate each bile, which may change their order in the map |
961 | */ |
962 | for (n = 0; n < tdb->nfolders; n++) { |
963 | obj = bile_find(tdb->bile, DB_FOLDER_RTYPE, ids[n]); |
964 | if (obj == NULL) |
965 | break; |
966 | |
967 | size = bile_read_alloc(tdb->bile, DB_FOLDER_RTYPE, obj->id, &data); |
968 | if (size == 0 || data == NULL) |
969 | break; |
970 | bile_unmarshall_object(tdb->bile, folder_object_fields, |
971 | nfolder_object_fields, data, size, (char *)(&tdb->folders[n]), |
972 | sizeof(struct folder), true); |
973 | xfree(&data); |
974 | } |
975 | |
976 | xfree(&ids); |
977 | |
978 | for (n = 0; n < tdb->nfolders; n++) { |
979 | snprintf((char *)folder_filename, sizeof(folder_filename), |
980 | "%s:%lu.%s", db_filename, tdb->folders[n].id, |
981 | FOLDER_FILENAME_EXT); |
982 | CtoPstr(folder_filename); |
983 | tdb->folders[n].bile = bile_open(folder_filename, |
984 | tdb->bile->vrefnum); |
985 | if (tdb->folders[n].bile != NULL) |
986 | continue; |
987 | |
988 | PtoCstr(folder_filename); |
989 | |
990 | if (bile_error(NULL) != fnfErr && |
991 | ask("Attempt recovery of %s with backup map?", folder_filename)) { |
992 | CtoPstr(folder_filename); |
993 | tdb->folders[n].bile = bile_open_recover_map(folder_filename, |
994 | tdb->bile->vrefnum); |
995 | if (tdb->folders[n].bile != NULL) |
996 | continue; |
997 | warn("Failed to recover folder bile %s: %d", |
998 | PtoCstr(folder_filename), bile_error(NULL)); |
999 | } |
1000 | |
1001 | if (ask("Failed to open folder bile %s (error %d), recreate it?", |
1002 | folder_filename, bile_error(NULL)) == false) |
1003 | exit(0); |
1004 | |
1005 | tdb->folders[n].bile = db_folder_create(tdb, &tdb->folders[n], |
1006 | true); |
1007 | if (tdb->folders[n].bile == NULL) |
1008 | panic("Failed to create folder bile %s: %d", |
1009 | folder_filename, bile_error(NULL)); |
1010 | } |
1011 | |
1012 | for (n = 0; n < tdb->nfolders; n++) { |
1013 | /* make sure directory exists */ |
1014 | memcpy(folder_dir, tdb->folders[n].path, sizeof(folder_dir)); |
1015 | CtoPstr(folder_dir); |
1016 | if (!FIsDir(folder_dir) && |
1017 | ask("Folder %ld path \"%s\" does not exist, create it?", |
1018 | tdb->folders[n].id, tdb->folders[n].path)) { |
1019 | error = DirCreate(db->bile->vrefnum, 0, folder_dir, &id); |
1020 | if (error) |
1021 | warn("Failed creating %s: %d", tdb->folders[n].path, |
1022 | error); |
1023 | } |
1024 | } |
1025 | } |
1026 | |
1027 | struct bile * |
1028 | db_folder_create(struct db *tdb, struct folder *folder, bool delete_first) |
1029 | { |
1030 | Str255 db_filename, folder_filename, folder_dir; |
1031 | struct bile *folder_bile; |
1032 | size_t size; |
1033 | short ret, newid, error; |
1034 | char *data; |
1035 | |
1036 | ret = bile_marshall_object(tdb->bile, folder_object_fields, |
1037 | nfolder_object_fields, folder, &data, &size); |
1038 | if (ret != 0 || size == 0) { |
1039 | warn("db_folder_create: failed to marshall object"); |
1040 | return NULL; |
1041 | } |
1042 | |
1043 | if (bile_write(tdb->bile, DB_FOLDER_RTYPE, folder->id, data, |
1044 | size) != size) |
1045 | panic("save of new folder failed: %d", bile_error(tdb->bile)); |
1046 | xfree(&data); |
1047 | |
1048 | if (getpath(tdb->bile->vrefnum, tdb->bile->filename, db_filename, |
1049 | false) != 0) |
1050 | panic("getpath failed on %s", PtoCstr(tdb->bile->filename)); |
1051 | PtoCstr(db_filename); |
1052 | |
1053 | snprintf((char *)&folder_filename, sizeof(folder_filename), |
1054 | "%s:%lu.%s", db_filename, folder->id, FOLDER_FILENAME_EXT); |
1055 | CtoPstr(folder_filename); |
1056 | |
1057 | if (delete_first) |
1058 | FSDelete(folder_filename, tdb->bile->vrefnum); |
1059 | |
1060 | folder_bile = bile_create(folder_filename, tdb->bile->vrefnum, |
1061 | SUBTEXT_CREATOR, DB_FOLDER_RTYPE); |
1062 | if (folder_bile == NULL) |
1063 | panic("Failed creating new folder bile at %s: %d", |
1064 | PtoCstr(folder_filename), bile_error(NULL)); |
1065 | |
1066 | memcpy(&folder_dir, folder->path, sizeof(folder_dir)); |
1067 | CtoPstr(folder_dir); |
1068 | if (!FIsDir(folder_dir)) { |
1069 | error = DirCreate(tdb->bile->vrefnum, 0, folder_dir, &newid); |
1070 | if (error) |
1071 | warn("Failed creating %s: %d", folder->path, error); |
1072 | } |
1073 | |
1074 | return folder_bile; |
1075 | } |
1076 | |
1077 | void |
1078 | db_folder_delete(struct db *tdb, struct folder *folder) |
1079 | { |
1080 | if (bile_delete(tdb->bile, DB_FOLDER_RTYPE, folder->id, 0) != 0) { |
1081 | warn("deletion of folder %ld failed: %d", folder->id, |
1082 | bile_error(tdb->bile)); |
1083 | return; |
1084 | } |
1085 | bile_close(folder->bile); |
1086 | } |