jcs
/subtext
/amendments
/8
db+user: Initial work on creating a database, creating users
Of note, 'C###' templates are a C string in ### bytes in hex, not dec
jcs made amendment 8 over 2 years ago
--- db.c Sun Dec 5 16:07:46 2021
+++ db.c Sun Dec 5 16:07:46 2021
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "db.h"
+#include "user.h"
+#include "util.h"
+
+struct tmpl tmpls[10];
+short ntmpls;
+
+struct db *db_init(SFReply reply, short is_new);
+short db_migrate(struct db *tdb, short is_new);
+
+void
+load_db_tmpls(void)
+{
+ short n;
+
+ ntmpls = Count1Resources('TMPL');
+ if (ntmpls > nitems(tmpls))
+ err(1, "ntmpls %d > %d", ntmpls, nitems(tmpls));
+ for (n = 0; n < ntmpls; n++) {
+ tmpls[n].h = GetIndResource('TMPL', n + 1);
+ if (tmpls[n].h == NULL)
+ err(1, "Failed fetching TMPL %d", n + 1);
+ GetResInfo(tmpls[n].h, &tmpls[n].id, &tmpls[n].type,
+ &tmpls[n].name);
+ DetachResource(tmpls[n].h);
+ }
+}
+
+struct db *
+db_open(AppFile *file)
+{
+ Point pt = { 75, 100 };
+ SFReply reply;
+ SFTypeList types;
+
+ if (file) {
+ reply.vRefNum = file->vRefNum;
+ memcpy(&reply.fName, file->fName, sizeof(reply.fName));
+ } else {
+ types[0] = DB_TYPE;
+
+ SFGetFile(pt, NULL, NULL, 1, &types, NULL, &reply);
+ if (!reply.good)
+ return db_create();
+ }
+
+ return db_init(reply, 0);
+}
+
+struct db *
+db_create(void)
+{
+ Point pt = { 75, 100 };
+ SFReply reply;
+ char *newpath = NULL;
+ short error, fh, tfh, i;
+
+ SFPutFile(pt, "\pCreate new Subtext DB:", NULL, NULL, &reply);
+ if (!reply.good)
+ return NULL;
+
+ getpath(reply.vRefNum, reply.fName, &newpath, 1);
+ CtoPstr(newpath);
+
+ error = Create(reply.fName, reply.vRefNum, SUBTEXT_CREATOR, DB_TYPE);
+ if (error == dupFNErr) {
+ error = FSDelete(reply.fName, reply.vRefNum);
+ if (error)
+ err(1, "Failed to re-create file %s: %d", PtoCstr(reply.fName),
+ error);
+ error = Create(reply.fName, reply.vRefNum, SUBTEXT_CREATOR, DB_TYPE);
+ }
+ if (error)
+ err(1, "Failed to create %s: %d", PtoCstr(reply.fName), error);
+
+ CreateResFile(newpath);
+ if (ResError() != 0)
+ err(1, "Failed to create DB %s: %d", PtoCstr(newpath), ResError());
+
+ free(newpath);
+
+ return db_init(reply, 1);
+}
+
+struct db *
+db_init(SFReply reply, short is_new)
+{
+ Str255 buf;
+ struct db *tdb;
+ char *newpath;
+ Handle resh;
+ short error, fh, i;
+
+ getpath(reply.vRefNum, reply.fName, &newpath, 1);
+ CtoPstr(newpath);
+
+ fh = OpenResFile(newpath);
+ if (fh == -1)
+ err(1, "Failed to open %s: %d", PtoCstr(newpath), ResError());
+
+ tdb = xmalloczero(sizeof(struct db));
+ tdb->fh = fh;
+ tdb->vrefnum = reply.vRefNum;
+
+ memcpy(tdb->filename, reply.fName, reply.fName[0] + 1);
+ PtoCstr(tdb->filename);
+
+ if (db_migrate(tdb, is_new) != 0) {
+ free(tdb);
+ return NULL;
+ }
+
+ return tdb;
+}
+
+void
+db_close(struct db *tdb)
+{
+ CloseResFile(tdb->vrefnum);
+ free(tdb);
+}
+
+short
+db_migrate(struct db *tdb, short is_new)
+{
+ Handle verh, resh;
+ short ver, add = 0, i, nfiles, size;
+ struct user *user;
+ char *error;
+
+ if (is_new) {
+ ver = DB_CUR_VERS;
+
+ /* setup some defaults */
+ sprintf(tdb->config.name, "Example Subtext BBS");
+ sprintf(tdb->config.phone_number, "(555) 867-5309");
+ sprintf(tdb->config.location, "Springfield");
+ sprintf(tdb->config.hostname, "bbs.example.com");
+ tdb->config.telnet_port = 23;
+
+ /* create a default sysop user */
+ user = user_build(tdb, "sysop", &error);
+ if (!user)
+ err(1, "Failed creating initial sysop user: %s", error);
+ user_set_password(tdb, user, "p4ssw0rd");
+ user->is_sysop = DB_TRUE;
+ user_save(tdb, user);
+ } else {
+ verh = Get1Resource(DB_VERS_RTYPE, 1);
+ if (verh == NULL)
+ ver = 1;
+ else {
+ ver = (unsigned char)((*verh)[0]);
+ ReleaseResource(verh);
+ }
+
+ if (ver == DB_CUR_VERS)
+ return 0;
+
+ if (ask("Migrate this database from version %d to %d to open it?",
+ ver, DB_CUR_VERS) != ASK_YES)
+ return -1;
+ }
+
+ /* per-version migrations */
+
+ /* store new version */
+ verh = Get1Resource(DB_VERS_RTYPE, 1);
+ if (verh == NULL) {
+ verh = NewHandle(1);
+ add = 1;
+ }
+ (*verh)[0] = ver;
+ if (add)
+ AddResource(verh, DB_VERS_RTYPE, 1, "\p");
+ else
+ ChangedResource(verh);
+
+ /* update templates */
+ for (i = 0; i < ntmpls; i++) {
+ resh = Get1Resource(tmpls[i].type, tmpls[i].id);
+ if (resh)
+ RmveResource(resh);
+ AddResource(tmpls[i].h, tmpls[i].type, tmpls[i].id, tmpls[i].name);
+ WriteResource(tmpls[i].h);
+ DetachResource(tmpls[i].h);
+ }
+
+ UpdateResFile(tdb->fh);
+
+ return 0;
+}
--- db.h Sun Dec 5 16:07:41 2021
+++ db.h Sun Dec 5 16:07:41 2021
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __DB_H__
+#define __DB_H__
+
+#include <time.h>
+
+#include "sha2.h"
+
+#define SUBTEXT_CREATOR 'SUBT'
+
+#define DB_TYPE 'STDB'
+
+#define DB_CONFIG_RTYPE 'STCF'
+#define DB_USER_RTYPE 'STUS'
+#define DB_BOARD_RTYPE 'STBD'
+#define DB_FILEAREA_RTYPE 'STFL'
+#define DB_VERS_RTYPE 'STVR'
+
+#define DB_CUR_VERS 1
+
+#define DB_TRUE 0x100
+#define DB_FALSE 0x000
+
+struct config {
+ char name[32];
+ char phone_number[16];
+ char location[64];
+ char hostname[32];
+ short telnet_port;
+};
+
+struct user {
+ short id;
+ char username[32];
+ char password_hash[SHA256_DIGEST_STRING_LENGTH + 1]; /* 66 */
+ char password_salt[SHA256_DIGEST_STRING_LENGTH + 1];
+ unsigned long created_at;
+ unsigned long last_seen_at;
+ short is_sysop;
+};
+
+struct db {
+ char filename[256];
+ short fh;
+ short vrefnum;
+ struct config config;
+ short nusers;
+};
+
+struct tmpl {
+ Handle h;
+ ResType type;
+ Str255 name;
+ short id;
+};
+
+extern struct tmpl tmpls[10];
+extern short ntmpls;
+
+void load_db_tmpls(void);
+struct db *db_open(AppFile *file);
+struct db *db_create(void);
+void db_close(struct db *tdb);
+
+#endif
--- main.c Tue Nov 30 21:57:29 2021
+++ main.c Fri Dec 3 17:34:34 2021
@@ -19,6 +19,7 @@
#include "subtext.h"
#include "console.h"
+#include "db.h"
#include "session.h"
#include "telnet.h"
#include "uthread.h"
@@ -27,7 +28,7 @@
MenuHandle file_menu;
short quitting = 0;
struct console *cur_console = NULL;
-struct config config;
+struct db *db;
void handle_menu(long menu_id);
void update_menu(void);
@@ -40,9 +41,10 @@ main(void)
EventRecord event;
WindowPtr event_win;
GrafPtr old_port;
- short event_in, n;
+ AppFile finder_file;
+ short event_in, n, finder_action, finder_count;
char key;
-
+
uthread_init();
InitGraf(&thePort);
@@ -56,7 +58,8 @@ main(void)
MaxApplZone();
err_init();
-
+ load_db_tmpls();
+
mbar = GetNewMBar(MBAR_ID);
SetMenuBar(mbar);
apple_menu = GetMHandle(APPLE_MENU_ID);
@@ -64,12 +67,20 @@ main(void)
file_menu = GetMHandle(FILE_MENU_ID);
update_menu();
DrawMenuBar();
-
- /* TODO: get these from resources */
- memset(&config, 0, sizeof(config));
- strcpy(config.name, "Kludge BBS");
- strcpy(config.hostname, "klud.ge");
+ /* see if we were started by double-clicking a .bbs file */
+ CountAppFiles(&finder_action, &finder_count);
+ if (finder_count) {
+ GetAppFiles(1, &finder_file);
+ ClrAppFiles(1);
+ finder_count = 0;
+ db = db_open(&finder_file);
+ } else
+ db = db_open(NULL);
+
+ if (!db)
+ ExitToShell();
+
cur_console = console_init();
telnet_init();
@@ -152,6 +163,8 @@ main(void)
break;
}
}
+
+ db_close(db);
return 0;
}
--- session.c Tue Nov 30 20:15:38 2021
+++ session.c Fri Dec 3 10:07:56 2021
@@ -174,7 +174,7 @@ session_run(struct uthread *uthread, void *arg)
session_output(s, "\r\n"
"Welcome to %s (%s)\r\n"
- "\r\n", config.name, s->node);
+ "\r\n", db->config.name, s->node);
session_output(s, "login: ");
line = session_field_input(s, 50);
--- subtext.π.r Mon Nov 22 10:07:07 2021
+++ subtext.π.r Sun Dec 5 16:19:28 2021
@@ -45,12 +45,25 @@ data 'DLOG' (128) {
$"0000 0080 001A 280A" /* ...Ä..(. */
};
-data 'TMPL' (128, "USER") {
- $"0269 6444 5752 4408 7573 6572 6E61 6D65" /* .idDWRD.username */
- $"4353 5452" /* CSTR */
-};
-
data 'USER' (128) {
$"0001 6A63 7300" /* ..jcs. */
+};
+
+data 'TMPL' (128, "STCF") {
+ $"046E 616D 6543 3032 300C 7068 6F6E 655F" /* .nameC020.phone_ */
+ $"6E75 6D62 6572 4330 3130 086C 6F63 6174" /* numberC010.locat */
+ $"696F 6E43 3034 3008 686F 7374 6E61 6D65" /* ionC040.hostname */
+ $"4330 3230 0B74 656C 6E65 745F 706F 7274" /* C020.telnet_port */
+ $"4457 5244" /* DWRD */
+};
+
+data 'TMPL' (129, "STUS") {
+ $"0269 6444 5752 4408 7573 6572 6E61 6D65" /* .idDWRD.username */
+ $"4330 3230 0D70 6173 7377 6F72 645F 6861" /* C020¬password_ha */
+ $"7368 4330 3432 0D70 6173 7377 6F72 645F" /* shC042¬password_ */
+ $"7361 6C74 4330 3432 0A63 7265 6174 6564" /* saltC042.created */
+ $"5F61 7444 4C4E 470C 6C61 7374 5F73 6565" /* _atDLNG.last_see */
+ $"6E5F 6174 444C 4E47 0869 735F 7379 736F" /* n_atDLNG.is_syso */
+ $"7042 4F4F 4C" /* pBOOL */
};
--- subtext.h Wed Nov 17 15:40:42 2021
+++ subtext.h Fri Dec 3 09:58:22 2021
@@ -17,6 +17,8 @@
#ifndef __SUBTEXT_H__
#define __SUBTEXT_H__
+#include "db.h"
+
#define PROGRAM_NAME "Subtext"
#define MBAR_ID 128
@@ -27,12 +29,7 @@
#define FILE_MENU_ID 129
#define FILE_MENU_QUIT_ID 1
-struct config {
- char name[32];
- char phone_number[16];
- char hostname[32];
-};
-extern struct config config;
+extern struct db *db;
extern MenuHandle file_menu;
--- user.c Sun Dec 5 16:06:29 2021
+++ user.c Sun Dec 5 16:06:29 2021
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <ctype.h>
+#include <string.h>
+
+#include "db.h"
+#include "sha2.h"
+#include "subtext.h"
+#include "user.h"
+#include "util.h"
+
+struct user *
+user_build(struct db *tdb, char *username, char **error)
+{
+ struct user suser;
+ struct user *user;
+ size_t len, n;
+
+ len = strlen(username);
+ if (len >= sizeof(suser.username)) {
+ *error = xstrdup("username is too long");
+ return NULL;
+ }
+
+ for (n = 0; n < len; n++) {
+ if (!isalnum(username[n])) {
+ *error = xstrdup("username cannot contain "
+ "non-alphanumeric characters");
+ return NULL;
+ }
+ }
+
+ if ((user = user_find(tdb, username))) {
+ free(user);
+ *error = xstrdup("username already exists");
+ return NULL;
+ }
+
+ user = xmalloczero(sizeof(struct user));
+ strncpy(user->username, username, sizeof(user->username));
+ user->created_at = Time;
+
+ return user;
+}
+
+void
+user_save(struct db *tdb, struct user *user)
+{
+ Handle huser = NULL;
+ size_t hsize = 0, len;
+ size_t datapos;
+ char *data, *lusername;
+ short n, is_new = 0;
+
+ if (user->id) {
+ huser = Get1Resource(DB_USER_RTYPE, user->id);
+ if (!huser)
+ err(1, "user_save: user %s has id %d but no resource",
+ user->username, user->id);
+ hsize = GetHandleSize(huser);
+ } else {
+ hsize = sizeof(struct user);
+ huser = xNewHandle(hsize);
+ user->id = Count1Resources(DB_USER_RTYPE) + 1;
+ user->created_at = Time;
+ is_new = 1;
+ }
+
+ HLock(huser);
+ data = (char *)(*huser);
+ datapos = 0;
+
+ memcpy(data + datapos, &user->id, sizeof(user->id));
+ datapos += sizeof(user->id);
+
+ memcpy(data + datapos, &user->username, sizeof(user->username));
+ datapos += sizeof(user->username);
+
+ memcpy(data + datapos, &user->password_hash, sizeof(user->password_hash));
+ datapos += sizeof(user->password_hash);
+
+ memcpy(data + datapos, &user->password_salt, sizeof(user->password_salt));
+ datapos += sizeof(user->password_salt);
+
+ memcpy(data + datapos, &user->created_at, sizeof(user->created_at));
+ datapos += sizeof(user->created_at);
+
+ memcpy(data + datapos, &user->last_seen_at, sizeof(user->last_seen_at));
+ datapos += sizeof(user->last_seen_at);
+
+ memcpy(data + datapos, &user->is_sysop, sizeof(user->is_sysop));
+ datapos += sizeof(user->is_sysop);
+
+ if (datapos != hsize)
+ err(1, "user_save handle size %ld, data position %ld",
+ hsize, datapos);
+
+ HUnlock(huser);
+
+ /* update resource name with lowercase username for index */
+ len = strlen(user->username);
+ lusername = xmalloc(len + 1);
+ lusername[0] = len;
+ for (n = 0; n < len; n++)
+ lusername[n + 1] = tolower(user->username[n]);
+
+ if (is_new) {
+ AddResource(huser, DB_USER_RTYPE, user->id, lusername);
+ if (ResError())
+ err(1, "user_save: failed to AddResource: %d", ResError());
+ } else {
+ SetResInfo(huser, user->id, lusername);
+ ChangedResource(huser);
+ }
+
+ WriteResource(huser);
+ if (ResError())
+ err(1, "user_save: failed to WriteResource: %d", ResError());
+ ReleaseResource(huser);
+
+ tdb->nusers = Count1Resources(DB_USER_RTYPE);
+}
+
+struct user *
+user_find(struct db *tdb, char *username)
+{
+ struct user suser;
+ short n;
+ Handle huser;
+ Str255 pusername = { 0 };
+ size_t len;
+
+ len = strlen(username);
+ if (len > sizeof(suser.username))
+ return NULL;
+
+ pusername[0] = len;
+ for (n = 0; n < len; n++)
+ pusername[n + 1] = tolower(username[n]);
+
+ huser = Get1NamedResource(DB_USER_RTYPE, pusername);
+ if (!huser)
+ return NULL;
+
+ return user_parse(tdb, huser);
+}
+
+struct user *
+user_parse(struct db *tdb, Handle huser)
+{
+ struct user *user;
+ short hlen, datapos;
+ char *data;
+
+ user = xmalloczero(sizeof(struct user));
+
+ data = (char *)(*huser);
+ datapos = 0;
+
+ hlen = GetHandleSize(huser);
+
+ memcpy(&user->id, data + datapos, sizeof(user->id));
+ datapos += sizeof(user->id);
+
+ memcpy(&user->username, data + datapos, sizeof(user->username));
+ datapos += sizeof(user->username);
+
+ memcpy(&user->password_hash, data + datapos, sizeof(user->password_hash));
+ datapos += sizeof(user->password_hash);
+
+ memcpy(&user->password_salt, data + datapos, sizeof(user->password_salt));
+ datapos += sizeof(user->password_salt);
+
+ memcpy(&user->created_at, data + datapos, sizeof(user->created_at));
+ datapos += sizeof(user->created_at);
+
+ memcpy(&user->last_seen_at, data + datapos, sizeof(user->last_seen_at));
+ datapos += sizeof(user->last_seen_at);
+
+ memcpy(&user->is_sysop, data + datapos, sizeof(user->is_sysop));
+ datapos += sizeof(user->is_sysop);
+
+ if (datapos != hlen)
+ err(1, "user_parse handle len %d, data position %d", hlen, datapos);
+
+ HUnlock(huser);
+ ReleaseResource(huser);
+
+ return user;
+}
+
+short
+user_authenticate(struct db *tdb, struct user *user, char *password)
+{
+
+}
+
+void
+user_set_password(struct db *tdb, struct user *user, char *password)
+{
+ char hash[SHA256_DIGEST_STRING_LENGTH];
+ char salt[SHA256_DIGEST_STRING_LENGTH];
+ char *salted;
+ unsigned long tmp[8];
+ size_t plen;
+ short n;
+
+ for (n = 0; n < sizeof(tmp); n++)
+ tmp[n] = xorshift32();
+
+ SHA256Data((const u_int8_t *)tmp, sizeof(tmp), salt);
+ memcpy(&user->password_salt, &salt, sizeof(user->password_salt));
+
+ plen = strlen(password);
+ salted = xmalloc(plen + sizeof(salt));
+ memcpy(salted, salt, sizeof(salt));
+ memcpy(salted + sizeof(salt), password, plen);
+ SHA256Data((const u_int8_t *)salted, plen + sizeof(salt), hash);
+ free(salted);
+
+ memcpy(&user->password_hash, &hash, sizeof(user->password_hash));
+
+ memset(&hash, 0, sizeof(hash));
+ memset(&salt, 0, sizeof(salt));
+}
--- user.h Sun Dec 5 08:41:18 2021
+++ user.h Sun Dec 5 08:41:18 2021
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2021 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __USER_H__
+#define __USER_H__
+
+#include "sha2.h"
+
+#define AUTH_USER_OK 1
+#define AUTH_USER_FAILED 2
+
+struct user *user_build(struct db *tdb, char *username, char **error);
+void user_save(struct db *tdb, struct user *user);
+struct user *user_find(struct db *tdb, char *username);
+short user_authenticate(struct db *tdb, struct user *user, char *password);
+void user_set_password(struct db *tdb, struct user *user, char *password);
+struct user *user_parse(struct db *tdb, Handle huser);
+
+#endif
--- util.c Thu Oct 28 17:01:17 2021
+++ util.c Sat Dec 4 20:28:02 2021
@@ -21,7 +21,10 @@
#include "util.h"
+/* ALRT resources */
#define ERROR_ALERT_ID 129
+#define ASK_ALERT_ID 130
+
#define ERROR_STRING_SIZE 1024
enum {
@@ -226,7 +229,52 @@ note(const char *format, ...)
va_end(ap);
}
+short
+ask(const char *format, ...)
+{
+ size_t len;
+ short ret;
+ WindowPtr win;
+ va_list ap;
+
+ GetPort(&win);
+
+ HLock(err_str);
+ va_start(ap, format);
+ len = vsprintf(*err_str, format, ap);
+ va_end(ap);
+ if (len >= ERROR_STRING_SIZE)
+ err(1, "ask string overflow!");
+
+ ParamText(CtoPstr(*err_str), "\p", "\p", "\p");
+ ret = StopAlert(ASK_ALERT_ID, nil);
+ HUnlock(err_str);
+
+ SetPort(win);
+ return ret;
+}
+
+
/*
+ * General Mac-specific non-GUI functions
+ */
+static unsigned long _xorshift_state = 0;
+unsigned long
+xorshift32(void)
+{
+ unsigned long x = _xorshift_state;
+ if (x == 0) {
+ x = Time;
+ /* TODO: stir in seed stored in resource file */
+ }
+ x ^= x << 13;
+ x ^= x >> 17;
+ x ^= x << 5;
+ return _xorshift_state = x;
+}
+
+
+/*
* Error checking wrappers for Mac toolkit functions
*/
@@ -266,6 +314,7 @@ xGetString(short id)
return h;
}
+
/*
* Filesystem utilities
*/
@@ -367,9 +416,9 @@ getpath(short vRefNum, Str255 fileName, char **ret, bo
warn("Unknown filesystem type 0x%x", wvol.ioVSigWord);
return 1;
}
-
- wcinfo.ioNamePtr = (StringPtr)&name;
+
wcinfo.ioVRefNum = vRefNum;
+ wcinfo.ioNamePtr = (StringPtr)&name;
wcinfo.ioFDirIndex = -1;
wcinfo.ioDrParID = wdir.ioWDDirID;
wcinfo.ioDrDirID = wdir.ioWDDirID;
@@ -482,6 +531,7 @@ FSReadLine(short frefnum, char *buf, size_t buflen)
/* nothing found until the end of the file */
return total_read;
}
+
/*
* General Mac-specific GUI functions
--- util.h Thu Oct 28 17:01:09 2021
+++ util.h Sun Dec 5 16:22:51 2021
@@ -61,15 +61,22 @@ void *xreallocarray(void *, size_t, size_t);
char *xstrdup(const char *);
short getline(char *str, size_t len, char **ret);
+#if 0
/* from strnatcmp.c */
int strnatcmp(char const *a, char const *b);
int strnatcasecmp(char const *a, char const *b);
+#endif
+unsigned long xorshift32(void);
+
void err_init(void);
void warnx(const char *format, ...);
void warn(const char *format, ...);
void err(short retcode, const char *format, ...);
void note(const char *format, ...);
+short ask(const char *format, ...);
+#define ASK_YES 1
+#define ASK_NO 2
Handle xNewHandle(unsigned long size);
Handle xGetResource(ResType type, short id);