--- /dev/null
+/*\r
+ * osxdlg.m: various PuTTY dialog boxes for OS X.\r
+ */\r
+\r
+#import <Cocoa/Cocoa.h>\r
+#include "putty.h"\r
+#include "storage.h"\r
+#include "dialog.h"\r
+#include "osxclass.h"\r
+\r
+/*\r
+ * The `ConfigWindow' class is used to start up a new PuTTY\r
+ * session.\r
+ */\r
+\r
+@class ConfigTree;\r
+@interface ConfigTree : NSObject\r
+{\r
+ NSString **paths;\r
+ int *levels;\r
+ int nitems, itemsize;\r
+}\r
+- (void)addPath:(char *)path;\r
+@end\r
+\r
+@implementation ConfigTree\r
+- (id)init\r
+{\r
+ self = [super init];\r
+ paths = NULL;\r
+ levels = NULL;\r
+ nitems = itemsize = 0;\r
+ return self;\r
+}\r
+- (void)addPath:(char *)path\r
+{\r
+ if (nitems >= itemsize) {\r
+ itemsize += 32;\r
+ paths = sresize(paths, itemsize, NSString *);\r
+ levels = sresize(levels, itemsize, int);\r
+ }\r
+ paths[nitems] = [[NSString stringWithCString:path] retain];\r
+ levels[nitems] = ctrl_path_elements(path) - 1;\r
+ nitems++;\r
+}\r
+- (void)dealloc\r
+{\r
+ int i;\r
+\r
+ for (i = 0; i < nitems; i++)\r
+ [paths[i] release];\r
+\r
+ sfree(paths);\r
+ sfree(levels);\r
+\r
+ [super dealloc];\r
+}\r
+- (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count\r
+{\r
+ int i, plevel;\r
+\r
+ if (item) {\r
+ for (i = 0; i < nitems; i++)\r
+ if (paths[i] == item)\r
+ break;\r
+ assert(i < nitems);\r
+ plevel = levels[i];\r
+ i++;\r
+ } else {\r
+ i = 0;\r
+ plevel = -1;\r
+ }\r
+\r
+ if (count)\r
+ *count = 0;\r
+\r
+ while (index > 0) {\r
+ if (i >= nitems || levels[i] != plevel+1)\r
+ return nil;\r
+ if (count)\r
+ (*count)++;\r
+ do {\r
+ i++;\r
+ } while (i < nitems && levels[i] > plevel+1);\r
+ index--;\r
+ }\r
+\r
+ return paths[i];\r
+}\r
+- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item\r
+{\r
+ return [self iterateChildren:index ofItem:item count:NULL];\r
+}\r
+- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item\r
+{\r
+ int count = 0;\r
+ /* pass nitems+1 to ensure we run off the end */\r
+ [self iterateChildren:nitems+1 ofItem:item count:&count];\r
+ return count;\r
+}\r
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item\r
+{\r
+ return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;\r
+}\r
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item\r
+{\r
+ /*\r
+ * Trim off all path elements except the last one.\r
+ */\r
+ NSArray *components = [item componentsSeparatedByString:@"/"];\r
+ return [components objectAtIndex:[components count]-1];\r
+}\r
+@end\r
+\r
+@implementation ConfigWindow\r
+- (id)initWithConfig:(Config)aCfg\r
+{\r
+ NSScrollView *scrollview;\r
+ NSTableColumn *col;\r
+ ConfigTree *treedata;\r
+ int by = 0, mby = 0;\r
+ int wmin = 0;\r
+ int hmin = 0;\r
+ int panelht = 0;\r
+\r
+ ctrlbox = ctrl_new_box();\r
+ setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol,\r
+ 0 /* protcfginfo */);\r
+ unix_setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol);\r
+\r
+ cfg = aCfg; /* structure copy */\r
+\r
+ self = [super initWithContentRect:NSMakeRect(0,0,300,300)\r
+ styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |\r
+ NSClosableWindowMask)\r
+ backing:NSBackingStoreBuffered\r
+ defer:YES];\r
+ [self setTitle:@"PuTTY Configuration"];\r
+\r
+ [self setIgnoresMouseEvents:NO];\r
+\r
+ dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));\r
+\r
+ scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];\r
+ treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];\r
+ [scrollview setBorderType:NSLineBorder];\r
+ [scrollview setDocumentView:treeview];\r
+ [[self contentView] addSubview:scrollview];\r
+ [scrollview setHasVerticalScroller:YES];\r
+ [scrollview setAutohidesScrollers:YES];\r
+ /* FIXME: the below is untested. Test it then remove this notice. */\r
+ [treeview setAllowsColumnReordering:NO];\r
+ [treeview setAllowsColumnResizing:NO];\r
+ [treeview setAllowsMultipleSelection:NO];\r
+ [treeview setAllowsEmptySelection:NO];\r
+ [treeview setAllowsColumnSelection:YES];\r
+\r
+ treedata = [[[ConfigTree alloc] init] retain];\r
+\r
+ col = [[NSTableColumn alloc] initWithIdentifier:nil];\r
+ [treeview addTableColumn:col];\r
+ [treeview setOutlineTableColumn:col];\r
+\r
+ [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];\r
+\r
+ /*\r
+ * Create the controls.\r
+ */\r
+ {\r
+ int i;\r
+ char *path = NULL;\r
+\r
+ for (i = 0; i < ctrlbox->nctrlsets; i++) {\r
+ struct controlset *s = ctrlbox->ctrlsets[i];\r
+ int mw, mh;\r
+\r
+ if (!*s->pathname) {\r
+\r
+ create_ctrls(dv, [self contentView], s, &mw, &mh);\r
+\r
+ by += 20 + mh;\r
+\r
+ if (wmin < mw + 40)\r
+ wmin = mw + 40;\r
+ } else {\r
+ int j = path ? ctrl_path_compare(s->pathname, path) : 0;\r
+\r
+ if (j != INT_MAX) { /* add to treeview, start new panel */\r
+ char *c;\r
+\r
+ /*\r
+ * We expect never to find an implicit path\r
+ * component. For example, we expect never to\r
+ * see A/B/C followed by A/D/E, because that\r
+ * would _implicitly_ create A/D. All our path\r
+ * prefixes are expected to contain actual\r
+ * controls and be selectable in the treeview;\r
+ * so we would expect to see A/D _explicitly_\r
+ * before encountering A/D/E.\r
+ */\r
+ assert(j == ctrl_path_elements(s->pathname) - 1);\r
+\r
+ c = strrchr(s->pathname, '/');\r
+ if (!c)\r
+ c = s->pathname;\r
+ else\r
+ c++;\r
+\r
+ [treedata addPath:s->pathname];\r
+ path = s->pathname;\r
+\r
+ panelht = 0;\r
+ }\r
+\r
+ create_ctrls(dv, [self contentView], s, &mw, &mh);\r
+ if (wmin < mw + 3*20+150)\r
+ wmin = mw + 3*20+150;\r
+ panelht += mh + 20;\r
+ if (hmin < panelht - 20)\r
+ hmin = panelht - 20;\r
+ }\r
+ }\r
+ }\r
+\r
+ {\r
+ int i;\r
+ NSRect r;\r
+\r
+ [treeview setDataSource:treedata];\r
+ for (i = [treeview numberOfRows]; i-- ;)\r
+ [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];\r
+\r
+ [treeview sizeToFit];\r
+ r = [treeview frame];\r
+ if (hmin < r.size.height)\r
+ hmin = r.size.height;\r
+ }\r
+\r
+ [self setContentSize:NSMakeSize(wmin, hmin+60+by)];\r
+ [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];\r
+ [treeview setDelegate:self];\r
+ mby = by;\r
+\r
+ /*\r
+ * Now place the controls.\r
+ */\r
+ {\r
+ int i;\r
+ char *path = NULL;\r
+ panelht = 0;\r
+\r
+ for (i = 0; i < ctrlbox->nctrlsets; i++) {\r
+ struct controlset *s = ctrlbox->ctrlsets[i];\r
+\r
+ if (!*s->pathname) {\r
+ by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);\r
+ } else {\r
+ if (!path || strcmp(s->pathname, path))\r
+ panelht = 0;\r
+\r
+ panelht += VSPACING + place_ctrls(dv, s, 2*20+150,\r
+ 40+mby+hmin-panelht,\r
+ wmin - (3*20+150));\r
+\r
+ path = s->pathname;\r
+ }\r
+ }\r
+ }\r
+\r
+ select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);\r
+\r
+ [treeview reloadData];\r
+\r
+ dlg_refresh(NULL, dv);\r
+\r
+ [self center]; /* :-) */\r
+\r
+ return self;\r
+}\r
+- (void)configBoxFinished:(id)object\r
+{\r
+ int ret = [object intValue]; /* it'll be an NSNumber */\r
+ if (ret) {\r
+ [controller performSelectorOnMainThread:\r
+ @selector(newSessionWithConfig:)\r
+ withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]\r
+ waitUntilDone:NO];\r
+ }\r
+ [self close];\r
+}\r
+- (void)outlineViewSelectionDidChange:(NSNotification *)notification\r
+{\r
+ const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];\r
+ select_panel(dv, ctrlbox, path);\r
+}\r
+- (BOOL)outlineView:(NSOutlineView *)outlineView\r
+ shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item\r
+{\r
+ return NO; /* no editing! */\r
+}\r
+@end\r
+\r
+/* ----------------------------------------------------------------------\r
+ * Various special-purpose dialog boxes.\r
+ */\r
+\r
+struct appendstate {\r
+ void (*callback)(void *ctx, int result);\r
+ void *ctx;\r
+};\r
+\r
+static void askappend_callback(void *ctx, int result)\r
+{\r
+ struct appendstate *state = (struct appendstate *)ctx;\r
+\r
+ state->callback(state->ctx, (result == NSAlertFirstButtonReturn ? 2 :\r
+ result == NSAlertSecondButtonReturn ? 1 : 0));\r
+ sfree(state);\r
+}\r
+\r
+int askappend(void *frontend, Filename filename,\r
+ void (*callback)(void *ctx, int result), void *ctx)\r
+{\r
+ static const char msgtemplate[] =\r
+ "The session log file \"%s\" already exists. "\r
+ "You can overwrite it with a new session log, "\r
+ "append your session log to the end of it, "\r
+ "or disable session logging for this session.";\r
+\r
+ char *text;\r
+ SessionWindow *win = (SessionWindow *)frontend;\r
+ struct appendstate *state;\r
+ NSAlert *alert;\r
+\r
+ text = dupprintf(msgtemplate, filename.path);\r
+\r
+ state = snew(struct appendstate);\r
+ state->callback = callback;\r
+ state->ctx = ctx;\r
+\r
+ alert = [[NSAlert alloc] init];\r
+ [alert setInformativeText:[NSString stringWithCString:text]];\r
+ [alert addButtonWithTitle:@"Overwrite"];\r
+ [alert addButtonWithTitle:@"Append"];\r
+ [alert addButtonWithTitle:@"Disable"];\r
+ [win startAlert:alert withCallback:askappend_callback andCtx:state];\r
+\r
+ return -1;\r
+}\r
+\r
+struct algstate {\r
+ void (*callback)(void *ctx, int result);\r
+ void *ctx;\r
+};\r
+\r
+static void askalg_callback(void *ctx, int result)\r
+{\r
+ struct algstate *state = (struct algstate *)ctx;\r
+\r
+ state->callback(state->ctx, result == NSAlertFirstButtonReturn);\r
+ sfree(state);\r
+}\r
+\r
+int askalg(void *frontend, const char *algtype, const char *algname,\r
+ void (*callback)(void *ctx, int result), void *ctx)\r
+{\r
+ static const char msg[] =\r
+ "The first %s supported by the server is "\r
+ "%s, which is below the configured warning threshold.\n"\r
+ "Continue with connection?";\r
+\r
+ char *text;\r
+ SessionWindow *win = (SessionWindow *)frontend;\r
+ struct algstate *state;\r
+ NSAlert *alert;\r
+\r
+ text = dupprintf(msg, algtype, algname);\r
+\r
+ state = snew(struct algstate);\r
+ state->callback = callback;\r
+ state->ctx = ctx;\r
+\r
+ alert = [[NSAlert alloc] init];\r
+ [alert setInformativeText:[NSString stringWithCString:text]];\r
+ [alert addButtonWithTitle:@"Yes"];\r
+ [alert addButtonWithTitle:@"No"];\r
+ [win startAlert:alert withCallback:askalg_callback andCtx:state];\r
+\r
+ return -1;\r
+}\r
+\r
+struct hostkeystate {\r
+ char *host, *keytype, *keystr;\r
+ int port;\r
+ void (*callback)(void *ctx, int result);\r
+ void *ctx;\r
+};\r
+\r
+static void verify_ssh_host_key_callback(void *ctx, int result)\r
+{\r
+ struct hostkeystate *state = (struct hostkeystate *)ctx;\r
+\r
+ if (result == NSAlertThirdButtonReturn) /* `Accept' */\r
+ store_host_key(state->host, state->port,\r
+ state->keytype, state->keystr);\r
+ state->callback(state->ctx, result != NSAlertFirstButtonReturn);\r
+ sfree(state->host);\r
+ sfree(state->keytype);\r
+ sfree(state->keystr);\r
+ sfree(state);\r
+}\r
+\r
+int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,\r
+ char *keystr, char *fingerprint,\r
+ void (*callback)(void *ctx, int result), void *ctx)\r
+{\r
+ static const char absenttxt[] =\r
+ "The server's host key is not cached. You have no guarantee "\r
+ "that the server is the computer you think it is.\n"\r
+ "The server's %s key fingerprint is:\n"\r
+ "%s\n"\r
+ "If you trust this host, press \"Accept\" to add the key to "\r
+ "PuTTY's cache and carry on connecting.\n"\r
+ "If you want to carry on connecting just once, without "\r
+ "adding the key to the cache, press \"Connect Once\".\n"\r
+ "If you do not trust this host, press \"Cancel\" to abandon the "\r
+ "connection.";\r
+ static const char wrongtxt[] =\r
+ "WARNING - POTENTIAL SECURITY BREACH!\n"\r
+ "The server's host key does not match the one PuTTY has "\r
+ "cached. This means that either the server administrator "\r
+ "has changed the host key, or you have actually connected "\r
+ "to another computer pretending to be the server.\n"\r
+ "The new %s key fingerprint is:\n"\r
+ "%s\n"\r
+ "If you were expecting this change and trust the new key, "\r
+ "press \"Accept\" to update PuTTY's cache and continue connecting.\n"\r
+ "If you want to carry on connecting but without updating "\r
+ "the cache, press \"Connect Once\".\n"\r
+ "If you want to abandon the connection completely, press "\r
+ "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "\r
+ "safe choice.";\r
+\r
+ int ret;\r
+ char *text;\r
+ SessionWindow *win = (SessionWindow *)frontend;\r
+ struct hostkeystate *state;\r
+ NSAlert *alert;\r
+\r
+ /*\r
+ * Verify the key.\r
+ */\r
+ ret = verify_host_key(host, port, keytype, keystr);\r
+\r
+ if (ret == 0)\r
+ return 1;\r
+\r
+ text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);\r
+\r
+ state = snew(struct hostkeystate);\r
+ state->callback = callback;\r
+ state->ctx = ctx;\r
+ state->host = dupstr(host);\r
+ state->port = port;\r
+ state->keytype = dupstr(keytype);\r
+ state->keystr = dupstr(keystr);\r
+\r
+ alert = [[NSAlert alloc] init];\r
+ [alert setInformativeText:[NSString stringWithCString:text]];\r
+ [alert addButtonWithTitle:@"Cancel"];\r
+ [alert addButtonWithTitle:@"Connect Once"];\r
+ [alert addButtonWithTitle:@"Accept"];\r
+ [win startAlert:alert withCallback:verify_ssh_host_key_callback\r
+ andCtx:state];\r
+\r
+ return -1;\r
+}\r
+\r
+void old_keyfile_warning(void)\r
+{\r
+ /*\r
+ * This should never happen on OS X. We hope.\r
+ */\r
+}\r
+\r
+static void connection_fatal_callback(void *ctx, int result)\r
+{\r
+ SessionWindow *win = (SessionWindow *)ctx;\r
+\r
+ [win endSession:FALSE];\r
+}\r
+\r
+void connection_fatal(void *frontend, char *p, ...)\r
+{\r
+ SessionWindow *win = (SessionWindow *)frontend;\r
+ va_list ap;\r
+ char *msg;\r
+ NSAlert *alert;\r
+\r
+ va_start(ap, p);\r
+ msg = dupvprintf(p, ap);\r
+ va_end(ap);\r
+\r
+ alert = [[NSAlert alloc] init];\r
+ [alert setInformativeText:[NSString stringWithCString:msg]];\r
+ [alert addButtonWithTitle:@"Proceed"];\r
+ [win startAlert:alert withCallback:connection_fatal_callback\r
+ andCtx:win];\r
+}\r