1 /*
2  * gtkmain.c: the common main-program code between the straight-up
3  * Unix PuTTY and pterm, which they do not share with the
4  * multi-session gtkapp.c.
5  */
6 
7 #define _GNU_SOURCE
8 
9 #include <string.h>
10 #include <assert.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <signal.h>
14 #include <stdio.h>
15 #include <time.h>
16 #include <errno.h>
17 #include <locale.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 #include <gtk/gtk.h>
23 #if !GTK_CHECK_VERSION(3,0,0)
24 #include <gdk/gdkkeysyms.h>
25 #endif
26 
27 #if GTK_CHECK_VERSION(2,0,0)
28 #include <gtk/gtkimmodule.h>
29 #endif
30 
31 #define MAY_REFER_TO_GTK_IN_HEADERS
32 
33 #include "putty.h"
34 #include "terminal.h"
35 #include "gtkcompat.h"
36 #include "gtkfont.h"
37 #include "gtkmisc.h"
38 
39 #ifndef NOT_X_WINDOWS
40 #include <gdk/gdkx.h>
41 #include <X11/Xlib.h>
42 #include <X11/Xutil.h>
43 #include <X11/Xatom.h>
44 #include "x11misc.h"
45 #endif
46 
47 static char *progname, **gtkargvstart;
48 static int ngtkargs;
49 
50 static const char *app_name = "pterm";
51 
x_get_default(const char * key)52 char *x_get_default(const char *key)
53 {
54 #ifndef NOT_X_WINDOWS
55     Display *disp;
56     if ((disp = get_x11_display()) == NULL)
57         return NULL;
58     return XGetDefault(disp, app_name, key);
59 #else
60     return NULL;
61 #endif
62 }
63 
fork_and_exec_self(int fd_to_close,...)64 void fork_and_exec_self(int fd_to_close, ...)
65 {
66     /*
67      * Re-execing ourself is not an exact science under Unix. I do
68      * the best I can by using /proc/self/exe if available and by
69      * assuming argv[0] can be found on $PATH if not.
70      *
71      * Note that we also have to reconstruct the elements of the
72      * original argv which gtk swallowed, since the user wants the
73      * new session to appear on the same X display as the old one.
74      */
75     char **args;
76     va_list ap;
77     int i, n;
78     int pid;
79 
80     /*
81      * Collect the arguments with which to re-exec ourself.
82      */
83     va_start(ap, fd_to_close);
84     n = 2;                             /* progname and terminating NULL */
85     n += ngtkargs;
86     while (va_arg(ap, char *) != NULL)
87         n++;
88     va_end(ap);
89 
90     args = snewn(n, char *);
91     args[0] = progname;
92     args[n-1] = NULL;
93     for (i = 0; i < ngtkargs; i++)
94         args[i+1] = gtkargvstart[i];
95 
96     i++;
97     va_start(ap, fd_to_close);
98     while ((args[i++] = va_arg(ap, char *)) != NULL);
99     va_end(ap);
100 
101     assert(i == n);
102 
103     /*
104      * Do the double fork.
105      */
106     pid = fork();
107     if (pid < 0) {
108         perror("fork");
109         sfree(args);
110         return;
111     }
112 
113     if (pid == 0) {
114         int pid2 = fork();
115         if (pid2 < 0) {
116             perror("fork");
117             _exit(1);
118         } else if (pid2 > 0) {
119             /*
120              * First child has successfully forked second child. My
121              * Work Here Is Done. Note the use of _exit rather than
122              * exit: the latter appears to cause destroy messages
123              * to be sent to the X server. I suspect gtk uses
124              * atexit.
125              */
126             _exit(0);
127         }
128 
129         /*
130          * If we reach here, we are the second child, so we now
131          * actually perform the exec.
132          */
133         if (fd_to_close >= 0)
134             close(fd_to_close);
135 
136         execv("/proc/self/exe", args);
137         execvp(progname, args);
138         perror("exec");
139         _exit(127);
140 
141     } else {
142         int status;
143         sfree(args);
144         waitpid(pid, &status, 0);
145     }
146 
147 }
148 
launch_duplicate_session(Conf * conf)149 void launch_duplicate_session(Conf *conf)
150 {
151     /*
152      * For this feature we must marshal conf and (possibly) pty_argv
153      * into a byte stream, create a pipe, and send this byte stream
154      * to the child through the pipe.
155      */
156     int i, ret;
157     strbuf *serialised;
158     char option[80];
159     int pipefd[2];
160 
161     if (pipe(pipefd) < 0) {
162         perror("pipe");
163         return;
164     }
165 
166     serialised = strbuf_new();
167 
168     conf_serialise(BinarySink_UPCAST(serialised), conf);
169     if (use_pty_argv && pty_argv)
170         for (i = 0; pty_argv[i]; i++)
171             put_asciz(serialised, pty_argv[i]);
172 
173     sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len);
174     noncloexec(pipefd[0]);
175     fork_and_exec_self(pipefd[1], option, NULL);
176     close(pipefd[0]);
177 
178     i = ret = 0;
179     while (i < serialised->len &&
180            (ret = write(pipefd[1], serialised->s + i,
181                         serialised->len - i)) > 0)
182         i += ret;
183     if (ret < 0)
184         perror("write to pipe");
185     close(pipefd[1]);
186     strbuf_free(serialised);
187 }
188 
launch_new_session(void)189 void launch_new_session(void)
190 {
191     fork_and_exec_self(-1, NULL);
192 }
193 
launch_saved_session(const char * str)194 void launch_saved_session(const char *str)
195 {
196     fork_and_exec_self(-1, "-load", str, NULL);
197 }
198 
read_dupsession_data(Conf * conf,char * arg)199 int read_dupsession_data(Conf *conf, char *arg)
200 {
201     int fd, i, ret, size;
202     char *data;
203     BinarySource src[1];
204 
205     if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
206         fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
207         exit(1);
208     }
209 
210     data = snewn(size, char);
211     i = ret = 0;
212     while (i < size && (ret = read(fd, data + i, size - i)) > 0)
213         i += ret;
214     if (ret < 0) {
215         perror("read from pipe");
216         exit(1);
217     } else if (i < size) {
218         fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
219                 appname);
220         exit(1);
221     }
222 
223     BinarySource_BARE_INIT(src, data, size);
224     if (!conf_deserialise(conf, src)) {
225         fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
226         exit(1);
227     }
228     if (use_pty_argv) {
229         int pty_argc = 0;
230         size_t argv_startpos = src->pos;
231 
232         while (get_asciz(src), !get_err(src))
233             pty_argc++;
234 
235         src->err = BSE_NO_ERROR;
236 
237         if (pty_argc > 0) {
238             src->pos = argv_startpos;
239 
240             pty_argv = snewn(pty_argc + 1, char *);
241             pty_argv[pty_argc] = NULL;
242             for (i = 0; i < pty_argc; i++)
243                 pty_argv[i] = dupstr(get_asciz(src));
244         }
245     }
246 
247     if (get_err(src) || get_avail(src) > 0) {
248         fprintf(stderr, "%s: malformed Duplicate Session data\n", appname);
249         exit(1);
250     }
251 
252     sfree(data);
253     return 0;
254 }
255 
help(FILE * fp)256 static void help(FILE *fp) {
257     if(fprintf(fp,
258 "pterm option summary:\n"
259 "\n"
260 "  --display DISPLAY         Specify X display to use (note '--')\n"
261 "  -name PREFIX              Prefix when looking up resources (default: pterm)\n"
262 "  -fn FONT                  Normal text font\n"
263 "  -fb FONT                  Bold text font\n"
264 "  -geometry GEOMETRY        Position and size of window (size in characters)\n"
265 "  -sl LINES                 Number of lines of scrollback\n"
266 "  -fg COLOUR, -bg COLOUR    Foreground/background colour\n"
267 "  -bfg COLOUR, -bbg COLOUR  Foreground/background bold colour\n"
268 "  -cfg COLOUR, -bfg COLOUR  Foreground/background cursor colour\n"
269 "  -T TITLE                  Window title\n"
270 "  -ut, +ut                  Do(default) or do not update utmp\n"
271 "  -ls, +ls                  Do(default) or do not make shell a login shell\n"
272 "  -sb, +sb                  Do(default) or do not display a scrollbar\n"
273 "  -log PATH, -sessionlog PATH  Log all output to a file\n"
274 "  -nethack                  Map numeric keypad to hjklyubn direction keys\n"
275 "  -xrm RESOURCE-STRING      Set an X resource\n"
276 "  -e COMMAND [ARGS...]      Execute command (consumes all remaining args)\n"
277          ) < 0 || fflush(fp) < 0) {
278         perror("output error");
279         exit(1);
280     }
281 }
282 
version(FILE * fp)283 static void version(FILE *fp) {
284     char *buildinfo_text = buildinfo("\n");
285     if(fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 ||
286        fflush(fp) < 0) {
287         perror("output error");
288         exit(1);
289     }
290     sfree(buildinfo_text);
291 }
292 
293 static const char *geometry_string;
294 
cmdline_error(const char * p,...)295 void cmdline_error(const char *p, ...)
296 {
297     va_list ap;
298     fprintf(stderr, "%s: ", appname);
299     va_start(ap, p);
300     vfprintf(stderr, p, ap);
301     va_end(ap);
302     fputc('\n', stderr);
303     exit(1);
304 }
305 
window_setup_error(const char * errmsg)306 void window_setup_error(const char *errmsg)
307 {
308     fprintf(stderr, "%s: %s\n", appname, errmsg);
309     exit(1);
310 }
311 
do_cmdline(int argc,char ** argv,bool do_everything,Conf * conf)312 bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
313 {
314     bool err = false;
315     char *val;
316 
317     /*
318      * Macros to make argument handling easier.
319      *
320      * Note that because they need to call `continue', they cannot be
321      * contained in the usual do {...} while (0) wrapper to make them
322      * syntactically single statements. I use the alternative if (1)
323      * {...} else ((void)0).
324      */
325 #define EXPECTS_ARG if (1) {                                            \
326         if (--argc <= 0) {                                              \
327             err = true;                                                 \
328             fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
329             continue;                                                   \
330         } else                                                          \
331             val = *++argv;                                              \
332     } else ((void)0)
333 #define SECOND_PASS_ONLY if (1) {               \
334         if (!do_everything)                     \
335             continue;                           \
336     } else ((void)0)
337 
338     while (--argc > 0) {
339         const char *p = *++argv;
340         int ret;
341 
342         /*
343          * Shameless cheating. Debian requires all X terminal
344          * emulators to support `-T title'; but
345          * cmdline_process_param will eat -T (it means no-pty) and
346          * complain that pterm doesn't support it. So, in pterm
347          * only, we convert -T into -title.
348          */
349         if ((cmdline_tooltype & TOOLTYPE_NONNETWORK) &&
350             !strcmp(p, "-T"))
351             p = "-title";
352 
353         ret = cmdline_process_param(p, (argc > 1 ? argv[1] : NULL),
354                                     do_everything ? 1 : -1, conf);
355 
356         if (ret == -2) {
357             cmdline_error("option \"%s\" requires an argument", p);
358         } else if (ret == 2) {
359             --argc, ++argv;            /* skip next argument */
360             continue;
361         } else if (ret == 1) {
362             continue;
363         }
364 
365         if (!strcmp(p, "-fn") || !strcmp(p, "-font")) {
366             FontSpec *fs;
367             EXPECTS_ARG;
368             SECOND_PASS_ONLY;
369             fs = fontspec_new(val);
370             conf_set_fontspec(conf, CONF_font, fs);
371             fontspec_free(fs);
372 
373         } else if (!strcmp(p, "-fb")) {
374             FontSpec *fs;
375             EXPECTS_ARG;
376             SECOND_PASS_ONLY;
377             fs = fontspec_new(val);
378             conf_set_fontspec(conf, CONF_boldfont, fs);
379             fontspec_free(fs);
380 
381         } else if (!strcmp(p, "-fw")) {
382             FontSpec *fs;
383             EXPECTS_ARG;
384             SECOND_PASS_ONLY;
385             fs = fontspec_new(val);
386             conf_set_fontspec(conf, CONF_widefont, fs);
387             fontspec_free(fs);
388 
389         } else if (!strcmp(p, "-fwb")) {
390             FontSpec *fs;
391             EXPECTS_ARG;
392             SECOND_PASS_ONLY;
393             fs = fontspec_new(val);
394             conf_set_fontspec(conf, CONF_wideboldfont, fs);
395             fontspec_free(fs);
396 
397         } else if (!strcmp(p, "-cs")) {
398             EXPECTS_ARG;
399             SECOND_PASS_ONLY;
400             conf_set_str(conf, CONF_line_codepage, val);
401 
402         } else if (!strcmp(p, "-geometry")) {
403             EXPECTS_ARG;
404             SECOND_PASS_ONLY;
405             geometry_string = val;
406         } else if (!strcmp(p, "-sl")) {
407             EXPECTS_ARG;
408             SECOND_PASS_ONLY;
409             conf_set_int(conf, CONF_savelines, atoi(val));
410 
411         } else if (!strcmp(p, "-fg") || !strcmp(p, "-bg") ||
412                    !strcmp(p, "-bfg") || !strcmp(p, "-bbg") ||
413                    !strcmp(p, "-cfg") || !strcmp(p, "-cbg")) {
414             EXPECTS_ARG;
415             SECOND_PASS_ONLY;
416 
417             {
418 #if GTK_CHECK_VERSION(3,0,0)
419                 GdkRGBA rgba;
420                 bool success = gdk_rgba_parse(&rgba, val);
421 #else
422                 GdkColor col;
423                 bool success = gdk_color_parse(val, &col);
424 #endif
425 
426                 if (!success) {
427                     err = true;
428                     fprintf(stderr, "%s: unable to parse colour \"%s\"\n",
429                             appname, val);
430                 } else {
431 #if GTK_CHECK_VERSION(3,0,0)
432                     int r = rgba.red * 255;
433                     int g = rgba.green * 255;
434                     int b = rgba.blue * 255;
435 #else
436                     int r = col.red / 256;
437                     int g = col.green / 256;
438                     int b = col.blue / 256;
439 #endif
440 
441                     int index;
442                     index = (!strcmp(p, "-fg") ? 0 :
443                              !strcmp(p, "-bg") ? 2 :
444                              !strcmp(p, "-bfg") ? 1 :
445                              !strcmp(p, "-bbg") ? 3 :
446                              !strcmp(p, "-cfg") ? 4 :
447                              !strcmp(p, "-cbg") ? 5 : -1);
448                     assert(index != -1);
449 
450                     conf_set_int_int(conf, CONF_colours, index*3+0, r);
451                     conf_set_int_int(conf, CONF_colours, index*3+1, g);
452                     conf_set_int_int(conf, CONF_colours, index*3+2, b);
453                 }
454             }
455 
456         } else if (use_pty_argv && !strcmp(p, "-e")) {
457             /* This option swallows all further arguments. */
458             if (!do_everything)
459                 break;
460 
461             if (--argc > 0) {
462                 int i;
463                 pty_argv = snewn(argc+1, char *);
464                 ++argv;
465                 for (i = 0; i < argc; i++)
466                     pty_argv[i] = argv[i];
467                 pty_argv[argc] = NULL;
468                 break;                 /* finished command-line processing */
469             } else
470                 err = true, fprintf(stderr, "%s: -e expects an argument\n",
471                                  appname);
472 
473         } else if (!strcmp(p, "-title")) {
474             EXPECTS_ARG;
475             SECOND_PASS_ONLY;
476             conf_set_str(conf, CONF_wintitle, val);
477 
478         } else if (!strcmp(p, "-log")) {
479             Filename *fn;
480             EXPECTS_ARG;
481             SECOND_PASS_ONLY;
482             fn = filename_from_str(val);
483             conf_set_filename(conf, CONF_logfilename, fn);
484             conf_set_int(conf, CONF_logtype, LGTYP_DEBUG);
485             filename_free(fn);
486 
487         } else if (!strcmp(p, "-ut-") || !strcmp(p, "+ut")) {
488             SECOND_PASS_ONLY;
489             conf_set_bool(conf, CONF_stamp_utmp, false);
490 
491         } else if (!strcmp(p, "-ut")) {
492             SECOND_PASS_ONLY;
493             conf_set_bool(conf, CONF_stamp_utmp, true);
494 
495         } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) {
496             SECOND_PASS_ONLY;
497             conf_set_bool(conf, CONF_login_shell, false);
498 
499         } else if (!strcmp(p, "-ls")) {
500             SECOND_PASS_ONLY;
501             conf_set_bool(conf, CONF_login_shell, true);
502 
503         } else if (!strcmp(p, "-nethack")) {
504             SECOND_PASS_ONLY;
505             conf_set_bool(conf, CONF_nethack_keypad, true);
506 
507         } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) {
508             SECOND_PASS_ONLY;
509             conf_set_bool(conf, CONF_scrollbar, false);
510 
511         } else if (!strcmp(p, "-sb")) {
512             SECOND_PASS_ONLY;
513             conf_set_bool(conf, CONF_scrollbar, true);
514 
515         } else if (!strcmp(p, "-name")) {
516             EXPECTS_ARG;
517             app_name = val;
518 
519         } else if (!strcmp(p, "-xrm")) {
520             EXPECTS_ARG;
521             provide_xrm_string(val, appname);
522 
523         } else if(!strcmp(p, "-help") || !strcmp(p, "--help")) {
524             help(stdout);
525             exit(0);
526 
527         } else if(!strcmp(p, "-version") || !strcmp(p, "--version")) {
528             version(stdout);
529             exit(0);
530 
531         } else if (!strcmp(p, "-pgpfp")) {
532             pgp_fingerprints();
533             exit(1);
534 
535         } else if (p[0] != '-') {
536             /* Non-option arguments not handled by cmdline.c are errors. */
537             if (do_everything) {
538                 err = true;
539                 fprintf(stderr, "%s: unexpected non-option argument '%s'\n",
540                         appname, p);
541             }
542 
543         } else {
544             err = true;
545             fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p);
546         }
547     }
548 
549     return err;
550 }
551 
make_gtk_toplevel_window(GtkFrontend * frontend)552 GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend)
553 {
554     return gtk_window_new(GTK_WINDOW_TOPLEVEL);
555 }
556 
557 const bool buildinfo_gtk_relevant = true;
558 
559 struct post_initial_config_box_ctx {
560     Conf *conf;
561     const char *geometry_string;
562 };
563 
post_initial_config_box(void * vctx,int result)564 static void post_initial_config_box(void *vctx, int result)
565 {
566     struct post_initial_config_box_ctx ctx =
567         *(struct post_initial_config_box_ctx *)vctx;
568     sfree(vctx);
569 
570     if (result > 0) {
571         new_session_window(ctx.conf, ctx.geometry_string);
572     } else {
573         /* In this main(), which only runs one session in total, a
574          * negative result from the initial config box means we simply
575          * terminate. */
576         conf_free(ctx.conf);
577         gtk_main_quit();
578     }
579 }
580 
session_window_closed(void)581 void session_window_closed(void)
582 {
583     gtk_main_quit();
584 }
585 
main(int argc,char ** argv)586 int main(int argc, char **argv)
587 {
588     Conf *conf;
589     bool need_config_box;
590 
591     setlocale(LC_CTYPE, "");
592 
593     /* Call the function in ux{putty,pterm}.c to do app-type
594      * specific setup */
595     setup(true);         /* true means we are a one-session process */
596 
597     progname = argv[0];
598 
599     /*
600      * Copy the original argv before letting gtk_init fiddle with
601      * it. It will be required later.
602      */
603     {
604         int i, oldargc;
605         gtkargvstart = snewn(argc-1, char *);
606         for (i = 1; i < argc; i++)
607             gtkargvstart[i-1] = dupstr(argv[i]);
608         oldargc = argc;
609         gtk_init(&argc, &argv);
610         ngtkargs = oldargc - argc;
611     }
612 
613     conf = conf_new();
614 
615     gtkcomm_setup();
616 
617     /*
618      * Block SIGPIPE: if we attempt Duplicate Session or similar and
619      * it falls over in some way, we certainly don't want SIGPIPE
620      * terminating the main pterm/PuTTY. However, we'll have to
621      * unblock it again when pterm forks.
622      */
623     block_signal(SIGPIPE, true);
624 
625     if (argc > 1 && !strncmp(argv[1], "---", 3)) {
626         read_dupsession_data(conf, argv[1]);
627         /* Splatter this argument so it doesn't clutter a ps listing */
628         smemclr(argv[1], strlen(argv[1]));
629 
630         assert(!dup_check_launchable || conf_launchable(conf));
631         need_config_box = false;
632     } else {
633         if (do_cmdline(argc, argv, false, conf))
634             exit(1);                   /* pre-defaults pass to get -class */
635         do_defaults(NULL, conf);
636         if (do_cmdline(argc, argv, true, conf))
637             exit(1);                   /* post-defaults, do everything */
638 
639         cmdline_run_saved(conf);
640 
641         if (cmdline_tooltype & TOOLTYPE_HOST_ARG)
642             need_config_box = !cmdline_host_ok(conf);
643         else
644             need_config_box = false;
645     }
646 
647     if (need_config_box) {
648         /*
649          * Put up the initial config box, which will pass the provided
650          * parameters (with conf updated) to new_session_window() when
651          * (if) the user selects Open. Or it might close without
652          * creating a session window, if the user selects Cancel. Or
653          * it might just create the session window immediately if this
654          * is a pterm-style app which doesn't have an initial config
655          * box at all.
656          */
657         struct post_initial_config_box_ctx *ctx =
658             snew(struct post_initial_config_box_ctx);
659         ctx->conf = conf;
660         ctx->geometry_string = geometry_string;
661         initial_config_box(conf, post_initial_config_box, ctx);
662     } else {
663         /*
664          * No initial config needed; just create the session window
665          * now.
666          */
667         new_session_window(conf, geometry_string);
668     }
669 
670     gtk_main();
671 
672     return 0;
673 }
674