]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - macosx/osxdlg.m
Sort out close-on-exit, connection_fatal(), fatalbox(), and
[PuTTY.git] / macosx / osxdlg.m
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
126     get_sesslist(&sl, TRUE);
127
128     ctrlbox = ctrl_new_box();
129     setup_config_box(ctrlbox, &sl, FALSE /*midsession*/, aCfg.protocol,
130                      0 /* protcfginfo */);
131     unix_setup_config_box(ctrlbox, FALSE /*midsession*/);
132
133     cfg = aCfg;                        /* structure copy */
134
135     self = [super initWithContentRect:NSMakeRect(0,0,300,300)
136             styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
137                        NSClosableWindowMask)
138             backing:NSBackingStoreBuffered
139             defer:YES];
140     [self setTitle:@"PuTTY Configuration"];
141
142     [self setIgnoresMouseEvents:NO];
143
144     dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
145
146     scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
147     treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
148     [scrollview setBorderType:NSLineBorder];
149     [scrollview setDocumentView:treeview];
150     [[self contentView] addSubview:scrollview];
151     [scrollview setHasVerticalScroller:YES];
152     [scrollview setAutohidesScrollers:YES];
153     /* FIXME: the below is untested. Test it then remove this notice. */
154     [treeview setAllowsColumnReordering:NO];
155     [treeview setAllowsColumnResizing:NO];
156     [treeview setAllowsMultipleSelection:NO];
157     [treeview setAllowsEmptySelection:NO];
158     [treeview setAllowsColumnSelection:YES];
159
160     treedata = [[[ConfigTree alloc] init] retain];
161
162     col = [[NSTableColumn alloc] initWithIdentifier:nil];
163     [treeview addTableColumn:col];
164     [treeview setOutlineTableColumn:col];
165
166     [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
167
168     /*
169      * Create the controls.
170      */
171     {
172         int i;
173         char *path = NULL;
174
175         for (i = 0; i < ctrlbox->nctrlsets; i++) {
176             struct controlset *s = ctrlbox->ctrlsets[i];
177             int mw, mh;
178
179             if (!*s->pathname) {
180
181                 create_ctrls(dv, [self contentView], s, &mw, &mh);
182
183                 by += 20 + mh;
184
185                 if (wmin < mw + 40)
186                     wmin = mw + 40;
187             } else {
188                 int j = path ? ctrl_path_compare(s->pathname, path) : 0;
189
190                 if (j != INT_MAX) {    /* add to treeview, start new panel */
191                     char *c;
192
193                     /*
194                      * We expect never to find an implicit path
195                      * component. For example, we expect never to
196                      * see A/B/C followed by A/D/E, because that
197                      * would _implicitly_ create A/D. All our path
198                      * prefixes are expected to contain actual
199                      * controls and be selectable in the treeview;
200                      * so we would expect to see A/D _explicitly_
201                      * before encountering A/D/E.
202                      */
203                     assert(j == ctrl_path_elements(s->pathname) - 1);
204
205                     c = strrchr(s->pathname, '/');
206                     if (!c)
207                         c = s->pathname;
208                     else
209                         c++;
210
211                     [treedata addPath:s->pathname];
212                     path = s->pathname;
213
214                     panelht = 0;
215                 }
216
217                 create_ctrls(dv, [self contentView], s, &mw, &mh);
218                 if (wmin < mw + 3*20+150)
219                     wmin = mw + 3*20+150;
220                 panelht += mh + 20;
221                 if (hmin < panelht - 20)
222                     hmin = panelht - 20;
223             }
224         }
225     }
226
227     {
228         int i;
229         NSRect r;
230
231         [treeview setDataSource:treedata];
232         for (i = [treeview numberOfRows]; i-- ;)
233             [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
234
235         [treeview sizeToFit];
236         r = [treeview frame];
237         if (hmin < r.size.height)
238             hmin = r.size.height;
239     }
240
241     [self setContentSize:NSMakeSize(wmin, hmin+60+by)];
242     [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
243     [treeview setDelegate:self];
244     mby = by;
245
246     /*
247      * Now place the controls.
248      */
249     {
250         int i;
251         char *path = NULL;
252         panelht = 0;
253
254         for (i = 0; i < ctrlbox->nctrlsets; i++) {
255             struct controlset *s = ctrlbox->ctrlsets[i];
256
257             if (!*s->pathname) {
258                 by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
259             } else {
260                 if (!path || strcmp(s->pathname, path))
261                     panelht = 0;
262
263                 panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
264                                                   40+mby+hmin-panelht,
265                                                   wmin - (3*20+150));
266
267                 path = s->pathname;
268             }
269         }
270     }
271
272     select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
273
274     [treeview reloadData];
275
276     dlg_refresh(NULL, dv);
277
278     [self center];                     /* :-) */
279
280     return self;
281 }
282 - (void)configBoxFinished:(id)object
283 {
284     int ret = [object intValue];       /* it'll be an NSNumber */
285     if (ret) {
286         [controller performSelectorOnMainThread:
287          @selector(newSessionWithConfig:)
288          withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
289          waitUntilDone:NO];
290     }
291     [self close];
292 }
293 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
294 {
295     const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
296     select_panel(dv, ctrlbox, path);
297 }
298 - (BOOL)outlineView:(NSOutlineView *)outlineView
299     shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
300 {
301     return NO;                         /* no editing! */
302 }
303 @end
304
305 /* ----------------------------------------------------------------------
306  * Various special-purpose dialog boxes.
307  */
308
309 struct appendstate {
310     void (*callback)(void *ctx, int result);
311     void *ctx;
312 };
313
314 static void askappend_callback(void *ctx, int result)
315 {
316     struct appendstate *state = (struct appendstate *)ctx;
317
318     state->callback(state->ctx, (result == NSAlertFirstButtonReturn ? 2 :
319                                  result == NSAlertSecondButtonReturn ? 1 : 0));
320     sfree(state);
321 }
322
323 int askappend(void *frontend, Filename filename,
324               void (*callback)(void *ctx, int result), void *ctx)
325 {
326     static const char msgtemplate[] =
327         "The session log file \"%s\" already exists. "
328         "You can overwrite it with a new session log, "
329         "append your session log to the end of it, "
330         "or disable session logging for this session.";
331
332     char *text;
333     SessionWindow *win = (SessionWindow *)frontend;
334     struct appendstate *state;
335     NSAlert *alert;
336
337     text = dupprintf(msgtemplate, filename.path);
338
339     state = snew(struct appendstate);
340     state->callback = callback;
341     state->ctx = ctx;
342
343     alert = [[NSAlert alloc] init];
344     [alert setInformativeText:[NSString stringWithCString:text]];
345     [alert addButtonWithTitle:@"Overwrite"];
346     [alert addButtonWithTitle:@"Append"];
347     [alert addButtonWithTitle:@"Disable"];
348     [win startAlert:alert withCallback:askappend_callback andCtx:state];
349
350     return -1;
351 }
352
353 struct algstate {
354     void (*callback)(void *ctx, int result);
355     void *ctx;
356 };
357
358 static void askalg_callback(void *ctx, int result)
359 {
360     struct algstate *state = (struct algstate *)ctx;
361
362     state->callback(state->ctx, result == NSAlertFirstButtonReturn);
363     sfree(state);
364 }
365
366 int askalg(void *frontend, const char *algtype, const char *algname,
367            void (*callback)(void *ctx, int result), void *ctx)
368 {
369     static const char msg[] =
370         "The first %s supported by the server is "
371         "%s, which is below the configured warning threshold.\n"
372         "Continue with connection?";
373
374     char *text;
375     SessionWindow *win = (SessionWindow *)frontend;
376     struct algstate *state;
377     NSAlert *alert;
378
379     text = dupprintf(msg, algtype, algname);
380
381     state = snew(struct algstate);
382     state->callback = callback;
383     state->ctx = ctx;
384
385     alert = [[NSAlert alloc] init];
386     [alert setInformativeText:[NSString stringWithCString:text]];
387     [alert addButtonWithTitle:@"Yes"];
388     [alert addButtonWithTitle:@"No"];
389     [win startAlert:alert withCallback:askalg_callback andCtx:state];
390
391     return -1;
392 }
393
394 struct hostkeystate {
395     char *host, *keytype, *keystr;
396     int port;
397     void (*callback)(void *ctx, int result);
398     void *ctx;
399 };
400
401 static void verify_ssh_host_key_callback(void *ctx, int result)
402 {
403     struct hostkeystate *state = (struct hostkeystate *)ctx;
404
405     if (result == NSAlertThirdButtonReturn)   /* `Accept' */
406         store_host_key(state->host, state->port,
407                        state->keytype, state->keystr);
408     state->callback(state->ctx, result != NSAlertFirstButtonReturn);
409     sfree(state->host);
410     sfree(state->keytype);
411     sfree(state->keystr);
412     sfree(state);
413 }
414
415 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
416                         char *keystr, char *fingerprint,
417                         void (*callback)(void *ctx, int result), void *ctx)
418 {
419     static const char absenttxt[] =
420         "The server's host key is not cached. You have no guarantee "
421         "that the server is the computer you think it is.\n"
422         "The server's %s key fingerprint is:\n"
423         "%s\n"
424         "If you trust this host, press \"Accept\" to add the key to "
425         "PuTTY's cache and carry on connecting.\n"
426         "If you want to carry on connecting just once, without "
427         "adding the key to the cache, press \"Connect Once\".\n"
428         "If you do not trust this host, press \"Cancel\" to abandon the "
429         "connection.";
430     static const char wrongtxt[] =
431         "WARNING - POTENTIAL SECURITY BREACH!\n"
432         "The server's host key does not match the one PuTTY has "
433         "cached. This means that either the server administrator "
434         "has changed the host key, or you have actually connected "
435         "to another computer pretending to be the server.\n"
436         "The new %s key fingerprint is:\n"
437         "%s\n"
438         "If you were expecting this change and trust the new key, "
439         "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
440         "If you want to carry on connecting but without updating "
441         "the cache, press \"Connect Once\".\n"
442         "If you want to abandon the connection completely, press "
443         "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
444         "safe choice.";
445
446     int ret;
447     char *text;
448     SessionWindow *win = (SessionWindow *)frontend;
449     struct hostkeystate *state;
450     NSAlert *alert;
451
452     /*
453      * Verify the key.
454      */
455     ret = verify_host_key(host, port, keytype, keystr);
456
457     if (ret == 0)
458         return 1;
459
460     text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
461
462     state = snew(struct hostkeystate);
463     state->callback = callback;
464     state->ctx = ctx;
465     state->host = dupstr(host);
466     state->port = port;
467     state->keytype = dupstr(keytype);
468     state->keystr = dupstr(keystr);
469
470     alert = [[NSAlert alloc] init];
471     [alert setInformativeText:[NSString stringWithCString:text]];
472     [alert addButtonWithTitle:@"Cancel"];
473     [alert addButtonWithTitle:@"Connect Once"];
474     [alert addButtonWithTitle:@"Accept"];
475     [win startAlert:alert withCallback:verify_ssh_host_key_callback
476      andCtx:state];
477
478     return -1;
479 }
480
481 void old_keyfile_warning(void)
482 {
483     /*
484      * This should never happen on OS X. We hope.
485      */
486 }
487
488 static void connection_fatal_callback(void *ctx, int result)
489 {
490     SessionWindow *win = (SessionWindow *)ctx;
491
492     [win endSession:FALSE];
493 }
494
495 void connection_fatal(void *frontend, char *p, ...)
496 {
497     SessionWindow *win = (SessionWindow *)frontend;
498     va_list ap;
499     char *msg;
500     NSAlert *alert;
501
502     va_start(ap, p);
503     msg = dupvprintf(p, ap);
504     va_end(ap);
505
506     alert = [[NSAlert alloc] init];
507     [alert setInformativeText:[NSString stringWithCString:msg]];
508     [alert addButtonWithTitle:@"Proceed"];
509     [win startAlert:alert withCallback:connection_fatal_callback
510      andCtx:win];
511 }