]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - unix/uxpgnt.c
Clean up Unix Pageant's setup and teardown.
[PuTTY.git] / unix / uxpgnt.c
1 /*
2  * Unix Pageant, more or less similar to ssh-agent.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <errno.h>
8 #include <assert.h>
9 #include <signal.h>
10
11 #include <sys/types.h>
12 #include <sys/wait.h>
13 #include <unistd.h>
14
15 #define PUTTY_DO_GLOBALS               /* actually _define_ globals */
16 #include "putty.h"
17 #include "ssh.h"
18 #include "misc.h"
19 #include "pageant.h"
20
21 SockAddr unix_sock_addr(const char *path);
22 Socket new_unix_listener(SockAddr listenaddr, Plug plug);
23
24 void fatalbox(char *p, ...)
25 {
26     va_list ap;
27     fprintf(stderr, "FATAL ERROR: ");
28     va_start(ap, p);
29     vfprintf(stderr, p, ap);
30     va_end(ap);
31     fputc('\n', stderr);
32     exit(1);
33 }
34 void modalfatalbox(char *p, ...)
35 {
36     va_list ap;
37     fprintf(stderr, "FATAL ERROR: ");
38     va_start(ap, p);
39     vfprintf(stderr, p, ap);
40     va_end(ap);
41     fputc('\n', stderr);
42     exit(1);
43 }
44 void nonfatal(char *p, ...)
45 {
46     va_list ap;
47     fprintf(stderr, "ERROR: ");
48     va_start(ap, p);
49     vfprintf(stderr, p, ap);
50     va_end(ap);
51     fputc('\n', stderr);
52 }
53 void connection_fatal(void *frontend, char *p, ...)
54 {
55     va_list ap;
56     fprintf(stderr, "FATAL ERROR: ");
57     va_start(ap, p);
58     vfprintf(stderr, p, ap);
59     va_end(ap);
60     fputc('\n', stderr);
61     exit(1);
62 }
63 void cmdline_error(char *p, ...)
64 {
65     va_list ap;
66     fprintf(stderr, "pageant: ");
67     va_start(ap, p);
68     vfprintf(stderr, p, ap);
69     va_end(ap);
70     fputc('\n', stderr);
71     exit(1);
72 }
73
74 FILE *pageant_logfp = NULL;
75 void pageant_log(void *ctx, const char *fmt, va_list ap)
76 {
77     if (!pageant_logfp)
78         return;
79
80     fprintf(pageant_logfp, "pageant: ");
81     vfprintf(pageant_logfp, fmt, ap);
82     fprintf(pageant_logfp, "\n");
83 }
84
85 /*
86  * In Pageant our selects are synchronous, so these functions are
87  * empty stubs.
88  */
89 int uxsel_input_add(int fd, int rwx) { return 0; }
90 void uxsel_input_remove(int id) { }
91
92 /*
93  * More stubs.
94  */
95 void logevent(void *frontend, const char *string) {}
96 void random_save_seed(void) {}
97 void random_destroy_seed(void) {}
98 void noise_ultralight(unsigned long data) {}
99 char *platform_default_s(const char *name) { return NULL; }
100 int platform_default_i(const char *name, int def) { return def; }
101 FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); }
102 Filename *platform_default_filename(const char *name) { return filename_from_str(""); }
103 char *x_get_default(const char *key) { return NULL; }
104 void old_keyfile_warning(void) {}
105 void timer_change_notify(unsigned long next) {}
106
107 /*
108  * Short description of parameters.
109  */
110 static void usage(void)
111 {
112     printf("Pageant: SSH agent\n");
113     printf("%s\n", ver);
114     printf("FIXME\n");
115     exit(1);
116 }
117
118 static void version(void)
119 {
120     printf("pageant: %s\n", ver);
121     exit(1);
122 }
123
124 void keylist_update(void)
125 {
126     /* Nothing needs doing in Unix Pageant */
127 }
128
129 #define PAGEANT_DIR_PREFIX "/tmp/pageant"
130
131 const char *const appname = "Pageant";
132
133 char *platform_get_x_display(void) {
134     return dupstr(getenv("DISPLAY"));
135 }
136
137 static int time_to_die = FALSE;
138
139 /* Stub functions to permit linking against x11fwd.c. These never get
140  * used, because in LIFE_X11 mode we connect to the X server using a
141  * straightforward Socket and don't try to create an ersatz SSH
142  * forwarding too. */
143 int sshfwd_write(struct ssh_channel *c, char *data, int len) { return 0; }
144 void sshfwd_write_eof(struct ssh_channel *c) { }
145 void sshfwd_unclean_close(struct ssh_channel *c, const char *err) { }
146 void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) {}
147 Conf *sshfwd_get_conf(struct ssh_channel *c) { return NULL; }
148 void sshfwd_x11_sharing_handover(struct ssh_channel *c,
149                                  void *share_cs, void *share_chan,
150                                  const char *peer_addr, int peer_port,
151                                  int endian, int protomajor, int protominor,
152                                  const void *initial_data, int initial_len) {}
153 void sshfwd_x11_is_local(struct ssh_channel *c) {}
154
155 /*
156  * These functions are part of the plug for our connection to the X
157  * display, so they do get called. They needn't actually do anything,
158  * except that x11_closing has to signal back to the main loop that
159  * it's time to terminate.
160  */
161 static void x11_log(Plug p, int type, SockAddr addr, int port,
162                     const char *error_msg, int error_code) {}
163 static int x11_receive(Plug plug, int urgent, char *data, int len) {return 0;}
164 static void x11_sent(Plug plug, int bufsize) {}
165 static int x11_closing(Plug plug, const char *error_msg, int error_code,
166                        int calling_back)
167 {
168     time_to_die = TRUE;
169     return 1;
170 }
171 struct X11Connection {
172     const struct plug_function_table *fn;
173 };
174
175 char *socketname;
176 void pageant_print_env(int pid)
177 {
178     printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n"
179            "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n",
180            socketname, (int)pid);
181 }
182
183 void pageant_fork_and_print_env(void)
184 {
185     pid_t pid = fork();
186     if (pid == -1) {
187         perror("fork");
188         exit(1);
189     } else if (pid != 0) {
190         pageant_print_env(pid);
191         exit(0);
192     }
193
194     /*
195      * Having forked off, we now daemonise ourselves as best we can.
196      * It's good practice in general to setsid() ourself out of any
197      * process group we didn't want to be part of, and to chdir("/")
198      * to avoid holding any directories open that we don't need in
199      * case someone wants to umount them; also, we should definitely
200      * close standard output (because it will very likely be pointing
201      * at a pipe from which some parent process is trying to read our
202      * environment variable dump, so if we hold open another copy of
203      * it then that process will never finish reading). We close
204      * standard input too on general principles, but not standard
205      * error, since we might need to shout a panicky error message
206      * down that one.
207      */
208     if (chdir("/") < 0) {
209         /* should there be an error condition, nothing we can do about
210          * it anyway */
211     }
212     close(0);
213     close(1);
214     setsid();
215 }
216
217 int signalpipe[2];
218
219 void sigchld(int signum)
220 {
221     if (write(signalpipe[1], "x", 1) <= 0)
222         /* not much we can do about it */;
223 }
224
225 int main(int argc, char **argv)
226 {
227     int *fdlist;
228     int fd;
229     int i, fdcount, fdsize, fdstate;
230     int errors;
231     unsigned long now;
232     char *username, *socketdir;
233     const char *err;
234     struct pageant_listen_state *pl;
235     Socket sock;
236     enum {
237         LIFE_UNSPEC, LIFE_X11, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC
238     } life = LIFE_UNSPEC;
239     const char *display = NULL;
240     int doing_opts = TRUE;
241     char **exec_args = NULL;
242     int termination_pid = -1;
243     Conf *conf;
244
245     fdlist = NULL;
246     fdcount = fdsize = 0;
247     errors = FALSE;
248
249     /*
250      * Process the command line.
251      */
252     while (--argc) {
253         char *p = *++argv;
254         if (*p == '-' && doing_opts) {
255             if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
256                 version();
257             } else if (!strcmp(p, "--help")) {
258                 usage();
259                 exit(0);
260             } else if (!strcmp(p, "-v")) {
261                 pageant_logfp = stderr;
262             } else if (!strcmp(p, "-X")) {
263                 life = LIFE_X11;
264             } else if (!strcmp(p, "--debug")) {
265                 life = LIFE_DEBUG;
266             } else if (!strcmp(p, "--permanent")) {
267                 life = LIFE_PERM;
268             } else if (!strcmp(p, "--exec")) {
269                 life = LIFE_EXEC;
270             } else if (!strcmp(p, "--")) {
271                 doing_opts = FALSE;
272             }
273         } else {
274             if (life == LIFE_EXEC) {
275                 exec_args = argv;
276                 break; /* everything else is now args to the exec command */
277             } else {
278                 fprintf(stderr, "pageant: unexpected argument '%s'\n", p);
279                 exit(1);
280             }
281         }
282     }
283
284     if (errors)
285         return 1;
286
287     if (life == LIFE_UNSPEC) {
288         fprintf(stderr, "pageant: expected a lifetime option\n");
289         exit(1);
290     }
291     if (life == LIFE_EXEC && !exec_args) {
292         fprintf(stderr, "pageant: expected a command with --exec\n");
293         exit(1);
294     }
295
296     /*
297      * Block SIGPIPE, so that we'll get EPIPE individually on
298      * particular network connections that go wrong.
299      */
300     putty_signal(SIGPIPE, SIG_IGN);
301
302     sk_init();
303     uxsel_init();
304
305     /*
306      * Set up a listening socket and run Pageant on it.
307      */
308     username = get_username();
309     socketdir = dupprintf("%s.%s", PAGEANT_DIR_PREFIX, username);
310     sfree(username);
311     assert(*socketdir == '/');
312     if ((err = make_dir_and_check_ours(socketdir)) != NULL) {
313         fprintf(stderr, "pageant: %s: %s\n", socketdir, err);
314         exit(1);
315     }
316     socketname = dupprintf("%s/pageant.%d", socketdir, (int)getpid());
317     pageant_init();
318     pl = pageant_listener_new();
319     sock = new_unix_listener(unix_sock_addr(socketname), (Plug)pl);
320     if ((err = sk_socket_error(sock)) != NULL) {
321         fprintf(stderr, "pageant: %s: %s\n", socketname, err);
322         exit(1);
323     }
324     pageant_listener_got_socket(pl, sock);
325
326     conf = conf_new();
327     conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
328
329     /*
330      * Lifetime preparations.
331      */
332     signalpipe[0] = signalpipe[1] = -1;
333     if (life == LIFE_X11) {
334         struct X11Display *disp;
335         void *greeting;
336         int greetinglen;
337         Socket s;
338         struct X11Connection *conn;
339
340         static const struct plug_function_table fn_table = {
341             x11_log,
342             x11_closing,
343             x11_receive,
344             x11_sent,
345             NULL
346         };
347
348         if (!display)
349             display = getenv("DISPLAY");
350         if (!display) {
351             fprintf(stderr, "pageant: no DISPLAY for -X mode\n");
352             exit(1);
353         }
354         disp = x11_setup_display(display, conf);
355
356         conn = snew(struct X11Connection);
357         conn->fn = &fn_table;
358         s = new_connection(sk_addr_dup(disp->addr),
359                            disp->realhost, disp->port,
360                            0, 1, 0, 0, (Plug)conn, conf);
361         if ((err = sk_socket_error(s)) != NULL) {
362             fprintf(stderr, "pageant: unable to connect to X server: %s", err);
363             exit(1);
364         }
365         greeting = x11_make_greeting('B', 11, 0, disp->localauthproto,
366                                      disp->localauthdata,
367                                      disp->localauthdatalen,
368                                      NULL, 0, &greetinglen);
369         sk_write(s, greeting, greetinglen);
370         smemclr(greeting, greetinglen);
371         sfree(greeting);
372
373         pageant_fork_and_print_env();
374     } else if (life == LIFE_PERM) {
375         pageant_fork_and_print_env();
376     } else if (life == LIFE_DEBUG) {
377         pageant_print_env(getpid());
378         pageant_logfp = stdout;
379     } else if (life == LIFE_EXEC) {
380         pid_t agentpid, pid;
381
382         agentpid = getpid();
383
384         /*
385          * Set up the pipe we'll use to tell us about SIGCHLD.
386          */
387         if (pipe(signalpipe) < 0) {
388             perror("pipe");
389             exit(1);
390         }
391         putty_signal(SIGCHLD, sigchld);
392
393         pid = fork();
394         if (pid < 0) {
395             perror("fork");
396             exit(1);
397         } else if (pid == 0) {
398             setenv("SSH_AUTH_SOCK", socketname, TRUE);
399             setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), TRUE);
400             execvp(exec_args[0], exec_args);
401             perror("exec");
402             _exit(127);
403         } else {
404             termination_pid = pid;
405         }
406     }
407
408     /*
409      * Now we've decided on our logging arrangements, pass them on to
410      * pageant.c.
411      */
412     pageant_listener_set_logfn(pl, NULL, pageant_logfp ? pageant_log : NULL);
413
414     now = GETTICKCOUNT();
415
416     while (!time_to_die) {
417         fd_set rset, wset, xset;
418         int maxfd;
419         int rwx;
420         int ret;
421         unsigned long next;
422
423         FD_ZERO(&rset);
424         FD_ZERO(&wset);
425         FD_ZERO(&xset);
426         maxfd = 0;
427
428         if (signalpipe[0] >= 0) {
429             FD_SET_MAX(signalpipe[0], maxfd, rset);
430         }
431
432         /* Count the currently active fds. */
433         i = 0;
434         for (fd = first_fd(&fdstate, &rwx); fd >= 0;
435              fd = next_fd(&fdstate, &rwx)) i++;
436
437         /* Expand the fdlist buffer if necessary. */
438         if (i > fdsize) {
439             fdsize = i + 16;
440             fdlist = sresize(fdlist, fdsize, int);
441         }
442
443         /*
444          * Add all currently open fds to the select sets, and store
445          * them in fdlist as well.
446          */
447         fdcount = 0;
448         for (fd = first_fd(&fdstate, &rwx); fd >= 0;
449              fd = next_fd(&fdstate, &rwx)) {
450             fdlist[fdcount++] = fd;
451             if (rwx & 1)
452                 FD_SET_MAX(fd, maxfd, rset);
453             if (rwx & 2)
454                 FD_SET_MAX(fd, maxfd, wset);
455             if (rwx & 4)
456                 FD_SET_MAX(fd, maxfd, xset);
457         }
458
459         if (toplevel_callback_pending()) {
460             struct timeval tv;
461             tv.tv_sec = 0;
462             tv.tv_usec = 0;
463             ret = select(maxfd, &rset, &wset, &xset, &tv);
464         } else if (run_timers(now, &next)) {
465             unsigned long then;
466             long ticks;
467             struct timeval tv;
468
469             then = now;
470             now = GETTICKCOUNT();
471             if (now - then > next - then)
472                 ticks = 0;
473             else
474                 ticks = next - now;
475             tv.tv_sec = ticks / 1000;
476             tv.tv_usec = ticks % 1000 * 1000;
477             ret = select(maxfd, &rset, &wset, &xset, &tv);
478             if (ret == 0)
479                 now = next;
480             else
481                 now = GETTICKCOUNT();
482         } else {
483             ret = select(maxfd, &rset, &wset, &xset, NULL);
484         }
485
486         if (ret < 0 && errno == EINTR)
487             continue;
488
489         if (ret < 0) {
490             perror("select");
491             exit(1);
492         }
493
494         for (i = 0; i < fdcount; i++) {
495             fd = fdlist[i];
496             /*
497              * We must process exceptional notifications before
498              * ordinary readability ones, or we may go straight
499              * past the urgent marker.
500              */
501             if (FD_ISSET(fd, &xset))
502                 select_result(fd, 4);
503             if (FD_ISSET(fd, &rset))
504                 select_result(fd, 1);
505             if (FD_ISSET(fd, &wset))
506                 select_result(fd, 2);
507         }
508
509         if (signalpipe[0] >= 0 && FD_ISSET(signalpipe[0], &rset)) {
510             char c[1];
511             if (read(signalpipe[0], c, 1) <= 0)
512                 /* ignore error */;
513             /* ignore its value; it'll be `x' */
514             while (1) {
515                 int status;
516                 pid_t pid;
517                 pid = waitpid(-1, &status, WNOHANG);
518                 if (pid <= 0)
519                     break;
520                 if (pid == termination_pid)
521                     time_to_die = TRUE;
522             }
523         }
524
525         run_toplevel_callbacks();
526     }
527
528     /*
529      * When we come here, we're terminating, and should clean up our
530      * Unix socket file if possible.
531      */
532     if (unlink(socketname) < 0) {
533         fprintf(stderr, "pageant: %s: %s\n", socketname, strerror(errno));
534         exit(1);
535     }
536
537     return 0;
538 }