/* * Copyright (c) 2021-2024 joshua stein * * 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 #include #include #include #include #include #include #include #include #include "settings.h" #include "util.h" enum { SETTING_STRING, SETTING_SHORT, SETTING_USHORT, SETTING_LONG, SETTING_PASSWORD, SETTING_BOOL }; enum { SETTING_FLAG_ALLOW_BLANK = (1 << 0) }; struct setting { char name[32]; short type; long flags; long min; long max; char sdefault[32]; short ditl_id; unsigned long offset; unsigned long size; char password_storage[256]; } settings_defs[] = { { "Server Name", SETTING_STRING, 0, 1, 0, DEFAULT_SERVER_NAME, SETTINGS_SERVER_ID, offsetof(struct settings, server), member_size(struct settings, server) }, { "Server Port", SETTING_USHORT, 0, 1, 65535, DEFAULT_PORT, SETTINGS_PORT_ID, offsetof(struct settings, port), member_size(struct settings, port) }, { "Server Password", SETTING_PASSWORD, 0, 0, 0, "", SETTINGS_PASSWORD_ID, offsetof(struct settings, password), member_size(struct settings, password) }, { "Nickname", SETTING_STRING, 0, 1, 0, "", SETTINGS_NICK_ID, offsetof(struct settings, nick), member_size(struct settings, nick) }, { "Ident", SETTING_STRING, 0, 1, 0, DEFAULT_IDENT, SETTINGS_IDENT_ID, offsetof(struct settings, ident), member_size(struct settings, ident) }, { "Realname", SETTING_STRING, 0, 1, 0, DEFAULT_REALNAME, SETTINGS_REALNAME_ID, offsetof(struct settings, realname), member_size(struct settings, realname) }, { "Hide MOTD", SETTING_BOOL, 0, 0, 1, DEFAULT_HIDE_MOTD, SETTINGS_HIDE_MOTD_ID, offsetof(struct settings, hide_motd), member_size(struct settings, hide_motd) }, { "Channel", SETTING_STRING, 0, 0, 0, DEFAULT_CHANNEL, SETTINGS_CHANNEL_ID, offsetof(struct settings, channel), member_size(struct settings, channel) }, }; void settings_find_prefs_folder(short *vrefnum, long *dirid); bool settings_load(void) { HParamBlockRec pb = { 0 }; struct settings tsettings = { 0 }; char fn[] = SETTINGS_FILENAME; char *res; short error, vrefnum, prefrefnum; long len, rlen, dirid; Handle h; GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.ioParam.ioPermssn = fsRdPerm; pb.fileParam.ioDirID = dirid; error = PBHOpen(&pb, false); if (error) { if (error != fnfErr) panic("Failed reading preferences file %s: %d", PtoCstr(fn), error); return false; } rlen = len = sizeof(struct settings); FSRead(pb.ioParam.ioRefNum, &rlen, &tsettings); while (tsettings.version < SETTINGS_VERSION) { switch (tsettings.version) { default: FSClose(pb.ioParam.ioRefNum); if (ask("Unknown preferences version (%d), reset?", tsettings.version)) { return false; } ExitToShell(); } tsettings.version++; } FSClose(pb.ioParam.ioRefNum); settings = tsettings; return true; } void settings_save(struct settings *tsettings) { HParamBlockRec pb = { 0 }; char fn[] = SETTINGS_FILENAME; short error, vrefnum, prefrefnum; long dirid, len; Handle h; GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.fileParam.ioDirID = dirid; error = PBHCreate(&pb, false); if (error && error != dupFNErr) goto create_failed; if (!error) { memset(&pb, 0, sizeof(pb)); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.fileParam.ioDirID = dirid; if ((error = PBHGetFInfo(&pb, false))) goto create_failed; pb.fileParam.ioDirID = dirid; /* required after PBHGetFInfo */ pb.fileParam.ioFlFndrInfo.fdType = 'pref'; pb.fileParam.ioFlFndrInfo.fdCreator = SETTINGS_FILE_CREATOR; if ((error = PBHSetFInfo(&pb, false))) goto create_failed; memset(&pb, 0, sizeof(pb)); pb.ioParam.ioVRefNum = vrefnum; PBFlushVol(&pb, false); } memset(&pb, 0, sizeof(pb)); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.ioParam.ioPermssn = fsWrPerm; pb.fileParam.ioDirID = dirid; error = PBHOpen(&pb, false); if (error) goto create_failed; len = sizeof(struct settings); tsettings->version = SETTINGS_VERSION; FSWrite(pb.ioParam.ioRefNum, &len, tsettings); FSClose(pb.ioParam.ioRefNum); return; create_failed: panic("Failed creating preferences file %s: %d", PtoCstr(fn), error); } bool settings_edit(bool use_defaults) { struct settings tsettings = settings; struct setting *s; DialogTHndl dlgh; DialogPtr dlg; Handle ihandle; Rect irect; size_t size, n, m, slen; long lval; bool bval, save; short hit, itype, ret; char *sdata, *strval; Str255 stmp; /* center dialog in screen */ dlgh = (DialogTHndl)xGetResource('DLOG', SETTINGS_DLOG_ID); HLock(dlgh); center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left, (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top, true, &(**dlgh).boundsRect); HUnlock(dlgh); if ((dlg = GetNewDialog(SETTINGS_DLOG_ID, nil, (WindowPtr)-1)) == NULL) panic("Can't find settings DLOG %d", SETTINGS_DLOG_ID); for (n = 0; n < nitems(settings_defs); n++) { s = &settings_defs[n]; sdata = (char *)&tsettings + s->offset; GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect); switch (s->type) { case SETTING_PASSWORD: PasswordDialogFieldFilterSetup(s->ditl_id, (char *)s->password_storage, sizeof(s->password_storage)); strval = (use_defaults ? (char *)s->sdefault : sdata); size = strlen(strval); if (size >= sizeof(s->password_storage) - 1) size = 0; /* show a masked value */ stmp[0] = size; for (m = 1; m <= size; m++) stmp[m] = '¥'; SetIText(ihandle, stmp); /* but store the actual password */ memcpy(s->password_storage, strval, size); s->password_storage[size] = '\0'; break; case SETTING_STRING: strval = (use_defaults ? s->sdefault : sdata); memcpy(stmp, strval, sizeof(stmp)); stmp[sizeof(stmp) - 1] = '\0'; CtoPstr(stmp); SetIText(ihandle, stmp); break; case SETTING_SHORT: case SETTING_USHORT: case SETTING_LONG: if (use_defaults) { memcpy(stmp, s->sdefault, sizeof(stmp)); stmp[sizeof(stmp) - 1] = '\0'; } else if (s->type == SETTING_SHORT) { snprintf((char *)stmp, sizeof(stmp), "%d", BYTES_TO_SHORT(sdata[0], sdata[1])); } else if (s->type == SETTING_USHORT) { snprintf((char *)stmp, sizeof(stmp), "%u", BYTES_TO_SHORT(sdata[0], sdata[1])); } else if (s->type == SETTING_LONG) { snprintf((char *)stmp, sizeof(stmp), "%ld", BYTES_TO_LONG(sdata[0], sdata[1], sdata[2], sdata[3])); } CtoPstr(stmp); SetIText(ihandle, stmp); break; case SETTING_BOOL: SetCtlValue(ihandle, (use_defaults ? s->sdefault[0] : sdata[0]) == 1); break; default: panic("Unknown setting type %d", s->type); } } ret = false; ShowWindow(dlg); SelectWindow(dlg); get_input: ModalDialog(PasswordDialogFieldFilter, &hit); switch (hit) { case -1: goto settings_done; case OK: goto verify; default: GetDItem(dlg, hit, &itype, &ihandle, &irect); if (itype == (ctrlItem + chkCtrl)) SetCtlValue(ihandle, !GetCtlValue(ihandle)); goto get_input; } save = false; verify: for (n = 0; n < nitems(settings_defs); n++) { s = &settings_defs[n]; sdata = (char *)&tsettings + s->offset; GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect); if (s->type == SETTING_PASSWORD) { memcpy((char *)&stmp, s->password_storage, sizeof(stmp)); } else if (s->type == SETTING_BOOL) { snprintf((char *)&stmp, sizeof(stmp), "%d", GetCtlValue(ihandle)); } else { GetIText(ihandle, stmp); PtoCstr(stmp); } switch (s->type) { case SETTING_STRING: case SETTING_PASSWORD: slen = strlen((char *)stmp); if (slen == 0 && (s->flags & SETTING_FLAG_ALLOW_BLANK)) { /* ok */ } else { if (s->min && s->max && s->min == s->max && slen != s->min) { warn("%s must be %ld character%s", s->name, s->min, s->min == 1 ? "" : "s"); goto get_input; } if (s->min && slen < s->min) { warn("%s is too short (minimum %ld)", s->name, s->min); goto get_input; } if (s->max && slen > s->max) { warn("%s is too long (maximum %ld)", s->name, s->max); goto get_input; } } if (save) { memset(sdata, 0, s->size); strlcpy(sdata, (char *)stmp, s->size); } break; case SETTING_SHORT: case SETTING_USHORT: case SETTING_LONG: lval = atol((char *)stmp); if (lval < s->min) { if (s->type == SETTING_USHORT) warn("%s must be at least %ul", s->name, s->min); else warn("%s must be at least %ld", s->name, s->min); goto get_input; } if (lval > s->max) { if (s->type == SETTING_USHORT) warn("%s must be less than %ul", s->name, s->max); else warn("%s must be less than %ld", s->name, s->max); goto get_input; } if (save) { if (s->type == SETTING_LONG) { sdata[0] = (lval >> 24) & 0xff; sdata[1] = (lval >> 16) & 0xff; sdata[2] = (lval >> 8) & 0xff; sdata[3] = lval & 0xff; } else { sdata[0] = (lval >> 8) & 0xff; sdata[1] = lval & 0xff; } } break; case SETTING_BOOL: if (save) sdata[0] = (stmp[0] == '1'); break; } } if (!save) { save = true; goto verify; } /* all validated ok and fields written */ settings_save(&tsettings); settings = tsettings; ret = true; settings_done: PasswordDialogFieldFinish(); DisposeDialog(dlg); ReleaseResource(dlgh); return ret; }