X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/954640a4967c588cff4c6f6c9003ef89691aa209..HEAD:/ssh-agent.c diff --git a/ssh-agent.c b/ssh-agent.c index 0615889b..df3a87d9 100644 --- a/ssh-agent.c +++ b/ssh-agent.c @@ -1,3 +1,4 @@ +/* $OpenBSD: ssh-agent.c,v 1.162 2009/09/01 14:43:17 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -34,27 +35,55 @@ */ #include "includes.h" -#include "openbsd-compat/fake-queue.h" -RCSID("$OpenBSD: ssh-agent.c,v 1.99 2002/08/12 10:46:35 markus Exp $"); + +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +# include +#endif +#ifdef HAVE_SYS_UN_H +# include +#endif +#include "openbsd-compat/sys-queue.h" #include #include +#include "openbsd-compat/openssl-compat.h" +#include +#include +#ifdef HAVE_PATHS_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "xmalloc.h" #include "ssh.h" #include "rsa.h" #include "buffer.h" -#include "bufaux.h" -#include "xmalloc.h" -#include "getput.h" #include "key.h" #include "authfd.h" #include "compat.h" #include "log.h" +#include "misc.h" #ifdef SMARTCARD #include "scard.h" #endif +#if defined(HAVE_SYS_PRCTL_H) +#include /* For prctl() and PR_SET_DUMPABLE */ +#endif + typedef enum { AUTH_UNUSED, AUTH_SOCKET, @@ -77,6 +106,7 @@ typedef struct identity { Key *key; char *comment; u_int death; + u_int confirm; } Identity; typedef struct { @@ -91,20 +121,31 @@ int max_fd = 0; /* pid of shell == parent of agent */ pid_t parent_pid = -1; +u_int parent_alive_interval = 0; /* pathname and directory for AUTH_SOCKET */ -char socket_name[1024]; -char socket_dir[1024]; +char socket_name[MAXPATHLEN]; +char socket_dir[MAXPATHLEN]; /* locking */ int locked = 0; char *lock_passwd = NULL; -#ifdef HAVE___PROGNAME extern char *__progname; -#else -char *__progname; -#endif + +/* Default lifetime (0 == forever) */ +static int lifetime = 0; + +static void +close_socket(SocketEntry *e) +{ + close(e->fd); + e->fd = -1; + e->type = AUTH_UNUSED; + buffer_free(&e->input); + buffer_free(&e->output); + buffer_free(&e->request); +} static void idtab_init(void) @@ -148,6 +189,22 @@ lookup_identity(Key *key, int version) return (NULL); } +/* Check confirmation of keysign request */ +static int +confirm_key(Identity *id) +{ + char *p; + int ret = -1; + + p = key_fingerprint(id->key, SSH_FP_MD5, SSH_FP_HEX); + if (ask_permission("Allow use of key %s?\nKey fingerprint %s.", + id->comment, p)) + ret = 0; + xfree(p); + + return (ret); +} + /* send list of supported public keys to 'client' */ static void process_request_identities(SocketEntry *e, int version) @@ -211,7 +268,7 @@ process_authentication_challenge1(SocketEntry *e) goto failure; id = lookup_identity(key, 1); - if (id != NULL) { + if (id != NULL && (!id->confirm || confirm_key(id) == 0)) { Key *private = id->key; /* Decrypt the challenge using the private key. */ if (rsa_private_decrypt(challenge, challenge, private->rsa) <= 0) @@ -220,7 +277,7 @@ process_authentication_challenge1(SocketEntry *e) /* The response is MD5 of decrypted challenge plus session id. */ len = BN_num_bytes(challenge); if (len <= 0 || len > 32) { - log("process_authentication_challenge: bad challenge length %d", len); + logit("process_authentication_challenge: bad challenge length %d", len); goto failure; } memset(buf, 0, 32); @@ -255,6 +312,7 @@ process_sign_request2(SocketEntry *e) u_char *blob, *data, *signature = NULL; u_int blen, dlen, slen = 0; extern int datafellows; + int odatafellows; int ok = -1, flags; Buffer msg; Key *key; @@ -265,16 +323,17 @@ process_sign_request2(SocketEntry *e) data = buffer_get_string(&e->request, &dlen); flags = buffer_get_int(&e->request); + odatafellows = datafellows; if (flags & SSH_AGENT_OLD_SIGNATURE) datafellows = SSH_BUG_SIGBLOB; key = key_from_blob(blob, blen); if (key != NULL) { Identity *id = lookup_identity(key, 2); - if (id != NULL) + if (id != NULL && (!id->confirm || confirm_key(id) == 0)) ok = key_sign(id->key, &signature, &slen, data, dlen); + key_free(key); } - key_free(key); buffer_init(&msg); if (ok == 0) { buffer_put_char(&msg, SSH2_AGENT_SIGN_RESPONSE); @@ -290,6 +349,7 @@ process_sign_request2(SocketEntry *e) xfree(blob); if (signature != NULL) xfree(signature); + datafellows = odatafellows; } /* shared */ @@ -309,7 +369,7 @@ process_remove_identity(SocketEntry *e, int version) buffer_get_bignum(&e->request, key->rsa->n); if (bits != key_size(key)) - log("Warning: identity keysize mismatch: actual %u, announced %u", + logit("Warning: identity keysize mismatch: actual %u, announced %u", key_size(key), bits); break; case 2: @@ -323,7 +383,7 @@ process_remove_identity(SocketEntry *e, int version) if (id != NULL) { /* * We have this key. Free the old key. Since we - * don\'t want to leave empty slots in the middle of + * don't want to leave empty slots in the middle of * the array, we actually free the key there and move * all the entries between the empty slot and the end * of the array. @@ -366,10 +426,11 @@ process_remove_all_identities(SocketEntry *e, int version) buffer_put_char(&e->output, SSH_AGENT_SUCCESS); } -static void +/* removes expired keys and returns number of seconds until the next expiry */ +static u_int reaper(void) { - u_int now = time(NULL); + u_int deadline = 0, now = time(NULL); Identity *id, *nxt; int version; Idtab *tab; @@ -378,20 +439,30 @@ reaper(void) tab = idtab_lookup(version); for (id = TAILQ_FIRST(&tab->idlist); id; id = nxt) { nxt = TAILQ_NEXT(id, next); - if (id->death != 0 && now >= id->death) { + if (id->death == 0) + continue; + if (now >= id->death) { + debug("expiring key '%s'", id->comment); TAILQ_REMOVE(&tab->idlist, id, next); free_identity(id); tab->nentries--; - } + } else + deadline = (deadline == 0) ? id->death : + MIN(deadline, id->death); } } + if (deadline == 0 || deadline <= now) + return 0; + else + return (deadline - now); } static void process_add_identity(SocketEntry *e, int version) { Idtab *tab = idtab_lookup(version); - int type, success = 0, death = 0; + Identity *id; + int type, success = 0, death = 0, confirm = 0; char *type_name, *comment; Key *k = NULL; @@ -442,33 +513,54 @@ process_add_identity(SocketEntry *e, int version) } break; } + /* enable blinding */ + switch (k->type) { + case KEY_RSA: + case KEY_RSA1: + if (RSA_blinding_on(k->rsa, NULL) != 1) { + error("process_add_identity: RSA_blinding_on failed"); + key_free(k); + goto send; + } + break; + } comment = buffer_get_string(&e->request, NULL); if (k == NULL) { xfree(comment); goto send; } - success = 1; while (buffer_len(&e->request)) { - switch (buffer_get_char(&e->request)) { + switch ((type = buffer_get_char(&e->request))) { case SSH_AGENT_CONSTRAIN_LIFETIME: death = time(NULL) + buffer_get_int(&e->request); break; - default: + case SSH_AGENT_CONSTRAIN_CONFIRM: + confirm = 1; break; + default: + error("process_add_identity: " + "Unknown constraint type %d", type); + xfree(comment); + key_free(k); + goto send; } } - if (lookup_identity(k, version) == NULL) { - Identity *id = xmalloc(sizeof(Identity)); + success = 1; + if (lifetime && !death) + death = time(NULL) + lifetime; + if ((id = lookup_identity(k, version)) == NULL) { + id = xmalloc(sizeof(Identity)); id->key = k; - id->comment = comment; - id->death = death; TAILQ_INSERT_TAIL(&tab->idlist, id, next); /* Increment the number of identities. */ tab->nentries++; } else { key_free(k); - xfree(comment); + xfree(id->comment); } + id->comment = comment; + id->death = death; + id->confirm = confirm; send: buffer_put_int(&e->output, 1); buffer_put_char(&e->output, @@ -519,16 +611,36 @@ no_identities(SocketEntry *e, u_int type) #ifdef SMARTCARD static void -process_add_smartcard_key (SocketEntry *e) +process_add_smartcard_key(SocketEntry *e) { char *sc_reader_id = NULL, *pin; - int i, version, success = 0; + int i, type, version, success = 0, death = 0, confirm = 0; Key **keys, *k; Identity *id; Idtab *tab; sc_reader_id = buffer_get_string(&e->request, NULL); pin = buffer_get_string(&e->request, NULL); + + while (buffer_len(&e->request)) { + switch ((type = buffer_get_char(&e->request))) { + case SSH_AGENT_CONSTRAIN_LIFETIME: + death = time(NULL) + buffer_get_int(&e->request); + break; + case SSH_AGENT_CONSTRAIN_CONFIRM: + confirm = 1; + break; + default: + error("process_add_smartcard_key: " + "Unknown constraint type %d", type); + xfree(sc_reader_id); + xfree(pin); + goto send; + } + } + if (lifetime && !death) + death = time(NULL) + lifetime; + keys = sc_get_keys(sc_reader_id, pin); xfree(sc_reader_id); xfree(pin); @@ -544,8 +656,9 @@ process_add_smartcard_key (SocketEntry *e) if (lookup_identity(k, version) == NULL) { id = xmalloc(sizeof(Identity)); id->key = k; - id->comment = xstrdup("smartcard key"); - id->death = 0; + id->comment = sc_get_key_label(k); + id->death = death; + id->confirm = confirm; TAILQ_INSERT_TAIL(&tab->idlist, id, next); tab->nentries++; success = 1; @@ -609,21 +722,12 @@ process_message(SocketEntry *e) u_int msg_len, type; u_char *cp; - /* kill dead keys */ - reaper(); - if (buffer_len(&e->input) < 5) return; /* Incomplete message. */ cp = buffer_ptr(&e->input); - msg_len = GET_32BIT(cp); + msg_len = get_u32(cp); if (msg_len > 256 * 1024) { - shutdown(e->fd, SHUT_RDWR); - close(e->fd); - e->fd = -1; - e->type = AUTH_UNUSED; - buffer_free(&e->input); - buffer_free(&e->output); - buffer_free(&e->request); + close_socket(e); return; } if (buffer_len(&e->input) < msg_len + 4) @@ -695,6 +799,7 @@ process_message(SocketEntry *e) break; #ifdef SMARTCARD case SSH_AGENTC_ADD_SMARTCARD_KEY: + case SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED: process_add_smartcard_key(e); break; case SSH_AGENTC_REMOVE_SMARTCARD_KEY: @@ -714,10 +819,9 @@ process_message(SocketEntry *e) static void new_socket(sock_type type, int fd) { - u_int i, old_alloc; + u_int i, old_alloc, new_alloc; - if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) - error("fcntl O_NONBLOCK: %s", strerror(errno)); + set_nonblock(fd); if (fd > max_fd) max_fd = fd; @@ -725,32 +829,32 @@ new_socket(sock_type type, int fd) for (i = 0; i < sockets_alloc; i++) if (sockets[i].type == AUTH_UNUSED) { sockets[i].fd = fd; - sockets[i].type = type; buffer_init(&sockets[i].input); buffer_init(&sockets[i].output); buffer_init(&sockets[i].request); + sockets[i].type = type; return; } old_alloc = sockets_alloc; - sockets_alloc += 10; - if (sockets) - sockets = xrealloc(sockets, sockets_alloc * sizeof(sockets[0])); - else - sockets = xmalloc(sockets_alloc * sizeof(sockets[0])); - for (i = old_alloc; i < sockets_alloc; i++) + new_alloc = sockets_alloc + 10; + sockets = xrealloc(sockets, new_alloc, sizeof(sockets[0])); + for (i = old_alloc; i < new_alloc; i++) sockets[i].type = AUTH_UNUSED; - sockets[old_alloc].type = type; + sockets_alloc = new_alloc; sockets[old_alloc].fd = fd; buffer_init(&sockets[old_alloc].input); buffer_init(&sockets[old_alloc].output); buffer_init(&sockets[old_alloc].request); + sockets[old_alloc].type = type; } static int -prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, int *nallocp) +prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, u_int *nallocp, + struct timeval **tvpp) { - u_int i, sz; + u_int i, sz, deadline; int n = 0; + static struct timeval tv; for (i = 0; i < sockets_alloc; i++) { switch (sockets[i].type) { @@ -794,6 +898,17 @@ prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, int *nallocp) break; } } + deadline = reaper(); + if (parent_alive_interval != 0) + deadline = (deadline == 0) ? parent_alive_interval : + MIN(deadline, parent_alive_interval); + if (deadline == 0) { + *tvpp = NULL; + } else { + tv.tv_sec = deadline; + tv.tv_usec = 0; + *tvpp = &tv; + } return (1); } @@ -804,9 +919,11 @@ after_select(fd_set *readset, fd_set *writeset) socklen_t slen; char buf[1024]; int len, sock; - u_int i; + u_int i, orig_alloc; + uid_t euid; + gid_t egid; - for (i = 0; i < sockets_alloc; i++) + for (i = 0, orig_alloc = sockets_alloc; i < orig_alloc; i++) switch (sockets[i].type) { case AUTH_UNUSED: break; @@ -814,55 +931,52 @@ after_select(fd_set *readset, fd_set *writeset) if (FD_ISSET(sockets[i].fd, readset)) { slen = sizeof(sunaddr); sock = accept(sockets[i].fd, - (struct sockaddr *) &sunaddr, &slen); + (struct sockaddr *)&sunaddr, &slen); if (sock < 0) { error("accept from AUTH_SOCKET: %s", strerror(errno)); break; } + if (getpeereid(sock, &euid, &egid) < 0) { + error("getpeereid %d failed: %s", + sock, strerror(errno)); + close(sock); + break; + } + if ((euid != 0) && (getuid() != euid)) { + error("uid mismatch: " + "peer euid %u != uid %u", + (u_int) euid, (u_int) getuid()); + close(sock); + break; + } new_socket(AUTH_CONNECTION, sock); } break; case AUTH_CONNECTION: if (buffer_len(&sockets[i].output) > 0 && FD_ISSET(sockets[i].fd, writeset)) { - do { - len = write(sockets[i].fd, - buffer_ptr(&sockets[i].output), - buffer_len(&sockets[i].output)); - if (len == -1 && (errno == EAGAIN || - errno == EINTR)) - continue; - break; - } while (1); + len = write(sockets[i].fd, + buffer_ptr(&sockets[i].output), + buffer_len(&sockets[i].output)); + if (len == -1 && (errno == EAGAIN || + errno == EWOULDBLOCK || + errno == EINTR)) + continue; if (len <= 0) { - shutdown(sockets[i].fd, SHUT_RDWR); - close(sockets[i].fd); - sockets[i].fd = -1; - sockets[i].type = AUTH_UNUSED; - buffer_free(&sockets[i].input); - buffer_free(&sockets[i].output); - buffer_free(&sockets[i].request); + close_socket(&sockets[i]); break; } buffer_consume(&sockets[i].output, len); } if (FD_ISSET(sockets[i].fd, readset)) { - do { - len = read(sockets[i].fd, buf, sizeof(buf)); - if (len == -1 && (errno == EAGAIN || - errno == EINTR)) - continue; - break; - } while (1); + len = read(sockets[i].fd, buf, sizeof(buf)); + if (len == -1 && (errno == EAGAIN || + errno == EWOULDBLOCK || + errno == EINTR)) + continue; if (len <= 0) { - shutdown(sockets[i].fd, SHUT_RDWR); - close(sockets[i].fd); - sockets[i].fd = -1; - sockets[i].type = AUTH_UNUSED; - buffer_free(&sockets[i].input); - buffer_free(&sockets[i].output); - buffer_free(&sockets[i].request); + close_socket(&sockets[i]); break; } buffer_append(&sockets[i].input, buf, len); @@ -875,7 +989,7 @@ after_select(fd_set *readset, fd_set *writeset) } static void -cleanup_socket(void *p) +cleanup_socket(void) { if (socket_name[0]) unlink(socket_name); @@ -883,38 +997,35 @@ cleanup_socket(void *p) rmdir(socket_dir); } -static void +void cleanup_exit(int i) { - cleanup_socket(NULL); - exit(i); + cleanup_socket(); + _exit(i); } +/*ARGSUSED*/ static void cleanup_handler(int sig) { - cleanup_socket(NULL); + cleanup_socket(); _exit(2); } static void -check_parent_exists(int sig) +check_parent_exists(void) { - int save_errno = errno; - if (parent_pid != -1 && kill(parent_pid, 0) < 0) { /* printf("Parent has died - Authentication agent exiting.\n"); */ - cleanup_handler(sig); /* safe */ + cleanup_socket(); + _exit(2); } - signal(SIGALRM, check_parent_exists); - alarm(10); - errno = save_errno; } static void usage(void) { - fprintf(stderr, "Usage: %s [options] [command [args ...]]\n", + fprintf(stderr, "usage: %s [options] [command [arg ...]]\n", __progname); fprintf(stderr, "Options:\n"); fprintf(stderr, " -c Generate C-shell commands on stdout.\n"); @@ -922,38 +1033,49 @@ usage(void) fprintf(stderr, " -k Kill the current agent.\n"); fprintf(stderr, " -d Debug mode.\n"); fprintf(stderr, " -a socket Bind agent socket to given name.\n"); + fprintf(stderr, " -t life Default identity lifetime (seconds).\n"); exit(1); } int main(int ac, char **av) { - int sock, c_flag = 0, d_flag = 0, k_flag = 0, s_flag = 0, ch, nalloc; + int c_flag = 0, d_flag = 0, k_flag = 0, s_flag = 0; + int sock, fd, ch, result, saved_errno; + u_int nalloc; char *shell, *format, *pidstr, *agentsocket = NULL; fd_set *readsetp = NULL, *writesetp = NULL; struct sockaddr_un sunaddr; #ifdef HAVE_SETRLIMIT struct rlimit rlim; #endif -#ifdef HAVE_CYGWIN int prev_mask; -#endif extern int optind; extern char *optarg; pid_t pid; char pidstrbuf[1 + 3 * sizeof pid]; + struct timeval *tvp = NULL; + size_t len; + + /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ + sanitise_stdfd(); /* drop */ setegid(getgid()); setgid(getgid()); +#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) + /* Disable ptrace on Linux without sgid bit */ + prctl(PR_SET_DUMPABLE, 0); +#endif + SSLeay_add_all_algorithms(); - __progname = get_progname(av[0]); + __progname = ssh_get_progname(av[0]); init_rng(); seed_rng(); - while ((ch = getopt(ac, av, "cdksa:")) != -1) { + while ((ch = getopt(ac, av, "cdksa:t:")) != -1) { switch (ch) { case 'c': if (s_flag) @@ -976,6 +1098,12 @@ main(int ac, char **av) case 'a': agentsocket = optarg; break; + case 't': + if ((lifetime = convtime(optarg)) == -1) { + fprintf(stderr, "Invalid lifetime\n"); + usage(); + } + break; default: usage(); } @@ -988,20 +1116,24 @@ main(int ac, char **av) if (ac == 0 && !c_flag && !s_flag) { shell = getenv("SHELL"); - if (shell != NULL && strncmp(shell + strlen(shell) - 3, "csh", 3) == 0) + if (shell != NULL && (len = strlen(shell)) > 2 && + strncmp(shell + len - 3, "csh", 3) == 0) c_flag = 1; } if (k_flag) { + const char *errstr = NULL; + pidstr = getenv(SSH_AGENTPID_ENV_NAME); if (pidstr == NULL) { fprintf(stderr, "%s not set, cannot kill agent\n", SSH_AGENTPID_ENV_NAME); exit(1); } - pid = atoi(pidstr); - if (pid < 1) { - fprintf(stderr, "%s=\"%s\", which is not a good PID\n", - SSH_AGENTPID_ENV_NAME, pidstr); + pid = (int)strtonum(pidstr, 2, INT_MAX, &errstr); + if (errstr) { + fprintf(stderr, + "%s=\"%s\", which is not a good PID: %s\n", + SSH_AGENTPID_ENV_NAME, pidstr, errstr); exit(1); } if (kill(pid, SIGTERM) == -1) { @@ -1018,7 +1150,7 @@ main(int ac, char **av) if (agentsocket == NULL) { /* Create private directory for agent socket */ - strlcpy(socket_dir, "/tmp/ssh-XXXXXXXX", sizeof socket_dir); + strlcpy(socket_dir, "/tmp/ssh-XXXXXXXXXX", sizeof socket_dir); if (mkdtemp(socket_dir) == NULL) { perror("mkdtemp: private socket dir"); exit(1); @@ -1038,25 +1170,21 @@ main(int ac, char **av) sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); + *socket_name = '\0'; /* Don't unlink any existing file */ cleanup_exit(1); } memset(&sunaddr, 0, sizeof(sunaddr)); sunaddr.sun_family = AF_UNIX; strlcpy(sunaddr.sun_path, socket_name, sizeof(sunaddr.sun_path)); -#ifdef HAVE_CYGWIN prev_mask = umask(0177); -#endif - if (bind(sock, (struct sockaddr *) & sunaddr, sizeof(sunaddr)) < 0) { + if (bind(sock, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { perror("bind"); -#ifdef HAVE_CYGWIN + *socket_name = '\0'; /* Don't unlink any existing file */ umask(prev_mask); -#endif cleanup_exit(1); } -#ifdef HAVE_CYGWIN umask(prev_mask); -#endif - if (listen(sock, 5) < 0) { + if (listen(sock, SSH_LISTEN_BACKLOG) < 0) { perror("listen"); cleanup_exit(1); } @@ -1108,9 +1236,14 @@ main(int ac, char **av) } (void)chdir("/"); - close(0); - close(1); - close(2); + if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + /* XXX might close listen socket */ + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } #ifdef HAVE_SETRLIMIT /* deny core dumps, since memory contains unencrypted private keys */ @@ -1122,12 +1255,9 @@ main(int ac, char **av) #endif skip: - fatal_add_cleanup(cleanup_socket, NULL); new_socket(AUTH_SOCKET, sock); - if (ac > 0) { - signal(SIGALRM, check_parent_exists); - alarm(10); - } + if (ac > 0) + parent_alive_interval = 10; idtab_init(); if (!d_flag) signal(SIGINT, SIG_IGN); @@ -1137,13 +1267,18 @@ skip: nalloc = 0; while (1) { - prepare_select(&readsetp, &writesetp, &max_fd, &nalloc); - if (select(max_fd + 1, readsetp, writesetp, NULL, NULL) < 0) { - if (errno == EINTR) + prepare_select(&readsetp, &writesetp, &max_fd, &nalloc, &tvp); + result = select(max_fd + 1, readsetp, writesetp, NULL, tvp); + saved_errno = errno; + if (parent_alive_interval != 0) + check_parent_exists(); + (void) reaper(); /* remove expired keys */ + if (result < 0) { + if (saved_errno == EINTR) continue; - fatal("select: %s", strerror(errno)); - } - after_select(readsetp, writesetp); + fatal("select: %s", strerror(saved_errno)); + } else if (result > 0) + after_select(readsetp, writesetp); } /* NOTREACHED */ }