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