Provide a Unix port of Pageant.

This is much more like ssh-agent than the Windows version is - it sets
SSH_AUTH_SOCK and SSH_AGENT_PID as its means of being found by other
processes, rather than Windows Pageant's approach of establishing
itself in a well-known location. But the actual agent code is the same
as Windows Pageant.

For the moment, this is an experimental utility and I don't expect it
to be useful to many people; its immediate use to me is that it
provides a way to test and debug the agent code on Unix, and also to
use the agent interface as a convenient way to exercise public key
functions I want to debug. And of course it means I can be constantly
using and testing my own code, on whatever platform I happen to be
using. In the further future, I have a list of possible features I
might add to it, but I don't know which ones I'll decide are
worthwhile.

One feature I've already put in is a wider range of lifetime
management options than ssh-agent: the -X mode causes Pageant to make
a connection to your X display, and automatically terminate when that
connection closes, so that it has the same lifetime as your X session
without having to do the cumbersome trick of exec()ing the subsequent
session-management process.
This commit is contained in:
Simon Tatham 2015-05-05 20:16:23 +01:00
Родитель 76e2ffe49d
Коммит c52108234b
2 изменённых файлов: 521 добавлений и 0 удалений

4
Recipe
Просмотреть файл

@ -303,5 +303,9 @@ puttygen : [U] cmdgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
pscp : [U] pscp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
psftp : [U] psftp uxsftp uxcons UXSSH BE_SSH SFTP wildcard UXMISC
pageant : [U] uxpgnt pageant sshrsa sshpubk sshdes sshbn sshmd5 version
+ tree234 misc sshaes sshsha sshdss sshsh256 sshsh512 sshecc
+ conf uxsignal nocproxy nogss be_none x11fwd ux_x11 UXMISC LIBS
PuTTY : [MX] osxmain OSXTERM OSXMISC CHARSET U_BE_ALL NONSSH UXSSH
+ ux_x11 uxpty uxsignal testback putty.icns info.plist

517
unix/uxpgnt.c Normal file
Просмотреть файл

