diff --git a/ssheven-console.c b/ssheven-console.c index d48832d..b28da1f 100644 --- a/ssheven-console.c +++ b/ssheven-console.c @@ -70,6 +70,23 @@ void check_cursor(void) } } +// convert Quickdraw colors into vterm's ANSI color indexes +int qd2idx(int qdc) +{ + switch (qdc) + { + case blackColor: return 0; + case redColor: return 1; + case greenColor: return 2; + case yellowColor: return 3; + case blueColor: return 4; + case magentaColor: return 5; + case cyanColor: return 6; + case whiteColor: return 7; + default: return 0; + } +} + // convert vterm's ANSI color indexes into Quickdraw colors inline int idx2qd(VTermColor c) { @@ -142,8 +159,8 @@ void draw_screen(Rect* r) TextFont(kFontIDMonaco); TextSize(9); TextFace(normal); - qd.thePort->bkColor = whiteColor; - qd.thePort->fgColor = blackColor; + qd.thePort->bkColor = prefs.bg_color; + qd.thePort->fgColor = prefs.fg_color; EraseRect(r); @@ -443,10 +460,10 @@ void console_setup(void) vterm_state_reset(vtermstate, 1); VTermColor fg = { .type = VTERM_COLOR_INDEXED }; - fg.indexed.idx = 0; // ANSI black + fg.indexed.idx = qd2idx(prefs.fg_color); VTermColor bg = { .type = VTERM_COLOR_INDEXED }; - bg.indexed.idx = 7; // ANSI white + bg.indexed.idx = qd2idx(prefs.bg_color); vterm_state_set_default_colors(vtermstate, &fg, &bg); diff --git a/ssheven.c b/ssheven.c index 2f39a60..f6b6540 100644 --- a/ssheven.c +++ b/ssheven.c @@ -18,19 +18,170 @@ // sinful globals struct ssheven_console con = { NULL, 0, 0, 0, 0, 0, 0, 0, 0, 1, NULL, NULL }; struct ssheven_ssh_connection ssh_con = { NULL, NULL, kOTInvalidEndpointRef, NULL, NULL }; +struct preferences prefs; enum { WAIT, READ, EXIT } read_thread_command = WAIT; enum { UNINTIALIZED, OPEN, CLEANUP, DONE } read_thread_state = UNINTIALIZED; -enum { KEY_LOGIN, PASSWORD_LOGIN } login_type = PASSWORD_LOGIN; -// pascal strings -char hostname[512] = {0}; -char username[256] = {0}; -char password[256] = {0}; +int save_prefs(void) +{ + int ok = 1; + short foundVRefNum = 0; + long foundDirID = 0; + FSSpec pref_file; + //int create_new = 0; + short prefRefNum = 0; -// malloc'd c strings -char* pubkey_path = NULL; -char* privkey_path = NULL; + OSType pref_type = 'SH7p'; + OSType creator_type = 'SSH7'; + + // find the preferences folder on the system disk, create folder if needed + OSErr e = FindFolder(kOnSystemDisk, kPreferencesFolderType, kCreateFolder, &foundVRefNum, &foundDirID); + if (e != noErr) ok = 0; + + // make an FSSpec for the new file we want to make + if (ok) + { + e = FSMakeFSSpec(foundVRefNum, foundDirID, PREFERENCES_FILENAME, &pref_file); + if (e == fnfErr) // file doesn't exist, but is a valid path + { + // so make the file + e = FSpCreate(&pref_file, creator_type, pref_type, smSystemScript); + if (e != noErr) ok = 0; + } + else if (e != noErr) ok = 0; + } + + // open the file + if (ok) + { + e = FSpOpenDF(&pref_file, fsRdWrPerm, &prefRefNum); + if (e != noErr) ok = 0; + } + + // write prefs to the file + if (ok) + { + // TODO: choose buffer size more effectively + size_t write_length = 8192; + + char* output_buffer = malloc(write_length); + memset(output_buffer, 0, write_length); + + long int i = snprintf(output_buffer, write_length, "%d\n%d\n", prefs.major_version, prefs.minor_version); + i += snprintf(output_buffer+i, write_length-i, "%d\n%d\n%d\n%d\n", (int)prefs.auth_type, (int)prefs.display_mode, (int)prefs.fg_color, (int)prefs.bg_color); + + snprintf(output_buffer+i, prefs.hostname[0]+1, "%s", prefs.hostname+1); i += prefs.hostname[0]; + i += snprintf(output_buffer+i, write_length-i, "\n"); + + snprintf(output_buffer+i, prefs.username[0]+1, "%s", prefs.username+1); i += prefs.username[0]; + i += snprintf(output_buffer+i, write_length-i, "\n"); + + snprintf(output_buffer+i, prefs.port[0]+1, "%s", prefs.port+1); i += prefs.port[0]; + i += snprintf(output_buffer+i, write_length-i, "\n"); + + i += snprintf(output_buffer+i, write_length-i, "%s\n%s\n", prefs.privkey_path, prefs.pubkey_path); + + // tell it to write all bytes + long int bytes = i; + e = FSWrite(prefRefNum, &bytes, output_buffer); + // FSWrite sets bytes to the actual number of bytes written + + if (e != noErr || (bytes != i)) ok = 0; + } + + // close the file + if (prefRefNum != 0) + { + e = FSClose(prefRefNum); + if (e != noErr) ok = 0; + } + + return ok; +} + +void init_prefs(void) +{ + // initialize everything to a safe default + prefs.major_version = SSHEVEN_VERSION_MAJOR; + prefs.minor_version = SSHEVEN_VERSION_MINOR; + + memset(&(prefs.hostname), 0, 512); + memset(&(prefs.username), 0, 256); + memset(&(prefs.password), 0, 256); + memset(&(prefs.port), 0, 256); + + // default port: 22 + prefs.port[0] = 2; + prefs.port[1] = '2'; + prefs.port[2] = '2'; + + prefs.pubkey_path = ""; + prefs.privkey_path = ""; + prefs.terminal_string = SSHEVEN_TERMINAL_TYPE; + prefs.auth_type = USE_PASSWORD; + prefs.display_mode = COLOR; + prefs.fg_color = blackColor; + prefs.bg_color = whiteColor; + + prefs.loaded_from_file = 0; +} + +void load_prefs(void) +{ + // now try to load preferences from the file + short foundVRefNum = 0; + long foundDirID = 0; + FSSpec pref_file; + short prefRefNum = 0; + + // find the preferences folder on the system disk + OSErr e = FindFolder(kOnSystemDisk, kPreferencesFolderType, kDontCreateFolder, &foundVRefNum, &foundDirID); + if (e != noErr) return; + + // make an FSSpec for the preferences file location and check if it exists + // TODO: if I just put PREFERENCES_FILENAME it doesn't work, wtf + e = FSMakeFSSpec(foundVRefNum, foundDirID, "\pssheven Preferences", &pref_file); + + if (e == fnfErr) // file not found, nothing to load + { + return; + } + else if (e != noErr) return; + + e = FSpOpenDF(&pref_file, fsCurPerm, &prefRefNum); + if (e != noErr) return; + + // actually read and parse the file + long int buffer_size = 8192; + char* buffer = NULL; + buffer = malloc(buffer_size); + prefs.privkey_path = malloc(2048); + prefs.pubkey_path = malloc(2048); + prefs.pubkey_path[0] = '\0'; + prefs.privkey_path[0] = '\0'; + + e = FSRead(prefRefNum, &buffer_size, buffer); + e = FSClose(prefRefNum); + + // check the version (first two numbers) + int items_got = sscanf(buffer, "%d\n%d", &prefs.major_version, &prefs.minor_version); + if (items_got != 2) return; + + // only load a prefs file if the saved version number matches ours + if ((prefs.major_version == SSHEVEN_VERSION_MAJOR) && (prefs.minor_version == SSHEVEN_VERSION_MINOR)) + { + prefs.loaded_from_file = 1; + items_got = sscanf(buffer, "%d\n%d\n%d\n%d\n%d\n%d\n%255[^\n]\n%255[^\n]\n%255[^\n]\n%[^\n]\n%[^\n]", &prefs.major_version, &prefs.minor_version, (int*)&prefs.auth_type, (int*)&prefs.display_mode, &prefs.fg_color, &prefs.bg_color, prefs.hostname+1, prefs.username+1, prefs.port+1, prefs.privkey_path, prefs.pubkey_path); + + // add the size for the pascal strings + prefs.hostname[0] = (unsigned char)strlen(prefs.hostname+1); + prefs.username[0] = (unsigned char)strlen(prefs.username+1); + prefs.port[0] = (unsigned char)strlen(prefs.port+1); + } + + if (buffer) free(buffer); +} // borrowed from Retro68 sample code // draws the "default" indicator around a button @@ -470,7 +621,7 @@ int ssh_setup_terminal(void) { int rc = 0; - SSH_CHECK(libssh2_channel_request_pty_ex(ssh_con.channel, SSHEVEN_TERMINAL_TYPE, (strlen(SSHEVEN_TERMINAL_TYPE)), NULL, 0, con.size_x, con.size_y, 0, 0)); + SSH_CHECK(libssh2_channel_request_pty_ex(ssh_con.channel, prefs.terminal_string, (strlen(prefs.terminal_string)), NULL, 0, con.size_x, con.size_y, 0, 0)); SSH_CHECK(libssh2_channel_shell(ssh_con.channel)); return 1; @@ -572,8 +723,8 @@ int password_dialog(int dialog_resource) // read out of the hidden text box GetDialogItem(dlog, 5, &type, &itemH, &box); - GetDialogItemText(itemH, (unsigned char*)password); - login_type = PASSWORD_LOGIN; + GetDialogItemText(itemH, (unsigned char*)prefs.password); + prefs.auth_type = USE_PASSWORD; DisposeDialog(dlog); @@ -689,41 +840,47 @@ int key_dialog(void) Handle full_path = NULL; int path_length = 0; - // get public key path - NoteAlert(ALRT_PUBKEY, nil); - StandardFileReply pubkey; - StandardGetFile(NULL, 0, NULL, &pubkey); - FSpPathFromLocation(&pubkey.sfFile, &path_length, &full_path); - pubkey_path = malloc(path_length+1); - strncpy(pubkey_path, (char*)(*full_path), path_length+1); - DisposeHandle(full_path); + // if we don't have a saved pubkey path, ask for one + if (prefs.pubkey_path == NULL || prefs.pubkey_path[0] == '\0') + { + NoteAlert(ALRT_PUBKEY, nil); + StandardFileReply pubkey; + StandardGetFile(NULL, 0, NULL, &pubkey); + FSpPathFromLocation(&pubkey.sfFile, &path_length, &full_path); + prefs.pubkey_path = malloc(path_length+1); + strncpy(prefs.pubkey_path, (char*)(*full_path), path_length+1); + DisposeHandle(full_path); + + // if the user hit cancel, 0 + if (!pubkey.sfGood) return 0; + } path_length = 0; full_path = NULL; - // if the user hit cancel, 0 - if (!pubkey.sfGood) return 0; + // if we don't have a saved privkey path, ask for one + if (prefs.privkey_path == NULL || prefs.privkey_path[0] == '\0') + { + NoteAlert(ALRT_PRIVKEY, nil); + StandardFileReply privkey; + StandardGetFile(NULL, 0, NULL, &privkey); + FSpPathFromLocation(&privkey.sfFile, &path_length, &full_path); + prefs.privkey_path = malloc(path_length+1); + strncpy(prefs.privkey_path, (char*)(*full_path), path_length+1); + DisposeHandle(full_path); - // get private key path - NoteAlert(ALRT_PRIVKEY, nil); - StandardFileReply privkey; - StandardGetFile(NULL, 0, NULL, &privkey); - FSpPathFromLocation(&privkey.sfFile, &path_length, &full_path); - privkey_path = malloc(path_length+1); - strncpy(privkey_path, (char*)(*full_path), path_length+1); - DisposeHandle(full_path); - - // if the user hit cancel, 0 - if (!privkey.sfGood) return 0; + // if the user hit cancel, 0 + if (!privkey.sfGood) return 0; + } // get the key decryption password if (!password_dialog(DLOG_KEY_PASSWORD)) return 0; - login_type = KEY_LOGIN; + prefs.auth_type = USE_KEY; return 1; } -int intro_dialog(char* hostname, char* username, char* password) +int intro_dialog(void) { // modal dialog setup TEInit(); @@ -742,29 +899,42 @@ int intro_dialog(char* hostname, char* username, char* password) GetDialogItem(dlg, 2, &type, &itemH, &box); SetDialogItem(dlg, 2, type, (Handle)NewUserItemUPP(&ButtonFrameProc), &box); - // get the handles for each of the text boxes + // get the handles for each of the text boxes, and load preference data in ControlHandle address_text_box; GetDialogItem(dlg, 4, &type, &itemH, &box); address_text_box = (ControlHandle)itemH; + SetDialogItemText((Handle)address_text_box, (ConstStr255Param)prefs.hostname); ControlHandle port_text_box; GetDialogItem(dlg, 5, &type, &itemH, &box); port_text_box = (ControlHandle)itemH; + SetDialogItemText((Handle)port_text_box, (ConstStr255Param)prefs.port); ControlHandle username_text_box; GetDialogItem(dlg, 7, &type, &itemH, &box); username_text_box = (ControlHandle)itemH; + SetDialogItemText((Handle)username_text_box, (ConstStr255Param)prefs.username); ControlHandle password_radio; GetDialogItem(dlg, 9, &type, &itemH, &box); password_radio = (ControlHandle)itemH; - SetControlValue(password_radio, 1); + SetControlValue(password_radio, 0); ControlHandle key_radio; GetDialogItem(dlg, 10, &type, &itemH, &box); key_radio = (ControlHandle)itemH; SetControlValue(key_radio, 0); + // recall last-used connection type + if (prefs.auth_type == USE_PASSWORD) + { + SetControlValue(password_radio, 1); + } + else + { + SetControlValue(key_radio, 1); + } + // let the modalmanager do everything // stop when the connect button is hit short item; @@ -783,12 +953,15 @@ int intro_dialog(char* hostname, char* username, char* password) } while(item != 1 && item != 8); // copy the text out of the boxes - GetDialogItemText((Handle)address_text_box, (unsigned char *)hostname); - GetDialogItemText((Handle)username_text_box, (unsigned char *)username); + GetDialogItemText((Handle)address_text_box, (unsigned char *)prefs.hostname); + GetDialogItemText((Handle)username_text_box, (unsigned char *)prefs.username); - // splice the port number onto the hostname (n.b. they're pascal strings) - GetDialogItemText((Handle)port_text_box, (unsigned char *)hostname+hostname[0]+1); - hostname[hostname[0]+1] = ':'; + GetDialogItemText((Handle)port_text_box, (unsigned char *)prefs.hostname+prefs.hostname[0]+1); + prefs.hostname[prefs.hostname[0]+1] = ':'; + + char* port_start = prefs.hostname+prefs.hostname[0] + 2; + prefs.port[0] = strlen(port_start); + strncpy(prefs.port+1, port_start, 255); int use_password = GetControlValue(password_radio); @@ -823,25 +996,25 @@ void* read_thread(void* arg) } // connect and log in - ok = init_connection(hostname+1); + ok = init_connection(prefs.hostname+1); YieldToAnyThread(); if (ok) { printf_i("Authenticating... "); YieldToAnyThread(); - if (login_type == PASSWORD_LOGIN) + if (prefs.auth_type == USE_PASSWORD) { - rc = libssh2_userauth_password(ssh_con.session, username+1, password+1); + rc = libssh2_userauth_password(ssh_con.session, prefs.username+1, prefs.password+1); } else { rc = libssh2_userauth_publickey_fromfile_ex(ssh_con.session, - username+1, - username[0], - pubkey_path, - privkey_path, - password+1); + prefs.username+1, + prefs.username[0], + prefs.pubkey_path, + prefs.privkey_path, + prefs.password+1); } if (rc == LIBSSH2_ERROR_NONE) @@ -850,13 +1023,20 @@ void* read_thread(void* arg) } else { - if (rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED && login_type == PASSWORD_LOGIN) StopAlert(ALRT_PW_FAIL, nil); - if (rc == LIBSSH2_ERROR_FILE) StopAlert(ALRT_FILE_FAIL, nil); - printf_i("failure: %s\r\n", libssh2_error_string(rc)); + if (rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED && prefs.auth_type == USE_PASSWORD) StopAlert(ALRT_PW_FAIL, nil); + else if (rc == LIBSSH2_ERROR_FILE) StopAlert(ALRT_FILE_FAIL, nil); + else if (rc == LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED) + { + printf_i("Invalid key files!\r\n"); // TODO: have an alert for this + } + else printf_i("failure: %s\r\n", libssh2_error_string(rc)); ok = 0; } } + // if we logged in, save the connection preferences, since we know they're ok + if (ok) save_prefs(); + // if we logged in, open and set up the tty if (ok) { @@ -994,48 +1174,6 @@ int safety_checks(void) return 1; } -int save_preferences(void) -{ - int ok = 1; - short foundVRefNum = 0; - long foundDirID = 0; - FSSpec pref_file; - int create_new = 0; - - OSType pref_type = 'SH7p'; - OSType creator_type = 'SSH7'; - - // find the preferences folder on the system disk, create folder if needed - OSErr e = FindFolder(kOnSystemDisk, kPreferencesFolderType, kCreateFolder, &foundVRefNum, &foundDirID); - if (e != noErr) ok = 0; - - // make an FSSpec for the new file we want to make - if (ok) - { - e = FSMakeFSSpec(foundVRefNum, foundDirID, PREFERENCES_FILENAME, &pref_file); - if (e == fnfErr) // file doesn't exist, but is a valid path - { - create_new = 1; - } - else if (e != noErr) ok = 0; - } - - if (ok && create_new) - { - e = FSpCreate(&pref_file, creator_type, pref_type, smSystemScript); - if (e != noErr) ok = 0; - } - - // TODO: actually save anything - - return ok; -} - -void load_preferences(void) -{ - // TODO: actually load anything -} - int main(int argc, char** argv) { OSStatus err = noErr; @@ -1049,7 +1187,9 @@ int main(int argc, char** argv) MoreMasters(); MoreMasters(); - load_preferences(); + // set default preferences, then load from preferences file if possible + init_prefs(); + load_prefs(); // general gui setup InitGraf(&qd.thePort); @@ -1091,6 +1231,15 @@ int main(int argc, char** argv) printf_i("Running in 68k mode.\r\n"); #endif + if (prefs.loaded_from_file) + { + printf_i("Loaded preferences file.\r\n"); + } + else + { + printf_i("Could not load from preferences file.\r\n"); + } + BeginUpdate(con.win); draw_screen(&(con.win->portRect)); EndUpdate(con.win); @@ -1103,9 +1252,7 @@ int main(int argc, char** argv) draw_screen(&(con.win->portRect)); EndUpdate(con.win); - ok = intro_dialog(hostname, username, password); - - if (ok) save_preferences(); + ok = intro_dialog(); if (!ok) printf_i("Cancelled, not connecting.\r\n"); @@ -1168,8 +1315,8 @@ int main(int argc, char** argv) if (ssh_con.recv_buffer != NULL) OTFreeMem(ssh_con.recv_buffer); if (ssh_con.send_buffer != NULL) OTFreeMem(ssh_con.send_buffer); - if (pubkey_path != NULL) free(pubkey_path); - if (privkey_path != NULL) free(privkey_path); + if (prefs.pubkey_path != NULL && prefs.pubkey_path[0] != '\0') free(prefs.pubkey_path); + if (prefs.privkey_path != NULL && prefs.privkey_path[0] != '\0') free(prefs.privkey_path); if (con.vterm != NULL) vterm_free(con.vterm); diff --git a/ssheven.h b/ssheven.h index becc3d8..ae53325 100644 --- a/ssheven.h +++ b/ssheven.h @@ -36,6 +36,8 @@ #include #include +#include + // sinful globals struct ssheven_console { @@ -76,3 +78,31 @@ extern struct ssheven_ssh_connection ssh_con; extern char key_to_vterm[256]; void ssh_write(char* buf, size_t len); + +struct preferences +{ + int major_version; + int minor_version; + + int loaded_from_file; + + // pascal strings + char hostname[512]; // of the form: "hostname:portnumber", size is first only + char username[256]; + char password[256]; + char port[256]; + + // malloc'd c strings + char* pubkey_path; + char* privkey_path; + + const char* terminal_string; + + enum { USE_KEY, USE_PASSWORD } auth_type; + + enum { FASTEST, MONOCHROME, COLOR } display_mode; + int fg_color; + int bg_color; +}; + +extern struct preferences prefs;