[BACK]Return to ssh-agent.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ssh

Diff for /src/usr.bin/ssh/ssh-agent.c between version 1.72 and 1.72.2.2

version 1.72, 2001/08/03 10:31:30 version 1.72.2.2, 2002/05/17 00:03:24
Line 1 
Line 1 
 /*      $OpenBSD$       */  
   
 /*  /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>   * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland   * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
Line 36 
Line 34 
  */   */
   
 #include "includes.h"  #include "includes.h"
   #include <sys/queue.h>
 RCSID("$OpenBSD$");  RCSID("$OpenBSD$");
   
 #include <openssl/evp.h>  #include <openssl/evp.h>
Line 46 
Line 45 
 #include "buffer.h"  #include "buffer.h"
 #include "bufaux.h"  #include "bufaux.h"
 #include "xmalloc.h"  #include "xmalloc.h"
 #include "packet.h"  
 #include "getput.h"  #include "getput.h"
 #include "mpaux.h"  
 #include "key.h"  #include "key.h"
 #include "authfd.h"  #include "authfd.h"
 #include "cipher.h"  
 #include "kex.h"  
 #include "compat.h"  #include "compat.h"
 #include "log.h"  #include "log.h"
   
 #ifdef SMARTCARD  #ifdef SMARTCARD
 #include <openssl/engine.h>  
 #include "scard.h"  #include "scard.h"
 #endif  #endif
   
   typedef enum {
           AUTH_UNUSED,
           AUTH_SOCKET,
           AUTH_CONNECTION
   } sock_type;
   
 typedef struct {  typedef struct {
         int fd;          int fd;
         enum {          sock_type type;
                 AUTH_UNUSED, AUTH_SOCKET, AUTH_CONNECTION  
         } type;  
         Buffer input;          Buffer input;
         Buffer output;          Buffer output;
 } SocketEntry;  } SocketEntry;
Line 73 
Line 71 
 u_int sockets_alloc = 0;  u_int sockets_alloc = 0;
 SocketEntry *sockets = NULL;  SocketEntry *sockets = NULL;
   
 typedef struct {  typedef struct identity {
           TAILQ_ENTRY(identity) next;
         Key *key;          Key *key;
         char *comment;          char *comment;
 } Identity;  } Identity;
   
 typedef struct {  typedef struct {
         int nentries;          int nentries;
         Identity *identities;          TAILQ_HEAD(idqueue, identity) idlist;
 } Idtab;  } Idtab;
   
 /* private key table, one per protocol version */  /* private key table, one per protocol version */
Line 101 
Line 100 
 idtab_init(void)  idtab_init(void)
 {  {
         int i;          int i;
         for (i = 0; i <=2; i++){          for (i = 0; i <=2; i++) {
                 idtable[i].identities = NULL;                  TAILQ_INIT(&idtable[i].idlist);
                 idtable[i].nentries = 0;                  idtable[i].nentries = 0;
         }          }
 }  }