@ -0,0 +1,517 @@
/*
* Unix Pageant, more or less similar to ssh-agent.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define PUTTY_DO_GLOBALS /* actually _define_ globals */
#include "putty.h"
#include "ssh.h"
#include "misc.h"
#include "pageant.h"
SockAddr unix_sock_addr(const char *path);
Socket new_unix_listener(SockAddr listenaddr, Plug plug);
void fatalbox(char *p, ...)
{
va_list ap;
fprintf(stderr, "FATAL ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
void modalfatalbox(char *p, ...)
{
va_list ap;
fprintf(stderr, "FATAL ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
void nonfatal(char *p, ...)
{
va_list ap;
fprintf(stderr, "ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
}
void connection_fatal(void *frontend, char *p, ...)
{
va_list ap;
fprintf(stderr, "FATAL ERROR: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
void cmdline_error(char *p, ...)
{
va_list ap;
fprintf(stderr, "pageant: ");
va_start(ap, p);
vfprintf(stderr, p, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
int pageant_logging = FALSE;
void pageant_log(void *ctx, const char *fmt, ...)
{
va_list ap;
if (!pageant_logging)
return;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
/*
* In Pageant our selects are synchronous, so these functions are
* empty stubs.
*/
int uxsel_input_add(int fd, int rwx) { return 0; }
void uxsel_input_remove(int id) { }
/*
* More stubs.
*/
void logevent(void *frontend, const char *string) {}
void random_save_seed(void) {}
void random_destroy_seed(void) {}
void noise_ultralight(unsigned long data) {}
char *platform_default_s(const char *name) { return NULL; }
int platform_default_i(const char *name, int def) { return def; }
FontSpec *platform_default_fontspec(const char *name) { return fontspec_new(""); }
Filename *platform_default_filename(const char *name) { return filename_from_str(""); }
char *x_get_default(const char *key) { return NULL; }
void old_keyfile_warning(void) {}
void timer_change_notify(unsigned long next) {}
/*
* Short description of parameters.
*/
static void usage(void)
{
printf("Pageant: SSH agent\n");
printf("%s\n", ver);
printf("FIXME\n");
exit(1);
}
static void version(void)
{
printf("pageant: %s\n", ver);
exit(1);
}
void keylist_update(void)
{
/* Nothing needs doing in Unix Pageant */
}
#define PAGEANT_DIR_PREFIX "/tmp/pageant"
const char *const appname = "Pageant";
Conf *conf;
char *platform_get_x_display(void) {
return dupstr(getenv("DISPLAY"));
}
int sshfwd_write(struct ssh_channel *c, char *data, int len) { return 0; }
void sshfwd_write_eof(struct ssh_channel *c) { /* FIXME: notify main loop instead */ exit(0); }
void sshfwd_unclean_close(struct ssh_channel *c, const char *err) { /* FIXME: notify main loop instead */ exit(1); }
void sshfwd_unthrottle(struct ssh_channel *c, int bufsize) {}
Conf *sshfwd_get_conf(struct ssh_channel *c) { return conf; }
void sshfwd_x11_sharing_handover(struct ssh_channel *c,
void *share_cs, void *share_chan,
const char *peer_addr, int peer_port,
int endian, int protomajor, int protominor,
const void *initial_data, int initial_len) {}
void sshfwd_x11_is_local(struct ssh_channel *c) {}
static void x11_log(Plug p, int type, SockAddr addr, int port,
const char *error_msg, int error_code) {}
static int x11_closing(Plug plug, const char *error_msg, int error_code,
int calling_back) { /* FIXME: notify main loop instead */ exit(0); }
static int x11_receive(Plug plug, int urgent, char *data, int len) { return 0; }
static void x11_sent(Plug plug, int bufsize) {}
struct X11Connection {
const struct plug_function_table *fn;
};
char *socketname;
void pageant_print_env(int pid)
{
printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n"
"SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n",
socketname, (int)pid);
}
void pageant_fork_and_print_env(void)
{
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
} else if (pid != 0) {
pageant_print_env(pid);
exit(0);
}
/*
* Having forked off, we now daemonise ourselves as best we can.
* It's good practice in general to setsid() ourself out of any
* process group we didn't want to be part of, and to chdir("/")
* to avoid holding any directories open that we don't need in
* case someone wants to umount them; also, we should definitely
* close standard output (because it will very likely be pointing
* at a pipe from which some parent process is trying to read our
* environment variable dump, so if we hold open another copy of
* it then that process will never finish reading). We close
* standard input too on general principles, but not standard
* error, since we might need to shout a panicky error message
* down that one.
*/
if (chdir("/") < 0) {
/* should there be an error condition, nothing we can do about
* it anyway */
}
close(0);
close(1);
setsid();
}
int signalpipe[2];
void sigchld(int signum)
{
if (write(signalpipe[1], "x", 1) <= 0)
/* not much we can do about it */;
}
int main(int argc, char **argv)
{
int *fdlist;
int fd;
int i, fdcount, fdsize, fdstate;
int errors;
unsigned long now;
char *username, *socketdir;
const char *err;
struct pageant_listen_state *pl;
Socket sock;
enum {
LIFE_UNSPEC, LIFE_X11, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC
} life = LIFE_UNSPEC;
const char *display = NULL;
int doing_opts = TRUE;
char **exec_args = NULL;
int termination_pid = -1;
fdlist = NULL;
fdcount = fdsize = 0;
errors = FALSE;
/*
* Process the command line.
*/
while (--argc) {
char *p = *++argv;
if (*p == '-' && doing_opts) {
if (!strcmp(p, "-V") || !strcmp(p, "--version")) {
version();
} else if (!strcmp(p, "--help")) {
usage();
exit(0);
} else if (!strcmp(p, "-X")) {
life = LIFE_X11;
} else if (!strcmp(p, "--debug")) {
life = LIFE_DEBUG;
} else if (!strcmp(p, "--permanent")) {
life = LIFE_PERM;
} else if (!strcmp(p, "--exec")) {
life = LIFE_EXEC;
} else if (!strcmp(p, "--")) {
doing_opts = FALSE;
}
} else {
if (life == LIFE_EXEC) {
exec_args = argv;
break; /* everything else is now args to the exec command */
} else {
fprintf(stderr, "pageant: unexpected argument '%s'\n", p);
exit(1);
}
}
}
if (errors)
return 1;
if (life == LIFE_UNSPEC) {
fprintf(stderr, "pageant: expected a lifetime option\n");
exit(1);
}
if (life == LIFE_EXEC && !exec_args) {
fprintf(stderr, "pageant: expected a command with --exec\n");
exit(1);
}
/*
* Block SIGPIPE, so that we'll get EPIPE individually on
* particular network connections that go wrong.
*/
putty_signal(SIGPIPE, SIG_IGN);
sk_init();
uxsel_init();
/*
* Set up a listening socket and run Pageant on it.
*/
username = get_username();
socketdir = dupprintf("%s.%s", PAGEANT_DIR_PREFIX, username);
sfree(username);
assert(*socketdir == '/');
if ((err = make_dir_and_check_ours(socketdir)) != NULL) {
fprintf(stderr, "pageant: %s: %s\n", socketdir, err);
exit(1);
}
socketname = dupprintf("%s/pageant.%d", socketdir, (int)getpid());
pageant_init();
pl = pageant_listener_new(NULL, pageant_log);
sock = new_unix_listener(unix_sock_addr(socketname), (Plug)pl);
if ((err = sk_socket_error(sock)) != NULL) {
fprintf(stderr, "pageant: %s: %s\n", socketname, err);
exit(1);
}
pageant_listener_got_socket(pl, sock);
conf = conf_new();
conf_set_int(conf, CONF_proxy_type, PROXY_NONE);
/*
* Lifetime preparations.
*/
signalpipe[0] = signalpipe[1] = -1;
if (life == LIFE_X11) {
struct X11Display *disp;
void *greeting;
int greetinglen;
Socket s;
struct X11Connection *conn;
static const struct plug_function_table fn_table = {
x11_log,
x11_closing,
x11_receive,
x11_sent,
NULL
};
if (!display)
display = getenv("DISPLAY");
if (!display) {
fprintf(stderr, "pageant: no DISPLAY for -X mode\n");
exit(1);
}
disp = x11_setup_display(display, conf);
conn = snew(struct X11Connection);
conn->fn = &fn_table;
s = new_connection(sk_addr_dup(disp->addr),
disp->realhost, disp->port,
0, 1, 0, 0, (Plug)conn, conf);
if ((err = sk_socket_error(s)) != NULL) {
fprintf(stderr, "pageant: unable to connect to X server: %s", err);
exit(1);
}
greeting = x11_make_greeting('B', 11, 0, disp->localauthproto,
disp->localauthdata,
disp->localauthdatalen,
NULL, 0, &greetinglen);
sk_write(s, greeting, greetinglen);
smemclr(greeting, greetinglen);
sfree(greeting);
pageant_fork_and_print_env();
} else if (life == LIFE_PERM) {
pageant_fork_and_print_env();
} else if (life == LIFE_DEBUG) {
pageant_print_env(getpid());
pageant_logging = TRUE;
} else if (life == LIFE_EXEC) {
pid_t agentpid, pid;
agentpid = getpid();
/*
* Set up the pipe we'll use to tell us about SIGCHLD.
*/
if (pipe(signalpipe) < 0) {
perror("pipe");
exit(1);
}
putty_signal(SIGCHLD, sigchld);
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
} else if (pid == 0) {
setenv("SSH_AUTH_SOCK", socketname, TRUE);
setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), TRUE);
execvp(exec_args[0], exec_args);
perror("exec");
_exit(127);
} else {
termination_pid = pid;
}
}
/*
* FIXME: and if life == LIFE_EXEC, then we use our own pid, but
* don't print_env - instead, fork, stuff it in the real
* environment, and exec our child.
*
* FIXME: and hang on, perhaps LIFE_PARENT won't work after all,
* because after we fork, of course, our ppid _isn't_ an indicator
* of whether the target process has gone away.
*/
now = GETTICKCOUNT();
while (1) {
fd_set rset, wset, xset;
int maxfd;
int rwx;
int ret;
unsigned long next;
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_ZERO(&xset);
maxfd = 0;
if (signalpipe[0] >= 0) {
FD_SET_MAX(signalpipe[0], maxfd, rset);
}
/* Count the currently active fds. */
i = 0;
for (fd = first_fd(&fdstate, &rwx); fd >= 0;
fd = next_fd(&fdstate, &rwx)) i++;
/* Expand the fdlist buffer if necessary. */
if (i > fdsize) {
fdsize = i + 16;
fdlist = sresize(fdlist, fdsize, int);
}
/*
* Add all currently open fds to the select sets, and store
* them in fdlist as well.
*/
fdcount = 0;
for (fd = first_fd(&fdstate, &rwx); fd >= 0;
fd = next_fd(&fdstate, &rwx)) {
fdlist[fdcount++] = fd;
if (rwx & 1)
FD_SET_MAX(fd, maxfd, rset);
if (rwx & 2)
FD_SET_MAX(fd, maxfd, wset);
if (rwx & 4)
FD_SET_MAX(fd, maxfd, xset);
}
if (toplevel_callback_pending()) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
ret = select(maxfd, &rset, &wset, &xset, &tv);
} else if (run_timers(now, &next)) {
unsigned long then;
long ticks;
struct timeval tv;
then = now;
now = GETTICKCOUNT();
if (now - then > next - then)
ticks = 0;
else
ticks = next - now;
tv.tv_sec = ticks / 1000;
tv.tv_usec = ticks % 1000 * 1000;
ret = select(maxfd, &rset, &wset, &xset, &tv);
if (ret == 0)
now = next;
else
now = GETTICKCOUNT();
} else {
ret = select(maxfd, &rset, &wset, &xset, NULL);
}
if (ret < 0 && errno == EINTR)
continue;
if (ret < 0) {
perror("select");
exit(1);
}
for (i = 0; i < fdcount; i++) {
fd = fdlist[i];
/*
* We must process exceptional notifications before
* ordinary readability ones, or we may go straight
* past the urgent marker.
*/
if (FD_ISSET(fd, &xset))
select_result(fd, 4);
if (FD_ISSET(fd, &rset))
select_result(fd, 1);
if (FD_ISSET(fd, &wset))
select_result(fd, 2);
}
if (signalpipe[0] >= 0 && FD_ISSET(signalpipe[0], &rset)) {
char c[1];
if (read(signalpipe[0], c, 1) <= 0)
/* ignore error */;
/* ignore its value; it'll be `x' */
while (1) {
int status;
pid_t pid;
pid = waitpid(-1, &status, WNOHANG);
if (pid == 0)
break;
if (pid == termination_pid)
exit(0);
}
}
run_toplevel_callbacks();
}
return 0;
}