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