Line 117 
Line 116 
 }  }
   
 /* return matching private key for given public key */  /* return matching private key for given public key */
 static Key *  static Identity *
 lookup_private_key(Key *key, int *idx, int version)  lookup_identity(Key *key, int version)
 {  {
         int i;          Identity *id;
   
         Idtab *tab = idtab_lookup(version);          Idtab *tab = idtab_lookup(version);
         for (i = 0; i < tab->nentries; i++) {          TAILQ_FOREACH(id, &tab->idlist, next) {
                 if (key_equal(key, tab->identities[i].key)) {                  if (key_equal(key, id->key))
                         if (idx != NULL)                          return (id);
                                 *idx = i;  
                         return tab->identities[i].key;  
                 }  
         }          }
         return NULL;          return (NULL);
 }  }
   
   static void
   free_identity(Identity *id)
   {
           key_free(id->key);
           xfree(id->comment);
           xfree(id);
   }
   
 /* send list of supported public keys to 'client' */  /* send list of supported public keys to 'client' */
 static void  static void
 process_request_identities(SocketEntry *e, int version)  process_request_identities(SocketEntry *e, int version)
 {  {
         Idtab *tab = idtab_lookup(version);          Idtab *tab = idtab_lookup(version);
         Buffer msg;          Buffer msg;
         int i;          Identity *id;
   
         buffer_init(&msg);          buffer_init(&msg);
         buffer_put_char(&msg, (version == 1) ?          buffer_put_char(&msg, (version == 1) ?
             SSH_AGENT_RSA_IDENTITIES_ANSWER : SSH2_AGENT_IDENTITIES_ANSWER);              SSH_AGENT_RSA_IDENTITIES_ANSWER : SSH2_AGENT_IDENTITIES_ANSWER);
         buffer_put_int(&msg, tab->nentries);          buffer_put_int(&msg, tab->nentries);
         for (i = 0; i < tab->nentries; i++) {          TAILQ_FOREACH(id, &tab->idlist, next) {
                 Identity *id = &tab->identities[i];  
                 if (id->key->type == KEY_RSA1) {                  if (id->key->type == KEY_RSA1) {
                         buffer_put_int(&msg, BN_num_bits(id->key->rsa->n));                          buffer_put_int(&msg, BN_num_bits(id->key->rsa->n));
                         buffer_put_bignum(&msg, id->key->rsa->e);                          buffer_put_bignum(&msg, id->key->rsa->e);
Line 168 
Line 172 
 static void  static void
 process_authentication_challenge1(SocketEntry *e)  process_authentication_challenge1(SocketEntry *e)
 {  {
         Key *key, *private;          Identity *id;
           Key *key;
         BIGNUM *challenge;          BIGNUM *challenge;
         int i, len;          int i, len;
         Buffer msg;          Buffer msg;
Line 178 
Line 183 
   
         buffer_init(&msg);          buffer_init(&msg);
         key = key_new(KEY_RSA1);          key = key_new(KEY_RSA1);
         challenge = BN_new();          if ((challenge = BN_new()) == NULL)
                   fatal("process_authentication_challenge1: BN_new failed");
   
         buffer_get_int(&e->input);                              /* ignored */          buffer_get_int(&e->input);                              /* ignored */
         buffer_get_bignum(&e->input, key->rsa->e);          buffer_get_bignum(&e->input, key->rsa->e);
Line 188 
Line 194 
         /* Only protocol 1.1 is supported */          /* Only protocol 1.1 is supported */
         if (buffer_len(&e->input) == 0)          if (buffer_len(&e->input) == 0)
                 goto failure;                  goto failure;
         buffer_get(&e->input, (char *) session_id, 16);          buffer_get(&e->input, session_id, 16);
         response_type = buffer_get_int(&e->input);          response_type = buffer_get_int(&e->input);
         if (response_type != 1)          if (response_type != 1)
                 goto failure;                  goto failure;
   
         private = lookup_private_key(key, NULL, 1);          id = lookup_identity(key, 1);
         if (private != NULL) {          if (id != NULL) {
                   Key *private = id->key;
                 /* Decrypt the challenge using the private key. */                  /* Decrypt the challenge using the private key. */
                 if (rsa_private_decrypt(challenge, challenge, private->rsa) <= 0)                  if (rsa_private_decrypt(challenge, challenge, private->rsa) <= 0)
                         goto failure;                          goto failure;
Line 235 
Line 242 
 process_sign_request2(SocketEntry *e)  process_sign_request2(SocketEntry *e)
 {  {
         extern int datafellows;          extern int datafellows;
         Key *key, *private;          Key *key;
         u_char *blob, *data, *signature = NULL;          u_char *blob, *data, *signature = NULL;
         u_int blen, dlen, slen = 0;          u_int blen, dlen, slen = 0;
         int flags;          int flags;
Line 253 
Line 260 
   
         key = key_from_blob(blob, blen);          key = key_from_blob(blob, blen);
         if (key != NULL) {          if (key != NULL) {
                 private = lookup_private_key(key, NULL, 2);                  Identity *id = lookup_identity(key, 2);
                 if (private != NULL)                  if (id != NULL)
                         ok = key_sign(private, &signature, &slen, data, dlen);                          ok = key_sign(id->key, &signature, &slen, data, dlen);
         }          }
         key_free(key);          key_free(key);
         buffer_init(&msg);          buffer_init(&msg);
Line 279 
Line 286 
 static void  static void
 process_remove_identity(SocketEntry *e, int version)  process_remove_identity(SocketEntry *e, int version)
 {  {
         Key *key = NULL, *private;          Key *key = NULL;
         u_char *blob;          u_char *blob;
         u_int blen;          u_int blen;
         u_int bits;          u_int bits;
         int success = 0;          int success = 0;
   
         switch(version){          switch (version) {
         case 1:          case 1:
                 key = key_new(KEY_RSA1);                  key = key_new(KEY_RSA1);
                 bits = buffer_get_int(&e->input);                  bits = buffer_get_int(&e->input);
Line 303 
Line 310 
                 break;                  break;
         }          }
         if (key != NULL) {          if (key != NULL) {
                 int idx;                  Identity *id = lookup_identity(key, version);
                 private = lookup_private_key(key, &idx, version);                  if (id != NULL) {
                 if (private != NULL) {  
                         /*                          /*
                          * We have this key.  Free the old key.  Since we                           * 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
Line 314 
Line 320 
                          * of the array.                           * of the array.
                          */                           */
                         Idtab *tab = idtab_lookup(version);                          Idtab *tab = idtab_lookup(version);
                         key_free(tab->identities[idx].key);  
                         xfree(tab->identities[idx].comment);  
                         if (tab->nentries < 1)                          if (tab->nentries < 1)
                                 fatal("process_remove_identity: "                                  fatal("process_remove_identity: "
                                     "internal error: tab->nentries %d",                                      "internal error: tab->nentries %d",
                                     tab->nentries);                                      tab->nentries);
                         if (idx != tab->nentries - 1) {                          TAILQ_REMOVE(&tab->idlist, id, next);
                                 int i;                          free_identity(id);
                                 for (i = idx; i < tab->nentries - 1; i++)  
                                         tab->identities[i] = tab->identities[i+1];  
                         }  
                         tab->identities[tab->nentries - 1].key = NULL;  
                         tab->identities[tab->nentries - 1].comment = NULL;  
                         tab->nentries--;                          tab->nentries--;
                         success = 1;                          success = 1;
                 }                  }
Line 340 
Line 339 
 static void  static void
 process_remove_all_identities(SocketEntry *e, int version)  process_remove_all_identities(SocketEntry *e, int version)
 {  {
         u_int i;  
         Idtab *tab = idtab_lookup(version);          Idtab *tab = idtab_lookup(version);
           Identity *id;
   
         /* Loop over all identities and clear the keys. */          /* Loop over all identities and clear the keys. */
         for (i = 0; i < tab->nentries; i++) {          for (id = TAILQ_FIRST(&tab->idlist); id;
                 key_free(tab->identities[i].key);              id = TAILQ_FIRST(&tab->idlist)) {
                 xfree(tab->identities[i].comment);                  TAILQ_REMOVE(&tab->idlist, id, next);
                   free_identity(id);
         }          }
   
         /* Mark that there are no identities. */          /* Mark that there are no identities. */
Line 387 
Line 387 
                 type_name = buffer_get_string(&e->input, NULL);                  type_name = buffer_get_string(&e->input, NULL);
                 type = key_type_from_name(type_name);                  type = key_type_from_name(type_name);
                 xfree(type_name);                  xfree(type_name);
                 switch(type) {                  switch (type) {
                 case KEY_DSA:                  case KEY_DSA:
                         k = key_new_private(type);                          k = key_new_private(type);
                         buffer_get_bignum2(&e->input, k->dsa->p);                          buffer_get_bignum2(&e->input, k->dsa->p);
Line 420 
Line 420 
                 goto send;                  goto send;
         }          }
         success = 1;          success = 1;
         if (lookup_private_key(k, NULL, version) == NULL) {          if (lookup_identity(k, version) == NULL) {
                 if (tab->nentries == 0)                  Identity *id = xmalloc(sizeof(Identity));
                         tab->identities = xmalloc(sizeof(Identity));                  id->key = k;
                 else                  id->comment = comment;
                         tab->identities = xrealloc(tab->identities,                  TAILQ_INSERT_TAIL(&tab->idlist, id, next);
                             (tab->nentries + 1) * sizeof(Identity));  
                 tab->identities[tab->nentries].key = k;  
                 tab->identities[tab->nentries].comment = comment;  
                 /* Increment the number of identities. */                  /* Increment the number of identities. */
                 tab->nentries++;                  tab->nentries++;
         } else {          } else {
Line 445 
Line 442 
 static void  static void
 process_add_smartcard_key (SocketEntry *e)  process_add_smartcard_key (SocketEntry *e)
 {  {
           Identity *id;
         Idtab *tab;          Idtab *tab;
         Key *n = NULL, *k = NULL;          Key **keys, *k;
         char *sc_reader_id = NULL;          char *sc_reader_id = NULL, *pin;
         int success = 0;          int i, version, success = 0;
   
         sc_reader_id = buffer_get_string(&e->input, NULL);          sc_reader_id = buffer_get_string(&e->input, NULL);
         k = sc_get_key(sc_reader_id);          pin = buffer_get_string(&e->input, NULL);
           keys = sc_get_keys(sc_reader_id, pin);
         xfree(sc_reader_id);          xfree(sc_reader_id);
           xfree(pin);
   
         if (k == NULL) {          if (keys == NULL || keys[0] == NULL) {
                 error("sc_get_pubkey failed");                  error("sc_get_keys failed");
                 goto send;                  goto send;
         }          }
         success = 1;          for (i = 0; keys[i] != NULL; i++) {
                   k = keys[i];
         tab = idtab_lookup(1);                  version = k->type == KEY_RSA1 ? 1 : 2;
         k->type = KEY_RSA1;                  tab = idtab_lookup(version);
         if (lookup_private_key(k, NULL, 1) == NULL) {                  if (lookup_identity(k, version) == NULL) {
                 if (tab->nentries == 0)                          id = xmalloc(sizeof(Identity));
                         tab->identities = xmalloc(sizeof(Identity));                          id->key = k;
                 else                          id->comment = xstrdup("smartcard key");
                         tab->identities = xrealloc(tab->identities,                          TAILQ_INSERT_TAIL(&tab->idlist, id, next);
                             (tab->nentries + 1) * sizeof(Identity));                          tab->nentries++;
                 n = key_new(KEY_RSA1);                          success = 1;
                 BN_copy(n->rsa->n, k->rsa->n);                  } else {
                 BN_copy(n->rsa->e, k->rsa->e);                          key_free(k);
                 RSA_set_method(n->rsa, sc_get_engine());                  }
                 tab->identities[tab->nentries].key = n;                  keys[i] = NULL;
                 tab->identities[tab->nentries].comment =  
                     xstrdup("rsa1 smartcard");  
                 tab->nentries++;  
         }          }
         k->type = KEY_RSA;          xfree(keys);
         tab = idtab_lookup(2);  
         if (lookup_private_key(k, NULL, 2) == NULL) {  
                 if (tab->nentries == 0)  
                         tab->identities = xmalloc(sizeof(Identity));  
                 else  
                         tab->identities = xrealloc(tab->identities,  
                             (tab->nentries + 1) * sizeof(Identity));  
                 n = key_new(KEY_RSA);  
                 BN_copy(n->rsa->n, k->rsa->n);  
                 BN_copy(n->rsa->e, k->rsa->e);  
                 RSA_set_method(n->rsa, sc_get_engine());  
                 tab->identities[tab->nentries].key = n;  
                 tab->identities[tab->nentries].comment =  
                     xstrdup("rsa smartcard");  
                 tab->nentries++;  
         }  
         key_free(k);  
 send:  send:
         buffer_put_int(&e->output, 1);          buffer_put_int(&e->output, 1);
         buffer_put_char(&e->output,          buffer_put_char(&e->output,
Line 504 
Line 484 
 static void  static void
 process_remove_smartcard_key(SocketEntry *e)  process_remove_smartcard_key(SocketEntry *e)
 {  {
         Key *k = NULL, *private;          Identity *id;
         int idx;          Idtab *tab;
         int success = 0;          Key **keys, *k = NULL;
         char *sc_reader_id = NULL;          char *sc_reader_id = NULL, *pin;
           int i, version, success = 0;
   
         sc_reader_id = buffer_get_string(&e->input, NULL);          sc_reader_id = buffer_get_string(&e->input, NULL);
         k = sc_get_key(sc_reader_id);          pin = buffer_get_string(&e->input, NULL);
           keys = sc_get_keys(sc_reader_id, pin);
         xfree(sc_reader_id);          xfree(sc_reader_id);
           xfree(pin);
   
         if (k == NULL) {          if (keys == NULL || keys[0] == NULL) {
                 error("sc_get_pubkey failed");                  error("sc_get_keys failed");
         } else {                  goto send;
                 k->type = KEY_RSA1;          }
                 private = lookup_private_key(k, &idx, 1);          for (i = 0; keys[i] != NULL; i++) {
                 if (private != NULL) {                  k = keys[i];
                         Idtab *tab = idtab_lookup(1);                  version = k->type == KEY_RSA1 ? 1 : 2;
                         key_free(tab->identities[idx].key);                  if ((id = lookup_identity(k, version)) != NULL) {
                         xfree(tab->identities[idx].comment);                          tab = idtab_lookup(version);
                         if (idx != tab->nentries)                          TAILQ_REMOVE(&tab->idlist, id, next);
                                 tab->identities[idx] = tab->identities[tab->nentries];  
                         tab->nentries--;                          tab->nentries--;
                           free_identity(id);
                         success = 1;                          success = 1;
                 }                  }
                 k->type = KEY_RSA;  
                 private = lookup_private_key(k, &idx, 2);  
                 if (private != NULL) {  
                         Idtab *tab = idtab_lookup(2);  
                         key_free(tab->identities[idx].key);  
                         xfree(tab->identities[idx].comment);  
                         if (idx != tab->nentries)  
                                 tab->identities[idx] = tab->identities[tab->nentries];  
                         tab->nentries--;  
                         success = 1;  
                 }  
                 key_free(k);                  key_free(k);
                   keys[i] = NULL;
         }          }
           xfree(keys);
   send:
         buffer_put_int(&e->output, 1);          buffer_put_int(&e->output, 1);
         buffer_put_char(&e->output,          buffer_put_char(&e->output,
             success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE);              success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE);
Line 557 
Line 531 
         u_char *cp;          u_char *cp;
         if (buffer_len(&e->input) < 5)          if (buffer_len(&e->input) < 5)
                 return;         /* Incomplete message. */                  return;         /* Incomplete message. */
         cp = (u_char *) buffer_ptr(&e->input);          cp = buffer_ptr(&e->input);
         msg_len = GET_32BIT(cp);          msg_len = GET_32BIT(cp);
         if (msg_len > 256 * 1024) {          if (msg_len > 256 * 1024) {
                 shutdown(e->fd, SHUT_RDWR);                  shutdown(e->fd, SHUT_RDWR);
Line 607 
Line 581 
 #ifdef SMARTCARD  #ifdef SMARTCARD
         case SSH_AGENTC_ADD_SMARTCARD_KEY:          case SSH_AGENTC_ADD_SMARTCARD_KEY:
                 process_add_smartcard_key(e);                  process_add_smartcard_key(e);
                 break;                  break;
         case SSH_AGENTC_REMOVE_SMARTCARD_KEY:          case SSH_AGENTC_REMOVE_SMARTCARD_KEY:
                 process_remove_smartcard_key(e);                  process_remove_smartcard_key(e);
                 break;                  break;
 #endif /* SMARTCARD */  #endif /* SMARTCARD */
         default:          default:
                 /* Unknown message.  Respond with failure. */                  /* Unknown message.  Respond with failure. */
Line 623 
Line 597 
 }  }
   
 static void  static void
 new_socket(int type, int fd)  new_socket(sock_type type, int fd)
 {  {
         u_int i, old_alloc;          u_int i, old_alloc;
         if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)          if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
Line 724 
Line 698 
                                 sock = accept(sockets[i].fd,                                  sock = accept(sockets[i].fd,
                                     (struct sockaddr *) &sunaddr, &slen);                                      (struct sockaddr *) &sunaddr, &slen);
                                 if (sock < 0) {                                  if (sock < 0) {
                                         perror("accept from AUTH_SOCKET");                                          error("accept from AUTH_SOCKET: %s",
                                               strerror(errno));
                                         break;                                          break;
                                 }                                  }
                                 new_socket(AUTH_CONNECTION, sock);                                  new_socket(AUTH_CONNECTION, sock);
Line 778 
Line 753 
 }  }
   
 static void  static void
 cleanup_socket(void)  cleanup_socket(void *p)
 {  {
         if (socket_name[0])          if (socket_name[0])
                 unlink(socket_name);                  unlink(socket_name);
Line 789 
Line 764 
 static void  static void
 cleanup_exit(int i)  cleanup_exit(int i)
 {  {
         cleanup_socket();          cleanup_socket(NULL);
         exit(i);          exit(i);
 }  }
   
 static void  static void
 cleanup_handler(int sig)  cleanup_handler(int sig)
 {  {
         cleanup_socket();          cleanup_socket(NULL);
         _exit(2);          _exit(2);
 }  }
   
Line 870 
Line 845 
         if (ac > 0 && (c_flag || k_flag || s_flag || d_flag))          if (ac > 0 && (c_flag || k_flag || s_flag || d_flag))
                 usage();                  usage();
   
         if (ac == 0 && !c_flag && !k_flag && !s_flag && !d_flag) {          if (ac == 0 && !c_flag && !s_flag) {
                 shell = getenv("SHELL");                  shell = getenv("SHELL");
                 if (shell != NULL && strncmp(shell + strlen(shell) - 3, "csh", 3) == 0)                  if (shell != NULL && strncmp(shell + strlen(shell) - 3, "csh", 3) == 0)
                         c_flag = 1;                          c_flag = 1;
Line 945 
Line 920 
         pid = fork();          pid = fork();
         if (pid == -1) {          if (pid == -1) {
                 perror("fork");                  perror("fork");
                 exit(1);                  cleanup_exit(1);
         }          }
         if (pid != 0) {         /* Parent - execute the given command. */          if (pid != 0) {         /* Parent - execute the given command. */
                 close(sock);                  close(sock);
Line 968 
Line 943 
                 perror(av[0]);                  perror(av[0]);
                 exit(1);                  exit(1);
         }          }
           /* child */
           log_init(__progname, SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 0);
   
         if (setsid() == -1) {          if (setsid() == -1) {
                 perror("setsid");                  error("setsid: %s", strerror(errno));
                 cleanup_exit(1);                  cleanup_exit(1);
         }          }
   
Line 982 
Line 959 
         /* deny core dumps, since memory contains unencrypted private keys */          /* deny core dumps, since memory contains unencrypted private keys */
         rlim.rlim_cur = rlim.rlim_max = 0;          rlim.rlim_cur = rlim.rlim_max = 0;
         if (setrlimit(RLIMIT_CORE, &rlim) < 0) {          if (setrlimit(RLIMIT_CORE, &rlim) < 0) {
                 perror("setrlimit rlimit_core failed");                  error("setrlimit RLIMIT_CORE: %s", strerror(errno));
                 cleanup_exit(1);                  cleanup_exit(1);
         }          }
   
 skip:  skip:
         if (atexit(cleanup_socket) < 0) {          fatal_add_cleanup(cleanup_socket, NULL);
                 perror("atexit");  
                 cleanup_exit(1);  
         }  
         new_socket(AUTH_SOCKET, sock);          new_socket(AUTH_SOCKET, sock);
         if (ac > 0) {          if (ac > 0) {
                 signal(SIGALRM, check_parent_exists);                  signal(SIGALRM, check_parent_exists);
Line 1009 
Line 983 
                 if (select(max_fd + 1, readsetp, writesetp, NULL, NULL) < 0) {                  if (select(max_fd + 1, readsetp, writesetp, NULL, NULL) < 0) {
                         if (errno == EINTR)                          if (errno == EINTR)
                                 continue;                                  continue;
                         exit(1);                          fatal("select: %s", strerror(errno));
                 }                  }
                 after_select(readsetp, writesetp);                  after_select(readsetp, writesetp);
         }          }

Legend:
Removed from v.1.72  
changed lines
  Added in v.1.72.2.2