Sebastian Kuschel reports that pfd_closing can be called for a socket
[u/mdw/putty] / macosx / osxdlg.m
CommitLineData
1ddda1ca 1/*
2 * osxdlg.m: various PuTTY dialog boxes for OS X.
3 */
4
5#import <Cocoa/Cocoa.h>
6#include "putty.h"
7#include "storage.h"
8#include "dialog.h"
9#include "osxclass.h"
10
11/*
12 * The `ConfigWindow' class is used to start up a new PuTTY
13 * session.
14 */
15
16@class ConfigTree;
17@interface ConfigTree : NSObject
18{
19 NSString **paths;
20 int *levels;
21 int nitems, itemsize;
22}
23- (void)addPath:(char *)path;
24@end
25
26@implementation ConfigTree
27- (id)init
28{
29 self = [super init];
30 paths = NULL;
31 levels = NULL;
32 nitems = itemsize = 0;
33 return self;
34}
35- (void)addPath:(char *)path
36{
37 if (nitems >= itemsize) {
38 itemsize += 32;
39 paths = sresize(paths, itemsize, NSString *);
40 levels = sresize(levels, itemsize, int);
41 }
42 paths[nitems] = [[NSString stringWithCString:path] retain];
43 levels[nitems] = ctrl_path_elements(path) - 1;
44 nitems++;
45}
46- (void)dealloc
47{
48 int i;
49
50 for (i = 0; i < nitems; i++)
51 [paths[i] release];
52
53 sfree(paths);
54 sfree(levels);
55
56 [super dealloc];
57}
58- (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count
59{
60 int i, plevel;
61
62 if (item) {
63 for (i = 0; i < nitems; i++)
64 if (paths[i] == item)
65 break;
66 assert(i < nitems);
67 plevel = levels[i];
68 i++;
69 } else {
70 i = 0;
71 plevel = -1;
72 }
73
74 if (count)
75 *count = 0;
76
77 while (index > 0) {
78 if (i >= nitems || levels[i] != plevel+1)
79 return nil;
80 if (count)
81 (*count)++;
82 do {
83 i++;
84 } while (i < nitems && levels[i] > plevel+1);
85 index--;
86 }
87
88 return paths[i];
89}
90- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
91{
92 return [self iterateChildren:index ofItem:item count:NULL];
93}
94- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
95{
96 int count = 0;
97 /* pass nitems+1 to ensure we run off the end */
98 [self iterateChildren:nitems+1 ofItem:item count:&count];
99 return count;
100}
101- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
102{
103 return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
104}
105- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
106{
107 /*
108 * Trim off all path elements except the last one.
109 */
110 NSArray *components = [item componentsSeparatedByString:@"/"];
111 return [components objectAtIndex:[components count]-1];
112}
113@end
114
115@implementation ConfigWindow
116- (id)initWithConfig:(Config)aCfg
117{
118 NSScrollView *scrollview;
119 NSTableColumn *col;
120 ConfigTree *treedata;
121 int by = 0, mby = 0;
122 int wmin = 0;
123 int hmin = 0;
124 int panelht = 0;
125
1ddda1ca 126 ctrlbox = ctrl_new_box();
12745e35 127 setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol,
1ddda1ca 128 0 /* protcfginfo */);
bc6cd8b6 129 unix_setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol);
1ddda1ca 130
131 cfg = aCfg; /* structure copy */
132
133 self = [super initWithContentRect:NSMakeRect(0,0,300,300)
134 styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
135 NSClosableWindowMask)
136 backing:NSBackingStoreBuffered
137 defer:YES];
138 [self setTitle:@"PuTTY Configuration"];
139
140 [self setIgnoresMouseEvents:NO];
141
142 dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
143
144 scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
145 treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
146 [scrollview setBorderType:NSLineBorder];
147 [scrollview setDocumentView:treeview];
148 [[self contentView] addSubview:scrollview];
149 [scrollview setHasVerticalScroller:YES];
150 [scrollview setAutohidesScrollers:YES];
151 /* FIXME: the below is untested. Test it then remove this notice. */
152 [treeview setAllowsColumnReordering:NO];
153 [treeview setAllowsColumnResizing:NO];
154 [treeview setAllowsMultipleSelection:NO];
155 [treeview setAllowsEmptySelection:NO];
156 [treeview setAllowsColumnSelection:YES];
157
158 treedata = [[[ConfigTree alloc] init] retain];
159
160 col = [[NSTableColumn alloc] initWithIdentifier:nil];
161 [treeview addTableColumn:col];
162 [treeview setOutlineTableColumn:col];
163
164 [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
165
166 /*
167 * Create the controls.
168 */
169 {
170 int i;
171 char *path = NULL;
172
173 for (i = 0; i < ctrlbox->nctrlsets; i++) {
174 struct controlset *s = ctrlbox->ctrlsets[i];
175 int mw, mh;
176
177 if (!*s->pathname) {
178
179 create_ctrls(dv, [self contentView], s, &mw, &mh);
180
181 by += 20 + mh;
182
183 if (wmin < mw + 40)
184 wmin = mw + 40;
185 } else {
186 int j = path ? ctrl_path_compare(s->pathname, path) : 0;
187
188 if (j != INT_MAX) { /* add to treeview, start new panel */
189 char *c;
190
191 /*
192 * We expect never to find an implicit path
193 * component. For example, we expect never to
194 * see A/B/C followed by A/D/E, because that
195 * would _implicitly_ create A/D. All our path
196 * prefixes are expected to contain actual
197 * controls and be selectable in the treeview;
198 * so we would expect to see A/D _explicitly_
199 * before encountering A/D/E.
200 */
201 assert(j == ctrl_path_elements(s->pathname) - 1);
202
203 c = strrchr(s->pathname, '/');
204 if (!c)
205 c = s->pathname;
206 else
207 c++;
208
209 [treedata addPath:s->pathname];
210 path = s->pathname;
211
212 panelht = 0;
213 }
214
215 create_ctrls(dv, [self contentView], s, &mw, &mh);
216 if (wmin < mw + 3*20+150)
217 wmin = mw + 3*20+150;
218 panelht += mh + 20;
219 if (hmin < panelht - 20)
220 hmin = panelht - 20;
221 }
222 }
223 }
224
225 {
226 int i;
227 NSRect r;
228
229 [treeview setDataSource:treedata];
230 for (i = [treeview numberOfRows]; i-- ;)
231 [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
232
233 [treeview sizeToFit];
234 r = [treeview frame];
235 if (hmin < r.size.height)
236 hmin = r.size.height;
237 }
238
239 [self setContentSize:NSMakeSize(wmin, hmin+60+by)];
240 [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
241 [treeview setDelegate:self];
242 mby = by;
243
244 /*
245 * Now place the controls.
246 */
247 {
248 int i;
249 char *path = NULL;
250 panelht = 0;
251
252 for (i = 0; i < ctrlbox->nctrlsets; i++) {
253 struct controlset *s = ctrlbox->ctrlsets[i];
254
255 if (!*s->pathname) {
256 by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
257 } else {
258 if (!path || strcmp(s->pathname, path))
259 panelht = 0;
260
261 panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
262 40+mby+hmin-panelht,
263 wmin - (3*20+150));
264
265 path = s->pathname;
266 }
267 }
268 }
269
270 select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
271
272 [treeview reloadData];
273
274 dlg_refresh(NULL, dv);
275
276 [self center]; /* :-) */
277
278 return self;
279}
280- (void)configBoxFinished:(id)object
281{
282 int ret = [object intValue]; /* it'll be an NSNumber */
283 if (ret) {
284 [controller performSelectorOnMainThread:
285 @selector(newSessionWithConfig:)
286 withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
287 waitUntilDone:NO];
288 }
289 [self close];
290}
291- (void)outlineViewSelectionDidChange:(NSNotification *)notification
292{
293 const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
294 select_panel(dv, ctrlbox, path);
295}
296- (BOOL)outlineView:(NSOutlineView *)outlineView
297 shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
298{
299 return NO; /* no editing! */
300}
301@end
302
303/* ----------------------------------------------------------------------
304 * Various special-purpose dialog boxes.
305 */
306
919baedb 307struct appendstate {
308 void (*callback)(void *ctx, int result);
309 void *ctx;
310};
311
312static void askappend_callback(void *ctx, int result)
313{
314 struct appendstate *state = (struct appendstate *)ctx;
315
316 state->callback(state->ctx, (result == NSAlertFirstButtonReturn ? 2 :
317 result == NSAlertSecondButtonReturn ? 1 : 0));
318 sfree(state);
319}
320
321int askappend(void *frontend, Filename filename,
322 void (*callback)(void *ctx, int result), void *ctx)
1ddda1ca 323{
919baedb 324 static const char msgtemplate[] =
325 "The session log file \"%s\" already exists. "
326 "You can overwrite it with a new session log, "
327 "append your session log to the end of it, "
328 "or disable session logging for this session.";
329
330 char *text;
331 SessionWindow *win = (SessionWindow *)frontend;
332 struct appendstate *state;
333 NSAlert *alert;
334
335 text = dupprintf(msgtemplate, filename.path);
336
337 state = snew(struct appendstate);
338 state->callback = callback;
339 state->ctx = ctx;
340
341 alert = [[NSAlert alloc] init];
342 [alert setInformativeText:[NSString stringWithCString:text]];
343 [alert addButtonWithTitle:@"Overwrite"];
344 [alert addButtonWithTitle:@"Append"];
345 [alert addButtonWithTitle:@"Disable"];
346 [win startAlert:alert withCallback:askappend_callback andCtx:state];
347
348 return -1;
1ddda1ca 349}
350
3d9449a1 351struct algstate {
352 void (*callback)(void *ctx, int result);
353 void *ctx;
354};
355
356static void askalg_callback(void *ctx, int result)
357{
358 struct algstate *state = (struct algstate *)ctx;
359
4763c1c2 360 state->callback(state->ctx, result == NSAlertFirstButtonReturn);
3d9449a1 361 sfree(state);
362}
363
364int askalg(void *frontend, const char *algtype, const char *algname,
365 void (*callback)(void *ctx, int result), void *ctx)
366{
367 static const char msg[] =
368 "The first %s supported by the server is "
369 "%s, which is below the configured warning threshold.\n"
370 "Continue with connection?";
371
372 char *text;
373 SessionWindow *win = (SessionWindow *)frontend;
374 struct algstate *state;
375 NSAlert *alert;
376
377 text = dupprintf(msg, algtype, algname);
378
379 state = snew(struct algstate);
380 state->callback = callback;
381 state->ctx = ctx;
382
4763c1c2 383 alert = [[NSAlert alloc] init];
3d9449a1 384 [alert setInformativeText:[NSString stringWithCString:text]];
385 [alert addButtonWithTitle:@"Yes"];
386 [alert addButtonWithTitle:@"No"];
387 [win startAlert:alert withCallback:askalg_callback andCtx:state];
388
389 return -1;
390}
391
392struct hostkeystate {
393 char *host, *keytype, *keystr;
394 int port;
395 void (*callback)(void *ctx, int result);
396 void *ctx;
397};
398
399static void verify_ssh_host_key_callback(void *ctx, int result)
1ddda1ca 400{
3d9449a1 401 struct hostkeystate *state = (struct hostkeystate *)ctx;
402
403 if (result == NSAlertThirdButtonReturn) /* `Accept' */
404 store_host_key(state->host, state->port,
405 state->keytype, state->keystr);
406 state->callback(state->ctx, result != NSAlertFirstButtonReturn);
407 sfree(state->host);
408 sfree(state->keytype);
409 sfree(state->keystr);
410 sfree(state);
1ddda1ca 411}
412
3d9449a1 413int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
414 char *keystr, char *fingerprint,
415 void (*callback)(void *ctx, int result), void *ctx)
1ddda1ca 416{
3d9449a1 417 static const char absenttxt[] =
418 "The server's host key is not cached. You have no guarantee "
419 "that the server is the computer you think it is.\n"
420 "The server's %s key fingerprint is:\n"
421 "%s\n"
422 "If you trust this host, press \"Accept\" to add the key to "
423 "PuTTY's cache and carry on connecting.\n"
424 "If you want to carry on connecting just once, without "
425 "adding the key to the cache, press \"Connect Once\".\n"
426 "If you do not trust this host, press \"Cancel\" to abandon the "
427 "connection.";
428 static const char wrongtxt[] =
429 "WARNING - POTENTIAL SECURITY BREACH!\n"
430 "The server's host key does not match the one PuTTY has "
431 "cached. This means that either the server administrator "
432 "has changed the host key, or you have actually connected "
433 "to another computer pretending to be the server.\n"
434 "The new %s key fingerprint is:\n"
435 "%s\n"
436 "If you were expecting this change and trust the new key, "
437 "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
438 "If you want to carry on connecting but without updating "
439 "the cache, press \"Connect Once\".\n"
440 "If you want to abandon the connection completely, press "
441 "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
442 "safe choice.";
443
1ddda1ca 444 int ret;
3d9449a1 445 char *text;
446 SessionWindow *win = (SessionWindow *)frontend;
447 struct hostkeystate *state;
448 NSAlert *alert;
1ddda1ca 449
450 /*
451 * Verify the key.
452 */
453 ret = verify_host_key(host, port, keytype, keystr);
454
455 if (ret == 0)
3d9449a1 456 return 1;
457
458 text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
459
460 state = snew(struct hostkeystate);
461 state->callback = callback;
462 state->ctx = ctx;
463 state->host = dupstr(host);
464 state->port = port;
465 state->keytype = dupstr(keytype);
466 state->keystr = dupstr(keystr);
467
468 alert = [[NSAlert alloc] init];
469 [alert setInformativeText:[NSString stringWithCString:text]];
470 [alert addButtonWithTitle:@"Cancel"];
471 [alert addButtonWithTitle:@"Connect Once"];
472 [alert addButtonWithTitle:@"Accept"];
473 [win startAlert:alert withCallback:verify_ssh_host_key_callback
474 andCtx:state];
475
476 return -1;
1ddda1ca 477}
478
479void old_keyfile_warning(void)
480{
481 /*
482 * This should never happen on OS X. We hope.
483 */
484}
485
ec5da310 486static void connection_fatal_callback(void *ctx, int result)
1ddda1ca 487{
ec5da310 488 SessionWindow *win = (SessionWindow *)ctx;
489
490 [win endSession:FALSE];
491}
492
493void connection_fatal(void *frontend, char *p, ...)
494{
495 SessionWindow *win = (SessionWindow *)frontend;
496 va_list ap;
497 char *msg;
498 NSAlert *alert;
499
500 va_start(ap, p);
501 msg = dupvprintf(p, ap);
502 va_end(ap);
503
504 alert = [[NSAlert alloc] init];
505 [alert setInformativeText:[NSString stringWithCString:msg]];
506 [alert addButtonWithTitle:@"Proceed"];
507 [win startAlert:alert withCallback:connection_fatal_callback
508 andCtx:win];
1ddda1ca 509}